Webpack知識點速記

1.Webpack是什麼?

Webpack是一個模塊打包工具,在Webpack裏一切文件皆模塊。經過loader轉換文件,經過plugin注入鉤子,最後輸出由多個模塊組合的文件。Webpack專一構建模塊化項目。
Webpack能夠看做是模塊打包機:它作的事情是,分析你的項目結構,找到JavaScript模塊以及其餘一些不能被瀏覽器直接運行的擴展語音(如:Scss,TypeScript等),並將其打包爲合適的格式以供瀏覽器使用。javascript

2. Webpack與Grunt、Gulp的不一樣?

Grunt/Gulp是一種可以優化前端開發流程的工具,而Webpack是一種模塊化的解決方案。css

2.1 工做方式不一樣:

  • Grunt/Gulp的工做方式是:在一個配置文件中,指明某些文件進行相似編譯/組合/壓縮等任務的具體步驟,以後工具能夠自動幫你完成這些任務
  • Webpack的工做方式是:把項目看成是一個總體,經過指定的入口文件,Webpack會從這個入口文件開始找到項目全部的依賴文件,而後使用loader處理它們,最後打包成一個或多個瀏覽器可以識別的JavaScript文件

2.2 構建思路不一樣

  • Grunt/Gulp須要將整個前端構建過程拆分紅多個task,合理控制全部task的調用關係
  • Webpack須要定義好入/出口,並須要清楚對於不一樣類型資源應該用什麼loader解析編譯

Grunt/Gulp是基於任務和流(taskstream)的。相似jQuery,找到一個(或一類)文件,對其作一系列的鏈式操做,更新流上的數據,整條鏈式操做構成了一個任務,多個任務就構成了整個Web的構建流程。
Webpack是基於入口的。Webpack會自動的遞歸解析入口所須要加載的全部資源文件,而後用不一樣的loader來處理不一樣的文件,用pulgin擴展Webpack功能。html

2.3 背景知識不一樣

Grunt/Gulp更像是後端開發者的思路,須要對整個流程瞭如指掌。Webpack更傾向於前端開發者的思路。前端

3. Webpack的構建流程是怎麼樣的?

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

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

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

4. 分別介紹bundle,chunk,module是什麼?

  • bundle:由Webpack打包出來的文件
  • chunk:代碼塊,webpack在進行模塊的依賴分析的時候,代碼分割出來的代碼塊
  • module:是開發中的單個模塊,在Webpack中,一切皆模塊,一個模塊對應一個文件

5. 什麼是Loader?什麼是Plugin?

  • loader: 模塊轉換器,用於對模塊的源代碼進行轉換
  • plugin: 自定義webpack打包過程的方式,插件含有apply屬性的JavaScript對象,apply屬性會被webpack compiler調用,而且compiler對象能夠在整個編譯生命週期內訪問

6. loader和plugin有哪些不一樣?

6.1 不一樣的做用

  • loader直譯爲「加載器",Webpack將一切文件視爲模塊,可是Webpack原生只能解析JavaScriptJSON類型文件。若是想加載解析其餘類型文件,就會用到loader。因此loader是讓Webpack擁有加載和解析非JavaScript文件的能力
  • plugin直譯爲」插件「,plugin能夠擴展Webpack的功能,讓Webpack具備更多的靈活性。在Webpack運行的生命週期中會廣播許多事件,plugin能夠監聽這些事件,在合適的時機經過Webpack提供的API改變輸出結果

6.2 不一樣的用法

  • loadermodule rules中配置,也就說它做爲模塊解析規則存在。類型爲Array,每一項都是一個Object,裏面描述了什麼類型的文件(test),使用什麼加載(loader)和使用的參數(options)
  • plugin單獨在plugins中單獨配置。類型爲Array,每項都是一個plugin的實例,參數是經過構造函數傳入

7. 有哪些常見的Loader?

  • file-loader: 將文件輸出到一個文件夾中,在代碼中經過相對路徑(url)去引用輸出的文件
  • url-loader: 和file-loader相似,可是能在文件很小的狀況下,以base64的方式將內容注入到代碼中
  • image-loader: 加載並壓縮圖片文件
  • babel-lodader: 將ES6轉成ES5
  • css-loader: 加載CSS,支持模塊化/壓縮/文件導入等特性
  • style-loader:把CSS代碼注入到JavaScript中,經過DOM操做去加載CSS
  • eslint-loader: 經過ESlint檢查JavaScript代碼

8. 有哪些常見的Plugin?

  • define-plugin: 定義環境變量
  • html-webpack-pulgin: 生成建立html入口文件,並引用對應的外部資源
  • uglifyjs-plugin: 經過Uglifyjs壓縮JavaScript代碼
  • mini-css-extract-plugin: 分離CSS文件
  • clean-webpack-plugin: 刪除打包文件
  • happypack: 實現多線程加速編譯

9. Tree Shaking

爲了使用tree shaking,須要知足如下條件:webpack

  • 使用ES2015語法(即importexport)
  • 在項目package.json文件中,添加sideEffects入口
  • 引入一個可以刪除未引用代碼(dead code)的壓縮工具(minifier)(例如:UglifyJSPlugin)

9.1 將文件標記爲無反作用(side-effect-free)

這種方式是經過package.jsonsideEffects屬性來實現的。nginx

{
  "sideEffects": false }

「反作用」的定義是,在導入時會執行特殊行爲的代碼,而不是僅僅暴露一個export或多個export。舉例說明,例如polyfill,它影響全局做用域,而且一般不提供export

注意,任何導入的文件都會受到tree shaking的影響。這意味着,若是在項目中使用相似css-loader並導入CSS文件,則須要將其添加到 side effect 列表中,以避免在生產模式中無心中將它刪除:web

{
  "sideEffects": ['*.css'] }

9.2 壓縮輸出

從 webpack 4 開始,也能夠經過 "mode" 配置選項輕鬆切換到壓縮輸出,只需設置爲 "production"。

也能夠在命令行接口中使用--optimize-minimize標記,來使用UglifyjsPluginshell

10 Code Splitting

code splitting的必要性

  • 不進行code splitting,打包後單文件提交較大,加載時長較長,影響用戶體驗
  • 不進行code splitting,常常修改業務代碼,從新打包後,瀏覽器不能進行緩存,致使性能較差,影響用戶體驗

10.1 同步代碼

import _ from 'lodash';

webpack.common.js配置以下:

....
optimization: {
  splitChunks: { chunks: 'all' } } ....

配置後,會將公用類庫進行打包,生成一個vendors~main.js文件。

10.2 異步代碼

function getComponent() { return import('lodash').then(({ default: _ }) => { var element = document.createElement('div'); element.innerHTML = _.join(['Clear', 'love'], ''); return element; }) } getComponent().then(element => { document.body.appendChild(element); })

11. webpack-dev-server和http服務器如nginx有什麼區別?

webpack-dev-server使用內存來存儲Webpack開發環境下打包的文件,而且可使用模塊熱更新,它比傳統的http服務對開發更加簡單高效。

12. 什麼 是模塊熱更新?

模塊熱更新是Webpack是的一個功能,它可使得代碼修改之後不需刷新瀏覽器就能夠更新,是高級版的自動刷新瀏覽器。devServer經過hot屬性能夠控制模塊熱更替。

12.1 經過配置文件

const webpack = require('webpack'); const path = require('path'); let env = process.env.NODE_ENV == "development" ? "development" : "production"; const config = { mode: env, devServer: { hot:true }, plugins: [ new webpack.HotModuleReplacementPlugin(), //熱加載插件 ] } module.exports = config;

12.2 經過命令行

"script": { "start": "NODE_EVN=development webpack-dev-server --config webpack-devlop-config.js --hot" }

13. Webpack的熱更新是如何作到的?說明其原理?

Webpack的熱更新有稱爲熱替換(Hot Module Replacement),縮寫爲HMR。這個機制能夠實現不刷新瀏覽器而將新變動的模塊替換舊的模塊。原來以下:
圖片描述

13.1 server端和client端都作了哪些具體工做:

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

14. 如何提升webpack的構建速度?

14.1 常規

14.1.1 保持版本最新

使用最新穩定版本的webpacknodenpm等,較新的版本更夠創建更高效的模塊樹以及提升解析速度。

14.1.2 loaders

因爲loader對文件的轉換操做很耗時,因此須要讓儘量少的文件被loader處理。咱們能夠經過如下3方面優化loader配置:

  • 優化正則匹配
  • 經過cacheDirectory選項開啓緩存
  • 經過includeexclude來減小被處理的文件
// webpack.common.js module: { rules: [ { test:/\.js$/, //babel-loader支持緩存轉換出的結果,經過cacheDirectory選項開啓 loader:'babel-loader?cacheDirectory', //只對項目根目錄下的src 目錄中的文件採用 babel-loader include: [path.resolve('src')], //排除 node_modules 目錄下的文件,node_modules 目錄下的文件都是採用的 ES5 語法,不必再經過 Babel 去轉換 exclude: path.resolve(__dirname, 'node_modules') } ] }

14.1.3 optimization.splitChunks 提取公共代碼

Webpack 4移除了CommonsChunkPlugin取而代之的是兩個新的配置項optimization.splitChunksoptimization.runtimeChunk來簡化代碼分割的配置。
經過設置 optimization.splitChunks.chunks: "all" 來啓動默認的代碼分割配置項。
當知足以下條件時,webpack 會自動打包 chunks:

  • 當前模塊是公共模塊(多處引用)或者模塊來自node_modules
  • 當前模塊大小大於30kb, 若是此模塊是按需加載,並行請求的最大數量小於等於5
  • 若是此模塊在初始頁面加載,並行請求的最大數量小於等於 3
optimization: {
    splitChunks: { chunks: 'async', // all async initial 是否對異步代碼進行的代碼分割 minSize: 30000, // 引入模塊大於30kb才進行代碼分割 maxSize: 0, // 引入模塊大於Xkb時,嘗試對引入模塊二次拆分引入 minChunks: 1, // 引入模塊至被使用X次後才進行代碼分割 maxAsyncRequests: 5, // maxInitialRequests: 3, automaticNameDelimiter: '~', // 模塊間的鏈接符,默認爲"~" name: true, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 // 優先級,越小優先級越高 }, default: { // 默認設置,可被重寫 minChunks: 2, priority: -20, reuseExistingChunk: true // 若是原本已經把代碼提取出來,則重用存在的而不是從新產生 } } } }

14.1.4 Smaller = false

減小編譯的總體大小,以提升構建性能。儘可能保持chunks小巧。

  • 使用更小/更少的庫
  • 移除不須要的代碼
  • 只編譯你在開發的代碼

14.1.5 Worker Pool

thread-loader能夠將很是耗性能的loaders轉存到worker pool中。
不要使用太多的workers,由於Node.jsruntimeloader有必定的啓動開銷。最小化workers和主進程間的模塊傳輸。進程間通信(IPC)是很是消耗資源的。

14.1.6 持久化緩存

對於一些性能開銷較大的loader以前能夠添加cache-loader,啓用持久化緩存。
使用package.json中的postinstall清楚緩存目錄。

14.1.7 Dlls

使用DllPlugin將更新不頻繁的代碼進行單獨編譯。這將改善引用程序的編譯速度。即便它增長了構建過程的複雜度。
利用DllPluginDllReferencePlugin預編譯資源模塊, 經過DllPlugin來對那些咱們引用可是絕對不會修改的npm包來進行預編譯,再經過DllReferencePlugin將預編譯的模塊加載進來。

14.1.8 解析(resolve)

如下幾步能夠提升解析速度:

  • 儘可能減小resolve.modulesresolve.extensionsresolve.mainFilesresolve.desciriptionsFiles中類目的數量,由於它們會增長文件系統的調用次數。
  • 若是你不使用symlinks,能夠設置resolve.symlinks: false
  • 若是你使用自定義解析plugins,而且沒有指定context信息,能夠設置resolve.cacheWithContext: false

14.2 Development

14.2.1 在內存中編譯

如下幾個實用的工具經過在內存中進行代碼的編譯和資源的提供,但並不寫入磁盤來提升性能:

  • webpack-dev-server
  • webpack-hot-middleware
  • webpack-dev-middleware

14.2.2 Devtool

須要注意在不一樣的devtool的設置,會致使不一樣的性能差別。

  • eval具備最好的性能,但不能幫你轉義代碼
  • 若是你能接受稍微差一些的mapping質量,你可使用cheap-source-map選擇來提升性能
  • 使用eval-source-map配置進行增量編譯 

在大多數狀況下,cheap-module-eval-source-map是最好的選擇。

14.2.3 避免在生產環境在纔會用到的工具

某些實用工具,pluginsloaders都只能在構建生產環境時才使用。例如,在開發時使用UglifyJsPlugin來壓縮和修改代碼是沒有意義的。如下這些工具在開發中一般被排除在外:

  • UglifyJsPlugin
  • ExtractTextPlugin
  • [hash]/[chunkhash]
  • AggressiveSplittingPlugin
  • AggressiveMergingPlugin
  • ModuleConcatenationPlugin

14.2.4 最小化入口chunk

webpack只會在文件系統中生成已更新的chunk。應當在生成入口chunk時,儘可能減小入口chunk的體積,以提升性能。

14.3 Production

不要爲了很是小的性能增益,犧牲了你應用程序的質量!!請注意,在大多數狀況下優化代碼質量,比構建性能更重要。

14.3.1 多個編譯時

當進行多個編譯時,如下工具能夠幫助到你:

  • parallel-webpack: 它容許編譯工做在woker池中進行。
  • cache-loader: 緩存能夠在多個編譯之間共享。

14.4 工具相關問題

14.4.1 Babel

項目中的preset/plugins數量最小化

14.4.2 TypeScript

  • 在單獨的進程中使用fork-ts-checker-webpack-plugin進行類型檢查
  • 配置loaders時跳過類型檢查
  • 使用ts-loader時,設置happyPackMode: true以及 transpileOnly: true

14.4.3 Saas

node-sass中有個來自Node.js線程池的阻塞線程的bug。當使用thread-loader時,須要設置workParallelJobs: 2

15. 如何利用Webpack來優化前端性能?(提升性能和體驗)

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

  • 壓縮代碼。刪除多餘的代碼/註釋,簡化代碼的寫法等等方式。能夠利用WebpackUglifyJsPluginParallelUglifyPlugin來壓縮JavaScript代碼。利用css-loader?minimize來壓縮CSS
  • 壓縮圖片。利用imagemin-webpack-plugin等圖片資源壓縮插件,對引用的圖片資源進行壓縮處理
  • 合理的圖片資源引用。使用url-loader加載解析圖片資源時,能夠經過配置options limit參數,將較小的圖片資源轉換成base64格式,減小http請求
  • 利用CDN加速。在構建過程當中,將引用的靜態資源路徑修改成CDN上對應的路徑。能夠利用Webpack對於output參數和各個loaderpublicPath參數來修改資源路徑
  • 刪除死代碼(Ttee Shaking)。將代碼中沒有引用的代碼片斷刪除掉。能夠經過在啓動Webpack時追加參數--optimize-minimize來實現
  • 提取公共代碼

16. npm打包時須要注意哪些?如何利用Webpack來更好的構建?

16.1 npm模塊須要注意如下問題:

  • 要支持CommonJS模塊化規範,因此打包後的最後結果也要支持該規則
  • npm模塊使用者的環境是不肯定的,頗有可能並不支持ES6,因此打包的最後結果應該是採用ES5編寫的。而且若是ES5是通過轉換的,請最好連同SourceMap一同上傳
  • npm包大小應該是儘可能小(有些倉庫會限制包大小)
  • 發佈的模塊不能將依賴的模塊也一同打包,應該讓用戶選擇性的去自行安裝。這樣能夠避免模塊應用者再次打包時出現底層模塊被重複打包的狀況
  • UI組件類的模塊應該將依賴的其它資源文件,例如.css文件也須要包含在發佈的模塊裏

16.2 基於以上須要注意的問題,咱們能夠對於Webpack配置作如下擴展和優化:

  • CommonJS模塊化規範的解決方案: 設置output.libraryTarget='commonjs2'使輸出的代碼符合CommonJS2模塊化規範,以供給其它模塊導入使用
  • 輸出ES5代碼的解決方案:使用babel-loaderES6代碼轉換成ES5的代碼。再經過開啓devtool: 'cheap-module-eval-source-map'輸出SourceMap以發佈調試
  • npm包大小盡可能小的解決方案:Babel在把ES6代碼轉換成ES5代碼時會注入一些輔助函數,最終致使每一個輸出的文件中都包含這段輔助函數的代碼,形成了代碼的冗餘。解決方法是修改.babelrc文件,爲其加入transform-runtime插件
  • 不能將依賴模塊打包到npm模塊中的解決方案:使用externals配置項來告訴Webpack哪些模塊不須要打包
  • 對於依賴的資源文件打包的解決方案:經過css-loader和extract-text-webpack-plugin來實現,配置以下:
const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { module: { rules: [ { test: /\.css$/, use: ExtractTextPlugin.extract({ use: ['css-loader'] }) // 提取出chunk中的css到單獨的文件中 } ] }, plugins: [ new ExtractTextPlugin({ filename: 'index.css' }) ] }
轉自https://segmentfault.com/a/1190000019890322,此人還會時不時的對此文章進行更新
相關文章
相關標籤/搜索