1 參考文章
詳解webpack4之splitchunksPlugin代碼包分拆
開發工具心得:如何 10 倍提高你的 Webpack 構建效率
2: webpack4中的 optimization.runtimeChunk的作用是什么?
首先看參考文章:
優化持久化緩存的, runtime 指的是 webpack 的運行環境(具體作用就是模塊解析, 加載) 和 模塊信息清單,
模塊信息清單在每次有模塊變更(hash 變更)時都會變更, 所以我們想把這部分代碼單獨打包出來,
配合后端緩存策略, 這樣就不會因為某個模塊的變更導致包含模塊信息的模塊(通常會被包含在最后一個 bundle 中)緩存失效.
optimization.runtimeChunk 就是告訴 webpack 是否要把這部分單獨打包出來.
假設一個使用動態導入的情況(使用import()),在app.js動態導入component.js
const app = () =>import('./component').then();
build之后,產生3個包。
0.01e47fe5.js
main.xxx.js
runtime.xxx.js
其中runtime,用於管理被分出來的包。下面就是一個runtimeChunk的截圖,可以看到chunkId這些東西。
...
function jsonpScriptSrc(chunkId) { /******/ return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + "." + {"0":"01e47fe5"}[chunkId] + ".bundle.js" /******/ }
...
如果采用這種分包策略
當更改app的時候runtime與(被分出的動態加載的代碼)0.01e47fe5.js的名稱(hash)不會改變,main的名稱(hash)會改變。
當更改component.js,main的名稱(hash)不會改變,runtime與 (動態加載的代碼) 0.01e47fe5.js的名稱(hash)會改變。
總結一下:
runtime.js文件相當於動態文件的索引文件,相當於一個文件夾中的index索引文件,告訴main.js要引用的文件的名字
這樣app.js 變化的時候 由於不影響 componment.js 所以生成的0.01.js 和runtime.js 不會發生變化;
當 componment.js 發生變化的時候,生成的0.01.js要發生變化,同時索引文件 runntime.js 也會發生變化。但是main.js引用的是runntime.js 則不會發生變化
---
3 使用 splitchunksPlugin 提取公共代碼
optimization:{ splitChunks: { chunks: 'all',//同步異步全都打包 minSize: 30000,//打包的庫或者文件必須大於這個字節才會進行拆分 minChunks: 1,//規定當模塊在生成的js文件(trunk)中被調用過多少次的時候再進行拆分 maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~',//如果不寫filename 默認名字 組名~[name] name: true, cacheGroups: {//緩存組,因為需要打包完成之后,在把所有要拆分的代碼合並拆分,所以先要緩存 vendors: { test: /[\\/]node_modules[\\/]/, //如果上面chunks定為all,就是找到所有的import文件,看他是不是調用於 node_modules 文件夾 是的話就拆分 priority: -10,//優先級 比如同時符合vender 和 default 這個優先級高 所以存在這里 filename: 'vendors.js', //拆分后打包的文件名字 }, default: {//像文件中 import進來的文件 如果不在 node_modules文件夾中 則走默認組,打包出的文件名字是 common.js priority: -20, minChunks: 2, reuseExistingChunk: true,//比如a.js 引用了 b.js;如果b.js在之前已經被拆分過,則這里不再對其進行拆分 filename: 'common.js' } } } }
為了支持異步js,
npm install --save-dev @babel/plugin-syntax-dynamic-import
記得修改 .babelrc
{ "presets":[ ["@babel/preset-env",{ "useBuiltIns":"usage", "corejs":2, "targets":{ "browsers":[">1%","last 2 version","not ie <= 8"] } }] ], "plugins": ["@babel/plugin-syntax-dynamic-import"] }
注意:
4: 使用 DLLPlugin 提取第三方不變化的代碼庫:
DLLPlugin 它能把第三方庫代碼分離開,並且每次文件更改的時候,它只會打包該項目自身的代碼。所以打包速度會更快。
const path = require('path'); const DllPlugin = require('webpack/lib/DllPlugin'); const {CleanWebpackPlugin} = require('clean-webpack-plugin'); module.exports = { mode:'production', // 入口文件 entry: { // 項目中用到該兩個依賴庫文件 vue: ['vue'] }, // 輸出文件 output: { // 文件名稱 filename: '[name].dll.js', // 將輸出的文件放到dist目錄下 path: path.resolve(__dirname, '../static'), /* 存放相關的dll文件的全局變量名稱,比如對於jquery來說的話就是 _dll_jquery, 在前面加 _dll 是為了防止全局變量沖突。 */ library: '[name]_library' }, plugins: [ new CleanWebpackPlugin(), //注意在webpack4 中 不需要定於刪除哪個文件夾 默認會根據output中生成的path路徑刪掉 // 使用插件 DllPlugin new DllPlugin({ /* 該插件的name屬性值需要和 output.library保存一致,該字段值,也就是輸出的 manifest.json文件中name字段的值。 比如在jquery.manifest文件中有 name: '_dll_jquery' */ name: '[name]_library', context:__dirname, //context (可選): manifest文件中請求的上下文,默認為該webpack文件上下文
/* 生成manifest文件輸出的位置和文件名稱 */ path: path.join(__dirname, '../static/', '[name].manifest.json') }) ] };
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin'); const proConfig = { plugins:[ new DllReferencePlugin({ context:__dirname, manifest:require('../static/vue.manifest.json') }) ] }
然后增加 package.json 文件:
"dll": "webpack --config ./build/webpack.dll.js --hide-modules --progress"
最后在模版文件中增加引用:
<script type="text/javascript" src="../static/vue.dll.js"></script>
所以先執行 npm run dll,在執行npm run build
但是這樣修該模版文件不太好,所以引入插件:
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin'); plugins:[ new AddAssetHtmlPlugin({ filepath: require.resolve('../static/vue.dll.js'),//相當於path.join(__dirname, '../static/vendordev.dll.js') includeSourcemap: false }) ]
注意:
1: html-webpack-include-assets-plugin 和 add-asset-html-webpack-plugin 的區別
兩個插件都是把規定的文件插入到html中:
主要的不同是 html-webpack-include-assets-plugin 不會copy文件,而 add-asset-html-webpack-plugin 會copy文件,什么意思呢?
使用 add-asset-html-webpack-plugin 后,會把引入的 filepath: require.resolve('../static/vue.dll.js') 自動引入到 dist 文件夾中,而 html-webpack-include-assets-plugin 則不會;
所以在dev環境下,直接把vue.dll.js 引入dist目錄下即可,所以使用 add-asset-html-webpack-plugin;而在production環境下,要把 vue.dll.js 放在dist/lib 文件夾下,所以使用CopyWebpackPlugin 復制vue.dll.js文件,配合 html-webpack-include-assets-plugin 插入html中;
所以分為dev和product環境:
const htmlWebpackIncludeAssetsPlugin = require('html-webpack-include-assets-plugin'); const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); //dev環境中,該插件會把 vue.dll.js 文件復制到當前路徑下 new AddAssetHtmlPlugin({ filepath: require.resolve('../static/vue.dll.js'),//相當於path.join(__dirname, '../static/vendordev.dll.js') includeSourcemap: false, }), //production 環境中 // 1 需要把 dll 文件復制到打包的 dist/lib 文件夾下 -- CopyWebpackPlugin // 2 html中引入 dist/lib 下的dll 文件 -- htmlWebpackIncludeAssetsPlugin new htmlWebpackIncludeAssetsPlugin({ //這個插件是把vue.dll.js 插入到 html 中 assets:['./lib/vue.dll.js'], append:false }), new CopyWebpackPlugin([ //文件復制到打包的 dist/lib 文件夾下 { from: path.join(__dirname, "../static/vue.dll.js"), to: path.join(__dirname, "../dist/lib/vue.dll.js") } ]),
注意2:
這里引入 AddAssetHtmlPlugin 你會發現即使不使用:
new DllReferencePlugin({ context:__dirname, manifest:require('../static/vue.manifest.json') })
頁面也可以使用vue。
那么 DllReferencePlugin 的作用是什么呢?
我們看打包生成的文件:
情況1: 不使用 DllReferencePlugin:
可以看出,index.js 文件中使用的vue居然還是來自 node_modules 說明並沒有把vue第三方庫剔除;
對比使用DllReferencePlugin :
發現index中已經沒有 vue 第三方庫,但是頁面仍舊能打開,說明使用的是 dll 文件。
再來看 DllReferencePlugin 的作用:
有了映射文件,在webpack打包的時候就可以結合映射文件以及生成的全局變量,來對需要打包的源代碼進行分析。
一旦發現你打包的源代碼里面用到了映射文件中已有的文件,則直接使用vendors.dll.js 中的內容,而不會去node_modules里引入該模塊
5. 引入的loader一定要加上 exclude和include 可以大幅降低打包的代碼文件大小
6. 使用別名優化:
resolve:{ extensions:['.js','.vue','.json'], alias:{ "@":path.resolve('src') } }
使用extensions省略后綴名字;使用alias給src加上別名,這樣可以簡化html中的路徑,比如:
pages/index/index.vue 中要使用 assest/imgs/logo.png 的圖片:
<img src="../../assest/imgs/logo.png" alt="" class="img-box">
可以簡化成:
<img src="@/assest/imgs/logo.png" alt="" class="img-box">
類似的:
//簡化前 import Chinese from '../../component/chinese.vue'; //簡化后 import Chinese from '@/component/chinese.vue';
但是對於css注意,在引用路徑的字符串前面加上 ~ 的符號,如:@import “~Css/…”。webpack 會以~符號作為前綴的路徑視為依賴模塊去解析
background: url('~@/assest/imgs/logo.png');
代碼:
import VueRouter from 'vue-router' import routers from './routers' Vue.use(VueRouter) const router = new VueRouter({ mode: 'history', routers })
原因:
routes:不是routers
解決辦法:
routers改為routes即可。
8.解決webpack中css獨立成文件后圖片路徑錯誤的問題
說明:此配置針對webpack4+
配置img圖片路徑問題:
output:{ filename:'js/[name].[chunkhash].js', path:path.resolve(__dirname,'../dist'), }, //module { test:/\.(png|gif|jpeg|jpg)$/, use:[ { loader:'url-loader?cacheDirectory=true', options:{ name:'img/[name].[ext]', limit:1024 } } ] }, //plugins new MiniCssExtractPlugin({ filename: devMode ? '[name].css' : 'css/[name].[contenthash].css', chunkFilename: devMode ? '[name].css' : 'css/[name].[contenthash].css', })
生成的圖片會在css文件中: css/img/logo.png 這樣的話 找不到該圖片,因為生成的圖片路徑應該是 img/logo.png
如果修改output的配置中加入一個 publicPath: '../' 配置,則所有的css文件和js的路徑都會變化,所以應該只處理css文件,設置css文件中的公共路徑:
output:{ filename:'js/[name].[chunkhash].js', path:path.resolve(__dirname,'../dist'), }, //module { test:/\.scss$/, use:[ { loader:MiniCssExtractPlugin.loader, options:{ publicPath:'../' } }, { loader:'css-loader', options:{ importLoaders:2, } }, 'postcss-loader', 'sass-loader' ] }, { test:/\.(png|gif|jpeg|jpg)$/, use:[ { loader:'url-loader?cacheDirectory=true', options:{ name:'img/[name].[ext]', limit:1024 } } ] }, //plugins new MiniCssExtractPlugin({ filename: devMode ? '[name].css' : 'css/[name].[contenthash].css', chunkFilename: devMode ? '[name].css' : 'css/[name].[contenthash].css', })
9 為了避免不同路由頁面間樣式沖突,css樣式要加 scoped
import Home from "./view/home"; const ProInsight = () => import(/* webpackPrefetch: true */ /* webpackChunkName: 'proInsight' */ "./view/proInsight"); const ProTarget = () => import(/* webpackPrefetch: true */ /* webpackChunkName: 'proTarget' */ "./view/proTarget"); Vue.use(VueRouter); const routes = [ { path: "/", name: "home", component: Home }, { path: "/proInsight", name: "proInsight", component: ProInsight }, { path: "/proTarget", name: "proTarget", component: ProTarget } ];
中,首頁入口 home文件沒有使用異步加載,則該組件所有引用的公共代碼,都會被打包到home中,而不會提取出來,
所以,home這個組件也要改成異步加載的形式。這樣所有的公共代碼才能被提取出來。


stats: { warningsFilter: (warning) => /Conflicting order between/gm.test(warning), children: false }