- webpack概述
- 入口起点(Entry Points)
- 输出(Output)
- 模块(Mode)
- 加载器(Loaders)
- 插件(Plugins)
- 配置(Configuration)
- 模块(Modules)
- 模块解析(Module Resolution)
- 依赖图表(Dependency Graph)
- 文件清单(Manifest)
- 构建目标(Targets)
- 模块热替换(Hot Module Replacement)
- 第二部分:配置
- 使用不同语言进行配置(Configuration Languages)
- 多种配置类型
- 入口和上下文(Entry and Context)
- 输出(Output)
- 模块(Module)
- 解析(Resolve)
- 插件(Plugins)
- 开发中 Server(DevServer)
- 开发工具(Devtool)
- 构建目标(Targets)
- Watch 和 WatchOptions
- 外部扩展(Externals)
- 性能(Performance)
- Node
- 统计(Stats)
- 其它选项(Other Options)
- 第三部分:API
- 命令行接口(CLI)
- 包含统计数据的文件(stats data)
- Node.js API
- 模块热替换(Hot Module Replacement)
- 加载器 API
- 模块方法(module methods)
- 模块变量(module variables)
- Plugin API
- compiler 钩子
- compilation 钩子
- resolver
- parser
- 第四部分:指南
- 安装
- 起步
- 管理资源
- 管理输出
- 开发
- 模块热替换
- Tree shaking
- 生产环境构建
- 代码拆分(Code Splitting)
- 懒加载(Lazy Loading)
- 缓存(Caching)
- 创建库 (Library)
- Shimming
- 渐进式网络应用程序
- TypeScript
- 迁移到新版本
- 使用环境变量
- 构建性能
- 内容安全策略
- 开发 - Vagrant
- 管理依赖
- Public Path(公共路径)
- 集成(Integrations)
- 第五部分:加载器
- babel-loader
- yaml-frontmatter-loader
- cache-loader
- coffee-loader
- coffee-redux-loader
- coverjs-loader
- css-loader
- exports-loader
- expose-loader
- extract-loader
- file-loader
- gzip-loader
- html-loader
- i18n-loader
- imports-loader
- istanbul-instrumenter-loader
- jshint-loader
- json-loader
- json5-loader
- less-loader
- bundle-loader
- multi-loader
- node-loader
- null-loader
- polymer-webpack-loader
- postcss-loader
- raw-loader
- react-proxy-loader
- restyle-loader
- sass-loader
- script-loader
- source-map-loader
- style-loader
- svg-inline-loader
- thread-loader
- transform-loader
- url-loader
- val-loader
- worker-loader
- mocha-loader
- 第六部分:插件
- AggressiveSplittingPlugin
- ZopfliWebpackPlugin
- BannerPlugin
- ClosureWebpackPlugin
- CommonsChunkPlugin
- ComponentWebpackPlugin
- CompressionWebpackPlugin
- ContextReplacementPlugin
- CopyWebpackPlugin
- DefinePlugin
- DllPlugin
- EnvironmentPlugin
- EvalSourceMapDevToolPlugin
- ExtractTextWebpackPlugin
- HashedModuleIdsPlugin
- HotModuleReplacementPlugin
- HtmlWebpackPlugin
- BabelMinifyWebpackPlugin
- IgnorePlugin
- LoaderOptionsPlugin
- MinChunkSizePlugin
- ModuleConcatenationPlugin
- NamedModulesPlugin
- NormalModuleReplacementPlugin
- NpmInstallWebpackPlugin
- PrefetchPlugin
- ProfilingPlugin
- ProvidePlugin
- SourceMapDevToolPlugin
- SplitChunksPlugin
- UglifyjsWebpackPlugin
- WatchIgnorePlugin
- I18nWebpackPlugin
Tree shaking
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import
和 export
。这个术语和概念实际上是兴起于 ES2015 模块打包工具 rollup。
新的 webpack 4 正式版本,扩展了这个检测能力,通过 package.json
的 "sideEffects"
属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 "pure(纯的 ES2015 模块)",由此可以安全地删除文件中未使用的部分。
T> 本指南的继承自起步指南。如果你尚未阅读该指南,请先行阅读。
添加一个通用模块
在我们的项目中添加一个新的通用模块文件 src/math.js
,此文件导出两个函数:
project
webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- bundle.js
|- index.html
|- /src
|- index.js
+ |- math.js
|- /node_modules
src/math.js
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
接着,更新入口脚本,使用其中一个新方法,并且为了简单,将 lodash
删除:
src/index.js
- import _ from 'lodash';
+ import { cube } from './math.js';
function component() {
- var element = document.createElement('div');
+ var element = document.createElement('pre');
- // lodash 是由当前 script 脚本 import 导入进来的
- element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+ element.innerHTML = [
+ 'Hello webpack!',
+ '5 cubed is equal to ' + cube(5)
+ ].join('\n\n');
return element;
}
document.body.appendChild(component());
注意,我们并未从 src/math.js
模块中 import
导入 square
方法。这个功能是所谓的“未引用代码(dead code)”,也就是说,应该删除掉未被引用的 export
。现在让我们运行我们的npm 脚本 npm run build
,并检查输出的 bundle:
dist/bundle.js (around lines 90 - 100)
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* unused harmony export square */
/* harmony export (immutable) */ __webpack_exports__["a"] = cube;
function square(x) {
return x * x;
}
function cube(x) {
return x * x * x;
}
注意,上面的 unused harmony export square
注释。如果你看下面的代码,你会注意到 square
没有被导入,但是,它仍然被包含在 bundle 中。我们将在下一节中解决这个问题。
将文件标记为无副作用(side-effect-free)
在一个纯粹的 ESM 模块世界中,识别出哪些文件有副作用很简单。然而,我们的项目无法达到这种纯度,所以,此时有必要向 webpack 的 compiler 提供提示哪些代码是“纯粹部分”。
这种方式是通过 package.json 的 "sideEffects"
属性来实现的。
{
"name": "your-project",
"sideEffects": false
}
如同上面提到的,如果所有代码都不包含副作用,我们就可以简单地将该属性标记为 false
,来告知 webpack,它可以安全地删除未用到的 export 导出。
T> 「副作用」的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。
如果你的代码确实有一些副作用,那么可以改为提供一个数组:
{
"name": "your-project",
"sideEffects": [
"./src/some-side-effectful-file.js"
]
}
数组方式支持相关文件的相对路径、绝对路径和 glob 模式。它在内部使用 micromatch。
T> 注意,任何导入的文件都会受到 tree shaking 的影响。这意味着,如果在项目中使用类似 css-loader
并导入 CSS 文件,则需要将其添加到 side effect 列表中,以免在生产模式中无意中将它删除:
{
"name": "your-project",
"sideEffects": [
"./src/some-side-effectful-file.js",
"*.css"
]
}
最后,还可以在 module.rules
配置选项 中设置 "sideEffects"
。
压缩输出
通过如上方式,我们已经可以通过 import
和 export
语法,找出那些需要删除的“未使用代码(dead code)”,然而,我们不只是要找出,还需要在 bundle 中删除它们。为此,我们将使用 -p
(/doc/webpack-guides-production) 这个 webpack 编译标记,来启用 uglifyjs 压缩插件。
T> 注意,--optimize-minimize
标记也会在 webpack 内部调用 UglifyJsPlugin
。
从 webpack 4 开始,也可以通过 "mode"
配置选项轻松切换到压缩输出,只需设置为 "/doc/webpack-guides-production"
。
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
- }
+ },
+ mode: "/doc/webpack-guides-production"
};
T> 注意,也可以在命令行接口中使用 --optimize-minimize
标记,来使用 UglifyJSPlugin
。
准备就绪后,然后运行另一个命令 npm run build
,看看输出结果有没有发生改变。
你发现 dist/bundle.js
中的差异了吗?显然,现在整个 bundle 都已经被精简过,但是如果仔细观察,则不会看到 square
函数被引入,但会看到 cube
函数的修改版本(function r(e){return e*e*e}n.a=r
)。现在,随着 tree shaking 和代码压缩,我们的 bundle 减小几个字节!虽然,在这个特定示例中,可能看起来没有减少很多,但是,在具有复杂的依赖树的大型应用程序上运行时,tree shaking 或许会对 bundle 产生显著的体积优化。
结论
为了学会使用 tree shaking,你必须……
- 使用 ES2015 模块语法(即
import
和export
)。 - 在项目
package.json
文件中,添加一个 "sideEffects" 入口。 - 引入一个能够删除未引用代码(dead code)的压缩工具(minifier)(例如
UglifyJSPlugin
)。
你可以将应用程序想象成一棵树。绿色表示实际用到的源码和 library,是树上活的树叶。灰色表示无用的代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。
如果你对优化输出很感兴趣的话,请进入到下一个指南,来了解生产环境中进行构建的详细细节。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论