前言:
我最近需要整理一下 webpack 这个前端构建工具的相关知识,希望对前端工程化的和模块化有更多的理解,我以前对 webpack打包机制感到非常的困惑,也没有深入的理解, 都是浅尝辄止, 最近看到了相关的文章介绍,并对
webpack
对js
打包有了深入的理解;
这篇文章会帮助你理解如下的问题:
1.webpack
当个文件如何进行打包?
2.webpack
多个文件如何进行代码切割?
3.webpack2
如何做到 tree shaking?
4.webpack3
如何做到 scope hoisting(提升)?
1.webpack
当个文件如何进行打包?
首先现在作为主流的前端模块化工具,在 webpack 刚刚才开始流行起来的时候,我们经常看到 webpack 将所有处理文件全部打包成一个
bundle
文件,现在我们看看下面的例子
// src/single/index.js let index2 = require("./index2"); let util = require("./util"); console.log(index2); console.log(util); // src/single/index2.js let util = require("./util"); console.log(util); module.exports = "index2 js"; // src/single/util.js module.exports = "hello liyao"; // 通过 webpack.config.js中的配置 const path = require("path"); const webpack = require("webpack"); module.exports = { entry: { index: [path.resolve(__dirname, "../src/single/index.js")] }, output: { path: path.resolve(__dirname, "../dist"), filename: "[name].[chunkhash:8].js" } };
那么我们将其使用 npm run build
来进行打包,我们将会得到下面的一段代码块
// dist/index.xxxx.js (function(modules) { // 已经加载过的模块 var installedModules = {}; // 模块加载函数 function __webpack_require__(moduleId) { if (installedModules[moduleId]) { return installedModules[moduleId].exports; } var module = (installedModules[moduleId] = { i: moduleId, l: false, exports: {} }); modules[moduleId].call( module.exports, module, module.exports, __webpack_require__ ); module.l = true; return module.exports; } return __webpack_require__((__webpack_require__.s = 3)); })([ /* 0 */ function(module, exports, __webpack_require__) { var util = __webpack_require__(1); console.log(util); module.exports = "index 2"; }, /* 1 */ function(module, exports) { module.exports = "Hello World"; }, /* 2 */ function(module, exports, __webpack_require__) { var index2 = __webpack_require__(0); index2 = __webpack_require__(0); var util = __webpack_require__(1); console.log(index2); console.log(util); }, /* 3 */ function(module, exports, __webpack_require__) { module.exports = __webpack_require__(2); } ]);
我们从上面知道 webpack 将所有的模块都包含在一个函数中,并传入默认的参数,这里有三个文件再加上一个入口模块一共有四个模块,将他们放在一个数组中,命名为
modules
, 并通过数组的下标来作为moduleId
;
将
modules
传入一个自执行的函数中, 自执行的函数中包含一个installModules
已经加载过的模块和一个模块加载函数, 最后加载入口模块并返回;
__webpack_raquire__ 模块加载, 先判断
installModules
中是否被加载过了,加载过了会直接就放回 exports 数据,没有加载过模块就通过 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 执行模块并且将 module.exports 给放回;
根据上面的解释, 我们需要注意的是:
-
每个模块 webpack 只会加载一次, 所以重复加载的模块只会执行一次,加载过的模块会放到 installModules, 下次需要该模块会从其中直接获取。
-
模块的 id 直接通过数组下标去一一对应的,这样保证简单唯一,通过其他的方式比如文件名或者是文件路径的方式就比较麻烦, 因为文件名可能会出现重名,不唯一,文件路径则会增大文件体积,并且将路径暴露给前端, 不够安全;
-
modules[moduleId].call(module.exports, module, module.exports, __module_require__), 保证了模块加载时, this 的指向 module.exports 并且传入默认的参数
webpack 多个文件如何进行代码切割?
webpack 单文件打包的方式应付一些简单场景就足够了, 但是我们在开发一些复杂的应用时,如果没有对代码进行切割,将第三方库 或 框架 和业务代码全部打包在一起的话,就会导致用户访问页面的速度很慢, 不能有效的利用缓存;那么网站的体验就会很差;
那么 webpack 多个文件入口如何进行代码切割呢?那么我们先来看下面一个例子:
// src/multiple/page1.js const utilA = require("./js/utilA"); // 模块B 被引用了两次:<1> const utilB = require("./js/utilB"); console.log(utilA); console.log(utilB); // src/multiple/page2.js const utilB = require("./js/utilB"); // <2>; console.log(utilB); // 异步加载文件, 类似于 import() const utilC = () => { return require.ensure(["./js/utilC"], function(require) { console.log(require("./js/utilC")); }); }; utilC(); // src/multiple/js/utilA.js 可类比于公共库; module.exports = "util A"; // src/multiple/js/utilB.js module.exports = "util B"; // src/multiple/js/utilC.js module.exports = "util C";
那么我们使用定义了两个入口 pageA 和 pageB 和第三个库 util, 那么我们希望做到:
-
两个入口都是用到 utilB, 我们希望把它抽离成单个文件,并且当用户访问 pageA 和 pageB 是时候都可 j 加载 utilB 这个公共的模块 e 而不是存在各自的入口文件中。
-
pageB 中 utilC 不是页面,一开始加载时候需 k 考虑的内容,假如 utilC 很大,我们不希望页面加载时就直接加载 utilC,而是当用户达到某个条件(如: 点击按钮) 才去异步加载 utilC, 这时候我们需将 utilC 抽离成单独的文件, 当用户需要的时候我们再去加载这个文件
那么我们的 webpack 的配置该如何的去配置呢?
// config/webpack.config.multiple.js 打包 const webpack = require("webpack"); const path = require("path"); module.exports = { entry: { pageA: [path.resolve(__dirname, "../src/multiple/pageA.js")], pageB: path.resolve(__dirname, "../src/multiple/pageB.js") }, output: { path: path.resolve(__dirname, "../dist"), filename: "[name].[chunkhash:8].js" }, plugins: [ new webpack.optimize.CommonChunkPlugin({ name: "vendor", minChunks: 2 }), new webpack.optimize.CommonChunkPlugin({ name: "manifest", chunks: ["vendor"] }) ] };
单个配置多个 entry 是不够的 -> 这样只会生成两个 bundle 文件,将 pageA 和 pageB 所需要的内容全部收入,跟单个入口文件并没有区别,要做到代码的切割,我们需要使用 webpackn 内置的插件
CommonsChunkPlugin
。首先 webpack 执行存在的一部分运行时代码,即一部分初始化工作,就像之前单文件中的 __webpack_require__, 这个部分需要加载于所有文件之前,相当于初始化工作,少了这部分初始化代码,后面加载过来的代码就无法识别并工作了。
// 这个代码的含义是,在这些入口文件中,找到那些 ***引用两次的模块*** (如:utilB), 那么 utilB 会抽离成一个叫 vendor 文件。此时那部分初始化工作的代码会被抽离到 vendor 文件中。
new webpack.optimize.CommonsChunkPlugin({ name: "vendor", minChunks: 2 });
// 这段代码的含义是在 vendor 文件中帮我把初始化代码抽离到 mainfest 文件中,此时 vendor 文件中就只剩下 utilB 这个模块了,那么我们为什么需要这么做呢?你可能会有这样的问题
new webpack.optimize.CommonsChunkPlugin({ name: "manifest", chunks: ["vendor"] // minChunks: Infinity 可写可不写 });
我们为什么需要将 webpack 的初始化模块的代码抽离到 manifest 文件中呢?
因为这样可以给 vendor 生成稳定的 hash 值。每次修改业务代码(pageA), 这段初始化代码会发生变化。那么如果将这段初始化代码放在 vendor 文件中的话。每次都会生成新的
vendor.xxxx.js, 这样不利于持久化缓存,好像对这个概念不是很理解。没关系,我们后面会讲到这个问题, 另外 webpack 默认会抽离异步加载代码。这个不需要做额外的配置,pageB 中异步加载的 utilC 文件会直接抽离为 chunk.xxxx.js 文件
那么这个时候我们的页面加载顺序会是:
manifest.xxx.js; // 初始化代码
vendor.xxx.js; // pageA 和 pageB 共同用到的模块,抽离
pageX.xxx.js; // 业务代码
// 当pageB 需要加载 utilC 的使用就会异步加载 utilC
那么我们执行 webpack 打包, 我们看到打包之后的内容,我们来看看 manifest 文件如何做到初始化的工作?
// dist/mainfest.xxx.js (function(modules) { // 在 window 对象中挂载了一个 webpack 的打包函数, 拿到 chunkIds, 和 modules // <函数1> window["webpackJsonp"] = function webpackJsonpCallback( chunkIds, moreModules ) { var moduleId, chunkId, i = 0, callbacks = []; for (; i < chunkIds.length; i++) { chunkId = chunkIds[i]; if (installedChunks[chunkId]) callbacks.push.apply(callbacks, installedChunks[chunkId]); installedChunks[chunkId] = 0; } for (moduleId in moreModules) { if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; } } while (callbacks.length) callbacks.shift().call(null, __webpack_require__); if (moreModules[0]) { installedModules[0] = 0; return __webpack_require__(0); } }; var installedModules = {}; var installedChunks = { 4: 0 }; // <函数2> function __webpack_require__(moduleId) { // 和单文件一致 } // requireEnsure __webpack_require__.e = function requireEnsure(chunkId, callback) { if (installedChunks[chunkId] === 0) return callback.call(null, __webpack_require__); if (installedChunks[chunkId] !== undefined) { installedChunks[chunkId].push(callback); } else { installedChunks[chunkId] = [callback]; var head = document.getElementsByTagName("head")[0]; var script = document.createElement("script"); script.type = "text/javascript"; script.charset = "utf-8"; script.async = true; script.src = __webpack_require__.p + "" + chunkId + "." + ({ "0": "pageA", "1": "pageB", "3": "vendor" }[chunkId] || chunkId) + "." + { "0": "e72ce7d4", "1": "69f6bbe3", "2": "9adbbaa0", "3": "53fa02a7" }[ chunkId ] + ".js"; head.appendChild(script); } }; })([]);
与单文件内容一致, 定义了一个自执行的函数,因为它不包含任何模块,所以传入的一个空数组,除了定义了一个 __webpack_require__ 加载的模块, 还另外定义了两个函数用来进行加载模块。
首先讲解代码前需要的两个概念: module 和 Chunk
- Chunk 代表生成后的
js 文件
,一个chunkId
对应一个打包好的 js 文件(共 5 个),从这段代码可以看出 manifest 的 chunkId 为 4,并且从代码中还可以看到 0-3 分别对应的 pageA, pageB,和异步加载的 utilC, vendor 公共模块,那么这个就是我们为什么不能将这段代码放在 vendor 的原因,因为文件的 hash 值会变,内容变了,vendor 生成的 hash 值也就变了 - module 对应的
模块
,可以简单的理解为打包前每个 js 文件对应的一个模块, 也就是之前 __webpack_require__ 加载模块, 同样使用数组下标作为 moduleId 且是唯一不重复的
那么为什么要区分 Chunk 和 module 呢?
首先使用 installedChunks 来保存每一个 chunkId 是否被加载过如果被加载过,则说该 chunk 中所包含的模块已经被放到 modules 中,注意的是 modules 而不是 installedModuls, 我们来看一下 vendor chunk 打包出来的内容:
// vendor.xxxx.js webpackJsonp([3, 4], { 3: function(module, exports) { module.exports = "util B"; } });
在执行完 manifest 后就会执行 vendor 文件,结合上面的 webpack.Jsonp 的定义,我们可以知道 [3,4] 代表 chunkId,当加载到 vendor 文件后,installedChunks[3] 和 installedChunks[4] 将会被变为 0, 这表明 chunk3 和 chunk4 被加载过了。
webpackJsonpCallback
一共存在两个参数, chunkIds 一般会包含 chunk 文件依赖的 chunkId 以及自身 chunkId,moreModules 代表的 chunk 文件带来新的模块
;
var moduleId, chunkId, i = 0, callbacks = []; for (; i < chunkIds.length; i++) { chunkId = chunkIds[i]; if (installedChunks[chunkId]) callbacks.push.apply(callbacks, installedChunks[chunkId]); installedChunks[chunkId] = 0; } for (moduleId in moreModules) { if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; } } while (callbacks.length) callbacks.shift().call(null, __webpack_require__); if (moreModules[0]) { installedModules[0] = 0; return __webpack_require__(0); }
简简单单来说 webpacJsonpCallback 做了哪些事情
-
首先判断 chunkIds 在 installedChunks 里没有回调函数未执行完, 有的话则放到 callbacks 中, 并且等一下统一执行,j 将 chunkIds 在 installedChunks 中全部表为 0, 然后将 moreModules 合并到 modules 中。
-
这里面只有 mouules[0] 不是固定的,其他 modules 下标都是唯一的,在打包的时候 webpack 已经为他们统一编号,而 0 则为入口文件即 pageA 和 pageB 各有一个 modules[0]。
-
将 callbacks 执行完毕并清空, 保证了该模块加载开始前所有前置依赖内容都会加载完毕。最后判断 morMModules[0]。有值说明该文件为入口文件,则开始执行入口文件的模块
那么上面的解释,好像 pageA 这种同步加载 manifest, vendor 以及 pageA 文件来说,每次加载的时候 callbacks 都是为空的,因为他们在 installedChunks 中的值要么为 uundefined(未加载), 要么为 0 (已被加载), installedChunks[chunkId] 的值永远为 false, 所以在这种情况下 callbacks 里面不会出现函数,如果仅仅是考虑这样的场景,上面的 webpacJsonCallback 完全可以改为下面这样
var moduleId, chunkId, i = 0, callbacks = []; for (; i < chunkIds.length; i++) { chunkId = chunkIds[i]; installedChunks[chunkId] = 0; } for (moduleId in moreModules) { if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; } } if (moreModules[0]) { installedModules[0] = 0; return __webpack_require__(0); }
我们来看看异步加载 js 文件的时候(比如 pageB 中异步加载了 utilC 文件), 那么这样就没有这么简单了,我们来看看 webpack 是如何异步加载脚本的:
// 异步加载函数挂载在 __webpack_require__.e 上 __webpack_require__.e = function requireEnsure(chunkId, callback) { var installedChunkData = installedChunks[chunkId]; if (installedChunkData === 0) { return new Promise(function(resolve) { resolve(); }); } // a Promise means "currently loading". if (installedChunkData) { return installedChunkData[2]; } // setup Promise in chunk cache var promise = new Promise(function(resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; }); installedChunkData[2] = promise; // start chunk loading var head = document.getElementsByTagName("head")[0]; var script = document.createElement("script"); script.type = "text/javascript"; script.charset = "utf-8"; script.async = true; script.timeout = 120000; if (__webpack_require__.nc) { script.setAttribute("nonce", __webpack_require__.nc); } script.src = __webpack_require__.p + "" + chunkId + "." + { "0": "15fddb8c", "1": "9b6201b1", "2": "292dfd7b", "3": "167999ad" }[ chunkId ] + ".js"; var timeout = setTimeout(onScriptComplete, 120000); script.onerror = script.onload = onScriptComplete; function onScriptComplete() { // avoid mem leaks in IE. script.onerror = script.onload = null; clearTimeout(timeout); var chunk = installedChunks[chunkId]; if (chunk !== 0) { if (chunk) { chunk[1](new Error("Loading chunk " + chunkId + " failed.")); } // 将该 chunk 变为未加载 installedChunks[chunkId] = undefined; } } head.appendChild(script); return promise; };
我们看到上面的异步加载,可以看出 webpack 是基于 promise 的, 所以需要引入 promise-polyfill,当脚本请求超时或者是加载失败,会将 installedChunks[chunkId] 清空, 当下次重新请求该 chunk 文件会重新加载,提高页面的容错性
大致分为三种情况(已经加载过,正在加载中以及从未加载过)
-
已经加载过该 Chunk 文件, 那就不用重新加载该 Chunk 了, 直接执行回调函数即可,可以理解假如页面中有两种操作需要加载
异步脚本,但是两个脚本都依赖于公共模块, 那么第二次加载的时候发现之前第一次操作已经加载过了该 Chunk, 则不用获取异步脚本了, 以为该公共模块已经被执行过了。 -
从未被加载过,则动态的去插入 script 脚本去请求 js 文件, 这就为什么取名 webpackJsonpCallback, 因为跟 jsonp 的思想很类型, 所以这种异步脚本在做脚本错误监控时会经常出现
script error
-
正在加载中代表该 Chunk 文件已经在加载中,比如说点击按钮触发异步脚本,用户点击太快了,连点两次就可能会出现这种情况,此时将回调函数放入 installedChunks。
我们通过 utiC 生成的 Chunk:
webpackJsonp( [0], [ , /* 0 */ /* 1 */ /***/ function(module, exports) { module.exports = "utils C"; /***/ } ] );
//那么需要异步加载这个 Chunk:
/***/ 6: /***/ (function(module, exports, __webpack_require__) { const utilB = __webpack_require__(0); console.log(utilB); // 异步加载文件,类似于 import() const utilC = () => __webpack_require__.e/* require.ensure */(0).then((function (require) { console.log(__webpack_require__(1)) }).bind(null, __webpack_require__)).catch(__webpack_require__.oe); utilC(); /***/ })
当 pageB 进行某些操作需要加载 utilC 时,就会执行 __webpack_require__.e(0).then(xxx), 代表需要加载的模块 chunkId(utilC), 异步加载 utilC 并将 callback 添加到 installedChunks 中, 然后当 utilC 的 Chunk 文件加载完毕后,chunkids 包含 0, 发 iinstalledChunks[2] 是一个数组, 里面还有之前未执行的 callback 函数,那么这样,那我们将自己带来的模块先放到 modules 中,然后在统一执行之前未完成的 callbacks 中的函数,这里指的是存放于 installedChunks[2] 中回调函数(可能存在多个),
这也说明这里先后顺序
:
// 先将 moreModules 合并到 modules, 再去执行 callbacks, 不然之前未执行的 callback 依赖于新来的模块,你不放进 module 我岂不是得不到想要的模块
for (moduleId in moreModules) { if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; } } while (callbacks.length) callbacks.shift().call(null, __webpack_require__);
在 webpack2 或者之后的版本中:
在 version2 中 moduleId[0] 不在为入口函数做保留,所以说明 moduleId[0] 不在是入口打包函数,取而代之的是
window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {}
那么传入的第三个参数 executeModules, 这个数组, 如果参数存在则说明它是入口函数模块, 然后在去执行
那么 webpack 的 tree shaking
(官方解释)
这里简单解释下, tree shaking 在打包过程中将没有用的代码进行清除(dead code), 一般 dead code 具有一下特征:
- 代码不会被执行, 不可到达
- 代码执行结果不会被用到
- 代码只会影响死变量(只写不读)
首先,模块引入要基于 ES6 模块机制,不在使用 commonjs 规范,因为 es6 模块的依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,然后清除没有用的代码, 而 commonjs 的依赖关系是要到运行时才能确定下来,其次需要开始 js 压缩, 使用 UglifysPlugin 这个插件对代码进行压缩,我们看下面的例子:
// webpack.config.js const webpack = require("webpack"); const path = require("path"); module.exports = { entry: { pageA: path.resolve(__dirname, "../src/es6/pageA.js") }, output: { path: path.resolve(__dirname, "../dist"), filename: "[name].[chunkhash:8].js" }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: "manifest", minChunks: Infinity }), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }) ] };
那么我们引入没有用的变量,函数会被清除,未执行的代码会被清除,但是类的方法不会被清除, 因为 webpack 不会区分是定义在 classC 的 prototype 还是其他 Array 的 prototype 的,比如将 classC 写成这样,
webpack 无法保证 prototype 挂载的对象是 classC, 这种代码,静态分析是完成不了的,就算可以,不能保证完全正确,所以 webpack 干脆不处理方法,不对类进行 tree shaking
const classC = function() {}; var a = "class" + "C"; var b; if (a === "Array") { b = a; } else { b = "classC"; } b.prototype.saySomething = function() { console.log("class C"); }; export default classC;
那么 webpack3 是如何做到 scope hoisting (作用域提升)
scope hoisting, 顾名思义就是将模块的作用域提升,在 webpack 中不能将所有的模块直接放在同一个作用下, 有一下几个原因:
- 按需加载的模块
- 使用 commonjs 规范的模块
- 被多个 entry 共享的模块
在 webpack3 中, 这些情况生成的模块不会进行作用域提升,下面个例子:
我们看这个例子比较典型, utilA 被 pageA 和 pageB 共享,utilB 被 pageB 单独加载, utilC 被 pageB 异步加载。那么 webpack3 生效,则需要 plugins 中添加 ModuleConcatenationPlugin。
// src/hoist/utilA.js export const utilA = "util A"; export function funcA() { console.log("func A"); } // src/hoist/utilB.js export const utilB = "util B"; export function funcB() { console.log("func B"); } // src/hoist/utilC.js export const utilC = "util C"; // src/hoist/pageA.js import { utilA, funcA } from "./utilA"; console.log(utilA); funcA(); // src/hoist/pageB.js import { utilA } from "./utilA"; import { utilB, funcB } from "./utilB"; funcB(); import("./utilC").then(function(utilC) { console.log(utilC); });
我们来看看这个配置如下:
const webpack = require("webpack"); const path = require("path"); module.exports = { entry: { pageA: path.resolve(__dirname, "../src/hoist/pageA.js"), pageB: path.resolve(__dirname, "../src/hoist/pageB.js") }, output: { path: path.resolve(__dirname, "../dist"), filename: "[name].[chunkhash:8].js" }, plugins: [ // 需要使用这个插件 new webpack.optimize.ModuleConcatenationPlugin(), new webpack.optimize.CommonsChunkPlugin({ name: "vendor", minChunks: 2 }), new webpack.optimize.CommonsChunkPlugin({ name: "manifest", minChunks: Infinity }) ] };
那么在 webpack4 中有那些新的东西呢?
- 配置默认初始化一个些配置, 比如: entry 默认是 ./src
- 开发模式和发布模式, 插件默认内置
- CommonsChunk 配置简化
- 使用 ES6 语法,比如: Map, Set, includes
- 新增 WebAssembly 构建支持
- 如果要使用 webpack cli 命令, 需要单独安装 webpack-cli
默认配置:
在webpack4 中不再要求强制指定 entry 和 output 路径, 在 webpack4 会默认使用 ./src (entry), ./dist (output);
构建mode:
webpack4 配置, 必p配置 mode 属性,k可选值有 development, production, none,
-
development 默认开启插件(无需配置):
-
NamedModulesPlugin > optimization.namedModules
-
development 模式, 使用 eval 构建 module, 用来提升构建速度
-
webpack.DefinePlugin 插件 process.env.NODE_ENV 的值不需要再定义, 默认是 development
-
production 默认开启插件(无需配置):
-
NoEmitOnErrorsPlugin > optimization.noEmitOnErrors
-
ModuleCOncatenationPlugin > optimization.concatenateModules
-
webpack.DefinePlugin 插件 process.env.NODE_ENV 的值不需要再定义, 默认是 production
公共代码提取:
webpack3 的 commonsChunk hash 问题不是很优雅, 使用复杂, webpack4 中直接将 CommonsChunkPlugin
插件直接改为
optimization.splitChunks
和 optmization.runTimeChunk
两个配置
-
webapck3: plugins:[ new webpack.optimize.CommonsChunkPlugin({ names: 'common'}), new webpack.optimize.CommonsChunkPlugin({ name: 'runtime', chunks:['common']}) ] webpack4 optimization: { splitChunks: { chunks: 'all', name: 'common', }, runtimeChunk: { name: 'runtime', } }
压缩
压缩插件更新到 uglifyjs-webpack-plugin 1.0 版本,支持多进程压缩,缓存以及 es6 语法, 无需单独安装转换器, 当
mode='production'
默认开始时压缩, 无需配置, 可以通过, ``optimization.minimize 和 optimization.minimizer``` 自定义配置, 测试发现,第二次打包时间是第一次打包的一半左右
. optimization.minimize 是否启用压缩
. optimizatioon.minimizer 制定压缩库, 默认 uglifyjs-webpack-plugin 1.0
optimization: { minimize:true }
配置优化
webpack4 提供了 sideEffects 的配置, 引入的第三方插件在 package.json 里面配置。sideEffects: fasle, 后, 据说是可以大幅度的减少打包出的体积; 目前初步了解 sideEffects 的信息: sideEffects: false, 标示该模块无副作用,当你需要导入,但不需要导出任何东西时,但需要导入时
【未完待续.......】
作者:Joah_l
链接:https://www.jianshu.com/p/e49306b87a28
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。