webpack学习记录(1)-mode模式

2,326 阅读4分钟

1.概述

在webpack4中,提供mode配置选项,告知webpack使用相应模式的内置优化。

mode配置有3个参数productiondevelopmentnone,默认为production。其中none很好理解,什么都没有,没有进行任何预设,而另外两个都是有预设插件使用。

我们这里就来研究下配置这些参数,都执行了什么事情。

2.none模式下的打包

none模式下,webpack打包出来的文件(注1)如下:

// index.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],{
/***/ 1:
/***/ (function(module, exports, __webpack_require__) {

__webpack_require__(2);

var hotClient = __webpack_require__(4);
...

/***/ }),

/***/ 111:
/***/ (function(module, exports, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _app_vue_vue_type_template_id_35385eed___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(112);
...

/***/ }),

},[[0,0,2,4,3]]]);

我们可以看见webpack将模块放置到一个数组中,并给与一个id。调用某个模块,就是调用这个模块在这个数组中的id。

但是,相对于development开发环境来说,代码的可读性不够友好;对于production正式环境来说,代码不够简洁。所以,我们在developmentproduction配置中,默认添加了一些插件来对代码进行加强。

注1: 为了便于代码阅读,我设置webpack的devtool的值为source-map,下同。

3.development模式下的打包

在官网中,对development模式的描述如下:

会将 process.env.NODE_ENV 的值设为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。

即等同于执行了以下的插件:

// webpack.dev.config.js
module.exports = {
+ mode: 'development'
- plugins: [
-   new webpack.DefinePlugin({ "process.env.NODE_ENV": '"development"' }),
-   new webpack.NamedModulesPlugin(),
-   new webpack.NamedChunksPlugin(),
- ]
}

3.1 设置process.env.NODE_ENV

modedevelopment的状态下,为了便于我们在项目中获取当前的模式,webpack会将全局变量process.env.NODE_ENV,并赋值为当前mode的值。等同于我们在webpack中添加插件new webpack.DefinePlugin({ "process.env.NODE_ENV": '"development"' })

3.2 NamedModulesPlugin

当开启 HMR 的时候使用该插件会显示模块的相对路径,建议用于开发环境。

原来webpack打包时,将模块与id进行关联,新增减模块都会导致id的变化。但我们使用了插件new webpack.NamedModulesPlugin(),则将模块与其名称进行关联,这样不管新增减多少模块,模块的序号都是固定的

// index.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[3],{

// 同上1
/***/ "./build/dev-client.js": 
/***/ (function(module, exports, __webpack_require__) {

__webpack_require__("./node_modules/eventsource-polyfill/dist/browserify-eventsource.js");
...

/***/ }),

// 同上111
/***/ "./src/app.vue":
/***/ (function(module, exports, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _app_vue_vue_type_template_id_35385eed___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/app.vue?vue&type=template&id=35385eed&");
...

/***/ }),

},[[0,0,4,9,2]]]);

3.3 NamedChunksPlugin

与每个模块都有一个id一样,在webpack中,每个chunk也有一个对应的id。在打包过程中,我们可以看见输出的chunk信息。

如果我们使用了runtimeChunk,我们也可以看见生成的runtime.js中关于chunk的获取逻辑:

// webpack.dev.config.js
module.exports = {
    optimization: {
        runtimeChunk: {
            name: 'runtime',
        },
    }
}

// runtime.js
/******/ 	function jsonpScriptSrc(chunkId) {
/******/ 		return __webpack_require__.p + "js/" + ({"2":"vendors", "9":"main"}[chunkId]||chunkId) + ".js"
/******/ 	}

如果我们使用了插件new webpack.NamedChunksPlugin(),则会用chunk的名称来替换其id,实现chunkid固化。

runtime.js中,我们也可以看见chunk的获取逻辑的改变:

/******/ 	function jsonpScriptSrc(chunkId) {
/******/ 		return __webpack_require__.p + "js/" + ({"vendors":"vendors","main":"main"}[chunkId]||chunkId) + ".js"
/******/ 	}

4.production模式下的打包

在官网中,对production模式的描述如下:

会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 TerserPlugin.

即等同于执行了以下的插件:

// webpack.pord.config.js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
+ mode: 'production'
- plugins: [
-   new webpack.DefinePlugin({ "process.env.NODE_ENV": '"production"' }),
-   new webpack.optimize.ModuleConcatenationPlugin(),
-   new webpack.NoEmitOnErrorsPlugin(),
-   new TerserPlugin(/* ... */),
- ]
}

4.1 设置process.env.NODE_ENV

这里的逻辑同3.1,设置process.env.NODE_ENV的值为production

4.2 OccurrenceOrderPlugin

原来名称为OccurenceOrderPluginwebpack.optimize.occurrenceOrder用于为模块分配id,通过这个插件webpack会分析使用频率最多的模块,并为其分配最小的id,这样模块会被更快找到。

// webpack.pord.config.js
module.exports = {
  optimization: {
    occurrenceOrder: true
  }
};

4.3 NoEmitOnErrorsPlugin

new webpack.NoEmitOnErrorsPlugin()插件可以用于防止程序报错。

在编译出现错误时,使用 NoEmitOnErrorsPlugin 来跳过输出阶段。这样可以确保输出资源不会包含错误。

4.4 ModuleConcatenationPlugin

使用new webpack.optimize.ModuleConcatenationPlugin()可用于实现作用域提升(Scope Hoisting),让Webpack打包出来的代码文件更小、运行的更快。

4.5 SideEffectsFlagPlugin

webpack.optimization.sideEffects用于实现treeshaking形式的死码删除。而为了实现treeshaking,需要满足几个条件:

  • 导入的模块已经标记了sideEffect,即package.json中的sideEffects这个属性为false。
  • 当前模块引用了无副作用的模块,且没有被使用

这样,经过SideEffectsFlagPlugin处理后,没有副作用且没有被使用的模块都会被打上sideEffectFree标记。 在ModuleConcatenationPlugin中,带着sideEffectFree标记的模块将不会被打包。

// webpack.pord.config.js
module.exports = {
  optimization: {
    sideEffects: true
  }
};

4.6 TerserPlugin

用于js代码压缩。在以前版本中,我们需要引入npm包terser-webpack-plugin来进行压缩,现在我们可以在optimize中进行配置达到同样的效果。

// webpack.pord.config.js
module.exports = {
    optimization: {
        minimize: true
    }
}

// js压缩
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
    optimization: {
        minimizer: [
            new TerserPlugin({
                cache: true,
                parallel: true,
                sourceMap: true, // Must be set to true if using source-maps in production
                terserOptions: {
                  // https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
                }
            }),
        ]
    }
}

4.7 FlagIncludedChunksPlugin

即配置optimization.flagIncludedChunks。该配置项会使webpack确认,若当前标记的chunka是另外一个chunkA的子集并且已经A加载完成,则a将不会再次加载(包含关系)。

告知 webpack 确定和标记出作为其他 chunk 子集的那些 chunk,其方式是在已经加载过较大的 chunk 之后,就不再去加载这些 chunk 子集。optimization.flagIncludedChunks 默认会在 production mode 中启用,其他情况禁用。

// webpack.pord.config.js
module.exports = {
  optimization: {
    flagIncludedChunks: true
  }
};

4.8 FlagDependencyUsagePlugin

标记没有用到的依赖。

注:还没有找到该插件的收益。

5.相关链接