source map入门案例

三年前,我收到了一个学校项目里的 xx.js 文件,只有一行代码,对于当时在严格要求自己代码格式规范的我来说,这简直不能忍。于是乎,傻傻地花了 20 分钟时间,一行行,一个个代码块手动更正,but 还是看不懂......直到最后被告知这是个压缩文件! 生产环境 .min.js 吃过没文化的亏,现在精通 xxx、xx 构建工具的我,直接把锅甩给了它们,谁让它们把源码 uglify,还压缩成一行的 😖 不过面对现实,生产环境确实可以优化: 代码压缩。减小文件大小 混淆加密。减少源码暴露风险 编译。生成浏览器可用代码,比如 es6->es5、ts->js 一时压缩一时爽,线上调试却头疼。控制台显示的日志是这样的: SyntaxError: aaa at http://localhost/js/index-7590496c88b19c44e9ed.js:1:4880 at u (http://localhost/js/common-3ee4144b260a56b17990.js:1:17621) 这完全找不到源代码哪行出错了,即使记录了错误日志也没有任何意义,只有将两个文件中代码位置对应起来,根据这层关系,才能定位到错误所在。 Source Map 就是解决这一问题的技术,各种编译工具在过程中生成 .map 文件,以 json 形式存储着源码的对应关系。 Source Map 格式 目前有很多工具都能生成 source map,比如 uglify-js、gulp、webpack 等等。工具不一样,生成的格式却是一样的,遵循第 3 版的提案: { "version": 3, "sourceRoot": "源文件地址前缀", "sources": ["源文件地址"], "mappings": "VLQ编码,VLQ编码;VLQ编码", "file": "文件名", "names": ["变量名和属性名"] } 最关键的就是 mappings 了,每个用 , 分隔开的 VLQ 编码都记录着代码的对应关系,而每个 ; 分隔开则表示着压缩文件的第几行。 VLQ 编码 Variable-Lenght Quantity,可变长量,可以用任意编码(进制)表示一个任意大的整数。 如果用 8 位二进制,VLQ 的结构大致是这样的: # 八位 VLQ 表示法 Continuation | Sign | | V V 7 6 5 4 3 2 1 0 -------------------------------- 1 2 3 4 5 6 7 8 最高位(Continuation)表示是否可连续。0 表示是最后一个 VLQ 编码,1 表示后面还有 最低位(Sign)可用于标志正负数。source map 中没有,略过... 因为第 1 位是连续位,所以转成 VLQ 时应该按照每块 7 位分隔,然后在每块第一位填充连续位。例如: 00101000 10001111 -------------------------------- 00 1010001 0001111 -------------------------------- 11010001 00001111 | | | V V Continuation Continuation 最后一块填充 0,其余都填充 1。 source map 采用了 Base64 编码,因其范围为 [0-63],只需要 6 位二进制即可。 Base64 VLQ 目前使用最多的是 mozilla/source-map,可查看其 base64-vlq.js 实现。 举个栗子,把 1234 转化成 base64 vlq: # 六位 VLQ 010011 010010 1234的二进制 -------------------------------- 01 00110 10010 -------------------------------- 100001 100110 010010 转为base64就是:hmS | | | | | V | V Continuation V Continuation Continuation mappings 位置对应 每个 VLQ 编码都记录着五个位置: 第一位,代表着压缩文件第几列 第二位,代表着 sources 数组 index 第三位,代表着源码第几行 第四位,代表着源码第几列 第五位,代表着 names 数组 index 好像少了压缩文件的第几行,其实它之前就被 ; 定义了。 一般来说,第五位如果对应不上 names,那它就没啥用。举个栗子: 901250 -------------------------------- 9|0|12|5|0 分段表示【重要】 -------------------------------- 9, index.js, 12, 5, 0 | | | | | | | | | V | | | V names第0个 | | V 源码第5列 | V 源码第12行 V sources第0个:index.js 压缩代码第9列 如何把一个任意大的数字分段表示?恰巧 VLQ 编码的连续位功能可以实现!! 案例 道理大家都懂,关键还得在浏览器里调试。首先在 F12 调试工具中开启: Enable JavaScript source maps Enable CSS source maps uglify-js uglify-js 是一个被广泛使用压缩 es5 代码的工具,利用它可以生成 source map。 可以使用 uglify-es 来压缩 es6,但作者已经长时间不维护了,社区基本都转用 terser,它保留了和 uglify-js 一样的 API 和 cli 参数。 uglifyjs --compress --mangle # 开启 source map --source-map --output dist/index.js -- example01-uglifyjs/index.js 给 dist/index.js 末尾添加一行注释,使浏览器调试工具启用: //# sourceMappingURL=index.js.map 更正 index.js.map 中的 sourceRoot,正确定位到源文件: {"sourceRoot":"../","sources":["example01-uglifyjs/index.js"]} 切换到 Sources 面板可以看到: webpack 前端生产工具 webpack 集成了众多功能,包括生成 source map。 可以在生产环境中使用: module.exports = { mode: 'production', devtool: 'source-map', }; 甚至连样式都能 inspect,开启配置生成 *.css.map: module.exports = { module: { rules: [ MiniCssExtractPlugin.loader, { use: 'css-loader', options: { sourceMap: true } }, { use: 'less-loader', options: { sourceMap: true } }, ], }, }; 压缩时需要额外的配置: module.exports = { optimization: { minimizer: [ new TerserWebpackPlugin({ sourceMap: true }), new OptimizeCSSAssetsPlugin({ cssProcessorOptions: { // 参考 postcss source map 配置 // https://github.com/postcss/postcss/blob/master/docs/source-maps.md map: true, }, }), ], }, }; 线上 production 线上报错日志,根据生成的 source map 文件定位到源码位置。利用现有工具 mozilla/source-map 直接开搞: const { SourceMapConsumer } = require('source-map'); // 解析线上错误日志,拿到报错的文件名、行号、列号 const { filename, line, column } = parse(error); // 读取服务器上的 source map 文件 const rawSourceMap = JSON.parse(fs.readFileSync(filename + '.map')); SourceMapConsumer.with(rawSourceMap, null, (consumer) => { const { name, // names 数组中的变量 or 属性名 source, // sources 数组中文件名 line, // 源代码列号 column, // 源代码行号 } = consumer.originalPositionFor({ line, column }); // 根据文件名找到 sourceContent 中对应文件源代码 const content = consumer.sourceContentFor(source); // 源代码内容以及报错位置均解析, // 可以人类化展示 }); 效果图: 参考链接 JavaScript Source Map 详解 debug 工具 —— source-map Source Maps 101 Source Map Revision 3 Proposal LitileXueZha/examples-source-map: 一些关于 source map 的例子

Markdown - 语法


JavaScript

上一篇:钢铁是怎样炼成的

下一篇:打酱油

Ctrl + Enter