咱们先引入一个应用场景,而后对这个场景进行分析,了解为何须要拆分代码。
首先,安装第三方库lodash
,而后在代码分割.js
中引入并编写业务代码。html
// 导入第三方库 const _ = require('lodash') // 业务逻辑代码 console.log(_.join(['a', 'b', 'c']))
接着,配置webpack.config.js
进行打包node
const path = require('path') const { CleanWebpackPlugin } = require('clean-webpack-plugin') module.exports = { entry: './代码分割.js', output: { path: path.resolve(__dirname, 'build'), // 打包文件的输出目录 filename: '[name].bundle.js', // 代码打包后的文件名 publicPath: __dirname + '/build/', // 引用的路径或者 CDN 地址 chunkFilename: '[name].js' // 代码拆分后的文件名 }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader' } } ], }, plugins: [ new CleanWebpackPlugin() // 会删除上次构建的文件,而后从新构建 ] }
打包完后会生成一个名为main.bundle.js
文件,咱们在index.html
中引入,打开浏览器执行。webpack
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <script src="./build/main.bundle.js"></script> </body> </html>
浏览器控制台会输出a,b,c
若是咱们改动index.js
中的业务代码,改成:git
// 导入第三方库 const _ = require('lodash') // 业务逻辑代码 console.log(_.join(['a', 'b', 'c'], '***'))
控制台结果变为:a\*\*\*b\*\*\*c
github
如今咱们抛出一个问题,当咱们把引入的第三方库和业务代码放在一块儿打包,这样会有什么问题?web
假设上面第三方库lodash
大小为1M
,业务代码为1M
,假设打包后的main.bundle.js
为2M
npm
浏览器每次打开index.html
时,都须要去加载main.bundle.js
,而后执行业务代码。加载2M
代码是很耗时的,可是浏览器有缓存机制,第二次加载同一文件时会从缓存中读取,刷新页面时网页加载速度更快。可是事与愿违,咱们的业务代码更新很频繁,致使不管是首次加载仍是再次加载都会很慢,那如何去解决这个问题呢?segmentfault
答案很明显,第三方库lodash
代码基本上是不会变的,若是咱们可以将业务代码和第三方库代码分开加载,那么第三方库的加载就可用到缓存机制,整个页面的加载时间也会缩短。浏览器
在多个js
文件都引入了一样的库或者代码的场景下也是能够进行拆分,避免重复加载。缓存
在 webpack4
以前是使用 commonsChunkPlugin
来拆分公共代码,v4
以后被废弃,并使用 splitChunksPlugins
在使用 splitChunksPlugins
以前,首先要知道 splitChunksPlugins
是 webpack
主模块中的一个细分模块,无需 npm
引入,只须要配置便可。
const path = require('path') const { CleanWebpackPlugin } = require('clean-webpack-plugin') module.exports = { entry: './代码分割.js', output: { path: path.resolve(__dirname, 'build'), // 打包文件的输出目录 filename: '[name].bundle.js', // 代码打包后的文件名 publicPath: __dirname + '/build/', // 引用的路径或者 CDN 地址 chunkFilename: '[name].js' // 代码拆分后的文件名 }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader' } } ] }, // 拆分代码配置项 optimization: { splitChunks: { chunks: 'all' } }, plugins: [ new CleanWebpackPlugin() // 会删除上次构建的文件,而后从新构建 ] }
咱们使用cnpm run dev
在开发模式下打包文件,开发模式下打包不会压缩文件,方便查看
能够看到代码拆分红了两个文件,打开main.bundle.js
文件能够看到里卖弄存放的都是业务代码,没有lodash
的代码
打开vendors~main.js文件能够看到lodash
代码都在里面
这样就完成了代码拆分,拆分完的两个文件都要引入到index.html
中
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <script src="./build/vendors~main.js"></script> <script src="./build/main.bundle.js"></script> </body> </html>
浏览器执行index.html
文件控制台结果:a\*\*\*b\*\*\*c
上面采用拆分代码的模式是all
,另外还有async、initial
,咱们来了解一下
async
: 只优化动态加载的代码,其余类型的代码正常打包。initial
: 针对原始 bundle 代码进行优化。all
: 针对全部代码进行优化。function(chunk)
自定义拆分函数详情能够参考本人另外一篇文章:
http://www.javashuo.com/article/p-nzqmzxip-ct.html
分割出来的文件名为vendors~main.js
,怎么来的,咱们来分析一下:
当在splitChunks
配置项中没有添加cacheGroups
对象中的name
属性时,默认会在文件名前面加上vendors
字段。如今咱们来配置一下name
属性更改分割的文件名。
// 拆分代码配置项 optimization: { splitChunks: { cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, chunks: 'all', name: 'test' // 定义分割文件名 } } } },
写到这,有细心的人可能会问cacheGroups
对象的做用是干吗的?
cacheGroups is a plain object with key being the name of chunk and value being some configuration of that chunk. By default, Webpack ships with vendors and default cacheGroups but let’s turn those off by setting their value to false, else it will just confuse you to understand code splitting.
主要概念就是cacheGroups
对象中的key
是分割块的名称,value
是分割块的相关配置。
如今有以下代码:
// a,js import './common' console.log('A') export default 'A' // b.js import './common' console.log('B') export default 'B' // common.js console.log('公共模块') export default 'common' // index.js // 异步代码 import(/* webpackChunkName: 'a'*/ './a').then(function(a) { console.log(a) }) import(/* webpackChunkName: 'b'*/ './b').then(function(b) { console.log(b) }) function getComponent() { // 使用异步的形式导入 lodash,default: _ 表示用 _ 代指 lodash return import('lodash').then(({ default: _ }) => { var element = document.createElement('div') element.innerHTML = _.join(['hello', 'world'], '-') return element }) } getComponent().then(element => { document.body.appendChild(element) })
目录结构为:webpack
配置:
const path = require('path') const { CleanWebpackPlugin } = require('clean-webpack-plugin') const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin module.exports = { entry: { main: './code_split_test/index.js' }, output: { path: path.resolve(__dirname, 'build'), // 打包文件的输出目录 filename: '[name].bundle.js', // 代码打包后的文件名 publicPath: __dirname + '/build/', // 引用的路径或者 CDN 地址 chunkFilename: '[name].js' // 代码拆分后的文件名 }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader' } } ] }, // 拆分代码配置项 optimization: { splitChunks: { chunks: 'all', minSize: 30000, maxSize: 0, minChunks: 1, maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~', name: true, cacheGroups: { lodash: { name: 'lodash', test: /[\\/]node_modules[\\/]/, priority: 10 }, common: { name: 'common', minSize: 0, //表示在压缩前的最小模块大小,默认值是 30kb,若是没设置为0,common模块就不会抽离为公共模块,由于原始大小小于30kb minChunks: 2, // 最小公用次数 priority: 5, // 优先级 reuseExistingChunk: true // 公共模块必开启 }, vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } } }, plugins: [ new CleanWebpackPlugin(), // 会删除上次构建的文件,而后从新构建 new BundleAnalyzerPlugin() ] }
打包结果:
咱们来分析一下文件之间的依赖关系:index.js
依赖于a.js、b.js、lodash.js
,而且是动态加载,a.js
和b.js
都依赖common.js
,是同步加载。
很明显,若是咱们采用aysnc
模式拆分,分割出的a.js
和b.js
里面都会存在common.js
,common.js
模块不会被提取成公共模块,得不到复用。
所以,咱们能够采用all
,common.js
模块会被提取成共享模块。
如今将打包后的主文件main.bundle.js
引入到index.html
中,奇怪的是主文件的引入并无使得分割后的文件自动引入
而后我去查看了一下主文件里面是否含有引入分割文件的脚本代码,发现是有的
这就很无语了,可是当我把ES6
转译有关配置注释掉再打包执行index.html
文件,发现是可行的
// module: { // rules: [ // { // test: /\.js$/, // exclude: /node_modules/, // use: { // loader: 'babel-loader' // } // } // ] // },
这个问题究竟是为何呢?直到如今我也尚未搞懂,但愿哪位大佬注意到后能帮我解决一下疑惑。
参考文章:
https://itxiaohao.github.io/passages/webpack4-code-splitting/