<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>

          有沒有必要上?帶你 Webpack5 快速開箱!

          共 9449字,需瀏覽 19分鐘

           ·

          2020-09-25 12:57


          大家一定看過很多電子設(shè)備開箱測(cè)評(píng),今天我們也來跑一個(gè)軟件新版的上手測(cè)評(píng) —— Webpack 5!

          從 2017 年發(fā)出關(guān)于 v5 的投票開始,到 2019 年 10 月發(fā)布第一個(gè) beta 版本,目前是 5.0.0-beta.16。現(xiàn)在在收集使用反饋、生態(tài)升級(jí)的過程中,相信不久后就可以正式發(fā)布了。這次升級(jí)重點(diǎn):性能改進(jìn)、Tree Shacking、Code Generation、Module Federation。

          下面我們跟著 Changelog 來動(dòng)手,測(cè)測(cè)重點(diǎn)內(nèi)容~

          優(yōu)化持久緩存

          首先簡(jiǎn)單說 Webpack 中 graph 的概念:

          Webpack 在執(zhí)行的時(shí)候,以配置的 entry 為入口,遞歸解析文件依賴,構(gòu)建一個(gè) graph,記錄代碼中各個(gè) module 之間的關(guān)系。每當(dāng)有文件更新的時(shí)候, 遞歸過程會(huì)重來,graph 發(fā)生改變。

          如果簡(jiǎn)單粗暴地重建 graph 再編譯,會(huì)有很大的性能開銷。Webpack 利用緩存實(shí)現(xiàn)增量編譯,從而提升構(gòu)建性能。

          緩存(內(nèi)存 / 磁盤兩種形式)中的主要內(nèi)容是 module objects,在編譯的時(shí)候會(huì)將 graph 以二進(jìn)制或者 json 文件存儲(chǔ)在硬盤上。每當(dāng)代碼變化、模塊之間依賴關(guān)系改變導(dǎo)致 graph 改變時(shí), Webpack 會(huì)讀取記錄做增量編譯。

          之前可以使用 loader 設(shè)置緩存:

          1. 使用 cache-loader 可以將編譯結(jié)果寫入硬盤緩存,Webpack 再次構(gòu)建時(shí)如果文件沒有發(fā)生變化則會(huì)直接拉取緩存
          2. 還有一部分 loader 自帶緩存配置,比如 babel-loader,可以配置參數(shù) cacheDirectory 使用緩存,將每次的編譯結(jié)果寫進(jìn)磁盤(默認(rèn)在 node_modules/.cache/babel-loader 目錄)

          v5 中緩存默認(rèn)是 memory,你可以修改設(shè)置寫入硬盤:

          module.exports = {  cache: {    type: 'filesystem',    // cacheDirectory 默認(rèn)路徑是 node_modules/.cache/webpack    cacheDirectory: path.resolve(__dirname, '.temp_cache')  }};

          注:對(duì)大部分 node_modules 哈希處理以構(gòu)建依賴項(xiàng),代價(jià)昂貴,還降低 Webpack 執(zhí)行速度。為避免這種情況出現(xiàn),Webpack 加入了一些優(yōu)化,默認(rèn)會(huì)跳過 node_modules,并使用 package.json 中的 versionname 作為數(shù)據(jù)源。

          優(yōu)化長(zhǎng)期緩存

          Webpack 5 針對(duì) moduleId ?和 chunkId 的計(jì)算方式進(jìn)行了優(yōu)化,增加確定性的 moduleId 和 chunkId 的生成策略。moduleId 根據(jù)上下文模塊路徑,chunkId 根據(jù) chunk 內(nèi)容計(jì)算,最后為 moduleId 和 chunkId 生成 3 - 4 位的數(shù)字 id,實(shí)現(xiàn)長(zhǎng)期緩存,生產(chǎn)環(huán)境下默認(rèn)開啟。

          1. 對(duì)比原來的 moduleId

          原來的 moduleId 默認(rèn)值是自增 id,容易導(dǎo)致文件緩存失效。在 v4 之前,可以安裝 HashedModuleIdsPlugin 插件覆蓋默認(rèn)的 moduleId 規(guī)則, 它會(huì)使用模塊路徑生成的 hash 作為 moduleId。在 v4 中,可以配置 optimization.moduleIds = 'hashed'

          1. 對(duì)比原來的 chunkId

          原來的 chunkId 默認(rèn)值自增 id。比如這樣的配置下,如果有新的 entry 增加,chunk 數(shù)量也會(huì)跟著增加,chunkId 也會(huì)遞增。之前可以安裝 NamedChunksPlugin 插件來穩(wěn)定 chunkId;或者配置 optimization.chunkIds = 'named'

          NodeJS 的 polyfill 腳本被移除

          最開始,Webpack 目標(biāo)是允許在瀏覽器中運(yùn)行 Node 模塊。但是現(xiàn)在在 Webpack 看來,大多模塊就是專門為前端開發(fā)的。在 v4 及以前的版本中,對(duì)于大多數(shù)的 Node 模塊會(huì)自動(dòng)添加 polyfill 腳本,polyfill 會(huì)加到最終的 bundle 中,其實(shí)通常情況下是沒有必要的。在 v5 中將停止這一行為。

          比如以下一段代碼:

          // index.jsimport sha256 from 'crypto-js/sha256'; const hashDigest = sha256('hello world');console.log(hashDigest);

          在 v4 中,會(huì)主動(dòng)添加 crypto 的 polyfill,也就是 crypto-browserify。我們運(yùn)行的代碼是不需要的,反而最后的包變大,編譯結(jié)果 「417 kb」

          在 v5 中,如果遇到了這樣的情況,會(huì)提示你進(jìn)行確認(rèn)。如果確認(rèn)不需要 node polyfill,按照提示 alias 設(shè)置為 false 即可。最后的編譯結(jié)果僅有 「5.69 kb」

          配置 resolve.alias: { crypto: false }

          瀏覽器執(zhí)行結(jié)果:

          更好的 TreeShaking

          現(xiàn)在有這樣一段代碼:

          // inner.jsexport const a = 'aaaaaaaaaa';export const b = 'bbbbbbbbbb';
          // module.jsimport * as inner from "./inner";export { inner };
          // index.jsimport * as module from "./module";console.log(module.inner.a);

          在 v4 中毫無(wú)疑問,以上代碼 a、b 變量是被全部打包的:

          但我們只調(diào)用了 a 變量,理想情況應(yīng)該是 b 被識(shí)別為 unused,不被打包。這一優(yōu)化在 v5 中實(shí)現(xiàn)了。在 v5 中會(huì)分析模塊 exportimport 之間的依賴關(guān)系,最終的代碼生成非常簡(jiǎn)潔:

          重大的變革

          如果說以上的變更優(yōu)化都是常規(guī)路數(shù),那么下面的功能有點(diǎn)出乎意料。

          Module Federation

          讓 Webpack 達(dá)到了線上 runtime 的效果,讓代碼直接在獨(dú)立應(yīng)用間利用 CDN 直接共享,不再需要本地安裝 NPM 包、構(gòu)建再發(fā)布了!

          設(shè)計(jì)初衷

          Webpack 認(rèn)同多個(gè)單獨(dú)的構(gòu)建應(yīng)能夠構(gòu)成一個(gè)應(yīng)用。這些獨(dú)立的構(gòu)建不相互依賴,因此可以單獨(dú)開發(fā)和部署。這通常稱為微型前端,但還不僅僅是如此。

          在之前我們希望共享代碼是如何做的?

          「NPM」

          維護(hù)一個(gè) CommonComponents 的 NPM 包,在不同項(xiàng)目中安裝、使用。如果 NPM 包升級(jí),對(duì)應(yīng)項(xiàng)目都需要安裝新版本,本地編譯,打包到 bundle 中。

          「UMD」

          UMD 優(yōu)點(diǎn)在 runtime。缺點(diǎn)也明顯,體積優(yōu)化不方便,容易有版本沖突。

          「微前端」

          獨(dú)立應(yīng)用間的共享也是問題。一般有兩種打包方式:

          1. 子應(yīng)用獨(dú)立打包,模塊解耦了,但公共的依賴不易維護(hù)處理
          2. 整體應(yīng)用一起打包,能解決公共依賴;但龐大的多個(gè)項(xiàng)目又使打包變慢,后續(xù)也不好擴(kuò)展

          Webpack 5 實(shí)現(xiàn)了全新的解決方案

          從圖中可以看到,這個(gè)方案是直接將一個(gè)應(yīng)用的 bundle,應(yīng)用于另一個(gè)應(yīng)用。

          應(yīng)用可以模塊化輸出,就是說它本身可以自我消費(fèi),也可以動(dòng)態(tài)分發(fā) runtime 子模塊給其他應(yīng)用。

          理論比較抽象,我們動(dòng)手試一下。

          實(shí)踐測(cè)試

          現(xiàn)在有兩個(gè)應(yīng)用 app1 (localhost:3001)、app2 (localhost:3002):

          入口文件:

          // app1 & app2: index.jsimport App from "./App";import React from "react";import ReactDOM from "react-dom";
          ReactDOM.render(, document.getElementById("root"));

          app2 生產(chǎn)了 Button 組件:

          // app2: Button.jsimport React from "react";
          const Button = () => ;
          export default Button;

          app2 自身消費(fèi) Button 組件:

          // app2: App.jsimport LocalButton from "./Button";import React from "react";
          const App = () => (

          Basic Host-Remote

          App 2

          );
          export default App;

          app1 引用 app2Button 組件:

          // app1: App.jsimport React from "react";const RemoteButton = React.lazy(() => import("app2/Button"));
          const App = () => (

          Basic Host-Remote

          App 1

          );
          export default App;

          先看生產(chǎn)了 Button 組件的 app2,其配置文件:

          // app2:webpack.config.jsconst HtmlWebpackPlugin = require("html-webpack-plugin");const { ModuleFederationPlugin } = require("webpack").container;const path = require("path");
          module.exports = { entry: "./src/index", mode: "development", devServer: { contentBase: path.join(__dirname, "dist"), port: 3002, }, output: { publicPath: "http://localhost:3002/", }, module: { rules: [ // ... ], }, plugins: [ new ModuleFederationPlugin({ name: "app2Lib", library: { type: "var", name: "app2Lib" }, filename: "app2-remote-entry.js", exposes: { Button: "./src/Button", }, shared: ["react", "react-dom"], }), new HtmlWebpackPlugin({ template: "./index.html", }), ],};

          這段配置描述了,需要暴露出 Button 組件、需要依賴 reactreact-dom。管理 exposesshared 的模塊為 app2Lib,生成入口文件名為 app-remote-entry.js

          app1 的配置文件:

          const HtmlWebpackPlugin = require("html-webpack-plugin");const { ModuleFederationPlugin } = require("webpack").container;const path = require("path");
          module.exports = { entry: "./src/index", mode: "development", devServer: { contentBase: path.join(__dirname, "dist"), port: 3001, }, output: { publicPath: "http://localhost:3001/", }, module: { rules: [ // ... ], }, plugins: [ new ModuleFederationPlugin({ name: "app1", library: { type: "var", name: "app1" }, remotes: { app2: "app2Lib", }, shared: ["react", "react-dom"], }), new HtmlWebpackPlugin({ template: "./index.html", }), ],};

          這段配置描述了,使用遠(yuǎn)端模塊 app2Lib,依賴 reactreact-dom

          最后一步:在 app1 html 中加載 app2-remote-entry.js

          // app1: index.html              

          運(yùn)行結(jié)果:

          「引用的 app2/Button 是如何找到的呢?」

          通過 app1 的配置文件,知道了 app2 是遠(yuǎn)端加載。在生成的 app1 main.js 描述為:

          看這里的 data 數(shù)組:

          data[1]webpack/container/reference/app2,這里是返回 app2Lib 對(duì)象:

          module.exports = app2Lib;

          data[0]webpack/container/remote-overrides/a46c3e,這里提供了 app2 需要的 reactreact-dom 依賴,并返回 app2Lib

          module.exports = (external) => {  if (external.override) {    external.override(Object.assign({      "react": () => {        return Promise.resolve().then(() => {          return () => __webpack_require__(/*! react */ "./node_modules/react/index.js")        })      },      "react-dom": () => {        return Promise.resolve().then(() => {          return () => __webpack_require__(/*! react-dom */ "./node_modules/react-dom/index.js")        })      }    }, __webpack_require__.O))  }  return external;};

          所以最后 promise 的賦值變成了:

          var promise = app2Lib.get('Button');

          這么一看,app2Lib 是全局變量呀。

          繼續(xù)看 app1 加載的 app2-remote-entry.js 內(nèi)容。果然,生成了一個(gè)全局變量 app2Lib

          app2Lib 對(duì)象擁有兩個(gè)方法,具體為:

          var get = (module) => {  return (    __webpack_require__.o(moduleMap, module)      ? moduleMap[module]()      : Promise.resolve().then(() => {        throw new Error('Module \"' + module + '\" does not exist in container.');      })  );};
          var override = (override) => { Object.assign(__webpack_require__.O, override);};

          所以,app2/Button 實(shí)際就是 app2Lib.get('Button'),然后根據(jù)映射找到模塊,隨后__webpack_require__

          var moduleMap = {  "Button": () => {    return __webpack_require__.e("src_Button_js").then(() =>       () => __webpack_require__(/*! ./src/Button */ "./src/Button.js")    );  }};

          最后再說 shared: ['react', 'react-dom']

          app2 中指明了需要依賴 reactreact-dom,并期望消費(fèi)的應(yīng)用提供。如果 app1 沒有提供,或沒有提供指定版本,如下把代碼注釋:

          plugins: [  new ModuleFederationPlugin({    name: "app1",    library: { type: "var", name: "app1" },    remotes: {      'app2': "app2Lib",    },    // shared: ["react", "react-dom"],    // 版本不一致同理    // shared: {    //   "react-15": "react",    //   "react-dom": "react-dom",    // },  }),  new HtmlWebpackPlugin({    template: "./index.html",  }),]

          那么,剛才 app1 main.js 中的 data[0]webpack/container/remote-overrides/a46c3e 會(huì)變?yōu)椋?/p>

          module.exports = (external) => {  if (external.override) {    external.override(__webpack_require__.O);    // external.override(Object.assign({    //   "react": () => {    //     return Promise.resolve().then(() => {    //       return () => __webpack_require__(/*! react */ "./node_modules/react/index.js")    //     })    //   },    //   "react-dom": () => {    //     return Promise.resolve().then(() => {    //       return () => __webpack_require__(/*! react-dom */ "./node_modules/react-dom/index.js")    //     })    //   }    // }, __webpack_require__.O))  }  return external;};

          app1 則從 app2 加載 react 依賴:

          總結(jié),根據(jù) app2 配置的 exposes & shared 內(nèi)容,產(chǎn)生對(duì)應(yīng)的模塊文件,以及模塊映射關(guān)系,通過全局變量 app2Lib 進(jìn)行訪問;app1 通過全局變量 get 能知道應(yīng)該去如何加載 button.jsoverride 能知道共享依賴的模塊。

          以上,F(xiàn)ederation 初看很像 DLL + External,但好處是你無(wú)需手動(dòng)維護(hù)、打包依賴,代碼運(yùn)行時(shí)加載。這種模式下,調(diào)試也變得容易,不再需要復(fù)制粘貼代碼或者 npm link,只需要啟動(dòng)應(yīng)用即可。這里僅以 Button 組件為例,Button 可以一個(gè)組件,也可以是一個(gè)頁(yè)面、一個(gè)應(yīng)用。Module Federation 的落地,結(jié)合自動(dòng)化流程等系列工作,還需要大家在各自場(chǎng)景中實(shí)踐。

          社區(qū)探索實(shí)踐

          其他特性

          • Top Level Await
          • SplitChunks 支持更靈活的資源拆分
          • 不包含 JS 代碼的 Chunk 將不再生成 JS 文件
          • Output 默認(rèn)生成 ES6 規(guī)范代碼,也支持配置為 5 - 11
          • ......

          詳細(xì)請(qǐng)閱讀 Changlog

          以上 Demo 官方也有給出,供大家參考。我們也將自己內(nèi)部項(xiàng)目做了升級(jí)嘗試,過程中會(huì)出現(xiàn)一些 plugins 不兼容的情況。根據(jù)官方 Changelog 說明,都可以找到答案,臨時(shí)修改下相關(guān) plugin 代碼。如果你的升級(jí)嘗試中也遇到了,可以自行處理下,同時(shí)也反饋回社區(qū),共同推進(jìn)新版發(fā)布進(jìn)程。

          總的來說,Webpack 5 初步上手體驗(yàn)后,打包體積、速度都有不錯(cuò)的提升,多數(shù)功能的使用配置也更便捷靈巧,Module Federation 讓人眼前一亮。拋磚引玉,大家感興趣可以來交流各自的解讀和研究。

          如果你對(duì)新鮮事物充滿好奇,喜歡專研技術(shù)、樂于分享,對(duì) ?IM 產(chǎn)品、桌面客戶端基礎(chǔ)引擎、基礎(chǔ)平臺(tái)建設(shè)感興趣,歡迎你的加入!

          ?

          文章作者:王欣瑜(Suite Commercialization Engineering 團(tuán)隊(duì))
          字節(jié)跳動(dòng)飛書業(yè)務(wù),海量 hc,極速響應(yīng),快來和我成為同事吧~職位介紹

          ?

          最后



          如果你覺得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:

          1. 點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)

          2. 歡迎加我微信「qianyu443033099」拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...

          3. 關(guān)注公眾號(hào)「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。

          點(diǎn)個(gè)在看支持我吧,轉(zhuǎn)發(fā)就更好了


          瀏覽 49
          點(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>
                  123操逼逼 | 79AV成人无码 | 午夜一区二区三区免费视频 | www日日日 | 91性爱在线观看 |