Step14.Webpack構建項目進一步優化
webpack dll VS external
webpack在打包后,生成的文件主要分為三種類型:
* 業務代碼
* 外部依賴庫
* webpack runtime
webpack中的dll和external在本質上其實是解決的同一個問題:避免將某些外部依賴庫打包進我們的業務代碼,而是在運行時提供這些依賴。
一方面實現了代碼拆分,以及依賴的復用,另一方面提升構建速度.
這兩種方案應該是各有各的優劣,分別適用於不同的環境。
(1) dll與externals的區別
dll 符合前端模塊化的要求
webpack配置上稍微復雜一些,需要預打包所需的dll資源,並在構建時配置相應的plugin,
使用dll的前提是,這些外部依賴一般不需要發生變更。所以,如果某天發生了變更,那就需要將項目重新構建,違背了dll的使用前提,必然要作出相應的犧牲。
external不太符合前端的模塊化思想,所需要的外部庫需要在瀏覽器全局環境下可訪問
外部庫升級的話,如果兼容之前的API,不需要項目重新構建
webpack配置上稍微簡單些,但是同樣需要將所需的外部庫打包為所需要的格式,並在運行態下引用
相比較而言的話,dll比external應該更加智能一些,主要體現在模塊的引用和打包上。比如說如下方式去引用了react中的一個方法:
import AA from 'react/lib/createClass'
如果采用dll的方式,是不會造成重復打包的,他會將引用直接指向dll。但是如果使用external的話,則會react中的部分代碼打包進來。
(2) externals
防止將某些 import 的包(package)打包到 bundle 中,而是在運行時(runtime)再去從外部獲取這些擴展依賴(external dependencies)。
例如,從 CDN 引入 jQuery,而不是把它打包
<script src="https://code.jquery.com/jquery-3.1.0.js""></script>
module.exports = {
//...
externals: {
jquery: 'jQuery'
}};
這樣就剝離了那些不需要改動的依賴模塊,換句話,下面展示的代碼還可以正常運行:
import $ from 'jquery';
$('.my-element').animate(/* ... */);
DLLPlugin 和 DLLReferencePlugin的使用
DLLPlugin為什么會出現?
在使用webpack進行打包時候,對於依賴的第三方庫,比如vue,vuex等這些不會修改的依賴,我們可以讓它和我們自己編寫的代碼分開打包,這樣做的好處是每次更改我本地代碼的文件的時候,webpack只需要打包我項目本身的文件代碼,而不會再去編譯第三方庫,那么第三方庫在第一次打包的時候只打包一次,以后只要我們不升級第三方包的時候,那么webpack就不會對這些庫去打包,這樣的可以快速的提高打包的速度。因此為了解決這個問題,DllPlugin 和 DllReferencePlugin插件就產生了。
https://www.webpackjs.com/plugins/dll-plugin/#dllplugin
DLLPlugin
DLLPlugin 它能把第三方庫代碼分離開,並且每次文件更改的時候,它只會打包該項目自身的代碼。所以打包速度會更快。
DLLPlugin 這個插件是在一個額外獨立的webpack設置中創建一個只有dll的bundle,也就是說我們在項目根目錄下除了有webpack.config.js,還會新建一個webpack.dll.config.js文件。webpack.dll.config.js作用是把所有的第三方庫依賴打包到一個bundle的dll文件里面,還會生成一個名為 manifest.json文件。
該manifest.json的作用是用來讓 DllReferencePlugin 映射到相關的依賴上去的。
DllReferencePlugin 這個插件是在webpack.config.js中使用的,該插件的作用是把剛剛在webpack.dll.config.js中打包生成的dll文件引用到需要的預編譯的依賴上來。什么意思呢?就是說在webpack.dll.config.js中打包后比如會生成 vendor.dll.js文件和vendor-manifest.json文件,vendor.dll.js文件包含所有的第三方庫文件,vendor-manifest.json文件會包含所有庫代碼的一個索引,當在使用webpack.config.js文件打包DllReferencePlugin插件的時候,會使用該DllReferencePlugin插件讀取vendor-manifest.json文件,看看是否有該第三方庫。vendor-manifest.json文件就是有一個第三方庫的一個映射而已。
所以說 第一次使用 webpack.dll.config.js 文件會對第三方庫打包,打包完成后就不會再打包它了,然后每次運行 webpack.config.js文件的時候,都會打包項目中本身的文件代碼,當需要使用第三方依賴的時候,會使用 DllReferencePlugin插件去讀取第三方依賴庫。所以說它的打包速度會得到一個很大的提升。
DLLPlugin 和 DLLReferencePlugin 用某種方法實現了拆分 bundles,同時還大大提升了構建的速度。
(1) 配置webpack.dll.config.js:
var path = require("path");
var webpack = require("webpack");
var SRC_PATH = path.resolve(__dirname,'./src');
module.exports = {
// 要打包的模塊的數組
entry: {
vendor: ['vue/dist/vue.esm.js','vue-router']
},
output: {
path: path.join(__dirname, './static/dll), // 打包后文件輸出的位置
filename: '[name].dll.js',// vendor.dll.js中暴露出的全局變量名。
library: '[name]_library' // 與webpack.DllPlugin中的`name: '[name]_library',`保持一致。
},
plugins: [
new webpack.DllPlugin({
path: path.join(SRC_PATH,'./static/dll/[name]-manifest.json'),
name: '[name]_library',
context: __dirname
}),
]
};
DllPlugin 插件有三個配置項參數如下:
context(可選): manifest文件中請求的上下文,默認為該webpack文件上下文。
name: 公開的dll函數的名稱,和 output.library保持一致。
path: manifest.json 生成文件的位置和文件名稱。
上面就是webpack.dll.conf.js的主要配置。執行之后會在static文件夾(在vue-cli生成的項目中用於存放不需要webpack構建的靜態文件【@vue/cli中的目錄名為Public】)下生成兩個文件夾(lib文件夾和mainfest文件夾)。其中lib下的文件為我們已經打包好的組件庫,mainfest下的文件在引入項目時有用(是一個JSON文件)。
在package.json的scripts里加上:
"dll": "webpack --config webpack.dll.config.js",
運行npm run dll 在static下生成dll文件夾,dll文件夾里包括js文件和json文件
(2) 在webpack.base.conf.js里加上:
// 添加DllReferencePlugin插件
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./static/dll/vendor-manifest.json')
})
],
DllReferencePlugin項的參數有如下:
context: manifest文件中請求的上下文。
manifest: 編譯時的一個用於加載的JSON的manifest的絕對路徑。
mainfest: 請求到模塊id的映射(默認值為 manifest.content)
name: dll暴露的地方的名稱(默認值為manifest.name)
scope: dll中內容的前綴。
sourceType: dll是如何暴露的libraryTarget。
(3) 在index.html中引入vendor.dll.js:
由於動態鏈接庫我們一般只編譯一次,除非依賴的三方庫更新,之后就不用編譯,因此入口的 index.js 文件中不包含這些模塊,所以要在 index.html 中單獨引入。
① 第一種方式, 一種是根據打包的路徑手動添加
<div id="app"></div><script src="./static/js/vendor.dll.js"></script>
② 第二種方式,用add-asset-html-webpack-plugin或者html-webpack-include-assets-plugin插入到html中,簡單自動化
下面着重講下add-asset-html-webpack-plugin與html-webpack-include-assets-plugin插件的使用,項目中使用add-asset-html-webpack-plugin
安裝大同小異
npm安裝:
npm install add-asset-html-webpack-plugin -D
npm install html-webpack-include-assets-plugin -D
Yarn安裝:
yarn add add-asset-html-webpack-plugin -D
yarn add html-webpack-include-assets-plugin -D
l add-asset-html-webpack-plugin的使用
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
...,
plugins: [
...,
# 給定的 JS 或 CSS 文件添加到 webpack 配置的文件中,並將其放入資源列表 html webpack插件注入到生成的 html 中。
new AddAssetHtmlPlugin([
{
# 要添加到編譯中的文件的絕對路徑
filepath: path.resolve(__dirname,'./static/dll/dll_vendor.js'),
outputPath: 'dll',
publicPath: 'dll',
includeSourcemap: false
}
])
]
}
l html-webpack-include-assets-plugin的使用
const HtmlWebpackIncludeAssetsPlugin = require('html-webpack-include-assets-plugin');
module.exports = {
...,
plugins: [
...,
// 給定的 JS 或 CSS 文件添加到 webpack 配置的文件中,並將其放入資源列表 html webpack插件注入到生成的 html 中。
new HtmlWebpackIncludeAssetsPlugin(
{
assets: ['dll/asset.dll.js','dll/vendor.dll.js'],
append: false
}
)
]
至此,配置之后的:
可以看到npm run build后的時間大幅度減少,在dist打包體積上也比之前的小。在項目優化中,可以很大程度上加快項目的構建速度和減少項目的打包體積。
使用 happypack 提升 Webpack 項目構建速度
提示:由於HappyPack 對file-loader、url-loader 支持的不友好,所以不建議對該loader使用。
webpack打包哪一步最耗時?可能要數loader對文件的轉換操作了,我們前面說過,我們使用loader將文件轉換為我們需要的類型,文件數量巨大,webpack執行又是單線程的,轉換的操作只能一個一個的處理,不能多件事一起做。
我們需要Webpack 能同一時間處理多個任務,發揮多核 CPU 電腦的威力,HappyPack 就能讓 Webpack 做到這點,我們將需要通過loader處理的文件先交給happypack去處理,happypack 在收集到這些文件的處理權限后,統一分配CPU資源.
happypack工作原理
happypack 通過new HappyPack(),去實例化一個HappyPack對象,其實就是告訴Happypack核心調度器如何通過一系列loader去轉換一類文件,並且可以指定如何為這類轉換器作分配子進程。
核心調度器的邏輯代碼在主進程里,也就是運行webpack的進程中,核心調度器會將一個個任務分配給當前空閑的子進程,子進程處理完后會將結果發送給核心調度器,它們之間的數據交換是通過進程間的通訊API實現的。
核心調度器收到來自子進程處理完畢的結果后,會通知webpack該文件已經處理完畢
參考:https://www.qdtalk.com/2018/11/16/webpack4plugin-2/
安裝:
yarn add happypack --dev
使用:
// 引入 happypack
const HappyPack = require('happypack');
// 創建 happypack 共享進程池,其中包含 6 個子進程
const happyThreadPool = HappyPack.ThreadPool({ size: 6 });
module.exports = {
//省略部分配置
module: {
rules: [
{
test: /\.js$/,
//把對.js 的文件處理交給id為happyBabel 的HappyPack 的實例執行
use: 'happypack/loader?id=happyBabel',
//排除node_modules 目錄下的文件,合理的使用排除可以事半功倍
exclude: path.resolve(__dirname,'node_modules')
},
]
},
plugins: [
new HappyPack({
//用id來標識 happypack處理那里類文件
id: 'happyBabel',
//如何處理js文件 用法和loader 的配置一樣
loaders: [{
loader: 'babel-loader?cacheDirectory=true',
}],
//使用共享進程池中的自進程去處理任務
threadPool: happyThreadPool,
//允許 HappyPack 輸出日志,默認為true
verbose: true,
})
]
}
在 Loader 配置中,所有文件的處理都交給了 happypack/loader 去處理,使用緊跟其后的 querystring ?id=babel 去告訴 happypack/loader 去選擇哪個 HappyPack 實例去處理文件。
在 Plugin 配置中,新增了兩個 HappyPack 實例分別用於告訴 happypack/loader 去如何處理 .js 和 .css 文件。選項中的 id 屬性的值和上面 querystring 中的 ?id=babel 相對應,選項中的 loaders 屬性和 Loader 配置中一樣。
對應的參數
id:String
用唯一的標識符 id 來代表當前的 HappyPack 是用來處理一類特定的文件.
loaders: Array
用法和 webpack Loader 配置中一樣.
threads: Number
代表開啟幾個子進程去處理這一類型的文件,默認是3個,類型必須是整數。
verbose: Boolean
是否允許 HappyPack 輸出日志,默認是 true。
threadPool: HappyThreadPool
代表共享進程池,即多個 HappyPack 實例都使用同一個共享進程池中的子進程去處理任務,以防止資源占用過多。
verboseWhenProfiling: Boolean
開啟webpack --profile ,仍然希望HappyPack產生輸出。
debug: Boolean
啟用debug 用於故障排查。默認 false。
https://blog.csdn.net/zgd826237710/article/details/88172290#_HappyPack_36
注意
注意,webpack4中的happypack要使用5.0.0版本,如果你是從webpack3升級到webpack4,記得升級happypack
上面的loader中出現一個陌生詞cacheDirectory:
cacheDirectory默認值為 false。
當有設置時,指定的目錄將用來緩存 loader 的執行結果。之后的 webpack 構建,將會嘗試讀取緩存,來避免在每次執行時,可能產生的、高性能消耗的 Babel 重新編譯過程(recompilation process)。如果設置了一個空值 (loader: ‘babel-loader?cacheDirectory’) 或者 true (loader: babel-loader?cacheDirectory=true),loader 將使用默認的緩存目錄 node_modules/.cache/babel-loader,如果在任何根目錄下都沒有找到 node_modules 目錄,將會降級回退到操作系統默認的臨時文件目錄。