coconilu/Blog

source-map实践

Opened this issue · 0 comments

source-map简介

source-map是用来映射源文件和构建输出文件的位置的一个方案(方法)。

源文件 <-- source-map <-- 输出文件

不论是压缩代码工具如uglifyjs还是打包构建工具,都希望输出体积更小的文件。这对用户来说是友好的,节省流量。但是对于开发者来说,就不太友好,因为输出文件与源文件不一样,错误堆栈的行和列都是对不上的。于是source-map应时而生。

source-map刚开始是Google的一款优化JS代码的闭包编译器(Closure Compiler)的产物。详情可以看:What is the Closure Compiler?

source-map文件里最重要的字段是mappings,它是基于VQL编码(可变长度的编码)和Base64编码,用分号分割每一个组(每一个组对应输出文件的一行),每一组里面由逗号分割成一个个段,每个段表示一个字符串(不包括JS里的关键字:const、require等)的信息,每个段分为五个域:

  1. 第一个域表示段对应于bundle文件的第几列(从第0列开始计算)
  2. 第二个域表示的是段对应的源文件在sources属性对应的源文件列表中的索引
  3. 第三个域表示的是段对应于源文件的第几行(从第0行开始计算)
  4. 第四个域表示的是段对应于源文件的第几列(从第0列开始计算)
  5. 第五个域表示的是段对应的变量名或属性在names属性对应的变量名或属性列表中的索引

生产环境应用

很多工具都支持source-map,比如uglify-jswebpack。通过source-map,我们可以把线上的错误堆栈还原到源代码的位置。

再继续往下说之前,我想来先介绍一下,前端一般是怎么收集错误堆栈的:

window.onerror = function(message, source, lineno, colno, error) { ... }

在onerror函数里把message, source, lineno, colno, error传到一个收集错误堆栈的应用里,其中最重要的是source和lineno、colno:source是构建输出的文件,lineno是行号,colno是列号。

其次,再来谈谈source-map一般是怎么创建的。前端最流行的打包构建工具webpack可以在构建过程中帮助我们生成,只需要在配置文件webpack.config.js里的导出对象加上一个devtool字段,并赋值hidden-source-map便可:devtool: 'hidden-source-map'。那么只要我们构建输出文件的时候就会多一个xxx.js.map文件,部署应用的时候,推荐把xxx.js.map文件一起部署上去。

devtool还有其它选项,不过我认为比较适合上线使用的是hidden-source-map,它不会为输出文件添加引用注释,也就不会向浏览器开发工具暴露你的source map,也会保留sourcesContent(源代码内容),这样就可以轻易还原源代码了。详情:devtool

接下来还要介绍一个npm库,Source Map,它提供了Node版本和Web版本的接口,通过SourceMapConsumer.prototype.originalPositionFor(generatedPosition)这个接口我们就可以得到源代码的路径和行列号了。

为了保护源代码,建议在网关屏蔽source-map请求,仅让内网访问。

下面是我写的一个开源的用于根据错误堆栈查看源代码的web app。

sourcemap-watch

参考

Closure Compiler
GlobalEventHandlers.onerror
sourcemap学习笔记
source map原理分析&vlq