[FE] SourceMapDevToolPlugin 和 optimization.minimizer

1. 生成source map的兩種配置

1.1 配置devtool

最簡單的生成source map的方式是,如下配置webpack.config.js,

const path = require('path');

module.exports = {
    devtool: 'source-map',
    entry: {
        index: path.resolve(__dirname, 'src/index.js'),
    },
    output: {
        devtoolModuleFilenameTemplate: '[resource-path]',
        path: path.resolve(__dirname, 'dist/'),
        filename: 'index.js',
    },
    module: {
        rules: [
            { test: /\.js$/, use: { loader: 'babel-loader', query: { presets: ['@babel/preset-env'] } } },
        ]
    },
};

其中,devtool默認值為false,
配置為source-map表示以獨立的文件形式生成source map。

因此,dist/ 文件夾下,會產(chǎn)生兩個文件,

index.js
index.js.map

index.js文件末尾,webpack會自動添加一行注釋,

//# sourceMappingURL=index.js.map

瀏覽器解析到這里,會自動根據(jù)index.js的相對路徑,請求map文件并加載它。

1.2 SourceMapDevToolPlugin

除了直接配置devtool之外,還可以使用webpack官方插件 SourceMapDevToolPlugin,
進行更細粒度的source map配置。

const path = require('path');
const webpack = require('webpack');

module.exports = {
    // devtool: 'source-map',
    entry: {
        index: path.resolve(__dirname, 'src/index.js'),
    },
    output: {
        // devtoolModuleFilenameTemplate: '[resource-path]',
        path: path.resolve(__dirname, 'dist/'),
        filename: 'index.js',
    },
    module: {
        rules: [
            { test: /\.js$/, use: { loader: 'babel-loader', query: { presets: ['@babel/preset-env'] } } },
        ]
    },
    plugins: [
        new webpack.SourceMapDevToolPlugin({
            filename: '[file].map',
            moduleFilenameTemplate: '[resource-path]',
            append: '\n//# sourceMappingURL=http://127.0.0.1:8080/dist/[url]'
        }),
    ],
};

以上配置中,我們注釋掉了devtooldevtoolModuleFilenameTemplate,
將它們配置到了SourceMapDevToolPlugin中。

這樣配置,dist/目錄最后也會生成兩個文件,

index.js
index.js.map

由于我們使用了該插件的append功能,修改了sourceMappingURL地址,
因此,index.js末尾source map文件的地址就變成了,

//# sourceMappingURL=http://127.0.0.1:8080/dist/index.js.map

注:
這里值得一提的是,同時配置devtoolSourceMapDevToolPlugin是不行的,
index.js文件末尾會被添加兩行sourceMappingURL,

//# sourceMappingURL=http://127.0.0.1:8080/dist/index.js.map
//# sourceMappingURL=index.js.map

而且map文件的內(nèi)容也不正確,是一個空的map文件,

{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js","sourceRoot":""}

2. optimization.minimizer

除了devtoolSourceMapDevToolPlugin之外,還有一個地方會影響source map,
那就是webpack支持用戶自定義文件壓縮方式,

const path = require('path');
const webpack = require('webpack');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
    // devtool: 'source-map',
    entry: {
        index: path.resolve(__dirname, 'src/index.js'),
    },
    output: {
        // devtoolModuleFilenameTemplate: '[resource-path]',
        path: path.resolve(__dirname, 'dist/'),
        filename: 'index.js',
    },
    module: {
        rules: [
            { test: /\.js$/, use: { loader: 'babel-loader', query: { presets: ['@babel/preset-env'] } } },
        ]
    },
    plugins: [
        new webpack.SourceMapDevToolPlugin({
            filename: '[file].map',
            moduleFilenameTemplate: '[resource-path]',
            append: '\n//# sourceMappingURL=http://127.0.0.1:8080/dist/[url]'
        }),
    ],
    optimization: {
        minimize: true,
        minimizer: [
            new UglifyJsPlugin({
                sourceMap: false,
            }),
        ],
    },
};

以上配置中,指定了optimization.minimizer,同時配置optimization.minimizetrue,
UglifyJsPlugin中配置sourceMapfalse。

這樣webpack構(gòu)建將只生成一個index.js文件了,文件末尾也沒有sourceMappingURL

3. 代碼壓縮

現(xiàn)在我們來看一下webpack發(fā)生了什么,
我們在項目node_modules,webpack源碼 lib/WebpackOptionsApply.js 第L463行打個斷點,

if (options.optimization.minimize) {
  for (const minimizer of options.optimization.minimizer) {
    if (typeof minimizer === "function") {
      minimizer.call(compiler, compiler);
    } else {
      minimizer.apply(compiler);
    }
  }
}

發(fā)現(xiàn)這里是判斷了webpack.config.js中是否配置了optimization.minimize,
然后依次調(diào)用了minimizer.apply(compiler)。

緊接著就跳轉(zhuǎn)到了,uglifyjs-webpack-plugin 第158行,

apply(compiler) {
  ...


此時,this.options.sourceMapfalse,
第191行判斷了,

if (this.options.sourceMap && asset.sourceAndMap) {
  const { source, map } = asset.sourceAndMap();

  input = source;

  if (UglifyJsPlugin.isSourceMap(map)) {
    inputSourceMap = map;
  } else {
    inputSourceMap = map;

    compilation.warnings.push(
      new Error(`${file} contains invalid source map`)
    );
  }
} else {
  input = asset.source();
  inputSourceMap = null;
}

如果this.options.sourceMapfalse,inputSourceMap就為空。
那么在 minify.js 第172行,
uglifyOptions.sourceMap就是null了,

反之,如果配置了optimization.minimizersourceMaptrue,
則此時,uglifyOptions.sourceMap就是babel轉(zhuǎn)譯資源后的innerSourceMap了,
可參考UglifyJS2: Minify options

4. 代碼生成

以上代碼壓縮階段只是生成了一個數(shù)據(jù)結(jié)構(gòu),用來存儲壓縮結(jié)果,還沒有寫入到文件中,
而在目標代碼尾部添加sourceMappingURL,則是在代碼生成階段完成的。

這塊代碼位于webpack源碼lib/SourceMapDevToolPlugin.js 第136行

const task = getTaskForFile(file, chunk, options, compilation);

它為每一個目標文件,看情況創(chuàng)建一個task,創(chuàng)建了task的文件在末尾添加sourceMappingURL。

下面我們來看下,什么時候才會創(chuàng)建task,getTaskForFile的定義位于當前文件第25行,

const getTaskForFile = (file, chunk, options, compilation) => {
  ...
  if (asset.sourceAndMap) {
    const sourceAndMap = asset.sourceAndMap(options);
    sourceMap = sourceAndMap.map;
    source = sourceAndMap.source;
  } else {
    sourceMap = asset.map(options);
    source = asset.source();
  }
  if (sourceMap) {
    return {
      ...
    };
  }

  // 這里隱含了 return undefined;
};

可見,getTaskForFile會判斷if (sourceMap) {,
如果代碼壓縮階段沒有生成source map,則getTaskForFile就會返回undefined了,即不生成task

反之,如果當時生成了source map,就會在第273行,
向源碼尾部添加了sourceMappingURL

if (currentSourceMappingURLComment !== false) {
  assets[file] = compilation.assets[file] = new ConcatSource(
    new RawSource(source),
    currentSourceMappingURLComment.replace(
      /\[url\]/g,
      sourceMapUrl
    )
  );
}


其中,currentSourceMappingURLComment正是我們在SourceMapDevToolPlugin中配置的append值,

\n//# sourceMappingURL=http://127.0.0.1:8080/dist/[url]

參考

webpack 4.29.6
uglifyjs-webpack-plugin 2.1.2

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • GitChat技術(shù)雜談 前言 本文較長,為了節(jié)省你的閱讀時間,在文前列寫作思路如下: 什么是 webpack,它要...
    蕭玄辭閱讀 12,916評論 7 110
  • 1. 新建一個文件夾,命名為 webpack-cli , webpack-cli 就是你的項目名,項目名建議使用小...
    魯大師666閱讀 1,670評論 1 3
  • publicPath指定了一個在瀏覽器中被引用的URL地址。 對于使用 和 加載器,當文件路徑不同于他們的本地磁盤...
    飛呀飛哥閱讀 1,760評論 0 0
  • 前端將大型項目分成一個個單獨的模塊,一般封裝好的每個模塊都會實現(xiàn)一個目的明確的完成的功能。如何處理這些模塊以及模塊...
    pixels閱讀 3,516評論 1 14
  • 什么都可以原諒,唯獨背叛不能原諒。
    可樂_2c88閱讀 232評論 0 0

友情鏈接更多精彩內(nèi)容