webpack 相關知識總結

綜述:雖然使用webpack很長時間,但是下邊的這些問題還是需要總結下,以加強認識和理解

爲什麼要總結 webpack 相關的問題?

隨着現代前端開發的複雜度和規模越來越龐大,已經不能拋開工程化來獨立開發了,如 react 的 jsx 代碼必須編譯後才能在瀏覽器中使用;又如 sass 和 less 的代碼瀏覽器也是不支持的。 而如果摒棄了這些開發框架,那麼開發的效率將大幅下降。在衆多前端工程化工具中,webpack脫穎而出成爲了當今最流行的前端構建工具。 然而大多數的使用者都只是單純的會使用,而並不知道其深層的原理。希望通過以下的問題總結可以幫助大家溫故知新、查缺補漏,知其然而又知其所以然。

問題列表

  1. webpack 與 grunt、gulp 的不同?
  2. 與 webpack 類似的工具還有哪些?談談你爲什麼最終選擇(或放棄)使用 webpack?
  3. 有哪些常見的 Loader?他們是解決什麼問題的?
  4. 有哪些常見的 Plugin?他們是解決什麼問題的?
  5. Loader 和 Plugin 的不同?
  6. webpack 的構建流程是什麼? 從讀取配置到輸出文件這個過程儘量說全
  7. 是否寫過 Loader 和 Plugin?描述一下編寫 loader 或 plugin 的思路?
  8. webpack 的熱更新是如何做到的?說明其原理?
  9. 如何利用 webpack 來優化前端性能?(提高性能和體驗)
  10. 如何提高 webpack 的構建速度?
  11. 怎麼配置單頁應用?怎麼配置多頁應用?
  12. npm 打包時需要注意哪些?如何利用 webpack 來更好的構建?
  13. 如何在 vue 項目中實現按需加載?

1. webpack 與 grunt、gulp 的不同?

三者都是前端構建工具,grunt 和 gulp 在早期比較流行,現在 webpack 相對來說比較主流,不過一些輕量化的任務還是會用 gulp 來處理,比如單獨打包 CSS 文件等。

grunt 和 gulp 是基於任務和流(Task、Stream)的。類似 jQuery,找到一個(或一類)文件,對其做一系列鏈式操作,更新流上的數據, 整條鏈式操作構成了一個任務,多個任務就構成了整個 web 的構建流程。

webpack 是基於入口的。webpack 會自動地遞歸解析入口所需要加載的所有資源文件,然後用不同的 Loader 來處理不同的文件,用 Plugin 來擴展 webpack 功能。

所以總結一下:

  • 從構建思路來說
gulp和grunt需要開發者將整個前端構建過程拆分成多個`Task`,併合理控制所有`Task`的調用關係
webpack需要開發者找到入口,並需要清楚對於不同的資源應該使用什麼Loader做何種解析和加工
  • 對於知識背景來說
    gulp 更像後端開發者的思路,需要對於整個流程瞭如指掌 webpack 更傾向於前端開發者的思路

2. 與 webpack 類似的工具還有哪些?談談你爲什麼最終選擇(或放棄)使用 webpack?

同樣是基於入口的打包工具還有以下幾個主流的:

從應用場景上來看:

  • webpack 適用於大型複雜的前端站點構建
  • rollup 適用於基礎庫的打包,如 vue、react
  • parcel 適用於簡單的實驗性項目,他可以滿足低門檻的快速看到效果

由於 parcel 在打包過程中給出的調試信息十分有限,所以一旦打包出錯難以調試,所以不建議複雜的項目使用 parcel

3. 有哪些常見的 Loader?他們是解決什麼問題的?

  • file-loader:把文件輸出到一個文件夾中,在代碼中通過相對 URL 去引用輸出的文件
  • url-loader:和 file-loader 類似,但是能在文件很小的情況下以 base64 的方式把文件內容注入到代碼中去
  • source-map-loader:加載額外的 Source Map 文件,以方便斷點調試
  • image-loader:加載並且壓縮圖片文件
  • babel-loader:把 ES6 轉換成 ES5
  • css-loader:加載 CSS,支持模塊化、壓縮、文件導入等特性
  • style-loader:把 CSS 代碼注入到 JavaScript 中,通過 DOM 操作去加載 CSS。
  • eslint-loader:通過 ESLint 檢查 JavaScript 代碼

4. 有哪些常見的 Plugin?他們是解決什麼問題的?

  • define-plugin:定義環境變量
  • commons-chunk-plugin:提取公共代碼
  • uglifyjs-webpack-plugin:通過UglifyES壓縮ES6代碼

5.Loader 和 Plugin 的不同?

不同的作用

  • Loader 直譯爲 "加載器"。Webpack 將一切文件視爲模塊,但是 webpack 原生是隻能解析 js 文件,如果想將其他文件也打包的話,就會用到loader。 所以 Loader 的作用是讓 webpack 擁有了加載和解析非 JavaScript 文件的能力。
  • Plugin 直譯爲 "插件"。Plugin 可以擴展 webpack 的功能,讓 webpack 具有更多的靈活性。 在 Webpack 運行的生命週期中會廣播出許多事件,Plugin 可以監聽這些事件,在合適的時機通過 Webpack 提供的 API 改變輸出結果。

不同的用法

  • Loader 在module.rules中配置,也就是說他作爲模塊的解析規則而存在。 類型爲數組,每一項都是一個Object,裏面描述了對於什麼類型的文件(test),使用什麼加載 (loader) 和使用的參數(options
  • Plugin 在plugins中單獨配置。 類型爲數組,每一項是一個plugin的實例,參數都通過構造函數傳入。

6.webpack 的構建流程是什麼? 從讀取配置到輸出文件這個過程儘量說全

Webpack 的運行流程是一個串行的過程,從啓動到結束會依次執行以下流程:

  1. 初始化參數:從配置文件和 Shell 語句中讀取與合併參數,得出最終的參數;
  2. 開始編譯:用上一步得到的參數初始化 Compiler 對象,加載所有配置的插件,執行對象的 run 方法開始執行編譯;
  3. 確定入口:根據配置中的 entry 找出所有的入口文件;
  4. 編譯模塊:從入口文件出發,調用所有配置的 Loader 對模塊進行翻譯,再找出該模塊依賴的模塊,再遞歸本步驟直到所有入口依賴的文件都經過了本步驟的處理;
  5. 完成模塊編譯:在經過第 4 步使用 Loader 翻譯完所有模塊後,得到了每個模塊被翻譯後的最終內容以及它們之間的依賴關係;
  6. 輸出資源:根據入口和模塊之間的依賴關係,組裝成一個個包含多個模塊的 Chunk,再把每個 Chunk 轉換成一個單獨的文件加入到輸出列表,這步是可以修改輸出內容的最後機會;
  7. 輸出完成:在確定好輸出內容後,根據配置確定輸出的路徑和文件名,把文件內容寫入到文件系統。

在以上過程中,Webpack 會在特定的時間點廣播出特定的事件,插件在監聽到感興趣的事件後會執行特定的邏輯,並且插件可以調用 Webpack 提供的 API 改變 Webpack 的運行結果。

7. 是否寫過 Loader 和 Plugin?描述一下編寫 loader 或 plugin 的思路?

Loader 像一個 "翻譯官" 把讀到的源文件內容轉義成新的文件內容,並且每個 Loader 通過鏈式操作,將源文件一步步翻譯成想要的樣子。

編寫 Loader 時要遵循單一原則,每個 Loader 只做一種 "轉義" 工作。 每個 Loader 的拿到的是源文件內容(source),可以通過返回值的方式將處理後的內容輸出,也可以調用this.callback()方法,將內容返回給 webpack。 還可以通過 this.async()生成一個callback函數,再用這個 callback 將處理後的內容輸出出去。 此外webpack還爲開發者準備了開發 loader 的工具函數集——loader-utils

相對於 Loader 而言,Plugin 的編寫就靈活了許多。 webpack 在運行的生命週期中會廣播出許多事件,Plugin 可以監聽這些事件,在合適的時機通過 Webpack 提供的 API 改變輸出結果。

8.webpack 的熱更新是如何做到的?說明其原理?

webpack 的熱更新又稱熱替換(Hot Module Replacement),縮寫爲 HMR。 這個機制可以做到不用刷新瀏覽器而將新變更的模塊替換掉舊的模塊。

原理:

圖片來自餓了麼前端 @知乎專欄

首先要知道 server 端和 client 端都做了處理工作

  1. 第一步,在 webpack 的 watch 模式下,文件系統中某一個文件發生修改,webpack 監聽到文件變化,根據配置文件對模塊重新編譯打包,並將打包後的代碼通過簡單的 JavaScript 對象保存在內存中。
  2. 第二步是 webpack-dev-server 和 webpack 之間的接口交互,而在這一步,主要是 dev-server 的中間件 webpack-dev-middleware 和 webpack 之間的交互,webpack-dev-middleware 調用 webpack 暴露的 API 對代碼變化進行監控,並且告訴 webpack,將代碼打包到內存中。
  3. 第三步是 webpack-dev-server 對文件變化的一個監控,這一步不同於第一步,並不是監控代碼變化重新打包。當我們在配置文件中配置了 devServer.watchContentBase 爲 true 的時候,Server 會監聽這些配置文件夾中靜態文件的變化,變化後會通知瀏覽器端對應用進行 live reload。注意,這兒是瀏覽器刷新,和 HMR 是兩個概念。
  4. 第四步也是 webpack-dev-server 代碼的工作,該步驟主要是通過 sockjs(webpack-dev-server 的依賴)在瀏覽器端和服務端之間建立一個 websocket 長連接,將 webpack 編譯打包的各個階段的狀態信息告知瀏覽器端,同時也包括第三步中 Server 監聽靜態文件變化的信息。瀏覽器端根據這些 socket 消息進行不同的操作。當然服務端傳遞的最主要信息還是新模塊的 hash 值,後面的步驟根據這一 hash 值來進行模塊熱替換。
  5. webpack-dev-server/client 端並不能夠請求更新的代碼,也不會執行熱更模塊操作,而把這些工作又交回給了 webpack,webpack/hot/dev-server 的工作就是根據 webpack-dev-server/client 傳給它的信息以及 dev-server 的配置決定是刷新瀏覽器呢還是進行模塊熱更新。當然如果僅僅是刷新瀏覽器,也就沒有後面那些步驟了。
  6. HotModuleReplacement.runtime 是客戶端 HMR 的中樞,它接收到上一步傳遞給他的新模塊的 hash 值,它通過 JsonpMainTemplate.runtime 向 server 端發送 Ajax 請求,服務端返回一個 json,該 json 包含了所有要更新的模塊的 hash 值,獲取到更新列表後,該模塊再次通過 jsonp 請求,獲取到最新的模塊代碼。這就是上圖中 7、8、9 步驟。
  7. 而第 10 步是決定 HMR 成功與否的關鍵步驟,在該步驟中,HotModulePlugin 將會對新舊模塊進行對比,決定是否更新模塊,在決定更新模塊後,檢查模塊之間的依賴關係,更新模塊的同時更新模塊間的依賴引用。
  8. 最後一步,當 HMR 失敗後,回退到 live reload 操作,也就是進行瀏覽器刷新來獲取最新打包代碼。

9. 如何利用 webpack 來優化前端性能?(提高性能和體驗)

用 webpack 優化前端性能是指優化 webpack 的輸出結果,讓打包的最終結果在瀏覽器運行快速高效。

  • 壓縮代碼。刪除多餘的代碼、註釋、簡化代碼的寫法等等方式。可以利用 webpack 的UglifyJsPluginParallelUglifyPlugin來壓縮 JS 文件, 利用cssnano(css-loader?minimize)來壓縮 css
  • 利用 CDN 加速。在構建過程中,將引用的靜態資源路徑修改爲 CDN 上對應的路徑。可以利用 webpack 對於output參數和各 loader 的publicPath參數來修改資源路徑
  • 刪除死代碼(Tree Shaking)。將代碼中永遠不會走到的片段刪除掉。可以通過在啓動 webpack 時追加參數--optimize-minimize來實現
  • 提取公共代碼。

10. 如何提高 webpack 的構建速度?

  1. 多入口情況下,使用CommonsChunkPlugin來提取公共代碼
  2. 通過externals配置來提取常用庫
  3. 利用DllPluginDllReferencePlugin預編譯資源模塊 通過DllPlugin來對那些我們引用但是絕對不會修改的 npm 包來進行預編譯,再通過DllReferencePlugin將預編譯的模塊加載進來。
  4. 使用Happypack 實現多線程加速編譯
  5. 使用webpack-uglify-parallel來提升uglifyPlugin的壓縮速度。 原理上webpack-uglify-parallel採用了多核並行壓縮來提升壓縮速度
  6. 使用Tree-shakingScope Hoisting來剔除多餘代碼

11. 怎麼配置單頁應用?怎麼配置多頁應用?

單頁應用可以理解爲 webpack 的標準模式,直接在entry中指定單頁應用的入口即可,這裏不再贅述

多頁應用的話,可以使用 webpack 的 AutoWebPlugin來完成簡單自動化的構建,但是前提是項目的目錄結構必須遵守他預設的規範。 多頁應用中要注意的是:

  • 每個頁面都有公共的代碼,可以將這些代碼抽離出來,避免重複的加載。比如,每個頁面都引用了同一套 css 樣式表
  • 隨着業務的不斷擴展,頁面可能會不斷的追加,所以一定要讓入口的配置足夠靈活,避免每次添加新頁面還需要修改構建配置

12.npm 打包時需要注意哪些?如何利用 webpack 來更好的構建?

Npm是目前最大的 JavaScript 模塊倉庫,裏面有來自全世界開發者上傳的可複用模塊。你可能只是 JS 模塊的使用者,但是有些情況你也會去選擇上傳自己開發的模塊。 關於 NPM 模塊上傳的方法可以去官網上進行學習,這裏只講解如何利用 webpack 來構建。

NPM 模塊需要注意以下問題:

  1. 要支持 CommonJS 模塊化規範,所以要求打包後的最後結果也遵守該規則。
  2. Npm 模塊使用者的環境是不確定的,很有可能並不支持 ES6,所以打包的最後結果應該是採用 ES5 編寫的。並且如果 ES5 是經過轉換的,請最好連同 SourceMap 一同上傳。
  3. Npm 包大小應該是儘量小(有些倉庫會限制包大小)
  4. 發佈的模塊不能將依賴的模塊也一同打包,應該讓用戶選擇性的去自行安裝。這樣可以避免模塊應用者再次打包時出現底層模塊被重複打包的情況。
  5. UI 組件類的模塊應該將依賴的其它資源文件,例如.css文件也需要包含在發佈的模塊裏。

基於以上需要注意的問題,我們可以對於 webpack 配置做以下擴展和優化:

  1. CommonJS 模塊化規範的解決方案: 設置output.libraryTarget='commonjs2'使輸出的代碼符合 CommonJS2 模塊化規範,以供給其它模塊導入使用
  2. 輸出 ES5 代碼的解決方案:使用babel-loader把 ES6 代碼轉換成 ES5 的代碼。再通過開啓devtool: 'source-map'輸出 SourceMap 以發佈調試。
  3. Npm 包大小盡量小的解決方案:Babel 在把 ES6 代碼轉換成 ES5 代碼時會注入一些輔助函數,最終導致每個輸出的文件中都包含這段輔助函數的代碼,造成了代碼的冗餘。解決方法是修改.babelrc文件,爲其加入transform-runtime插件
  4. 不能將依賴模塊打包到 NPM 模塊中的解決方案:使用externals配置項來告訴 webpack 哪些模塊不需要打包。
  5. 對於依賴的資源文件打包的解決方案:通過css-loaderextract-text-webpack-plugin來實現,配置如下:
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
  module: {
    rules: [
      {
        // 增加對 CSS 文件的支持
        test: /\.css/,
        // 提取出 Chunk 中的 CSS 代碼到單獨的文件中
        use: ExtractTextPlugin.extract({
          use: ['css-loader']
        }),
      },
    ]
  },
  plugins: [
    new ExtractTextPlugin({
      // 輸出的 CSS 文件名稱
      filename: 'index.css',
    }),
  ],
};

13. 如何在 vue 項目中實現按需加載?

Vue UI 組件庫的按需加載 爲了快速開發前端項目,經常會引入現成的 UI 組件庫如 ElementUI、iView 等,但是他們的體積和他們所提供的功能一樣,是很龐大的。 而通常情況下,我們僅僅需要少量的幾個組件就足夠了,但是我們卻將龐大的組件庫打包到我們的源碼中,造成了不必要的開銷。

不過很多組件庫已經提供了現成的解決方案,如 Element 出品的 babel-plugin-component和 AntDesign 出品的 babel-plugin-import 安裝以上插件後,在.babelrc配置中或babel-loader的參數中進行設置,即可實現組件按需加載了。

{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

單頁應用的按需加載 現在很多前端項目都是通過單頁應用的方式開發的,但是隨着業務的不斷擴展,會面臨一個嚴峻的問題——首次加載的代碼量會越來越多,影響用戶的體驗。

通過import(*)語句來控制加載時機,webpack 內置了對於import(*)
的解析,會將import(*)中引入的模塊作爲一個新的入口在生成一個 chunk。 當代碼執行到import(*)語句時,會去加載 Chunk 對應生成的文件。import()會返回一個 Promise 對象,所以爲了讓瀏覽器支持,需要事先注入Promise polyfill

14.瞭解哪些構建工具?優缺點?

 

15.工作中的項目構建工具,構建流是什麼?

 

16.這些構建工具幫助你解決了什麼事情?具體體現是什麼?

 

 17.構建過程中碰到的問題有哪些?如何解決?

 

 18.是否獨立寫過構建工具支持的插件或者加載器?(加分)

 

 19.構建過程的優化?使用webpack實現前端項目的優化?

   20.webpack的主要作用有哪些?

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章