Webpack 你知道多少?

1. 有哪些常見的Loader?

  • raw-loader 加載文件原始內(nèi)容(utf-8)
  • file-loader 把文件輸出到一個(gè)文件夾中,在代碼中通過相對(duì)URL去引用輸出的文件(處理圖片和字體)
  • url-loader 與file-loader類似,區(qū)別是用戶可以設(shè)置一個(gè)閥值,大于閥值時(shí)返回其publicPath,小于閥值時(shí)返回文件base64形式編碼(處理圖片和字體)
  • source-map-loader加載 額外的source map文件,以方便斷點(diǎn)調(diào)試
  • svg-inline-loader 將壓縮后的SVG內(nèi)容注入代碼中
  • image-loader 加載并且壓縮圖片文件
  • json-loader 加載JSON文件(默認(rèn)包含)
  • handlebars-loader 將handlebars 模板編譯成函數(shù)并返回
  • babel-loader 把ES6轉(zhuǎn)化成ES5
  • ts-loader 將 TypeScript 轉(zhuǎn)換成 JavaScript
  • awesome-typescript-loader 將 TypeScript 轉(zhuǎn)換成 JavaScript,性能優(yōu)于 ts-loader
  • style-loader 將 CSS 代碼注入 JavaScript 中,通過 DOM 操作去加載 CSS
  • css-loader 加載 CSS,支持模塊化、壓縮、文件導(dǎo)入等特性
  • style-loader 把 CSS 代碼注入到 JavaScript 中,通過 DOM 操作去加載 CSS
  • postcss-loader 擴(kuò)展 CSS 語法,使用下一代 CSS,可以配合 autoprefixer 插件自動(dòng)補(bǔ)齊 CSS3 前綴
  • eslint-loader 通過 ESLint 檢查 JavaScript 代碼
  • tslint-loader 通過 TSLint檢查 TypeScript 代碼
  • mocha-loader 加載 Mocha 測(cè)試用例的代碼
  • coverjs-loader 計(jì)算測(cè)試的覆蓋率
  • vue-loader 加載 Vue.js 單文件組件
  • i18n-loader 國際化
  • cache-loader 可以在一些性能開銷較大的 Loader 之前添加,目的是將結(jié)果緩存到磁盤里

更多 Loader 請(qǐng)參考官網(wǎng)

2. 常見的Plugin

  • define-plugin定義環(huán)境變量 (Webpack4 之后指定 mode 會(huì)自動(dòng)配置)
  • ignore-plugin忽略部分文件
  • html-webpack-plugin簡化 HTML 文件創(chuàng)建 (依賴于 html-loader)
  • web-webpack-plugin可方便地為單頁應(yīng)用輸出 HTML,比 html-webpack-plugin 好用
  • uglifyjs-webpack-plugin不支持 ES6 壓縮 (Webpack4 以前)
  • terser-webpack-plugin支持壓縮 ES6 (Webpack4)
  • webpack-parallel-uglify-plugin多進(jìn)程執(zhí)行代碼壓縮,提升構(gòu)建速度
  • mini-css-extract-plugin分離樣式文件,CSS 提取為獨(dú)立文件,支持按需加載 (替代extract-text-webpack-plugin)
  • serviceworker-webpack-plugin為網(wǎng)頁應(yīng)用增加離線緩存功能
  • clean-webpack-plugin目錄清理
  • ModuleConcatenationPlugin開啟 Scope Hoisting
  • speed-measure-webpack-plugin可以看到每個(gè) Loader 和 Plugin 執(zhí)行耗時(shí) (整個(gè)打包耗時(shí)、每個(gè) Plugin 和 Loader 耗時(shí))
  • webpack-bundle-analyzer可視化 Webpack 輸出文件的體積 (業(yè)務(wù)組件、依賴第三方模塊)

更多 Plugin 請(qǐng)參考官網(wǎng)

3. Loader和Plugin的區(qū)別

Loader 本質(zhì)就是一個(gè)函數(shù),在該函數(shù)中對(duì)接收到的內(nèi)容進(jìn)行轉(zhuǎn)換,返回轉(zhuǎn)化后的結(jié)果,因?yàn)閃ebpack只認(rèn)識(shí)JavaScript ,所以Loader就成了翻譯官,對(duì)其他類型的資源進(jìn)行轉(zhuǎn)譯的預(yù)處理工作。在module.rules中配置,作為模板的解析規(guī)則,類型為數(shù)組。每一項(xiàng)都是一個(gè)Ocject,內(nèi)部包含了test(類型文件)、loader、option(參數(shù))等屬性。

Plugin 就是插件,基于事件流框架Tapable,插件可以擴(kuò)展Webpack運(yùn)行生命周期中廣播出許多事件,Plugin可以監(jiān)聽這些事件,在合適的時(shí)機(jī)通過Webpack提供的Api改變輸出的結(jié)果。在plugins中單獨(dú)配置,類型為數(shù)組,每一項(xiàng)是一個(gè)Plugin的實(shí)例,參數(shù)都是通構(gòu)造函數(shù)傳入。

4. Webpack構(gòu)建流程

Webpack的運(yùn)行流程是一個(gè)串行的過程,從啟動(dòng)到結(jié)束會(huì)依次執(zhí)行以下流程:

  • [初始化參數(shù)]:從配置文件和Shell語句中讀取與合并參數(shù),得出最終的參數(shù)
  • [開始編譯]:用上一步得到的參數(shù)初始化Complier 對(duì)象,加載所有配置的插件,執(zhí)行對(duì)象的run方法開始執(zhí)行編譯
  • [確定入口]:根據(jù)配置中的entry找出所有的入口文件
  • [編譯模塊]:從入口文件出發(fā),調(diào)用所有的配置的Loader對(duì)模塊進(jìn)行翻譯,再找出該模塊以來的模塊,再遞歸本步驟直到所有的入口依賴的文件都經(jīng)過本步驟的處理
  • [完成模塊編譯]:在經(jīng)過上一步使用Loader翻譯完所有的模塊后,得到了每一個(gè)模塊被編譯后的最終內(nèi)容以及它們之間的依賴關(guān)系
  • [輸出資源]:根據(jù)入口和模塊之間的依賴關(guān)系,組裝成一個(gè)個(gè)包含多個(gè)模塊的Chunk,再把每個(gè)Chunk轉(zhuǎn)化成一個(gè)單獨(dú)的文件加入到輸出列表,這步是可以修改輸出內(nèi)容的最后機(jī)會(huì)
  • [輸出完成]:在確定好輸出內(nèi)容后,根據(jù)配置確定輸出的路徑和文件名,把文件內(nèi)容寫入到文件系統(tǒng)

在以上過程中,Webpack 會(huì)在特定的時(shí)間點(diǎn)廣播出特定的事件,插件在監(jiān)聽到隊(duì)員的事件后會(huì)執(zhí)行特定的邏輯,并且插件可以調(diào)用Webpack提供的API改變Webpack的運(yùn)行結(jié)果。

簡單的流程圖:

graph LR
初始化-->編譯
編譯-->輸出

初始化:啟動(dòng)構(gòu)建,讀取與合并配置參數(shù),加載 Plugin,實(shí)例化 Compiler

編譯:從 Entry 出發(fā),針對(duì)每個(gè) Module 串行調(diào)用對(duì)應(yīng)的 Loader 去翻譯文件的內(nèi)容,再找到該 Module 依賴的 Module,遞歸地進(jìn)行編譯處理

輸出:將編譯后的 Module 組合成 Chunk,將 Chunk 轉(zhuǎn)換成文件,輸出到文件系統(tǒng)中

5. 使用webpack開發(fā)時(shí),可以提高效率的插件

  • webpack-dashboard可以更友好的展示相關(guān)打包信息
  • webpack-merge提取公共配置,減少重復(fù)配置代碼
  • speed-measure-webpack-plugin簡稱 SMP,分析出 Webpack 打包過程中 Loader 和 Plugin 的耗時(shí),有助于找到構(gòu)建過程中的性能瓶頸。
  • size-plugin監(jiān)控資源體積變化,盡早發(fā)現(xiàn)問題
  • HotModuleReplacementPlugin模塊熱替換

6. source map生產(chǎn)環(huán)境的使用

source map 是將編譯、打包、壓縮后的代碼映射回源代碼的過程,打包壓縮后的代碼不具備良好的可讀性,想要調(diào)試源碼就需要source map

map文件只要不打開瀏覽器的開發(fā)者工具,瀏覽器是不會(huì)加載的。

線上環(huán)境一般有三種處理方案:

hidden-source-map:借助第三方錯(cuò)誤監(jiān)控平臺(tái)Sentry 使用

nosources-source-map:只會(huì)顯示具體行數(shù)以及查看源代碼的錯(cuò)誤棧。安全性比 sourcemap 高

sourcemap:通過 nginx 設(shè)置將 .map 文件只對(duì)白名單開放(公司內(nèi)網(wǎng))

注意:避免在生產(chǎn)環(huán)境中使用inline-eval-,因?yàn)樗鼈儠?huì)增加bundle 體積大小,并降低整體性能。

7. 模塊打包原理

Webpack 實(shí)際上為每個(gè)模塊創(chuàng)造了一個(gè)可以導(dǎo)出和導(dǎo)入的環(huán)境,本質(zhì)上并沒有修改代碼的執(zhí)行邏輯,代碼執(zhí)行順序與模塊加載順序也完全一致。

8. 文件監(jiān)聽原理

在發(fā)現(xiàn)源碼發(fā)生變化時(shí),自動(dòng)重新構(gòu)建出新的輸出文件。
Webpack開啟監(jiān)聽模式,有兩種方式:

  1. 啟動(dòng) webpack 命令時(shí),帶上 --watch 參數(shù)
  2. 在配置 webpack.config.js 中設(shè)置 watch:true

缺點(diǎn):每次需要手動(dòng)刷新瀏覽器

原理:輪詢判斷文件的最后編輯時(shí)間是否變化,如果某個(gè)文件發(fā)生了變化,并不會(huì)立刻告訴監(jiān)聽者,而是先緩存起來,等 aggregateTimeout 后再執(zhí)行。

module.export = {
    // 默認(rèn)false,也就是不開啟
    watch: true,
    // 只有開啟監(jiān)聽模式時(shí),watchOptions才有意義
    watchOptions: {
        // 默認(rèn)為空,不監(jiān)聽的文件或者文件夾,支持正則匹配
        ignored: /node_modules/,
        // 監(jiān)聽到變化發(fā)生后會(huì)等300ms再去執(zhí)行,默認(rèn)300ms
        aggregateTimeout:300,
        // 判斷文件是否發(fā)生變化是通過不停詢問系統(tǒng)指定文件有沒有變化實(shí)現(xiàn)的,默認(rèn)每秒問1000次
        poll:1000
    }
}

9. Webpack 的熱更新原理

Webpack 的熱更新又稱熱替換(Hot Module Replacement),縮寫為HMR。這個(gè)機(jī)制是可以做到不用刷新瀏覽器而將新變更的模塊替換舊的模塊。

HMR的核心是客戶端從服務(wù)端拉取更新后的文件,準(zhǔn)確的說是 chunk diff (chunk 需要更新的部分),實(shí)際上WDS與瀏覽器之間維護(hù)了一個(gè)Websocket,當(dāng)本地資源發(fā)生變化時(shí),WDS會(huì)向?yàn)g覽器推送更新,并帶上構(gòu)建時(shí)的hash,讓客戶端與上一次資源進(jìn)行對(duì)比。客戶端對(duì)比出差異后會(huì)向WDS發(fā)起Ajax請(qǐng)求來獲取更改內(nèi)容(文件列表、hash),這樣客戶端就可以再借助這些信息繼續(xù)向WDS發(fā)起jsonp請(qǐng)求獲取該chunk的新增更新。

后續(xù)的部分(拿到增量更新之后如何處理?哪些狀態(tài)該保留?哪些又需要更新?)由HotModelPlugin 來完成,提供了相關(guān)API以供開發(fā)者對(duì)自身場(chǎng)景進(jìn)行處理,像react-hot-loader 和 vue-loader 都是借助這些 API 實(shí)現(xiàn) HMR。

細(xì)節(jié)請(qǐng)參考Webpack HMR 原理解析

10. 如何對(duì)bundle體積進(jìn)行監(jiān)控和分析

VSCode 中有一個(gè)插件 Import Cost 可以幫助我們對(duì)引入模塊的大小進(jìn)行實(shí)時(shí)監(jiān)測(cè),還可以使用 webpack-bundle-analyzer 生成 bundle 的模塊組成圖,顯示所占體積。

bundlesize 工具包可以進(jìn)行自動(dòng)化資源體積監(jiān)控。

11. 文件指紋

文件指紋是打包后輸出的文件名的后綴。

  • Hash和整個(gè)項(xiàng)目的構(gòu)建相關(guān),只要項(xiàng)目文件有修改,整個(gè)項(xiàng)目構(gòu)建的 hash 值就會(huì)更改
  • Chunkhash和 Webpack 打包的 chunk 有關(guān),不同的 entry 會(huì)生出不同的 chunkhash
  • Contenthash根據(jù)文件內(nèi)容來定義 hash,文件內(nèi)容不變,則 contenthash 不變

++JS的文件指紋設(shè)置++

設(shè)置 output 的 filename,用 chunkhash。

module.exports = {
    entry: {
        app: './scr/app.js',
        search: './src/search.js'
    },
    output: {
        filename: '[name][chunkhash:8].js',
        path:__dirname + '/dist'
    }
}

CSS的文件指紋設(shè)置

設(shè)置 MiniCssExtractPlugin 的 filename,使用 contenthash。

module.exports = {
    entry: {
        app: './scr/app.js',
        search: './src/search.js'
    },
    output: {
        filename: '[name][chunkhash:8].js',
        path:__dirname + '/dist'
    },
    plugins:[
        new MiniCssExtractPlugin({
            filename: `[name][contenthash:8].css`
        })
    ]
}

圖片的文件指紋設(shè)置

設(shè)置file-loader的name,使用hash。

占位符名稱及含義:

& ext 資源后綴名

& name 文件名稱

& path 文件相對(duì)路徑

& folder 文件所在的文件夾

& contenthash 文件的內(nèi)容hash,默認(rèn)是md5生成

& hash 文件內(nèi)容的hash,默認(rèn)是md5生成

& emoji 一個(gè)隨機(jī)的指代文件內(nèi)容的emoji

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename:'bundle.js',
        path:path.resolve(__dirname, 'dist')
    },
    module:{
        rules:[{
            test:/\.(png|svg|jpg|gif)$/,
            use:[{
                loader:'file-loader',
                options:{
                    name:'img/[name][hash:8].[ext]'
                }
            }]
        }]
    }
}

12. 配置文件,如何保證各個(gè)loader按照預(yù)想方式工作

可以使用 enforce 強(qiáng)制執(zhí)行 loader 的作用順序,pre 代表在所有正常 loader 之前執(zhí)行,post 是所有 loader 之后執(zhí)行。(inline 官方不推薦使用)

13. 優(yōu)化 Webpack 的構(gòu)建速度

  • 使用高版本的 Webpack 和 Node.js
  • 壓縮代碼
  1. webpack-paralle-uglify-plugin
  2. uglifyjs-webpack-plugin 開啟 parallel 參數(shù) (不支持ES6)
  3. terser-webpack-plugin 開啟 parallel 參數(shù)
  4. 多進(jìn)程并行壓縮
  5. 通過 mini-css-extract-plugin 提取 Chunk 中的 CSS 代碼到單獨(dú)文件,通過 css-loader 的 minimize 選項(xiàng)開啟 cssnano 壓縮 CSS。
  • 圖片壓縮
  1. 使用基于 Node 庫的 imagemin (很多定制選項(xiàng)、可以處理多種圖片格式)
  2. 配置 image-webpack-loader
  • 縮小打包作用域
  1. exclude/include (確定 loader 規(guī)則范圍)
  2. resolve.modules 指明第三方模塊的絕對(duì)路徑 (減少不必要的查找)
  3. resolve.mainFields 只采用 main 字段作為入口文件描述字段 (減少搜索步驟,需要考慮到所有運(yùn)行時(shí)依賴的第三方模塊的入口文件描述字段)
  4. resolve.extensions 盡可能減少后綴嘗試的可能性
  5. noParse 對(duì)完全不需要解析的庫進(jìn)行忽略 (不去解析但仍會(huì)打包到 bundle 中,注意被忽略掉的文件里不應(yīng)該包含 import、require、define 等模塊化語句)
  6. IgnorePlugin (完全排除模塊)
  7. 合理使用alias
  • 提取頁面公共資源
  1. 使用html-webpack-externals-plugin,將基礎(chǔ)包通過CDN引入,不打入bundel中
  2. 使用SplitChunksPlugin進(jìn)行(公共腳本、基礎(chǔ)包、頁面公共文件)分離
  3. 基礎(chǔ)包分離
  • DLL
  1. 使用DllPlugin進(jìn)行分包,使用DllReferencePlugin(索引鏈接)對(duì)manifest.json引用,讓一些基本不會(huì)改動(dòng)的代碼先打包成靜態(tài)資源,避免反復(fù)編譯浪費(fèi)時(shí)間。
  2. HashedModuleIdsPlugin 可以解決模塊數(shù)字id問題
  • 充分利用緩存提升二次構(gòu)建速度
  1. babel-loader 開啟緩沖
  2. terser-webpack-plugin開啟緩沖
  3. 使用 cache-loader 或者h(yuǎn)ard-source-webpack-plugin
  • Tree shaking
  1. purgecss-webpack-plugin 和 mini-css-extract-plugin配合使用(建議)
  2. 打包過程中檢測(cè)工程中沒有引用過的模板并進(jìn)行標(biāo)記,在資源壓縮時(shí)將它們從最終的bundle中去掉(只能對(duì)ES6 Modlue生效)開發(fā)中盡可能使用ES6 Model的模塊,提高tree shaking效率
  3. 禁用babel-loader的模塊依賴,否則Webpack接收到的就都是轉(zhuǎn)換過的CommonJS形式的模塊,無法進(jìn)行tree-shaking
  4. 使用 PurifyCSS(不在維護(hù)) 或者 uncss 去除無用 CSS 代碼
  • Scope hoisting
  1. 構(gòu)建后的代碼會(huì)存在大量包,造成體積增大,運(yùn)行代碼時(shí)創(chuàng)建的函數(shù)作用域變多,內(nèi)存開銷變大。Scope hoisting將所有的模塊的代碼按照引用順序從放在一個(gè)函數(shù)作用域里,然后適當(dāng)?shù)闹孛恍┳兞恳苑乐棺兞棵麤_突
  2. 必須是ES6 的語法,因?yàn)橛泻芏嗟谌綆烊圆捎肅ommonJS語法,為了充分發(fā)揮Scope hoisting的作用,需要配置mainFields對(duì)第三方模塊優(yōu)先采用jsnext:main中指向的ES6模塊語法
  • 動(dòng)態(tài)Polyfill:建議采用 polyfill-service 只給用戶返回需要的polyfill,社區(qū)維護(hù)。(部分國內(nèi)奇葩瀏覽器UA可能無法識(shí)別,但可以降級(jí)返回所需全部polyfill)

14. 代碼分割的本質(zhì)是什么

代碼分割的本質(zhì)其實(shí)就是在源代碼直接上線和打包成唯一腳本main.bundle.js這兩種極端方案之間的一種更適合實(shí)際場(chǎng)景的中間狀態(tài)。

「用可接受的服務(wù)器性能壓力增加來換取更好的用戶體驗(yàn)。」

源代碼直接上線:雖然過程可控,但是http請(qǐng)求多,性能開銷大。

打包成唯一腳本:一把梭完自己爽,服務(wù)器壓力小,但是頁面空白期長,用戶體驗(yàn)不好。

15. Babel原理

大多數(shù)JavaScript Parser遵循 estree 規(guī)范,Babel 最初基于 acorn 項(xiàng)目(輕量級(jí)現(xiàn)代 JavaScript 解析器)Babel大概分為三大部分:

  • 解析:將代碼轉(zhuǎn)換成 AST
  1. 詞法分析:將代碼(字符串)分割為 token 流,即語法單元成的數(shù)組
  2. 語法分析:分析 token 流(上面生成的數(shù)組)并生成 AST
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。