咱們先引入一個應用場景,而後對這個場景進行分析,瞭解爲何須要拆分代碼。
首先,安裝第三方庫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/