在講解性能優化的方案之前,我們需要了解一下webpack的整個作業流程,

方案一:減少模塊決議
也就是省略了構建chunk依賴模塊的這幾個步驟

如果沒有loader對該模塊進行處理,該模塊的原始碼就是最終打包結果的代碼,不對某個模塊進行決議,可以縮短構建時間
哪些模塊不需要決議?
模塊中無其他依賴
webpack配置
配置module.noParse,它是一個正則,被正則匹配到的模塊不會決議
module.exports = { mode: "development", module: { noParse: /test/ } }
方案二:優化loader
1.對于某些庫,不使用loader
例如:babel-loader可以轉換ES6或更高版本的語法,可是有些庫本身就是用ES5語法書寫的,不需要轉換,使用babel-loader反而會浪費構建時間
通過module.rule.exclude或module.rule.include,排除或僅包含需要應用loader的場景,可以直接排除掉node_modules的所有包,也可以僅排除單獨的包
module.exports = { module: { rules: [ { test: /\.js$/, exclude: /node_modules/,// exclude: /lodash/ //或 // include: /src/, use: "babel-loader" } ] } }
2.利用cache-loader對模塊進行快取
如果某個檔案內容不變,經過相同的loader決議后,決議后的結果也不變,所以可以將loader的決議結果保存下來,讓后續的決議直接使用保存的結果
module.exports = { module: { rules: [ { test: /\.js$/, use: ['cache-loader', 'babel-loader'] }, ], }, };
大家可能會感到疑惑,明明loader是從后往前執行的,那么cache-loader是怎么拿到babel-loader的結果的呢?
其實,每個loader上,還有一個pitch的靜態方法
function loader(source){ return `new source` } loader.pitch = function(filePath){ // 可回傳可不回傳 // 如果回傳,回傳源代碼 } module.exports = loader;
loader真正執行的順序是這樣的:
loader1.pitch => loader2.pitch => loader3.pitch => loader3 => loader2 => loader1
因此,以['cache-loader', 'babel-loader']為例,
第一次打包:
- 先呼叫cache-loader.pitch,發現無快取,往后執行,
- 呼叫babel-loader.pitch,也發現無快取,往后執行,
- 讀取當前需要處理的模塊的代碼
- 呼叫babel-loader,回傳修改成es5的代碼
- 呼叫cache-loader,回傳babel-loader處理的結果代碼并快取
第二次打包:
- 先呼叫cache-loader.pitch,發現有快取,則回傳源代碼
- 直接回傳上次處理好的源代碼,不會繼續往后走了
當然對于babel-loader,使用它本身的配置也是可以快取的
module.exports = { module: { rules: [ { test: /\.js$/, use: 'babel-loader?cacheDirectory' }, ], }, };
3.開啟thread-loader
它會把后續的loader放到執行緒池的執行緒中運行,以提高構建效率
由于后續的loader會放到新的執行緒中,所以,后續的loader不能:
- 使用 webpack api 生成檔案 (loader背景關系中的emitFile、emitError等api)
- 無法使用自定義的 plugin api (某些插件提供了自身的plugin和loader,plugin會向webpack注入新的api,loader中會使用)
- 無法訪問 webpack的配置
注意,開啟和管理執行緒需要消耗時間,在小型專案中使用thread-loader反而會增加構建時間
方案三:熱替換
熱替換并不能降低構建時間(可能還會稍微增加),但可以減少代碼改動到效果呈現的時間
// webpack配置 module.exports = { devServer:{ hot:true // 開啟HMR } }
// index.js if(module.hot){ // 是否開啟了熱更新 module.hot.accept() // 接受熱更新 }
方案四:元件
什么情況下使用?
當打包出來的多個bundle.js檔案都有重復的第三方代碼,會增加檔案的體積,不利于傳輸
打包的程序:
1.使用output.library配置公共模塊的全域變數名
// webpack.dll.config.js module.exports = { mode: "production", entry: { jquery: ["jquery"], lodash: ["lodash"] }, output: { filename: "dll/[name].js", library: "[name]"// 每個buldle暴露的全域變數名 } };
打包結果
// dist/dll/lodash var lodash=function(n){xxx} // dist/dll/jquery var jquery=function(n){xxx}
2.用DllPlugin創建資源清單(包含資訊:全域變數名、node_modules對應包的路徑)
// webpack.dll.config.js module.exports = { plugins: [ new webpack.DllPlugin({ path: path.resolve(__dirname, "dll", "[name].manifest.json"), //資源清單的保存位置 name: "[name]"//資源清單中,暴露的變數名 }) ] };
打包生成的資源清單
// dll/lodash.manifest.json { "name": "lodash", "content": { "./node_modules/lodash/lodash.js": { xxx } } } // dll/jquery.manifest.json { "name": "jquery", "content": { "./node_modules/jquery/dist/jquery.js": { xxx } } }
3.用DllReferencePlugin使用資源清單
在頁面中手動引入公共模塊
<script src="./dll/jquery.js"></script> <script src="./dll/lodash.js"></script>
重新設定clean-webpack-plugin
如果使用了插件clean-webpack-plugin,為了避免它把公共模塊清除,需要做出以下配置,webpack.config.js配置(注意不是和output.library、DllPlugin在同一個組態檔中哦):
new CleanWebpackPlugin({ // 要清除的檔案或目錄 // 排除掉dll目錄本身和它里面的檔案 cleanOnceBeforeBuildPatterns: ["**/*", '!dll', '!dll/*'] })
使用DllReferencePlugin
new webpack.DllReferencePlugin({ manifest: require("./dll/jquery.manifest.json") }), new webpack.DllReferencePlugin({ manifest: require("./dll/lodash.manifest.json") })
打包程序:首先要根據webpack.dll.config.js組態檔打包一次,之后再根據webpack.config.js打包
最終打包結果的格式:
(function(modules){ //... })({ // index.js檔案的打包結果并沒有變化 "./src/index.js": function(module, exports, __webpack_require__){ var $ = __webpack_require__("./node_modules/jquery/index.js") var _ = __webpack_require__("./node_modules/lodash/index.js") _.isArray($(".red")); }, // 由于資源清單中存在,jquery的代碼并不會出現在這里 "./node_modules/jquery/index.js": function(module, exports, __webpack_require__){ module.exports = jquery; }, // 由于資源清單中存在,lodash的代碼并不會出現在這里 "./node_modules/lodash/index.js": function(module, exports, __webpack_require__){ module.exports = lodash; } })
優點:
- 極大提升自身模塊的打包速度
- 極大的縮小了自身檔案體積
- 有利于瀏覽器快取第三方庫的公共代碼
缺點:
- 使用非常繁瑣
- 如果第三方庫中包含重復代碼,則效果不太理想
方案五:抽離公共代碼
有多個模塊都參考了公共模塊,當一個模塊加載時,訪問了公共模塊,并快取下來,另一個模塊加載就可以直接使用快取的結果,
module.exports = { optimization: { splitChunks: { // 分包策略 chunks: "all", cacheGroups: { // 公共模塊 common: { mixSize: 0, minChunks: 2, // 至少被幾個檔案參考 }, vendors: { test: /[\\/]node_modules[\\/]/, // 當匹配到相應模塊時,將這些模塊進行單獨打包 priority: 1 // 快取組優先級,優先級越高,該策略越先進行處理,默認值為0 }, } } } }
還可以抽離公共樣式,使用MiniCssExtractPlugin
module.exports = { optimization: { splitChunks: { chunks: "all", cacheGroups: { styles: { test: /\.css$/, // 匹配樣式模塊 minSize: 0, // 覆寫默認的最小尺寸,這里僅僅是作為測驗 minChunks: 2 // 覆寫默認的最小chunk參考數 } } } }, module: { rules: [{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] }] }, plugins: [ new CleanWebpackPlugin(), new MiniCssExtractPlugin({ filename: "[name].[hash:5].css", // chunkFilename是配置來自于分割chunk的檔案名 chunkFilename: "common.[hash:5].css" }) ] }
通過cdn方式引入js、css檔案,將不怎么需要更新的第三方庫脫離webpack打包,不被打入bundle中,從而減少打包時間,但又不影響運用第三方庫的方式,例如import方式等,
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
module.exports = {
plugins: [
new HtmlWebpackExternalsPlugin({
externals: [{
module: 'vue',
entry: 'https://lib.baomitu.com/vue/2.6.12/vue.min.js',
global: 'Vue'
}]
})
],
}
最后看到在dist/index.html中動態添加了如下代碼:
<script type="text/javascript" src="https://lib.baomitu.com/vue/2.6.12/vue.min.js"></script>
方案六:代碼壓縮
壓縮js和css代碼:
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
optimization: {
// 是否要啟用壓縮,默認情況下,生產環境會自動開啟
minimize: true,
minimizer: [ // 壓縮時使用的插件
// 壓縮js檔案
new TerserPlugin({
parallel: true // 開啟多執行緒壓縮
}),
// 壓縮css檔案
new OptimizeCSSAssetsPlugin()
],
},
};
使用compression-webpack-plugin插件對打包結果進行預壓縮,可以移除服務器的壓縮時間
new CmpressionWebpackPlugin({ test: /\.js/, minRatio: 0.5 })
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/296367.html
標籤:JavaScript
上一篇:js原生搜索框自動搜索
下一篇:作業記錄:8個有用的JS技巧
