代码分离
常用的代码分离方法有三种:
- 入口起点:使用
entry
配置手动地分离代码。 - 防止重复:使用 入口依赖 或者
SplitChunksPlugin
去重和分离 chunk - 动态导入:通过模块的内联函数调用分离代码。
入口起点
projectwebpack-demo |- package.json |- package-lock.json |- webpack.config.js |- /dist |- /src |- index.js |- another-module.js |- /node_modules
another-module.js
import _ from 'lodash'; console.log(_.join(['Another', 'module', 'loaded!'], ' '));
webpack.config.js
const path = require('path'); module.exports = { mode: 'development', entry: { index: './src/index.js', another: './src/another-module.js', }, output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist'), }, };
这种方式存在一些隐患:
- 如果入口 chunk 之间包含一些重复的模块,那么这些重复模块会被引入到各个 bundle 中。
- 这种方法不够灵活,并且不能动态地拆分应用程序逻辑中的核心代码。
防止重复
入口依赖
在配置文件中配置dependOn
选项,以在多个 chunk 之间共享模块:
webpack.config.js
const path = require('path'); module.exports = { mode: 'development', entry: { index: { import: './src/index.js', dependOn: 'shared', }, another: { import: './src/another-module.js', dependOn: 'shared', }, shared: 'lodash', }, output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist'), },
// 在一个html中使用多个入口起点,需要设置optimization.runtimeChumk为single
optimization: {
runtimeChunk: 'single',
},
};
使用上面配置进行构建。可以看到,除了 shared.bundle.js
,index.bundle.js
和 another.bundle.js
之外,还生成了一个 runtime.bundle.js
文件。
entry: { page: ['./analytics', './app'] }
。这样可以获得更好的优化效果,并在使用异步脚本标签时保证执行顺序一致。
注意:上面 entry: { page: ['./analytics', './app'] } 的page里的元素可以是文件路径
SplitChunksPlugin
SplitChunksPlugin
插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。让我们使用这个插件去除之前示例中重复的 lodash
模块:
webpack.config.js
const path = require('path'); module.exports = { mode: 'development', entry: { index: './src/index.js', another: './src/another-module.js', }, output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist'), }, optimization: { splitChunks: { chunks: 'all', }, }, };
使用 optimization.splitChunks
配置选项后构建,将会发现 index.bundle.js
和 another.bundle.js
已经移除了重复的依赖模块。从插件将 lodash
分离到单独的 chunk,并且将其从 main bundle 中移除,减轻了 bundle 大小。
动态导入
两种方式实现动态导入:
- 使用符合 ECMAScript 提案 的
import()
语法 实现动态导入 - 使用 webpack 特定的
require.ensure
import()
会在内部使用 promise。因此如果在旧版本浏览器中(如 IE 11)使用 import()
,需要使用一个 polyfill 库(例如 es6-promise 或 promise-polyfill)shim Promise
。
提示
在稍后示例中,当需要根据计算后的变量导入特定模块时,可以通过向 import()
传入一个 动态表达式 实现。
预获取/预加载模块
声明 import
时使用下列内置指令可以让 webpack 输出“Resource Hint”告知浏览器:
- 预获取(prefetch):将来某些导航下可能需要的资源
- 预加载(preload):当前导航下可能需要资源
LoginButton.js
import(/* webpackPrefetch: true */ './path/to/LoginModal.js');
上面的代码在构建时会生成 <link rel="prefetch" href="login-modal-chunk.js">
并追加到页面头部,指示浏览器在闲置时间预获取 login-modal-chunk.js
文件。
只要父 chunk 完成加载,webpack 就会添加预获取提示。
与预获取指令相比,预加载指令有许多不同之处:
- 预加载 chunk 会在父 chunk 加载时以并行方式开始加载;而预获取 chunk 会在父 chunk 加载结束后开始加载。
- 预加载 chunk 具有中等优先级,并会立即下载;而预获取 chunk 则在浏览器闲置时下载。
- 预加载 chunk 会在父 chunk 中立即请求,用于当下时刻;而预获取 chunk 则用于未来的某个时刻。
- 浏览器支持程度不同。
import(/* webpackPreload: true */ 'ChartingLibrary');
不正确地使用 webpackPreload
会有损性能,请谨慎使用。
分析 bundle
分析bundle,官方分析工具 是一个不错的开始。
还有其他的工具:
- webpack-chart:webpack stats 可交互饼图。
- webpack-visualizer:分析并可视化 bundle,检查哪些模块占用空间,哪些可能是重复使用的。
- webpack-bundle-analyzer:一个 plugin 和 CLI 工具,它将 bundle 内容展示为一个便捷的、交互式、可缩放的树状图形式。
- webpack bundle optimize helper:这个工具会分析 bundle,并提供可操作的改进措施,以减少 bundle 的大小。
- bundle-stats:生成一个 bundle 报告(bundle 大小、资源、模块),并比较不同构建之间的结果。
延伸阅读
- webpack 中的 <link rel="prefetch/preload" />
- Chrome 中的预加载、预获取和优先级(Preload, Prefetch And Priorities)
- 使用用 <link rel="preload" /> 预加载内容