webpack4 打包優化


1 參考文章

徹底解決 webpack 打包文件體積過大

webpack4提升180%編譯速度

詳解webpack4之splitchunksPlugin代碼包分拆

webpack v4 中的斷舍離

開發工具心得:如何 10 倍提高你的 Webpack 構建效率

Webpack打包構建太慢了?試試幾個方法

上手webpack4並進階?來看這里~

 

注意合並 webpack.config.js 文件的時候,把 commonConfig 放在前面 否則一些地方會報錯
module.exports = merger(commonConfig,prodConfig);

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"]
}

 

注意:

minSize: 指的是打包前的文件大小,並且指的是 組成 commonjs的所有文件的和的大小,而不是其中一個文件的大小;
比如 組成commonjs的三個子組件是 a.js b.js c.js 分別是10k,則minSize需大於 30k 才不會打包這幾個文件,
而不是 10k。

1: chunks: "initial"-- 表示只從入口模塊進行拆分
1.1 有異步引入的js文件:三個入口文件+公共的common文件+子組件【相當於原來的文件分別單獨打包】
1.2 全是同步引入js文件:三個入口文件+公共的common文件{包括了子組件}

2: chunks: "async" -- 表示只從異步加載得模塊(動態加載import())里面進行拆分
2.1 有異步引入的js文件:三個入口文件【common文件被重復打包到入口文件中】+子組件
2.2 全是同步引入js文件:三個入口文件{每個文件均包括了common和三個子組件}

3: chunks: "all"
3.1 有異步引入的js文件:三個入口文件+common文件+三個子組件【相當於原來的文件分別單獨打包】
3.2 全是同步引入js文件:三個入口文件+common文件【包括common本身和引入的子組件】

 

4: 使用 DLLPlugin 提取第三方不變化的代碼庫:

 
splitchunksPlugin 每次打包的時候還是會去處理一些第三方依賴庫,只是它能把第三方庫文件和我們的代碼分開掉,生成一個獨立的js文件。但是它還是不能提高打包的速度。

DLLPlugin 它能把第三方庫代碼分離開,並且每次文件更改的時候,它只會打包該項目自身的代碼。所以打包速度會更快。

DLLPlugin:
vendor.dll.js文件:存放第三方庫
venfor-manifest.json文件:包含所有代碼庫的一個索引

DllReferencePlugin:在webpack.config.js 文件中使用的
作用是用該插件讀取 venfor-manifest.json文件 查詢第三方庫
 
首先編寫webpack.dll.js文件:
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')
    })
  ]
};

 

在 webpack.prod.js 文件中調用索引文件
 
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');

 

7 :vue-router的問題

代碼:

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

<style lang="scss" scoped>
否則 兩個路由中對同一個div做不同的樣式,后面的樣式會覆蓋掉前一個路由頁面的樣式。
 
10: 使用 splitchunksPlugin 提取公共組件和代碼不生效,要注意入口js是否使用了異步加載
比如路由js:
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這個組件也要改成異步加載的形式。這樣所有的公共代碼才能被提取出來。

 
11. 打包時,報一堆警告:
 
 
stats: { 
    warningsFilter: (warning) => /Conflicting order between/gm.test(warning),
    children: false 
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM