如何使用 webpack 尽可能减小应用的大小
优化应用时,首先要做的就是让其体积尽可能小 下面介绍了如何使用 Webpack 执行此操作。
使用生产模式(仅限 webpack 4)
Webpack 4 引入了新的 mode
标志。您可以设置
将此标志设置为 'development'
或 'production'
,以提示您正在构建的 Webpack
特定环境的应用:
// webpack.config.js
module.exports = {
mode: 'production',
};
在构建正式版应用时,请务必启用 production
模式。
这会使 webpack 应用优化,例如缩减大小、移除开发专用代码
等。
深入阅读
启用缩减大小功能
缩减大小是指通过移除多余的空格、缩短变量名称和 依此类推。示例如下:
// Original code
function map(array, iteratee) {
let index = -1;
const length = array == null ? 0 : array.length;
const result = new Array(length);
while (++index < length) {
result[index] = iteratee(array[index], index, array);
}
return result;
}
↓
// Minified code
function map(n,r){let t=-1;for(const a=null==n?0:n.length,l=Array(a);++t<a;)l[t]=r(n[t],t,n);return l}
Webpack 支持两种缩减代码的方法:软件包级缩减和 特定于加载程序的选项。这两种模式应同时使用。
软件包级缩减大小
软件包级缩减大小会在编译后压缩整个软件包。具体流程如下:
您编写如下代码:
// comments.js import './comments.css'; export function render(data, target) { console.log('Rendered!'); }
Webpack 将其编译为大致如下代码:
// bundle.js (part of) "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony export (immutable) */ __webpack_exports__["render"] = render; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__comments_css__ = __webpack_require__(1); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__comments_css_js___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__comments_css__); function render(data, target) { console.log('Rendered!'); }
压缩器会将其压缩成大致如下:
// minified bundle.js (part of) "use strict";function t(e,n){console.log("Rendered!")} Object.defineProperty(n,"__esModule",{value:!0}),n.render=t;var o=r(1);r.n(o)
在 Webpack 4 中,在正式版中,系统会自动启用软件包级缩减大小
也不使用这种模式它使用 UglifyJS 缩减器
功能。(如果您需要停用缩减大小功能,只需使用开发模式
或者将 false
传递给 optimization.minimize
选项。)
在 webpack 3 中,您需要使用 UglifyJS 插件
。该插件与 webpack 捆绑在一起:如需启用,请将其添加到 plugins
部分:
// webpack.config.js
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.optimize.UglifyJsPlugin(),
],
};
特定于加载程序的选项
第二种缩减代码的方法是特定于加载器的选项(
is)。借助加载程序选项,您可以
因为缩减器无法缩减大小例如,当您导入包含
css-loader
,该文件将被编译为一个字符串:
/* comments.css */
.comment {
color: black;
}
// minified bundle.js (part of)
exports=module.exports=__webpack_require__(1)(),
exports.push([module.i,".comment {\r\n color: black;\r\n}",""]);
压缩工具无法压缩此代码,因为它是一个字符串。为了缩小文件内容,我们需要 配置加载器以执行此操作:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{ loader: 'css-loader', options: { minimize: true } },
],
},
],
},
};
深入阅读
- UglifyJsPlugin 文档
- 其他热门的压缩工具:Babel 缩减大小、Google Closure 编译器
指定 NODE_ENV=production
减小前端大小的另一种方法是设置 NODE_ENV
环境变量
设置为 production
值。
库会读取 NODE_ENV
变量来检测它们应在哪种模式下运行,即在
无论是开发阶段还是生产阶段某些库的行为会因此变量而异。对于
例如,当 NODE_ENV
未设置为 production
时,Vue.js 会进行额外的检查并输出结果
警告:
// vue/dist/vue.runtime.esm.js
// …
if (process.env.NODE_ENV !== 'production') {
warn('props must be strings when using array syntax.');
}
// …
React 的工作方式与之类似,它会加载包含警告的开发 build:
// react/index.js
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react.production.min.js');
} else {
module.exports = require('./cjs/react.development.js');
}
// react/cjs/react.development.js
// …
warning$3(
componentClass.getDefaultProps.isReactClassApproved,
'getDefaultProps is only used on classic React.createClass ' +
'definitions. Use a static property named `defaultProps` instead.'
);
// …
此类检查和警告在生产环境中通常没有必要,但它们会保留在代码中,
增加库的大小在 Webpack 4 中,通过添加
optimization.nodeEnv: 'production'
选项:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
nodeEnv: 'production',
minimize: true,
},
};
在 webpack 3 中,请改用 DefinePlugin
:
// webpack.config.js (for webpack 3)
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
}),
new webpack.optimize.UglifyJsPlugin()
]
};
optimization.nodeEnv
选项和 DefinePlugin
的工作方式相同:
它们会将出现的所有 process.env.NODE_ENV
替换为指定的值。使用
config:
Webpack 会将所有出现的
process.env.NODE_ENV
替换为"production"
:// vue/dist/vue.runtime.esm.js if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; } else if (process.env.NODE_ENV !== 'production') { warn('props must be strings when using array syntax.'); }
↓
// vue/dist/vue.runtime.esm.js if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; } else if ("production" !== 'production') { warn('props must be strings when using array syntax.'); }
然后,Minifier会将所有此类对象
if
分支,因为"production" !== 'production'
始终为 false, 并且插件理解这些分支中的代码永远不会执行:// vue/dist/vue.runtime.esm.js if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; } else if ("production" !== 'production') { warn('props must be strings when using array syntax.'); }
↓
// vue/dist/vue.runtime.esm.js (without minification) if (typeof val === 'string') { name = camelize(val); res[name] = { type: null }; }
深入阅读
使用 ES 模块
减小前端大小的另一种方法是使用 ES 模块。
当您使用 ES 模块时,webpack 能够执行摇树优化。“摇树优化”是指 遍历整个依赖项树,检查使用了哪些依赖项,并移除未使用的依赖项。因此, 如果您使用 ES 模块语法,webpack 可以消除未使用的代码:
您编写了一个包含多个导出的文件,但应用仅使用其中一个:
// comments.js export const render = () => { return 'Rendered!'; }; export const commentRestEndpoint = '/rest/comments'; // index.js import { render } from './comments.js'; render();
Webpack 发现
commentRestEndpoint
不会被使用,也不会生成 在 bundle 中单独导出一个导出点:// bundle.js (part that corresponds to comments.js) (function(module, __webpack_exports__, __webpack_require__) { "use strict"; const render = () => { return 'Rendered!'; }; /* harmony export (immutable) */ __webpack_exports__["a"] = render; const commentRestEndpoint = '/rest/comments'; /* unused harmony export commentRestEndpoint */ })
缩减器会移除未使用的变量:
// bundle.js (part that corresponds to comments.js) (function(n,e){"use strict";var r=function(){return"Rendered!"};e.b=r})
即使库是使用 ES 模块编写的,也是如此。
不过,您并不一定要使用 webpack 的内置缩减器 (UglifyJsPlugin
)。
任何支持移除死代码的压缩工具
(例如 Babel Minify 插件)
或 Google Closure 编译器插件)
就可以做到这一点
深入阅读
Webpack 文档:关于摇树优化
优化图像
图片会带来不止
页面大小的一半。虽然他们
不像 JavaScript 那么重要(例如,它们不会阻塞渲染),但它们仍然占有很大一部分
带宽。使用url-loader
、svg-url-loader
和image-webpack-loader
进行优化
webpack。
url-loader
将小型静态文件内嵌到
应用。如果不进行配置,则获取传递的文件,将其放在已编译的 bundle 旁边,然后返回
该文件的网址。不过,如果我们指定 limit
选项,它会对小于或等于
Base64 数据网址,并返回此网址。这个
将图片内嵌到 JavaScript 代码中并保存 HTTP 请求:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif)$/,
loader: 'url-loader',
options: {
// Inline files smaller than 10 kB (10240 bytes)
limit: 10 * 1024,
},
},
],
}
};
// index.js
import imageUrl from './image.png';
// → If image.png is smaller than 10 kB, `imageUrl` will include
// the encoded image: 'data:image/png;base64,iVBORw0KGg…'
// → If image.png is larger than 10 kB, the loader will create a new file,
// and `imageUrl` will include its url: `/2fcd56a1920be.png`
svg-url-loader
的工作方式与 url-loader
类似,
只不过它会对包含网址
编码,而不是 Base64
一个。这对于 SVG 图片非常有用,因为 SVG 文件只是纯文本,所以此编码
规模效益更高
module.exports = {
module: {
rules: [
{
test: /\.svg$/,
loader: "svg-url-loader",
options: {
limit: 10 * 1024,
noquotes: true
}
}
]
}
};
image-webpack-loader
可压缩
它支持 JPG、PNG、GIF 和 SVG 图片,因此我们将将其用于所有这些类型。
此加载器不会将图片嵌入应用,因此它必须与 url-loader
和
svg-url-loader
。要避免将其复制并粘贴到两个规则(一个用于 JPG/PNG/GIF 图片,另一个用于
一个是 SVG 格式),我们将将此加载器作为单独的规则添加到 enforce: 'pre'
中:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/,
loader: 'image-webpack-loader',
// This will apply the loader before the other ones
enforce: 'pre'
}
]
}
};
加载器的默认设置已经可以使用,但如果您想对其进行配置, 请参阅插件选项。接收者 选择要指定哪些选项,请查看 Addy Osmani 提供的优质图片指南 优化。
深入阅读
- “什么是 base64 编码?” 用途?”
- Addy Osmani 的图片优化指南
优化依赖项
JavaScript 平均大小有一半以上来自依赖项,而其中一部分可能 没必要这样做。
例如,Lodash(从 v4.17.4 开始)会在 bundle 中添加 72 KB 的缩减代码。但如果您只使用 比如 20 个方法,那么大约 65 KB 的压缩后代码将什么都不做。
另一个例子是 Moment.js。其 2.19.1 版需要 223 KB 的缩减代码,这个大小非常大: 网页的平均 JavaScript 大小在 10 月份为 452 KB 2017 年。然而,该大小的 170 KB 是本地化 文件。如果 您没有将 Moment.js 与多种语言搭配使用, 目的。
所有这些依赖项都可以轻松优化。我们收集了多种优化方法, GitHub 代码库 – 查看!
为 ES 模块启用模块串联(也称为作用域提升)
在构建 bundle 时,webpack 将每个模块封装到一个函数中:
// index.js
import {render} from './comments.js';
render();
// comments.js
export function render(data, target) {
console.log('Rendered!');
}
↓
// bundle.js (part of)
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
var __WEBPACK_IMPORTED_MODULE_0__comments_js__ = __webpack_require__(1);
Object(__WEBPACK_IMPORTED_MODULE_0__comments_js__["a" /* render */])();
}),
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_exports__["a"] = render;
function render(data, target) {
console.log('Rendered!');
}
})
过去,需要将 CommonJS/AMD 模块彼此隔离开来。不过,这增加了 每个模块的大小和性能开销
Webpack 2 引入了对 ES 模块的支持,与 CommonJS 和 AMD 模块不同,该模块可以捆绑 而无需使用函数封装每个对象。webpack 3 让这种捆绑成为可能, 模块串联。以下是 模块串联的用途:
// index.js
import {render} from './comments.js';
render();
// comments.js
export function render(data, target) {
console.log('Rendered!');
}
↓
// Unlike the previous snippet, this bundle has only one module
// which includes the code from both files
// bundle.js (part of; compiled with ModuleConcatenationPlugin)
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
// CONCATENATED MODULE: ./comments.js
function render(data, target) {
console.log('Rendered!');
}
// CONCATENATED MODULE: ./index.js
render();
})
看到了区别?在普通软件包中,模块 0 需要模块 1 中的 render
。包含
模块串联,require
直接替换为所需的函数,模块 1 则
已移除。该 bundle 的模块更少,模块开销也更少!
如需启用此行为,请在 webpack 4 中启用 optimization.concatenateModules
选项:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
concatenateModules: true
}
};
在 webpack 3 中,请使用 ModuleConcatenationPlugin
:
// webpack.config.js (for webpack 3)
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
]
};
深入阅读
如果您同时拥有 webpack 和非 webpack 代码,请使用 externals
您可能有一个大型项目,其中一些代码是使用 webpack 编译的,而另一些代码则不是。点赞 一个视频托管网站(其中播放器微件可能是用网页包构建的), 可能不是:
<ph type="x-smartling-placeholder">如果这两段代码有共同的依赖项,那么您可以共享它们,以免下载其代码
。这是通过 Webpack 的 externals
实现的
选项 - 它会用变量或
其他外部导入
如果 window
中有依赖项
如果您的非 Webpack 代码依赖于可作为 window
中的变量提供的依赖项,请将别名设为
从依赖项名称更改为变量名称:
// webpack.config.js
module.exports = {
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
}
};
使用此配置时,Webpack 不会捆绑 react
和 react-dom
软件包。相反,它们将被
替换为如下内容:
// bundle.js (part of)
(function(module, exports) {
// A module that exports `window.React`. Without `externals`,
// this module would include the whole React bundle
module.exports = React;
}),
(function(module, exports) {
// A module that exports `window.ReactDOM`. Without `externals`,
// this module would include the whole ReactDOM bundle
module.exports = ReactDOM;
})
如果依赖项作为 AMD 软件包加载
如果您的非 Webpack 代码不会向 window
公开依赖项,那么情况就会更加复杂。
不过,如果非 webpack 代码使用同一个代码,您仍然可以避免
依赖项作为 AMD 软件包。
为此,请将 webpack 代码编译为 AMD 捆绑包,并将别名模块编译为库网址:
// webpack.config.js
module.exports = {
output: {
libraryTarget: 'amd'
},
externals: {
'react': {
amd: '/libraries/react.min.js'
},
'react-dom': {
amd: '/libraries/react-dom.min.js'
}
}
};
Webpack 会将该 bundle 封装到 define()
中,并使其依赖于以下网址:
// bundle.js (beginning)
define(["/libraries/react.min.js", "/libraries/react-dom.min.js"], function () { … });
如果非 webpack 代码使用相同的网址加载其依赖项,则系统将加载这些文件 仅一次 – 其他请求将使用加载器缓存。
深入阅读
externals
上的 Webpack 文档
汇总
- 如果您使用 webpack 4,请启用生产模式
- 使用软件包级缩减器和加载器选项最大限度地缩减代码
- 通过将
NODE_ENV
替换为production
来移除开发专用代码 - 使用 ES 模块启用摇树优化
- 压缩图片
- 应用依赖项专用优化
- 启用模块串联
- 如果需要,请使用
externals