<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          構(gòu)建效率大幅提升,webpack5 在企鵝輔導(dǎo)的升級(jí)實(shí)踐

          共 12605字,需瀏覽 26分鐘

           ·

          2021-06-26 04:37

          | 導(dǎo)語(yǔ)  2020 年 10 月 10 日,webpack5 正式發(fā)布,并帶來(lái)了諸多重大的變更,將會(huì)使前端的構(gòu)建效率與質(zhì)量大為提升。其實(shí)現(xiàn)在各大博客網(wǎng)站已經(jīng)有很多關(guān)于 webpack5 的文章,但真正通過(guò)業(yè)務(wù)實(shí)踐并獲得第一手?jǐn)?shù)據(jù)的并不多,所以今天就給大家介紹一下 webpack5 在企鵝輔導(dǎo)業(yè)務(wù)中的升級(jí)與實(shí)踐 。

          對(duì)比 webpack4

          下面是企鵝輔導(dǎo)h5項(xiàng)目分別在 webpack4 和 webpack5 版本下的構(gòu)建實(shí)測(cè)數(shù)據(jù),測(cè)試環(huán)境為我的 MacBook Pro 15 寸高配。

          webpack版本
          第一次
          build時(shí)間
          第二次
          build時(shí)間
          第三次
          build時(shí)間
          v4
          19.6s
          6.8s
          7.4s
          v5
          14.8s
          1.6s
          1.5s

          在上表打包的結(jié)果基礎(chǔ)之上,修改項(xiàng)目中的代碼后,重新進(jìn)行打包得到如下結(jié)果:

          webpack版本
          第一次
          build時(shí)間
          第二次
          build時(shí)間
          第三次
          build時(shí)間
          v4
          10.5s
          7.3s
          6.8s
          v5
          4.0s
          1.5s
          1.6s

          打包后文件的大?。?/span>

          webpack版本
          build產(chǎn)生的文件的大小
          v4
          2.16M
          v5
          2.05M

          從上表的測(cè)試結(jié)果可以看出,webpack5 構(gòu)建性能相對(duì)于 webpack4 提升很多,但在打包完成的 bundle 大小上,與 v4 差距不大。由此可以看出 webpack5 的新特性帶來(lái)了一些優(yōu)化,下面結(jié)合這些新的特性來(lái)分析為什么能夠做到這些優(yōu)化。

          webpack5 新特性

          webpack5 的發(fā)布帶來(lái)了很多新的特性,例如優(yōu)化持久緩存、優(yōu)化長(zhǎng)期緩存、Node Polyfill 腳本的移除、更優(yōu)的 tree-shaking 以及 Module Federation 等。下面針對(duì)這些新的特性作出分析。

          1、編譯緩存

          顧名思義,編譯緩存就是在首次編譯后把結(jié)果緩存起來(lái),在后續(xù)編譯時(shí)復(fù)用緩存,從而達(dá)到加速編譯的效果。

          1.1、webpack4 緩存方案

          webpack4 及之前的版本本身是沒(méi)有持久化緩存的能力的,只能借助其他的插件或 loader 來(lái)實(shí)現(xiàn),例如:

          • 使用 cache-loader 來(lái)緩存編譯結(jié)果到硬盤(pán),再次構(gòu)建時(shí)在緩存的基礎(chǔ)上增量編譯長(zhǎng)期緩存。

          • 使用自帶緩存的 loader,如:babel-loader,可以配置 cacheDirectory 來(lái)將 babel 編譯的結(jié)果緩存下來(lái)。

          • 使用 hard-source-webpack-plugin 來(lái)為模塊提供中間緩存。

          如下圖所示,使用以上緩存方案的結(jié)果,默認(rèn)存儲(chǔ)在 node_modules/.cache 目錄下:

          1.2、webpack5 緩存方案

          webpack5 統(tǒng)一了持久化緩存的方案,有效降低了配置的復(fù)雜性。另外由于 webpack 提供了構(gòu)建的 runtime,所有被 webpack 處理的模塊都能得到有效的緩存,大大提高了緩存的覆蓋率,因此 webpack5 的持久化緩存方案將會(huì)比其他第三方插件緩存性能要好很多。

          webpack5 緩存的開(kāi)啟可以通過(guò)以下配置來(lái)實(shí)現(xiàn):

          module.exports = {
              cache: {
                // 將緩存類(lèi)型設(shè)置為文件系統(tǒng)
                type: "filesystem"
                buildDependencies: {
                  /* 將你的 config 添加為 buildDependency,
                     以便在改變 config 時(shí)獲得緩存無(wú)效*/

                  config: [__filename],
                  /* 如果有其他的東西被構(gòu)建依賴(lài),
                     你可以在這里添加它們*/

                  /* 注意,webpack.config,
                     加載器和所有從你的配置中引用的模塊都會(huì)被自動(dòng)添加*/

                },
                // 指定緩存的版本
                version: '1.0' 
              }
          }

          如下圖所示,webpack5 默認(rèn)將構(gòu)建的緩存結(jié)果放在 node_modules/.cache 目錄下,可以通過(guò)配置更改目錄:

          注意事項(xiàng):

          • cache 的屬性 type 會(huì)在開(kāi)發(fā)模式下被默認(rèn)設(shè)置成 memory,而且在生產(chǎn)模式中被禁用,所以如果想要在生產(chǎn)打包時(shí)使用緩存需要顯式的設(shè)置。

          • 為了防止緩存過(guò)于固定,導(dǎo)致更改構(gòu)建配置無(wú)感知,依然使用舊的緩存,默認(rèn)情況下,每次修改構(gòu)建配置文件都會(huì)導(dǎo)致重新開(kāi)始緩存。當(dāng)然也可以自己主動(dòng)設(shè)置 version 來(lái)控制緩存的更新。

          更多緩存的配置可以參考官方文檔:

          https://webpack.js.org/configuration/other-options/#cache

          2、長(zhǎng)效緩存

          長(zhǎng)效緩存指的是能充分利用瀏覽器緩存,盡量減少由于模塊變更導(dǎo)致的構(gòu)建文件 hash 值的改變,從而導(dǎo)致文件緩存失效。

          2.1、webpack4 長(zhǎng)效緩存方案

          webpack4 及之前的版本 moduleIdchunkId 默認(rèn)是自增的,更改模塊的數(shù)量,容易導(dǎo)致緩存的失效。

          使用腳手架創(chuàng)建一個(gè)簡(jiǎn)單的項(xiàng)目,構(gòu)建結(jié)果如下:

          import React from 'react';
          import ReactDOM from 'react-dom';

          ReactDOM.render(
            <React.StrictMode>
              <div />
            </React.StrictMode>,
            document.getElementById('root')
          );

          注釋掉入口文件 test.js  里引用的 css 文件,如上代碼,構(gòu)建結(jié)果如下:

          由上圖可知,僅僅改了其中一個(gè)文件,結(jié)果構(gòu)建出來(lái)的所有 js 文件的 hash 值都變了,不利于瀏覽器進(jìn)行長(zhǎng)效緩存。v4 之前的解決辦法是使用 HashedModuleIdsPlugin 固定 moduleId,它會(huì)使用模塊路徑生成的 hash 作為 moduleId;使用 NamedChunksPlugin 來(lái)固定 chunkId。

          其中 webpack4 中可以根據(jù)如下配置來(lái)解決此問(wèn)題:

          optimization.moduleIds = 'hashed'
          optimization.chunkIds = 'named'

          2.2、webpack5 長(zhǎng)效緩存方案

          webpack5 增加了確定的 moduleIdchunkId 的支持,如下配置:

          optimization.moduleIds = 'deterministic'
          optimization.chunkIds = 'deterministic'

          此配置在生產(chǎn)模式下是默認(rèn)開(kāi)啟的,它的作用是以確定的方式為 modulechunk 分配 3-5 位數(shù)字 id,相比于 v4 版本的選項(xiàng) hashed,它會(huì)導(dǎo)致更小的文件 bundles。

          由于 moduleIdchunkId 確定了,構(gòu)建的文件的 hash 值也會(huì)確定,有利于瀏覽器長(zhǎng)效緩存。同時(shí)此配置有利于減少文件打包大小。

          在開(kāi)發(fā)模式下,建議使用:

          optimization.moduleIds = 'named'
          optimization.chunkIds = 'named'

          此選項(xiàng)生產(chǎn)對(duì)調(diào)試更友好的可讀的 id。

          3、Node Polyfill 腳本被移除

          webpack4 版本中附帶了大多數(shù) Node.js 核心模塊的 polyfill,一旦前端使用了任何核心模塊,這些模塊就會(huì)自動(dòng)應(yīng)用,但是其實(shí)有些是不必要的。

          webpack5 將不會(huì)自動(dòng)為 Node.js 模塊添加 polyfill,而是更專(zhuān)注的投入到前端模塊的兼容中。因此需要開(kāi)發(fā)者手動(dòng)添加合適的 polyfill。

          import sha256 from 'crypto-js/sha256';

          const hashDigest = sha256('hello world1');
          console.log(hashDigest);

          上面代碼在v4中打包結(jié)果如下:

          使用 wepack4 打包,主動(dòng)添加了crypto 的 polyfill,即 crypto-browserify,打包大小為 441k。在 wepack5 中打包這樣的代碼,構(gòu)建會(huì)提示開(kāi)發(fā)者進(jìn)行確認(rèn)是否需要 node polyfill,如下圖:

          如果確認(rèn)不需要 polyfill,可根據(jù)提示設(shè)置 fallback,如下:

          resolve: {
            fallback: { "crypto"false }
          }

          打包結(jié)果為:

          打包后 js 文件小了 305k,去除掉項(xiàng)目不需要的 node polyfill,對(duì)于減小打包大小收益很可觀。

          4、更優(yōu)的 tree-shaking

          // const.js
          export const a = 'hello';
          export const b = 'world';

          // module.js
          export * as module from './const';

          // index.js
          import * as main from './module';
          console.log(main.module.a)

          有如上的一段代碼,在 v4 構(gòu)建中打包后的結(jié)果如下:

          從上圖可以看出,const.js 導(dǎo)出的 a,b 變量都被打包了,但實(shí)際上我們只用到了 a,期待的是b 應(yīng)該不被打包進(jìn)去。

          webpack5 對(duì) tree-shaking 進(jìn)行了優(yōu)化,分析模塊的 exportimport 的依賴(lài)關(guān)系,去掉未被使用的模塊,打包結(jié)果如下:

          !function(){"use strict"console.log("hello")}();

          可以看出代碼非常簡(jiǎn)潔。

          5、Module Federation

          Module Federation 使得使 JavaScript 應(yīng)用得以從另一個(gè) JavaScript 應(yīng)用中動(dòng)態(tài)地加載代碼 —— 同時(shí)共享依賴(lài)。相當(dāng)于 webpack 提供了線(xiàn)上 runtime 的環(huán)境,多個(gè)應(yīng)用利用 CDN 共享組件或應(yīng)用,不需要本地安裝 npm 包再構(gòu)建了,這就有點(diǎn)云組件的概念了。

          以 github 上的例子為例,basic-host-remote

          上圖是項(xiàng)目的目錄結(jié)構(gòu),可以看出存在 2 個(gè)應(yīng)用 app1、app2。其中 app1 使用了 app2 的代碼,那么 app1 是如何引用 app2 的代碼呢?看下面的代碼:

          // app1

          import React from "react";

          const RemoteButton = React.lazy(() => import("app2/Button"));

          const App = () => (
            <div>
              <h1>Basic Host-Remote</h1>
              <h2>App 1</h2>
              <React.Suspense fallback="Loading Button">
                <RemoteButton />
              </React.Suspense>
            </div>
          );

          export default App;

          其中最重要的就是

          const RemoteButton = React.lazy(() => import("app2/Button"));

          直接在 app1 的項(xiàng)目中引用了 app2 項(xiàng)目的代碼。是如何做到的?我們看下構(gòu)建配置:

          先看提供組件 Button 的 app2 的配置:

          const HtmlWebpackPlugin = require("html-webpack-plugin");
          const { ModuleFederationPlugin } = require("webpack").container;
          const path = require("path");

          module.exports = {
           // 有刪減
            plugins: [
              new ModuleFederationPlugin({
                name"app2",
                library: { type"var"name"app2" },
                filename"remoteEntry.js",
                exposes: {
                  "./Button""./src/Button",
                },
                shared: { react: { singletontrue }, "react-dom": { singletontrue } },
              }),
              new HtmlWebpackPlugin({
                template"./public/index.html",
              }),
            ],
          };

          依賴(lài)共享主要是由插件 ModuleFederationPlugin 來(lái)提供的,由上面的配置可以看出 app2 暴露出了 Button 組件,依賴(lài) react、react-dom,生成入口文件為 remoteEntru.js。下面再來(lái)看下 app1的配置:

          const HtmlWebpackPlugin = require("html-webpack-plugin");
          const { ModuleFederationPlugin } = require("webpack").container;
          const path = require("path");

          module.exports = {

            //http://localhost:3002/remoteEntry.js
            plugins: [
              new ModuleFederationPlugin({
                name"app1",
                remotes: {
                  app2"app2@http://localhost:3002/remoteEntry.js",
                },
                shared: { react: { singletontrue }, "react-dom": { singletontrue } },
              }),
            ],
          };

          結(jié)合之前 app2 的配置來(lái)看,app1 加載遠(yuǎn)程的 app2 模塊,依賴(lài) react、react-dom。

          瀏覽器里運(yùn)行效果如圖:

          Module Federation 還有很多的潛力可以挖掘,例如可以將我們項(xiàng)目中常用的依賴(lài)包 react 全家桶等打成一個(gè)包,做成一個(gè) runtime,開(kāi)發(fā)環(huán)境和生產(chǎn)環(huán)境依賴(lài)一個(gè) runtime,這樣可以大大減少項(xiàng)目的大小,提高編譯速度。

          一些更實(shí)用的用法需要我們?cè)趯?shí)際使用中繼續(xù)探索,發(fā)揮 webpack5 更大的價(jià)值。

          6、其他新特性

          1、在 webpack4 中標(biāo)記過(guò)期的功能都已經(jīng)在 webpack5 移除了。

          2、開(kāi)發(fā)環(huán)境下默認(rèn)使用可讀的名稱(chēng)為 module 命名,不需要使用如下語(yǔ)法:

          import(/* webpackChunkName: "name" */ "module")

          3、原生 worker 支持

          ......

          本文針對(duì) webpack5 的比較重要的特性進(jìn)行了說(shuō)明,具體的一些變更可以去參考官方文檔。

          升級(jí)踩坑

          升級(jí)的過(guò)程比較枯燥,基本上就是調(diào)試、修改、繼續(xù)調(diào)試的過(guò)程,下面列出幾個(gè)比較典型的問(wèn)題。

          1、升級(jí) webpack 及相關(guān)包的版本

          這個(gè)過(guò)程是比較耗時(shí)的,需要將 webpack 的版本及相關(guān) loaderplugin 的版本進(jìn)行升級(jí),如今 webpack5 已正式發(fā)布,相關(guān)插件基本上都兼容了 webpack5,所以大部分問(wèn)題都能通過(guò)升級(jí)包版本解決。

          2、配置 webpack5 編譯緩存不生效

          這個(gè)問(wèn)題就比較坑了,腳手架創(chuàng)建一個(gè)簡(jiǎn)單項(xiàng)目后,根據(jù)官網(wǎng)文檔配置 cache,啟動(dòng)構(gòu)建:

          webpack --config webpack-dist.config.js

          cache: {
             type'filesystem'
          }

          結(jié)果構(gòu)建是成功,但是相應(yīng)的緩存卻一直沒(méi)有生成,其中構(gòu)建提示如下:

          提示說(shuō) webpack-dist.config.js 找不到,當(dāng)時(shí)就很懵了,這個(gè)文件明明是存在的,而且配置緩存策略時(shí),并沒(méi)有這個(gè)文件。查閱大量文檔之后開(kāi)始翻看源碼,其中部分如下:

          // webpack/lib/cache/PackFileCacheStrategy.js

          if (newBuildDependencies.size > 0 || !this.buildSnapshot) {
              if (reportProgress) reportProgress(0.5"resolve build dependencies");
              this.logger.debug(`Capturing build dependencies... (${Array.from(newBuildDependencies).join(", ")})`);
              promise = new Promise((resolve, reject) => {
                  this.logger.time("resolve build dependencies");
                  this.fileSystemInfo.resolveBuildDependencies(this.context,
          newBuildDependencies,) ...

          打印 newBuildDependencies 得到結(jié)果:

          發(fā)現(xiàn)還真有這個(gè)文件,而且相比于其他絕對(duì)路徑,這個(gè)相對(duì)路徑可能無(wú)法找到。

          繼續(xù)斷點(diǎn)調(diào)試,追溯這里的 newBuildDependencies 的值,發(fā)現(xiàn)webpack-dist.config.js 這個(gè)文件是在 webpack-cli 里寫(xiě)入的,

          const cacheDefaults = (finalConfig, parsedArgs) => {
              // eslint-disable-next-line no-prototype-builtins
              const hasCache = finalConfig.hasOwnProperty('cache');
              let cacheConfig = {};
              if (hasCache && parsedArgs.config) {

                  if (finalConfig.cache && finalConfig.cache.type === 'filesystem') {
                      cacheConfig.buildDependencies = {
                          config: parsedArgs.config,
                      };
                  }
                  console.log(3333, cacheConfig)
                  return { cache: cacheConfig };
              }
              return cacheConfig;
          };

          從這里看出當(dāng)配置持久緩存時(shí),使用命令行自動(dòng)的給 cache 加上 config 后面的參數(shù)。由于找不到這個(gè)相對(duì)路徑,從而導(dǎo)致緩存邏輯執(zhí)行報(bào)錯(cuò),緩存失敗。

          我的解決辦法:

          const path = require('path');
          const exec = require('child_process').exec;

          const config = path.resolve(__dirname, 'webpack-dist.config.js');
          const cmdStr = `webpack --config ${config}`;

          exec(cmdStr, function(err,stdout,stderr){
            if(err) {
                console.log('get weather api error:'+stderr);
            } else {
                console.log(stdout);
            }
          });

          獲取 webpack-dist.config.js 的絕對(duì)路徑,傳給命令行,就可以解決。可能還有更優(yōu)雅的解決方法,后面繼續(xù)探索。

          3、loader 配置參數(shù)修改

          出現(xiàn)如下報(bào)錯(cuò)時(shí),表示 webpack5 不兼容以前的 webpack 的寫(xiě)法了,需要按最新版的規(guī)則來(lái)修改:

          {
            test: /\.css$/,
            loaders: ['css-loader'],
                  // 提取出css
          }

          loaders改為use 

          {
            test: /\.css$/,
            use: ['css-loader'],
                  // 提取出css
          }

          4、去掉 node polyfill

          由于 webpack5 會(huì)自動(dòng)去掉 polyfill,因此會(huì)出現(xiàn)如下提示

          解決辦法是按照提示修改,確認(rèn)是否需要添加 polyfill

          resolve: {
            fallback: { "domain": false }
          }

          總結(jié)

          webpack5 正式發(fā)布已經(jīng)有一段時(shí)間了,總的來(lái)說(shuō):
          1. 構(gòu)建性能大幅度提升,依賴(lài)核心代碼層面的持久緩存,覆蓋率更高,配置更簡(jiǎn)單。

          2. 打包后的代碼體積減少。

          3. 默認(rèn)支持瀏覽器長(zhǎng)期緩存,降低配置門(mén)檻。

          4. 令人激動(dòng)的新特性 Module Federation,蘊(yùn)含極大的可能性


            內(nèi)推社群


            我組建了一個(gè)氛圍特別好的騰訊內(nèi)推社群,如果你對(duì)加入騰訊感興趣的話(huà)(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行面試相關(guān)的答疑、聊聊面試的故事、并且在你準(zhǔn)備好的時(shí)候隨時(shí)幫你內(nèi)推。下方加 winty 好友回復(fù)「面試」即可。


          瀏覽 78
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品久久久久影院 | 国产精品秘 精品久久久 | 天天干天天日天天草 | 中文字幕三级片在线观看 | 国产亚洲精品久久久久久青梅 |