代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
常用的代码分离方法有三种:
入口起点:使用entry配置手动地分离代码防止重复:使用 Entrydependencies或者SplitChunksPlugin去重和分离 chunk动态导入:通过模块的内联函数调用来分离代码。
1、入口起点(entry)
我们创建一个index.html和webpack.config.js配置文件,同时在src目录下面创建一个index.js和another-code.js的文件
index.js
console.log('index')
another-code.js
console.log('another-code')
index.html(随便写点东西)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>中国男足,永远的神</title>
</head>
<body>
</body>
</html>
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
//代码入口分离
entry : {
index:'./src/index.js',
another:'./src/another-code.js'
},
output : {
filename:'[name].bundle.js',
path:path.resolve(__dirname,'./dist'),
clean:true,
},
mode : 'development',
devtool:'inline-source-map',
plugins:[
new HtmlWebpackPlugin({
template:'./index.html',
filename:'app.html',
inject:"body"
}),
],
devServer:{
static:'./dist'
},
}
我们在webpack.config.js配置文件的entry部分规定了2个入口,同时在output部分filename:'[name].bundle.js'规定了打包后的文件名字,防止打包后文件名之重复。
我们执行npx webpack打包,可以看出,打包后的文件体积很小,打包后的app.html也是引入了这两个打包的js
执行
npx webpack-dev-server --open打开浏览器,控制台打印了index和another-code
我们看到,
entry配置多个入口确实可以帮助我们实现代码分离,现在我们在2个js文件中分别引入lodash
index.js
import _ from 'lodash'
console.log('index')
console.log(_.join(['index','lodash']))
another-code.js
import _ from 'lodash'
console.log('another-code')
console.log(_.join(['another-code','lodash']))
执行npx webpack打包,我们发现,这次打包后的2个bundle.js体积一下就变大了许多
执行
npx webpack-dev-server --open打开浏览器,控制台打印了相关字符串
所以我们看出这种方法有一个
缺点:
- 如果入口 chunk 之间包含一些重复的模块,那些
重复模块都会被引入到各个 bundle 中 - 这种方法
不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来
以上两点中,第一点对我们的示例来说无疑是个问题,因为之前我们在 ./src/index.js 中也引入过 lodash,这样就在两个 bundle 中造成重复引用。所以我们需要防止重复。
2、防止重复(prevent duplication)
2.1、入口依赖(dependOn)
我们在entry入口配置的时候,可以配置 dependOn option 选项,然后通过shared这个option选项,这样可以在多个 chunk 之间共享模块:
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
//代码入口分离
entry : {
//index:'./src/index.js',
//another:'./src/another-code.js'
//防止重复,抽离公共的方法,代码分离(1)
index:{
import:'./src/index.js',
dependOn:'shared'
},
another:{
import:'./src/another-code.js',
dependOn:'shared'
},
shared:'lodash'
},
output : {
filename:'[name].bundle.js',
path:path.resolve(__dirname,'./dist'),
clean:true,
},
mode : 'development',
devtool:'inline-source-map',
plugins:[
new HtmlWebpackPlugin({
template:'./index.html',
filename:'app.html',
inject:"body"
}),
],
devServer:{
static:'./dist'
},
}
上面shared:'lodash'就表明,会把各个模块间依赖的lodash这个库给抽取出来
我们执行npx webpack打包,可以看到,抽离出了shares.bundle.js这个文件,同时打包后的index.bundle.js和another.bundle.js的体积大幅减小
我们看
index.html中也分别引入了这3个打包后的js文件
执行
npx webpack-dev-server --open打开浏览器,看到body下面分别加载了三个js文件,控制台打印了相关字符串
2.2、SplitChunksPlugin
SplitChunksPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。我们使用这个插件,同时将之前的webpack.config.js中重复的 lodash 模块去除
entry入口仍是简单的2个入口,我们通过optimization这个option选项,通过配置splitChunks为chunks:'all',起到抽离作用
webpack.config.js
module.exports = {
//代码入口分离
entry : {
index:'./src/index.js',
another:'./src/another-code.js'
},
···
···
optimization:{
//防止重复,抽离公共的方法,代码分离(2)
splitChunks:{
chunks:'all'
}
}
}
我们执行npx webpack打包,可以看到,这次没有生成shares.bundle.js这个文件,但是生成了vendors-node_modules_lodash_lodash_js.bundle.js这个文件
我们看
index.html中也分别引入了这3个打包后的js文件
执行
npx webpack-dev-server --open打开浏览器,看到body下面分别加载了三个js文件,控制台打印了相关字符串
3、动态导入(import)
当涉及到动态代码拆分时,webpack 提供了两个类似的技术。
- 第一种,也是
推荐选择的方式是,使用符合ECMAScript 提案的import() 语法来实现动态导入。 - 第二种,则是 webpack 的
遗留功能,使用 webpack 特定的require.ensure。
3.1、纯动态
这里我们用第一种import() 语法 ,在我们开始之前,先把之前示例的配置中移除掉多余的 entry 和 optimization.splitChunks,入口也先只从index.js导入
webpack.config.js
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
},
···
//optimization: {
// splitChunks: {
// chunks: 'all',
// },
//},
};
我们在src目录下新创建一个async-code.js,我们不再使用 statically import(静态导入) lodash,而是通过 dynamic import(动态导入) 来分离出一个 chunk
async-code.js(由于 import() 会返回一个 promise,所以我们用then来连接)
function getComponent(){
return import('lodash').then(({default:_})=>{
const element = document.createElement('div')
element.innerHTML = _.join(['async','code','lodash'],'-')
return element
})
}
getComponent().then((element) => {
document.body.appendChild(element)
})
index.js(index.js中引入动态导入的js文件,同时先把静态导入lodash注释)
// import _ from 'lodash'
import './async-code.js'
console.log('index')
// console.log(_.join(['index','lodash']))
执行npx webpack打包,同样是生成了vendors-node_modules_lodash_lodash_js.bundle.js这个文件,帮助我们抽离出相关模块
执行
npx webpack-dev-server --open打开浏览器,页面上显示了我们动态导入中输出的字符串
由此我们可以看出,动态
import()也可以成功帮助我们成功分割代码,那么如果将刚才注释的代码恢复,使用 statically import(静态导入) lodash 和 dynamic import(动态导入) 共同组合呢,这时是否会抽离出共同的chunk?
3.2、动态 + 静态
我们将刚才index.js的lodash恢复,同时将webpack.config.js的optimization.splitChunks恢复
index.js(index.js中引入动态导入的js文件,同时使用静态导入lodash)
import _ from 'lodash'
import './async-code.js'
console.log('index')
console.log(_.join(['index','lodash']))
webpack.config.js
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
},
···
optimization: {
splitChunks: {
chunks: 'all',
},
},
};
执行npx webpack打包,依旧正常
执行
npx webpack-dev-server --open打开浏览器,页面和控制台中同时显示了我们动态导入和静态导入的字符串,
3.3、动态 + 静态
我们将另一个another-code.js静态导入也重新恢复
webpack.config.js
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
another:'./src/another-code.js'
},
···
optimization: {
splitChunks: {
chunks: 'all',
},
},
};
执行npx webpack打包
执行
npx webpack-dev-server --open打开浏览器,页面和控制台中同时显示了我们动态导入和2个静态导入的字符串
可以看出,我们现在代码抽离chunk已完全实现
4、懒加载
懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把
你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用
或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为有些代码模块永远也用不到。
我们配合上面聊的动态导入import()的方式,来说说懒加载,在src目录下面创建一个math.js,里面输出add和minus两种方法
export const add = (x,y) => {
return x + y
}
export const minus = (x,y) => {
return x - y
}
在index.js中通过import引入math.js,
index.js
import _ from 'lodash'
import './async-code.js'
console.log('index')
console.log(_.join(['index','lodash']))
const BTN = document.createElement('button')
BTN.textContent = '点击我运行'
BTN.addEventListener('click',()=>{
import('./math.js').then(({add,minus})=>{
console.log(add(3,5))
console.log(minus(3,5))
})
})
document.body.appendChild(BTN)
执行npx webpack打包,我们发现,又多打包了个src_math_js.bundle.js,
npx webpack-dev-server --open打开浏览器,页面中有个按钮,刚开始控制台没有打印,当我们点击按钮后,控制台console才打印了add和minus的执行结果,同时我们观察network中,开始没有请求该js文件,但是点击后,请求了该打包后的src_math_js.bundle.js,这样会节省网络流量
webpackChunkName(魔法注释)
我们可以使用 magic comment 来修改动态 import 导出的 chunkname
index.js
import _ from 'lodash'
import './async-code.js'
console.log('index')
console.log(_.join(['index','lodash']))
const BTN = document.createElement('button')
BTN.textContent = '点击我运行'
BTN.addEventListener('click',()=>{
import(/* webpackChunkName: 'math' */'./math.js').then(({add,minus})=>{
console.log(add(3,5))
console.log(minus(3,5))
})
})
document.body.appendChild(BTN)
执行npx webpack打包,这时打包后的bundle就是起的math别名
5、预获取(prefetch)
Webpack v4.6.0+ 增加了对预获取的支持。
prefetch(预获取):将来某些导航下可能需要的资源
我们在刚才懒加载webpack.config.js的配置文件的基础上,添加一句魔法注释
/* webpackPrefetch: true */
即webpack.config.js
import _ from 'lodash'
import './async-code.js'
console.log('index')
console.log(_.join(['index','lodash']))
const BTN = document.createElement('button')
BTN.textContent = '点击我运行'
BTN.addEventListener('click',()=>{
import(/* webpackChunkName: 'math',webpackPrefetch: true */'./math.js').then(({add,minus})=>{
console.log(add(3,5))
console.log(minus(3,5))
})
})
document.body.appendChild(BTN)
不用打包,直接在浏览器上刷新页面,我们发现之前的math.bundle.js提前加载了出来,此时控制台也未打印任何值
此时会生成 <link rel="prefetch" href="math.bundle.js"> 并追加到页面head头部,这表示浏览器在首页面其他加载完毕后,浏览器在闲置时间预获取 math.bundle.js 文件
我们点击按钮,发现math.bundle.js又被懒加载了出来,控制台打印了字符串
关于另一种
预加载preload,我在这里就不多描述了,有兴趣的可以点击这里
查看详细用法。
__ 本博客参考: