代码分离
是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
常用的代码分离方法有三种
:
入口起点
:使用 entry
配置手动地分离代码防止重复
:使用 Entry dependencies
或者 SplitChunksPlugin
去重和分离 chunk动态导入
:通过模块
的内联函数调用来分离代码。我们创建一个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
打开浏览器,控制台打印了相关字符串
所以我们看出这种方法有一个缺点
:
重复
模块都会被引入
到各个 bundle 中不够灵活
,并且不能动态
地将核心应用程序逻辑中的代码拆分
出来以上两点中,第一点对我们的示例来说无疑是个问题,因为之前我们在 ./src/index.js 中也引入过 lodash,这样就在两个 bundle 中造成重复引用。所以我们需要防止重复。
我们在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文件,控制台打印了相关字符串
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文件,控制台打印了相关字符串
当涉及到动态代码拆分时,webpack 提供了两个类似的技术。
推荐
选择的方式是,使用符合 ECMAScript 提案
的 import() 语法
来实现动态导入。遗留功能
,使用 webpack 特定的 require.ensure
。这里我们用第一种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?
我们将刚才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
打开浏览器,页面和控制台中同时
显示了我们动态导入
和静态导入
的字符串,
我们将另一个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已完全实现
懒加载
或者按需加载
,是一种很好的优化网页或应用的方式。这种方式实际上是先把
你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用
或即将引用另外一些新的代码块。这样加快
了应用的初始加载速度
,减轻了它的总体体积
,因为有些代码模块永远也用不到。
我们配合上面聊的动态导入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别名
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
提前加载了出来,此时控制台也未打印任何值
此时会生成 并追加到页面head头部,这表示浏览器在首页面其他加载完毕后,浏览器在
闲置时间
预获取 math.bundle.js
文件
我们点击按钮,发现math.bundle.js
又被懒加载了出来,控制台打印了字符串
关于另一种预加载preload
,我在这里就不多描述了,有兴趣的可以点击这里
查看详细用法。
__
本博客参考: