webpack優化策略

  • 速度分析

    • webpack有時侯打包很慢,而我們在項目中可能用了很多的pluginloader,想知道那個環節慢,下面這個插件可以計算pluginloader的耗時
    yarn add -D speed-measure-webpack-plugin
    
  • 體積分析

    • 打包后的體積優化是一個很重要的點,譬如引入的一些第三方組件庫過大,這是就要考慮是否需要尋找替代品了。這里可以采用webpack-bundle-analyzer,它可以用交互式可縮放樹形圖顯示 webpack輸出文件的大小。
    yarn add -D webpack-bundle-analyzer
    
    • 安裝完后,配置webpcak.config.js,配置好后,yarn run dev啟動后,會默認起一個端口號為8888的本地服務器。這樣就能看到每個模塊的的文件大小,進行針對性的優化。
  • 多線程/多實例構建

    • 大家都知道webpack是運行在node環境中,二node,是單線程的。webpack的打包過程是io密集和計算機密集型操作,如果能fork,多個進程并行處理各個任務,將會有效縮短構建時間。一般使用較多的兩個是thread-loaderHappyPack。

      • thread-loader(官方推薦)
       yarn add -D thread-loader
      

      thread-loader會將你的loader放置在一個worker池里面運行,以達到多線程構建。
      注:放置在這個thread-loader之后的loader會在一個單獨的worker池(worker pool)中運行。

      • HappyPack(該插件作者不在維護)
      yarn add -    D happypack
      

      HappyPack可以讓Webpack同一時間處理多個任務,發揮多喝CPU的能力,將任務分解給多個子進程去并發的執行,子進程處理完成后,再把結果發送給主進程。通過多進程模型,來加速代碼的構建。

  • 多進程并行壓縮代碼

    • 通常我們在開發環境,代碼構建時間比較快,而構建用于發布到線上的代碼時會添加壓縮代碼這一流程,則會道濟計算量大耗時多
    • webpack默認提供了UglifyJS插件來壓縮JS代碼,但是使用的是單線程壓縮代碼,也就是說多個js文件需要被壓縮,它需要一個個文件進行壓縮。所以說在正式環境打包壓縮代碼速度非常慢(因為壓縮JS代碼需要先把代碼解析成用Object抽象表示的AST語法樹,再應用各種規則分析處理AST,導致這個過程耗時非常大)。所以我們要對壓縮代碼這里一步驟進行優化,常用的做法是多進程并行壓縮,目前主要有三種主流的壓縮方案。
      • paralle-uglify-plugin

      • uglifyjs-webpack-plugin

      • terser-webpack-plugin

        • parallel-uglify-plugin
          上面介紹的HappyPack的思想是使用多個子進程去解析和編譯JS,CSS等,這樣就可以并行處理多個子任務,多個子任務完成后,再將結果發到主進程中,有了這個思想后,ParallelUglifyPlugin插件就產生了。

          webpack有多個JS文件需要輸出和壓縮時,原來會使用UglifyJS去一個個壓縮并且輸出,而ParallelUglifyPlugin插件則會開啟多個子進程,把對多個文件壓縮的工作分給多個子進程去完成,但是每個子進程還是通過UglifyJS去壓縮代碼。并行壓縮可以顯著的提升效率

          yarn add -D webpack-parallel-uglify-plugin
          

          注:webpack-parallel-uglify-plugin已不再維護,這里不推薦使用

        • uglifyjs-webpack-plugin

          yarn add -D uglifyjs-webpack-plugin
          

          注:其實它和上面的parallel-uglify-plugin類似,也可通過設置parallel: true開啟多進程壓縮。

        • terser-webpack-plugin
          不知道你有沒有發現:webpack4 已經默認支持 ES6語法的壓縮。而這離不開terser-webpack-plugin.

        yarn add -D uglifyjs-webpack-plugin
        

        注:其實它和上面的parallel-uglify-plugin類似,也可通過設置parallel: true開啟多進程壓縮。

  • 預編譯資源模塊

    • 什么是預編譯資源模塊?
      在使用webpack進行打包時候,對于依賴的第三方庫,比如vue,vuex等這些不會修改的依賴,我們可以讓它和我們自己編寫的代碼分開打包,這樣做的好處是每次更改我本地代碼的文件的時候,webpack只需要打包我項目本身的文件代碼,而不會再去編譯第三方庫。那么第三方庫在第一次打包的時候只打包一次,以后只要我們不升級第三方包的時候,那么webpack就不會對這些庫去打包,這樣的可以快速的提高打包的速度。其實也就是預編譯資源模塊。webpack中,我們可以結合DllPluginDllReferencePlugin插件來實現。
    • DllPlugin是什么?
      它能把第三方庫代碼分離開,并且每次文件更改的時候,它只會打包該項目自身的代碼。所以打包速度會更快。DLLPlugin插件是在一個額外獨立的webpack設置中創建一個只有dllbundle,也就是說我們在項目根目錄下除了有webpack.config.js,還會新建一個webpack.dll.js文件。webpack.dll.js的作用是把所有的第三方庫依賴打包到一個bundledll文件里面,還會生成一個名為manifest.json文件。該manifest.json的作用是用來讓DllReferencePlugin映射到相關的依賴上去的。
    • DllReferencePlugin又是什么?
      這個插件是在webpack.config.js中使用的,該插件的作用是把剛剛在webpack.dll.js中打包生成的dll文件引用到需要的預編譯的依賴上來。什么意思呢?就是說在webpack.dll.js中打包后比如會生成 vendor.dll.js文件和vendor-manifest.json文件,vendor.dll.js文件包含了所有的第三方庫文件,vendor-manifest.json文件會包含所有庫代碼的一個索引,當在使用webpack.config.js文件打包DllReferencePlugin插件的時候,會使用該DllReferencePlugin插件讀取vendor-manifest.json文件,看看是否有該第三方庫。vendor-manifest.json文件就是一個第三方庫的映射而已。
    • 這么在項目中使用
    • 上面說了這么多,主要是為了方便大家對于預編譯資源模塊和DllPluginDllReferencePlugin插件作用的理解。先來看下完成的項目目錄結構,主要在兩塊配置,分別是webpack.dll.jswebpack.config.js(對應這里我是webpack.base.js
      image.png
    • webpack.dll.js
      const path = require('path');
      const webpack = require('webpack');
    
      module.exports = {
        mode: 'production',
        entry: {
        vendors: ['lodash', 'jquery'],
        react: ['react', 'react-dom']
      },
     output: {
      filename: '[name].dll.js',
      path: path.resolve(__dirname, './dll'),
      library: '[name]'
    },
    plugins: [
      new webpack.DllPlugin({
        name: '[name]',
        path: path.resolve(__dirname, './dll/[name].manifest.json')
        })
      ]
    }
    

    這里我拆了兩部分:vendors(存放了lodash、jquery等)和react(存放了 react相關的庫,react、react-dom等)

    • webpack.config.js(對應我這里就是webpack.base.js)
    const path = require("path");
    const fs = require('fs');
    // ...
    const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
    const webpack = require('webpack');
    
    const plugins = [
     // ...
    ];
    
    const files = fs.readdirSync(path.resolve(__dirname, './dll'));
    files.forEach(file => {
     if(/.*\.dll.js/.test(file)) {
       plugins.push(new AddAssetHtmlWebpackPlugin({
         filepath: path.resolve(__dirname, './dll', file)
       }))
     }  
     if(/.*\.manifest.json/.test(file)) {
       plugins.push(new webpack.DllReferencePlugin({
         manifest: path.resolve(__dirname, './dll', file)
       }))
     }
    })
    
    module.exports = {
     entry: {
       main: "./src/index.js"
     },
     module: {
       rules: []
     },
     plugins,
    
     output: {
       // publicPath: "./",
       path: path.resolve(__dirname, "dist")
       }
     }
    

    由于上面我把第三方庫做了一個拆分,所以對應生成也就會是多個文件,這里讀取了一下文件,做了一層遍歷。最后在package.json里面再添加一條腳本就可以了:

    "scripts": {
      "build:dll": "webpack --config ./webpack.dll.js",
    },
    

    運行yarn build:dll就會生成本小節開頭貼的那張項目結構圖了~

  • 利用緩存提升二次構建速度

    • 一般來說,對于靜態資源,我們都希望瀏覽器能夠進行緩存,那樣以后進入頁面就可以直接使用緩存資源,頁面打開速度會顯著加快,既提高了用戶的體驗也節省了寬帶資源。當然瀏覽器緩存方法有很多種,這里只簡單討論下在webpack中如何利用緩存來提升二次構建速度。在webpack中利用緩存一般有以下幾種思路:
      • babel-loader開啟緩存
      • 使用cache-loader
      • 使用hard-source-webpack-plugin
    • babel-loader
      • babel-loader在執行的時候,可能會產生一些運行期間重復的公共文件,造成代碼體積冗余,同時也會減慢編譯效率??梢约由?code>cacheDirectory參數開啟緩存:
       {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [{
          loader: "babel-loader",
          options: {
            cacheDirectory: true
          }
        }],
      },
      
    • cache-loader
      • 在一些性能開銷較大的loader之前添加此loader,以將結果緩存到磁盤里。
      yarn add -D cache-loader
      
      • 使用cache-loader的配置很簡單,放在其他loader之前即可。修改Webpack的配置如下:
      // webpack.config.js
      module.exports = {
        module: {
          rules: [
            {
              test: /\.ext$/,
              use: [
                'cache-loader',
                ...loaders
              ],
              include: path.resolve('src')
             }
          ]
        }
      }
      
      注:請注意,保存和讀取這些緩存文件會有一些時間開銷,所以請只對性能開銷較大的loader使用此loader
    • hard-source-webpack-plugin
      • HardSourceWebpackPlugin為模塊提供了中間緩存,緩存默認的存放路徑是: node_modules/.cache/hard-source。配置hard-source-webpack-plugin后,首次構建時間并不會有太大的變化,但是從第二次開始,構建時間大約可以減少 80%左右。
        yarn add -D hard-source-webpack-plugin
        
      • 使用
        // webpack.config.js
        var HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
        
        module.exports = {
          entry: // ...
          output: // ...
          plugins: [
              new HardSourceWebpackPlugin()
           ]
        }
        
      注:webpack5中會內置hard-source-webpack-plugin。
  • 縮小構建目標

    • 有時候我們的項目中會用到很多模塊,但有些模塊其實是不需要被解析的。這時我們就可以通過縮小構建目標或者減少文件搜索范圍的方式來對構建做適當的優化。
    • 主要是exclude 與 include的使用:
      • exclude: 不需要被解析的模塊
      • include: 需要被解析的模塊
      const path = require('path');
      module.exports = {
        ...
        module: {
        rules: [
         {
            test: /\.js$/,
            exclude: /node_modules/,
            // include: path.resolve('src'),
            use: ['babel-loader']
          }
        ]
      }
      
      這里babel-loader就會排除對node_modules下對應js的解析,提升構建速度。
  • 減少文件搜索范圍

    • 這個主要是resolve相關的配置,用來設置模塊如何被解析。通過resolve的配置,可以幫助Webpack快速查找依賴,也可以替換對應的依賴。
      • resolve.modules:告訴webpack解析模塊時應該搜索的目錄
      • resolve.mainFields:當從npm包中導入模塊時(例如,import * as React from 'react'),此選項將 決定在package.json中使用哪個字段導入模塊。根據webpack配置中指定的target不同,默認值也會有所不同
      • resolve.mainFiles:解析目錄時要使用的文件名,默認是index
      • resolve.extensions:文件擴展名
       // webpack.config.js
      const path = require('path');
      module.exports = {
        ...
        resolve: {
          alias: {
            react: path.resolve(__dirname, './node_modules/react/umd/react.production.min.js')
          }, //直接指定react搜索模塊,不設置默認會一層層的搜尋
          modules: [path.resolve(__dirname, 'node_modules')], //限定模塊路徑
          extensions: ['.js'], //限定文件擴展名
          mainFields: ['main'] //限定模塊入口文件名
        }
        ...
      }
      
  • 動態 Polyfill 服務

    • 什么是 babel-polyfill?
      babel只負責語法轉換,比如將ES6的語法轉換成ES5。但如果有些對象、方法,瀏覽器本身不支持,比如:
      • 全局對象:Promise、WeakMap 等。
      • 全局靜態函數:Array.from、Object.assign 等。
      • 實例方法:比如 Array.prototype.includes 等。
        此時,需要引入babel-polyfill來模擬實現這些對象、方法。這種一般也稱為墊片
    • 怎么使用 babel-polyfill?
      • 使用也非常簡單,在webpack.config.js文件作如下配置就可以了:
        module.exports = {
          entry: ["@babel/polyfill", "./app/js"],
        };
        
      babel-polyfill由于是一次性全部導入整個polyfill,所以用起來很方便,但與此同時也帶來了一個大問題:文件很大,所以后續的方案都是針對這個問題做的優化。
    • 動態Polyfill服務
      image.png

      每次打開頁面,瀏覽器都會向Polyfill Service發送請求,Polyfill Service識別 User Agent,下發不同的 Polyfill,做到按需加載Polyfill的效果。
    • 怎么使用動態Polyfill服務?
      //訪問url,根據User Agent 直接返回瀏覽器所需的 polyfills
      https://polyfill.io/v3/polyfill.min.js
      
  • Scope Hoisting

    • 什么是Scope Hoisting?
      Scope hoisting 直譯過來就是「作用域提升」。熟悉 JavaScript 都應該知道「函數提升」和「變量提升」,JavaScript 會把函數和變量聲明提升到當前作用域的頂部。「作用域提升」也類似于此,webpack 會把引入的 js 文件“提升到”它的引入者頂部。Scope Hoisting 可以讓 Webpack 打包出來的代碼文件更小、運行的更快。

    • 啟用Scope Hoisting

      • 要在 Webpack 中使用 Scope Hoisting 非常簡單,因為這是 Webpack 內置的功能,只需要配置一個插件,相關代碼如下:
      // webpack.config.js
      const webpack = require('webpack')
      
      module.exports = mode => {
        if (mode === 'production') {
          return {}
        }
      
        return {
          devtool: 'source-map',
          plugins: [new webpack.optimize.ModuleConcatenationPlugin()],
        }
      }
      
      • 好處
        • 代碼體積更小,因為函數申明語句會產生大量代碼;
        • 代碼在運行時因為創建的函數作用域更少了,內存開銷也隨之變小。

      Scope Hoisting 的實現原理其實很簡單:分析出模塊之間的依賴關系,盡可能的把打散的模塊合并到一個函數中去,但前提是不能造成代碼冗余。因此只有那些被引用了一次的模塊才能被合并。

      注意:由于 Scope Hoisting 需要分析出模塊之間的依賴關系,因此源碼必須采用 ES6模塊化語句,不然它將無法生效。

文章轉自:https://mp.weixin.qq.com/s?__biz=MzU2MzM2NzU0NA==&mid=2247486539&idx=1&sn=7484798f6c42611c1e81447fc5a51739&chksm=fc5a175ccb2d9e4aa4f33f049bee8fa18e5a1c27ed315be2911ca5c8a1802760701b818c500e&mpshare=1&scene=1&srcid=0915Ld1r2gSXJFB9I7tNdaa5&sharer_sharetime=1600137668291&sharer_shareid=a8f4498c49789458cc5918ae43d0aed2&key=7f54b3443b683033173cffe97dc7047f2921f7d585f87442c5b17eb942738599ea5f40012f9783eacf8c4f1a5789472fca6fa37449cd0fcbeb9dbd7bd6c184e8c6a24f652623e6d12b6b414b310815b80f59947220ee0919dbd2ec4e0a9f3b463e09cebee1ab37d6d7ccbc29b4def16a8854304e29ba208adbd3b98dbe475920&ascene=1&uin=MjIxNzU2Mjg4NA%3D%3D&devicetype=Windows+10+x64&version=62090538&lang=zh_CN&exportkey=A4LNOZ5CDhlUAgdwZU%2Bg4Vc%3D&pass_ticket=sU%2F%2FRSgUTGKQVQgrUbMNdKmZ6urS7BLB4AhRGceH6Mq8KLX6W06k8Zdvf2hhC9RU&wx_header=0

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,155評論 3 425
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,635評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,255評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,646評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,838評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,399評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,146評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,338評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,565評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,059評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,296評論 2 376