在最開始使用Webpack的時候, 都是將全部的js文件所有打包到一個build.js文件中(文件名取決與在webpack.config.js文件中output.filename), 可是在大型項目中, build.js可能過大, 致使頁面加載時間過長. 這個時候就須要code splitting, code splitting就是將文件分割成塊(chunk), 咱們能夠定義一些分割點(split point), 根據這些分割點對文件進行分塊, 並實現按需加載。html
代碼分割,也就是Code Splitting通常須要作這些事情:vue
來自螞蟻金服數據體驗技術Webpack 4 配置最佳實踐,這裏雖然用的webpack3,也一樣試用。react
下面就用Code Splitting來實現上面幾點。webpack
webpack的代碼分割Code Splitting,主要有兩種方式:git
第一種:webpack1經過require.ensure定義分割點,將代碼進行分割打包,異步加載。第二種:在動態代碼拆分方面,webpack支持符合ECMAScript提議的import()語法進行代碼分割和異步加載。es6
webpack 在編譯時,會靜態地解析代碼中的 require.ensure(),同時將模塊添加到一個分開的 chunk 當中,這個新的 chunk 會被 webpack 經過 jsonp 來異步加載,其餘包則會同步加載。github
require.ensure(dependencies: String[], callback: function(require), chunkName: String)
第一個參數是dependencies依賴列表,webpack會加載模塊,但不會執行。web
第二個參數是一個回調,當全部的依賴都加載完成後,webpack會執行這個回調函數,在其中可使用require導入模塊,導入的模塊會被代碼分割到一個分開的chunk中。json
第三個參數指定第二個參數中代碼分割的chunkname。數組
將下面代碼拷貝到webpack.config.RequireEnsure.js:
var webpack = require('webpack'); var path = require('path'); module.exports = { entry: { 'pageA': './src/pageA', 'vendor': ['lodash'] // 指定單獨打包的第三方庫(和CommonsChunkPlugin結合使用),能夠用數組指定多個 }, output: { path: path.resolve(__dirname, './dist'), filename: '[name].bundle.js', chunkFilename: '[name].chunk.js', // code splitting的chunk是異步(動態)加載,須要指定chunkFilename(具體能夠了解和filename的區別) }, plugins: [ // 爲第三方庫和和manifest(webpack runtime)單獨打包 new webpack.optimize.CommonsChunkPlugin({ name: ['vendor', 'manifest'], minChunks: Infinity }), ] }
src/pageA.js、src/subPageA.js、src/subPageB.js、src/module.js代碼以下:
// src/pageA.js import * as _ from 'lodash'; import subPageA from './subPageA'; import subPageB from './subPageB'; console.log('this is pageA'); export default 'pageA'; // src/subPageA.js import module from './module'; console.log('this is subPageA'); export default 'subPageA'; // src/subPageB.js import module from './module'; console.log('this is subPageB'); export default 'subPageB'; // src/module.js const s = 'this is module' export default s;
其中subPageA和subPageB模塊使用共同模塊module.js,命令行運行webpack --config webpack.config.RequireEnsure.js,打包生成:
pageA.bundle.js vendor.bundle.js // 爲 Vendor 單獨打包 manifest.bundle.js // 爲Manifest單獨打包
知足咱們文檔一剛開始說的代碼分割的兩點要求,可是咱們想要subPageA和subPageB單獨打包:
修改src/pageA.js,把import導入方式改爲require.ensure的方式就能夠代碼分割:
// import subPageA from './subPageA'; // import subPageB from './subPageB'; require.ensure([], function() { // 分割./subPageA模塊 var subPageA = require('./subPageA'); }, 'subPageA'); require.ensure([], function () { var subPageB = require('./subPageB'); }, 'subPageB');
再次打包,生成:
pageA.bundle.js subPageA.chunk.js // 代碼分割 subPageB.chunk.js vendor.bundle.js // 爲 Vendor 單獨打包 manifest.bundle.js // 爲Manifest單獨打包
會發現用了require.ensure的模塊被代碼分割了,達到了咱們想要的目的,可是因爲subPageA和subPageB有公共模塊module.js,打開subPageA.chunk.js和subPageB.chunk.js發現都有公共模塊module.js,這時候就須要在require.ensure代碼前面加上require.include('./module')
src/pageA.js:
require.include('./module'); // 加在require.ensure代碼前
再次打包,公共模塊modle.js被打包在了pageA.bundle.js,解決了爲異步加載的代碼打一個公共的包問題。
最後測試一下webpack打包後的動態加載/異步加載:
在index.html裏面引入打包文件:
<html> <body> <script src="./dist/manifest.bundle.js"></script> <script src="./dist/vendor.bundle.js"></script> <script src="./dist/pageA.bundle.js"></script> </body> </html>
webpack.config.RequireEnsure.js加上動態加載路徑的配置後再次打包:
output: { ... publicPath: './dist/' // 動態加載的路徑 }
瀏覽器中打開index.html文件,會發現subPageA.chunk.js和subPageB.chunk.js沒有引入也被導入了進來。其實這兩個文件是webpack runtime打包文件根據代碼分割的文件自動異步加載的。
<img src="https://user-gold-cdn.xitu.io...;h=115&f=png&s=12335">
requre.ensure好像已被webpack4廢棄。es6提供了一種更好的代碼分割方案也就是dynamic import(動態加載)的方式,webpack打包時會根據import()自動代碼分割;雖然在提案中,能夠安裝babel插件兼容,推薦用這種方式:
import(/* webpackChunkName: chunkName */ chunk) .then( res => { // handle something }) .catch(err => { // handle err });
其中/ chunkName /爲指定代碼分割包名,chunk指定須要代碼分割的文件入口。注意不要把 import關鍵字和import()方法弄混了,該方法是爲了進行動態加載。
import()調用內部使用promises。若是您使用import()較舊的瀏覽器,請記住Promise使用諸如es6-promise或promise-polyfill之類的polyfill進行填充。
下面修改pageA.js:
import * as _ from 'lodash'; import(/* webpackChunkName: 'subPageA' */'./subPageA').then(function(res){ console.log('import()', res) }) import(/* webpackChunkName: 'subPageB' */'./subPageB').then(function(res){ console.log('import()', res) }) console.log('this is pageA'); export default 'pageA';
將下面代碼拷貝到webpack.config.import.js:
var webpack = require('webpack'); var path = require('path'); module.exports = { entry: { 'pageA': './src/pageA', 'vendor': ['lodash'] // 指定單獨打包的第三方庫(和CommonsChunkPlugin結合使用),能夠用數組指定多個 }, output: { path: path.resolve(__dirname, './dist'), filename: '[name].bundle.js', chunkFilename: '[name].chunk.js', // code splitting的chunk是異步(動態)加載,須要指定chunkFilename(具體能夠了解和filename的區別) publicPath: './dist/' // 動態加載的路徑 }, plugins: [ // 爲第三方庫和和manifest(webpack runtime)單獨打包 new webpack.optimize.CommonsChunkPlugin({ name: ['vendor', 'manifest'], minChunks: Infinity }), ] }
命令行webpack --config webpack.config.import.js,打包發現和require.ensure同樣的結果:
pageA.bundle.js subPageA.chunk.js // 代碼分割 subPageB.chunk.js vendor.bundle.js // 爲 Vendor 單獨打包 manifest.bundle.js // 爲Manifest單獨打包
爲了分離出subPageA.chunk.js和subPageB.chunk.js的公共模塊module.js,能夠用CommonsChunkPlugin的async方式給異步加載的打包提取公共模塊:
在webpack.config.import.js加上下面配置:(其實這種方式能夠替代require.ensure中的require.include方式提取公共代碼,更自動和簡單)
plugins: [ // 爲異步公共加載的代碼打一個的包 new webpack.optimize.CommonsChunkPlugin({ async: 'async-common', // 異步公共的代碼 children: true, // 要加上children,會從入口的子依賴開始找 minChunks: 2 // 出現2次或以上相同代碼就打包 }), ... ]
從新打包,打包文件以下:
pageA.bundle.js subPageA.chunk.js // 代碼分割 subPageB.chunk.js async-common-pageA.chunk.js // 爲異步公共加載的代碼打的包 vendor.bundle.js // 爲 Vendor 單獨打包 manifest.bundle.js // 爲Manifest單獨打包
會發現多了一個異步加載包subPageA.chunk.js和subPageB.chunk.js的公共模塊async-common-pageA.chunk.js包,配置成功!
一樣,仍是進行測試,index.html引入同步加載的包:
<html> <body> <script src="./dist/manifest.bundle.js"></script> <script src="./dist/vendor.bundle.js"></script> <script src="./dist/pageA.bundle.js"></script> </body> </html>
瀏覽器中打開index.html文件,會發現subPageA.chunk.js、subPageB.chunk.js和async-common-pageA.chunk.js被自動異步加載了。
<img src="https://user-gold-cdn.xitu.io...;h=136&f=png&s=15038">
關於文檔中屢次提到webpack代碼分割的包,在瀏覽器中webpack runtime的包會自動異步加載代碼分割的包,那麼在react和vue應用中,若是這些代碼分割包在頁面初始化也會自動異步加載,那不是分包的做用不大?緣由實際上是咱們上面例子執行了import()或require.ensure,而在應用中,寫法是當請求路由的時候才執行import()或require.ensure,而後再異步加載,webpack遇到import()或require.ensure的配置的時候只會進行代碼切割,這種思路就是按需加載的基礎。
部分參考連接:
文檔中出現的源代碼在個人github,感興趣的歡迎star。