深入前端调试原理

发表于 5月以前  | 总阅读数:352 次

调试是开发者需要掌握的一项重要的技能, 它能够帮助我们快速定位和修复代码中的问题。本文主要介绍前端调试的基本原理。

“本文是笔者在学习了 前端调试通关秘籍[1] 后,并结合平时实践过程中一些经验进行的梳理和总结。主要以 Chrome,VSCode 作为调试工具,在其他编辑器中,配置虽有不同,但原理是相通的。

本文所使用的示例代码均在 debug-dojo[2] 仓库。

从一个简单的示例入手

以上面代码为例,简单实现了按钮在点击后文字更新点击次数的能力:

当我们打开 Devtools 中的 Sources 目录,并在 click 的回调设置断点后,再次点击按钮,程序就会在此停住,并将内部的运行状态暴露出来:

这背后到底是怎么实现的呢?Devtools 是如何将程序内部的运行状态暴露出来,并且通过 UI 的形式呈现的呢?

Chrome Devtools 原理

Devtools 主要包含以下四个关键的组成部分:

  • 后端:和 Js Runtime,内部的布局、渲染器深度绑定,用于将内部的状态通过协议暴露出来。
  • 前端:这里主要是 Devtools 的各个调试模块,负责对接协议,做 UI 展示和交互。前端本质上是独立的,任何对接协议用于展示数据的项目都可以作为调试前端。
  • 通信方式:前端后端通过 websocket 进行通信。
  • Chrome Devtools Protocol(简称:CDP): 前后端的通信协议。

CDP 协议的具体内容可以通过 官网文档[3] 进行查看,它按照不同的域进行划分,基本上包含我们平时所使用的 Devtools 的不同场景:

可以在 Chrome Devtools 设置中打开 Protocol Monitor,就可以查看前后端的协议通信了:

VSCode Debugger 原理

除了 Devtools 外,VSCode Debugger 也是常见的调试工具,在 VSCode 的项目中 .vscode/launch.json 中加入如下的配置即可调试:

VSCode Debugger 的原理大致相同,唯一特殊的是:VSCode 并不是 JS 语言的专属编辑器,它可以用于多种语言的开发,自然不能对某一种语言的调试协议进行深度适配,所以它提供了 Debugger 适配层和适配器协议,不同的语言可以通过提供 Debugger 插件的形式,增加 VSCode 对不同语言的调试能力:

如此,VSCode Debugger 就能以唯一的 Debugger 前端调试各个不同的语言,插件市场中也提供了诸多不同语言的调试插件:

调试模式

Attach 模式

从上面调试的四个关键部分可以看出,前后端之间是通过 websocket 进行通信的,所以确保前后端能正确的连接是调试成功的关键。

除了在需要调试的网页中直接打开 Devtools 的方式外,我们使用第三方前端工具进行调试时,都需要知道所需要调试的网页的后端 ws 地址才行。因此我们需要让 Chrome 以指定调试端口的形式跑起来,使用 --remote-debugging-port 参数指定调试端口:

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222

“Chrome 运行参数非常多,可以通过 该文档[4] 进行查看。

在 Chrome 运行起来后,随意的打开几个网页,然后访问 localhost:9222/json/list 网址,此时就能得到所有运行的网页后端 ws 地址:

在百度网页中同时打开了 Devtools, 可以看到连 Devtools 的调试信息都一起打印出来了,因为 Devtools 本质上也是一个网页程序,所以甚至可以做到对 Devtools Debugger 进行 Debug 这样的套娃操作。

有了 ws 信息,我们就可以使用其他 Debugger 进行连接了,比如使用下面的 VSCode Debug 配置:

Node.js 程序在运行时则是通过 --inspect 或者 --inspect-brk 参数开启调试模式:

Node.js 调试协议经过了漫长的迭代,最终也是以 CDP 协议进行调试,因此 Node.js 程序可以直接使用 Devtools 进行调试,在 Chrome 中访问 chrome://inspect/#devices 就可以看到调试目标了:

如果当前的 Node 项目没有被发现,可能是检测端口不是默认的 9229 ,可以通过 Configure 进行配置。

VSCode Debugger 同样能够连接上 Node.js 项目并进行调试,

Launch 模式

前面讲到的都是首先通过调试模式启动一个项目,然后再手动进行前端 ws 连接,最后再调试的模式。相对较繁琐,VSCode Debugger 提供了 launch 模式,它相当于是将上面的流程自动化了,以调试模式将 Chrome 或者 Node.js 程序运行起来,然后 Debugger 自动 attach 到调试端口,然后直接调试。

“VSCode Debugger 的各种调试配置不是本文的重点,有兴趣可以阅读 官方文档[5],已经讲得很详细了,也提供常见使用场景的 Recipes[6]。

SourceMap

实际的项目往往不会如此简单,要引入各种开源库,然后经过诸如 Webpack, Rollup 等等打包工具做编译打包,才能运行起来。编译压缩后的代码是不具备可读性的,在它上面进行调试也没有意义,所以我们需要一个技术,将源码和编译后的代码进行映射,这就是 SourceMap。

以如下代码为例:

SourceMap 文件规则

在经过 Webpack 打包后,会生成压缩文件,以及对应的 SourceMap 文件:

SourceMap 文件内容主要包含:

  • version: SourceaMap 的版本
  • file: 对应编译后的文件名
  • sourceRoot:源码的根目录
  • sources:对应源码文件路径
  • sourcesContent:对应的源码文件内容
  • names:源码转化前的变量,属性名
  • mappings:源码和编译后代码的对应关系

mappings 使用了 BaseVLQ 编码形式

  • 使用 ,; 做分隔,一个 ; 对应转换后代码的一行,, 代表一个位置的映射
  • 每个 maping 通常使用 5 位长度表示映射关系(也会根据实际的打包规则进行简化),1 到 5 位分别表示:
  • 对应 转换后 代码第几列(行号已经通过 ; 确定)
  • 对应转换前哪个文件(对应 sources 里面下标索引)
  • 对应转换前第几行
  • 对应转换前第几列
  • 对应转换前源码哪个变量名(对应 names 里面的下标索引)

说起来有点绕,好在可以通过 在线的 SouceMap 可视化工具[7] 进行查看:

SourceMap 提供的是源码和编译后代码的映射能力,无关乎代码的类型,所以在不管是 js 代码,还是 less 代码,都可以为其提供映射:

Webpack SourceMap 配置

Webpack 提供的 SourceMap 配置能力应该是最丰富,也是最复杂的,基本上掌握了 Webpack 的 SourceMap 配置,其他的打包工具就难不倒我们了。

在配置之前,首先说明几个概念:

  • Original:源码
  • Transformed:经过各种 loader 转化后的代码,比如 babel-loader, less-loader 等等
  • Genrated: weback 对每一个模块按照 Webpack 加载处理后的代码
  • Bundled: 最中生成的代码

devtool 配置参见 官方文档[8],需要满足 [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map 这样的规则。

inline|hidden|eval 控制 SourceMap 文件的生成方式,当不指定时,会单独生成 SourceMap 文件,并在对应的打包文件的尾部添加关联:

hidden 则表示单独生成 SourceMap 但是不关联。

inline 则表示将生成的 SourceMap 内容以 Base64 形式直接内嵌到打包文件中,打包的文件体积会显著增加:

eval 相对而言比较复杂,JS 可以通过 eval api 动态执行代码:

我们可以通过后面映射的 VM242:1 虚拟文件查看源码,设置断点,注意:该文件不会出现在 Sources 面板的目录中。

如果在 eval 代码的尾部加上 //#sourceURL=xxx,那么 Devtools 就会以 xxx 的路径将文件加入到 Sources 面板目录中,就好像是直接运行 xxx 路径的文件代码一样:

Webpack 利用 eval 来优化 SourceMap 生成的性能,是比较推荐的 development 模式下配置,它将每个模块都用 eval 包裹,并搭配 sourceURL, 就能将也映射到每个源文件了,不需要从 Bundled 的代码做映射。但现在映射到的只是 Generated 级别的代码:

所以只是 sourceURL 还是不够的,需要搭配其他 SourceMap 配置,比如 eval-source-map,就能进一步将 SourceMap 关联上:

此时的行为就和 inline 模式的差不多。

nosources 表示不将对应源码写入 SourceMap 的 sourcesContent 字段中,这会显著的减少 SourceMap 文件的体积。

cheap 表示映射只精确到行,而不到列,也可以有效的加快 SourceMap 的生成速度,毕竟精确到行已经足够我们排查问题了。

module 主要用来处理 loader 生成的 SourceMap。通常代码要经过多个 loader 处理转换,比如 React 组件代码必须得经过 babel-loader 进行转换,然后在交由 Webpack 处理,而在 loader 的转换过程也会生成 SourceMap,如果不指定 module,Webpack 只能生成从 Transformed 代码到 Bundled 代码的映射,即映射级别只能到 Transformed。在指定了 module 后,Webpack 会结合 loader 过程中生成的 SourceMap, 和自己从 Transformed 代码到 Bundled 代码的映射,就能生成 Original 级别的映射了。

需要注意的是:即使开启了 module,Webpack 也只会处理经由 loader 传递下来的 SourceMap,我们常常会在 babel-loader 中排除 node_modules 里面的文件处理,因为大多数情况下里面的开源包都是转换后的产物,直接交由 Webpack 处理即可,但如果库里面也包含 SourceMap,Webpack 是不会处理它的,所以此时只能映射到开源库的打包产物级别。

@antv/s2 为例:

即使开启了 module,也只能映射到 esm/index.js 这个级别:

为了能正确加上开源库的 SourceMap 信息,需要搭配 source-map-loader[9] 处理开源库的 SourceMap, 然后传递给 Webpack,Webpack 就能正确的处理经由 loader 传递下来的 SourceMap 了,配置如下:

现在再来看官方文档中不同配置之间的速度差异,以及 Production 级别,是不是就能清楚一点了。

SourceMap 加载

编译后的代码以及 SourceMap 都有了,现在来讲讲 SourceMap 是如何被 Devtools 加载解析的。

浏览器虽会加载 SourceMap 资源,但它们并不会出现在 Network 面板中,需要在 Developer Resources 面板中查看:

“不过目前 Developer Resources 面板比较的简单,只能查看成功与否等简单信息。

以单独的 SourceMap 文件为例,当 Devtools 加载完代码文件后,如果文件尾部包含 //#sourceMappingURL ,就会单独去请求该链接,在拿到 SourceMap 文件后再开始做解析,坏处就是多了一个的网络请求,解析速度就会慢一点。

inline 模式则是直接内嵌的 SourceMap,无需单独请求, 可以在代码文件加载完成后,就直接解析了。inline 模式下 Developer Resources 里面展示的链接就是 Base64 的形式了:

上面说过 hidden 完全不关联 SourceMap, 所以 Devtools 是不会去加载 SourceMap 的。这种模式主要用于生产环境,我们并不希望直接在线上暴露源码,所以不能直接关联上。而是将生成的 SourceMap 上传到监控平台,就可以结合线上的报错信息顺利找到报错的源码位置了。

当然还可以借助 Sources 面板中的 Add source map... 临时添加源码映射,不过重新刷新后就没了:

nosources 不将源码写入 SourceMap 文件中,既能减少 SourceMap 文件体积,也能到达隐藏源码信息,比如我们在源码中打印了些信息出来,虽然从 Console 面板中看到映射到了正确的文件路径,但是点击跳转过去后,会发现无法查看源码:

这种模式下,借助 VSCode Debugger 又有别样的体验。编辑器调试的好处在于:如果映射出来文件在当前的工程项目中,编辑器就会直接打开该文件,不管有没有源码存在于 SourceMap 中。调试配置如下:

如果映射的文件路径不在当前项目中,那么打开的结果就和 Devtoos 一样了。

多说一句,将源码加入到 SourceMap 中后。如果映射到的文件在当前项目中,那么跳转过去后,是可以直接进行编辑的;如果不在,则该文件就只读。比如下面的 @antv/s2 源码中的某一个文件显然不在项目中,虽然可以查看源码,但是无法编辑:

eval 模式的加载情况情况大致相同,只是多了将 //#sourceURL 映射到 Sources 目录的布置而已,sourceMappingURL 处理就和 inline 模式一致。所以不再赘述。

SourceMap 加载到底发生在哪里?

SourceMap 的加载和解析完全是前端行为(Devtools,VSCode Debugger)等,后端并不涉及到任何 SourceMap 的处理。比如我们在源码位置添加的断点,通过 Protocol Monitor 可以看到传给后端的是打包产物代码的位置:

其实也能理解这样的设计,本来源码的调试和断点就是前端行为,后端只是提供了运行时暂停和状态暴露的能力。如果后端来解析,会影响代码运行时的执行效率。而且前端处理能更自由,不同 Debugger 工具可能对 SourceMap 进行再映射。比如前面提到的 VSCode Debugger 在调试时,如果映射的路径不在项目中就无法编辑,就可以通过 sourceMapPathOverrides 等配置再重新映射。

正因为前端负责 SourceMap 的解析,所以我们打的断点在 SourceMap 解析完成之前是没法告诉后端正确的地址的。所以如果 SourceMap 加载比较慢,可能后端代码已经执行就完了,前端才将断点信息传递过去,就会出现打了断点但是无效的情况。在 VSCode Debugger 中打断点提示无效也大概是这个原因,没有完成 SourceMap 的解析,就无法正确映射。

好在 Devtools 会将这些断点信息进行缓存,所以在刷新网页后,能立马将正确的断点信息传递给后端。所以有些网页在打了断点后即使关闭了网页后过一段时间再打开,依旧可以看到断点信息。但 VSCode Debugger 则不会缓存这些信息,其实也不应该去缓存。所以在调试断开后,再重新调试,又需要重新进行 SourceMap 的加载解析,虽然有 pauseForSourceMap 等配置让程序等到 Debugger 加载完成 SourceMap 再执行,但是目前 VSCode Debugger 整体的加载解析 SourceMap 的效率还是比较低,期待未来能做到更好。

“有兴趣可以关注 vscode-js-debug[10],它就是 VSCode 所使用的 CDP Debugger。

Vite

Vite 是目前大火的构建工具,相比于传统的构建工具如 Webpack 和 Rollup,Vite 的最大特点是“快”。这得益于 Vite 利用了浏览器原生的 ES modules 功能 。具体来说,Vite 会根据入口文件中的依赖关系,生成一棵依赖树,并将各个模块作为单独的文件提供给浏览器。也无需单独配置 SourceMap 就能映射到源码。那它是怎么做到的呢?

Vite 会将每个文件进行转换,然后提供给浏览器,而转换的文件中就已经 inline 了 SourceMap,所以我们可以直接对源码进行断点调试了。

Jest

Jest 作为目前主流的单测工具,它又是怎么做到单测时断点调试的呢?其实它和 vite 类似,在实际运行代码前,也会对代码进行转换,并将 SourceMap inline 到转换后的文件中,所以我们也可以直接对源码进行调试,以如下的 VSCode Debug 配置为例:

再聊聊 CDP

CDP 简单讲就是一组 API,用于与 Chrome DevTools 进行通信。它允许开发人员以编程方式控制 Chrome,例如在 Chrome 中打开一个新的选项卡,加载网页,设置网络条件等。CDP 可以通过 WebSocket 进行通信,也可以通过 HTTP 请求进行通信。上文内容更多的聚焦在代码调试这一块,但是 CDP 远不止于此,Chrome DevTools 的大部分功能都是基于 CDP 实现的。

Puppeteer 是一个著名的自动化库,用于自动化控制 Chrome 或 Chromium 浏览器。本质上就是使用 CDP 协议来与浏览器进行通信,相当于是对 CDP 的高级封装版。

基于 CDP,我们可以做很多有趣的事,比如自己打造一个独享版的 Devtools,可以使用 Chrome 提供的 chrome-remote-interface[11],它是对 CDP 的 Node.js 封装,使用起来就像是 Pupeteer 一样;也可以直接基于 Chrome Devtools 进行修改,Chrome 也将 Devtools[12] 仓库开源了,比如小程序的调试器就可以基于 Devtools 项目做二次封装。

至此就是本文的全部内容,希望能对你有所帮助,如有错误欢迎指正。

参考资料

  • [1]前端调试通关秘籍: https://juejin.cn/book/7070324244772716556?utm_source=profile_book
  • [2]debug-dojo: https://github.com/wjgogogo/debug-dojo
  • [3]官网文档: https://chromedevtools.github.io/devtools-protocol/
  • [4]该文档: https://peter.sh/experiments/chromium-command-line-switches/
  • [5]官方文档: https://code.visualstudio.com/docs/editor/debugging
  • [6]Recipes: https://code.visualstudio.com/docs/nodejs/debugging-recipes
  • [7]在线的 SouceMap 可视化工具: https://evanw.github.io/source-map-visualization/
  • [8]官方文档: https://webpack.js.org/configuration/devtool/
  • [9]source-map-loader: https://webpack.js.org/loaders/source-map-loader/
  • [10]vscode-js-debug: https://github.com/microsoft/vscode-js-debug
  • [11]chrome-remote-interface: https://github.com/cyrus-and/chrome-remote-interface
  • [12]Devtools: https://github.com/ChromeDevTools/devtools-frontend

本文由哈喽比特于5月以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/Cun6AE0a60ayFtZUUPw_wg

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:8月以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:8月以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:8月以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:8月以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:8月以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:8月以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:8月以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:8月以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:8月以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:8月以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:8月以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:8月以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:8月以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:8月以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:8月以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:8月以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:8月以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:8月以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:8月以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:8月以前  |  398次阅读  |  详细内容 »
 相关文章
为Electron程序添加运行时日志 4年以前  |  19600次阅读
Node.js下通过配置host访问URL 5年以前  |  5632次阅读
用 esbuild 让你的构建压缩性能翻倍 4年以前  |  5367次阅读
 目录