代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后便能按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle、控制资源加载优先级,如果使用合理,会极大减小加载时间。
常用的代码分离方法有三种:
入口起点:使用 entry 配置手动地分离代码。
防止重复:使用 入口依赖 或者 SplitChunksPlugin 去重和分离 chunk。
动态导入:通过模块的内联函数调用分离代码。
入口起点,这是迄今为止最简单直观的实现代码分离的方式。不过,这种方式手动配置较多,并有一些隐患。不过,我们将会介绍如何解决这些隐患。先来看看如何从 main bundle 中分离另一个模块:
项目目录结构:
其他跟着官网例子来。这种方式存在一些隐患:
1.如果入口 chunk 之间包含一些重复的模块,那么这些重复模块会被引入到各个 bundle 中。(例子中的两个js文件都引入了lodash)
2.这种方法不够灵活,并且不能动态地拆分应用程序逻辑中的核心代码。(后面没怎么懂暂时)
以上两点中,第一点所对应的问题已经在我们上面的实例中体现出来了。除了 ./src/another-module.js,我们也曾在 ./src/index.js 中引入过 lodash,这就导致了重复引用。
在配置文件中配置 dependOn 选项,以在多个 chunk 之间共享模块:
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.runtimeChunk: ‘single’,否则会遇到 此处 所述的麻烦。
我们这里是多入口起点,所以要设置:
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'),
},
optimization: {
runtimeChunk: 'single',
},
};
构建结果如下:
可以看到,除了 shared.bundle.js,index.bundle.js 和 another.bundle.js 之外,还生成了一个 runtime.bundle.js 文件。
存在四个打包文件的原因如下:
index.js 和 another-module.js 分别作为两个独立的入口点(index 和another)进行打包,因此会生成两个对应的 bundle 文件。
lodash 被指定为共享的依赖模块(shared),Webpack将其提取到一个单独的共享块中。
此外,Webpack 还会生成一个额外的运行时文件,其中包含了模块加载和执行的逻辑。
因此,根据上述配置,最终会生成四个打包文件:
index.js:包含 ./src/index.js 的代码。
another.js:包含 ./src/another-module.js
的代码。 shared.js:包含 lodash 的代码。
runtime.js:包含 Webpack 运行时代码。
这种是可以将重复的依赖防止重复的,但是我就还不清楚重复的依赖在哪个文件里了暂时。
尽管 webpack 允许每个页面使用多个入口起点,但在可能的情况下,应该避免使用多个入口起点,而使用具有多个导入的单个入口起点:entry: { page: [‘./analytics’, ‘./app’] }。这样可以获得更好的优化效果,并在使用异步脚本标签时保证执行顺序一致。
(这句话的意思是,尽管 Webpack 允许在配置中使用多个入口起点,但在可能的情况下,最好使用具有多个导入的单个入口起点,以获得更好的优化效果并确保异步脚本标签的执行顺序一致。
在 Webpack 中,每个入口起点都会生成一个输出文件,这意味着如果你使用多个入口起点,就会生成多个输出文件。这种情况下,每个输出文件都需要加载并执行,会增加额外的网络请求和加载时间。
相比之下,使用具有多个导入的单个入口起点可以将所有的代码逻辑打包到一个输出文件中,从而减少了网络请求和加载时间。这样可以提高应用程序的性能和加载速度。
另外,当你使用异步脚本标签(
而当你使用具有多个导入的单个入口起点时,所有的代码逻辑都被打包到一个输出文件中,保证了代码的执行顺序一致性,避免了潜在的依赖问题。
因此,为了获得更好的优化效果和保证执行顺序的一致性,建议在可能的情况下使用具有多个导入的单个入口起点。
需要注意的是,这并不是绝对的规则,而是在一般情况下的最佳实践。在某些特殊情况下,使用多个入口起点可能是合理的,例如需要将应用程序分割成多个模块或按需加载某些功能模块。)
SplitChunksPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。让我们使用这个插件去除之前示例中重复的 lodash 模块:
在webpack的配置文件中配置:
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'),
clean: true,
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
};
使用 optimization.splitChunks 配置选项后构建,将会发现 index.bundle.js 和 another.bundle.js 已经移除了重复的依赖模块。从插件将 lodash 分离到单独的 chunk,并且将其从 main bundle 中移除,减轻了 bundle 大小。执行 npm run build 查看效果:
终于到最后一个了-动态导入。
webpack 提供了两个类似的技术实现动态代码分离。第一种,也是推荐选择的方式,是使用符合 ECMAScript 提案 的 import() 语法 实现动态导入。第二种则是 webpack 的遗留功能,使用 webpack 特定的 require.ensure。让我们先尝试使用第一种。
现在,我们不再静态导入 lodash,而是通过动态导入来分离出一个 chunk:
src/index.js:
//动态导入
function getComponent() {
return import('lodash')
.then(({ default: _ }) => {
const element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
})
.catch((error) => 'An error occurred while loading the component');
}
getComponent().then((component) => {
document.body.appendChild(component);
});
需要 default 的原因是自 webpack 4 之后,在导入 CommonJS 模块时,将不再解析为 module.exports 的值,而是创建一个人工命名空间对象来表示此 CommonJS 模块。参阅 webpack 4: import() and CommonJs 以了解更多有关信息。
打包:
lodash会分离到一个单独的 bundle。
由于 import() 会返回 promise,因此它可以和 async 函数 一起使用。下面是使用 async 简化后的代码:
src/index.js:
//动态导入 - async写法
async function getComponent() {
const element = document.createElement('div');
const { default: _ } = await import('lodash');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
}
getComponent().then((component) => {
document.body.appendChild(component);
});
vendors-node_modules_lodash_lodash_js.bundle.js 是一个由 Webpack 打包生成的 JavaScript 文件。它通常包含了 Lodash 库的代码,并可能还包含其他第三方库的代码,这取决于你的项目中使用了哪些库和配置。
Lodash 是一个流行的 JavaScript 实用工具库,提供了许多常用的功能和工具函数,用于简化 JavaScript 编程任务。lodash.js 是 Lodash 库的核心文件,其中包含了 Lodash 提供的各种功能和方法。
当你在项目中使用 Lodash 并通过 Webpack 打包时,Webpack 会将 Lodash 的代码打包成一个单独的文件,这就是 vendors-node_modules_lodash_lodash_js.bundle.js 的由来。打包成单独的文件有助于提高浏览器加载速度,并允许你在需要时进行缓存和异步加载。
我的理解就是把lodash单独分离出去了的,就是例子中的第二个文件,代码分离了,如果其他地方调用了的话也不用再打包一个bundle出来了。
好像官网没举这个例子。有句话:
警告
require.ensure() 是 webpack 特有的,已被 import() 取代。
能看懂意思,不知道具体能用在代码里的哪里。。。
webpack打包出来的bundle,光从命令行感觉是很难看出些什么的,不大直观。刚好有插件能解决这个问题,以webpack-bundle-analyzer为例。
先安装:
npm install --save-dev webpack-bundle-analyzer
然后在webpack配置文件中配置:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
// ...其他配置
plugins: [
new BundleAnalyzerPlugin()
]
};
然后打包,完成了它会默认打开浏览器一个网页:
1.webpack的代码分离方式:
入口起点:使用 entry 配置手动地分离代码。
防止重复:使用 入口依赖 或者 SplitChunksPlugin 去重和分离 chunk。
动态导入:通过模块的内联函数调用分离代码。
2.预获取/预加载模块,暂时不知道能干嘛
3.分析bundle,通过插件能够更直观的分析打包后的各个bundle包