前言:
我最近需要整理一下 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, 标示该模块无副作用,当你需要导入,但不需要导出任何东西时,但需要导入时
【未完待续.......】
网友评论