细说 js 压缩、sourcemap、经过 sourcemap 查找原始报错信息

细说 js 压缩、sourcemap、经过 sourcemap 查找原始报错信息

1. js 压缩

js 压缩对前端开发者来讲是一门必修课。javascript

通常来讲,压缩 js 主要出于如下两个目的:html

  1. 减少代码体积,加快前端资源加载速度
  2. 保护源代码不被别人获取

压缩 js 使用的工具库:前端

压缩 js 的主要过程:java

  1. 移除无用代码
  2. 混淆代码中变量名称、函数名称等
  3. 预编译代码
  4. 对结构进行扁平化处理

1. 移除无用代码

去掉全部对解析引擎来讲无用的字符,包括空格、注释、换行、没有用的变量声明、函数声明等。webpack

2. 混淆代码中变量名称、函数名称等

把一些局部变量名称、函数名称等用 a, b, ...$1, $2, ..._1, _2, ... 之类的简略字符进行替换,达到混淆的目的。git

源代码es6

(function () {
  var hello = 'hi';
  var print = function (str) {
    console.log(str);
  };

  print(hello);
})();

压缩后的代码(仅演示混淆功能)github

(function () {
  var a = 'hi';
  var b = function (c) {
    console.log(c);
  };

  b(a);
})();

3. 预编译代码

把不依赖外部环境的逻辑提早进行运算,并把运算结果替换到相应的源码处,而后从源码中移除这段逻辑。web

源代码浏览器

(function () {
  var hello = 'hi' + ' everyone, ';
  var count = 3 * 5;

  console.log(hello + count + ' girls');
})();

压缩后的代码(仅演示预编译功能)

(function () {
  var hello = 'hi everyone, ';
  var count = 15;

  console.log(hello + count + ' girls');
})();

4. 对结构进行扁平化处理

对于 js 来讲,嵌套越深,执行越慢,对代码进行扁平化处理也是优化代码的一种方式。

源代码

(function () {
  var say = {
    hello: function (str) {
      console.log('hello ' + str);
    }
  };

  say.hello('everyone');
})();

压缩后的代码(仅演示扁平化结构功能)

!function(str){console.log("hello "+str)}("everyone");

完整示例

源代码

(function () {
  var say = {
    hello: function (str) {
      console.log('hello ' + str);
    }
  };

  say.hello('everyone');
})();

压缩后的代码

!function(l){console.log("hello "+l)}("50 girls");

2. sourcemap

一般 js 压缩后只有一行代码,而且里面的变量名与函数名等都是混淆了的,这在实际运行中会有一个问题,就是 js 的报错信息将会失真,没法追踪到是在源代码哪一行哪一列报的错。

sourcemap 即是为了解决这个问题而生的。

sourcemap 文件就是记录了从源代码文件到压缩文件的一个代码对应关系记录表,经过压缩文件和 sourcemap 文件能够原本来本找出源代码文件。

查看阮一峰老师的 JavaScript Source Map 详解 了解 sourcemap 的原理与格式。

通常在压缩 js 的过程当中,会生成相应的 sourcemap 文件,而且在压缩的 js 文件末尾追加 sourcemap 文件的连接 //# sourceMappingURL=bundle-file-name.js.map。这样,浏览器在加载这个压缩 js 的时候,就知道还有一个相应的 sourcemap 文件,也一并加载下来,运行的过程当中若是 js 报错,也会给出相应源代码的行号与列号,而非压缩文件的。

好比,对下面的源码进行压缩:

(function () {
  var say = {
    hi: function () {
      console.log('hi');
    }
  };

  say.hello();

  return say;
})();

未加 sourcemap 文件时,报错信息是:

图片描述

图片描述

加上 sourcemap 文件时,报错信息是:

图片描述

图片描述

sourcemap 扩展

webpacksourcemap 作了扩展,定义在 devtool 配置项中:

  • eval: 每一个模块都使用 eval() 执行,而且都有 //@ sourceURL,构建很快,但没法正确显示行号
  • eval-source-map: 每一个模块使用 eval() 执行,而且 source map 转换为 DataUrl 后添加到 eval() 中,通常开发模式中使用这种方式
  • cheap-eval-source-map: 相似 eval-source-map,但只映射行,不映射列,并忽略源自 loadersource map,仅显示转译后的代码
  • cheap-module-eval-source-map: 相似 cheap-eval-source-map,但会保留源自 loadersource map
  • inline-source-map: source map 转换为 DataUrl 后添加到 bundle
  • cheap-source-map: 只映射行,不映射列,并忽略源自 loadersource map,仅显示转译后的代码
  • inline-cheap-source-map: inline-source-mapcheap-source-map 的结合
  • cheap-module-source-map: 相似 cheap-module-eval-source-map,但不使用 eval() 执行
  • inline-cheap-module-source-map: inline-source-mapcheap-module-source-map 的结合
  • source-map: 整个 source map 做为一个单独的文件生成,产品环境通常使用这种模式
  • hidden-source-map: 相似 source-map,但不会把 //# sourceMappingURL=bundle-file-name.js.map 追加到压缩文件后面
  • nosources-source-map: 相似 source-map,但只有堆栈信息,没有源码信息

更详细信息能够参考:

使用建议

对于使用 webpack 来构建项目,建议在开发时使用 eval-source-map,产品环境使用 source-map

由于用压缩文件与 sourcemap 文件是能够原本来本的找到源代码的,因此,为了保护源代码,能够这样隐藏 sourcemap 文件:

  1. web 服务器设置外部不能访问 sourcemap 文件,只能内部访问
  2. 直接把 sourcemap 文件存放到其余地方

3. 经过 sourcemap 查找原始报错信息

通常而言,在产品阶段,咱们会用 window.onerror 来捕获 js 报错,而后上报到服务器,以此来收集用户使用时发生的 bug

window.onerror = function(message, source, lineno, colno, error) {
  // message: 错误信息
  // source: 报错脚本的 url 地址
  // lineno: 行号
  // colno: 列号
  // error: 错误对象
  // 上报必要的信息到服务器
}

但产品环境的代码都是压缩的,行号和列号都是失真的,因此就须要用 sourcemap 文件来找到错误对应源代码的行号与列号,以及其余的信息。

使用工具: mozilla/source-map

源代码

(function () {
  var say = {
    hi: function () {
      console.log('hi');
    }
  };

  say.hello();

  return say;
})();

压缩后报错信息

window.onerror = function(message, source, lineno, colno, error) {
  console.log(`message: ${message}`);
  console.log(`source: ${source}`);
  console.log(`lineno: ${lineno}`);
  console.log(`colno: ${colno}`);
  console.log(`error: ${error}`);
}

// message: Uncaught TypeError: e.hello is not a function
// source: url/to/bundle.min.js
// lineno: 1
// colno: 982
// error: TypeError: e.hello is not a function

经过 source-map 查找原始报错信息

const fs = require('fs');
const SourceMap = require('source-map');

const { readFileSync } = fs;
const { SourceMapConsumer } = SourceMap;

const rawSourceMap = JSON.parse(readFileSync('path/to/js/map/file', 'utf8'));

SourceMapConsumer.with(rawSourceMap, null, consumer => {
  const pos = consumer.originalPositionFor({
    line: 1,
    column: 982
  });

  console.log(pos);
});

查找到的原始信息

{ 
  source: 'path/to/index.js',
  line: 8,
  column: 7,
  name: 'hello' 
}

这样,便找到了原始报错信息:

  • 原始报错文件:path/to/index.js
  • 原始报错行号:8
  • 原始报错列号:7
  • 原始对象名称:hello

如此,便能一会儿就找到错误在哪里了。

更多用法,参考 mozilla/source-map

后续

更多博客,查看 https://github.com/senntyou/blogs

做者:深予之 (@senntyou)

版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证

相关文章
相关标签/搜索