Webpack問答

到目前,Webpack已發(fā)布到v3.8.1,網(wǎng)上有很多Webpack入門到精通的教程及文檔,此文就從問答的角度梳理下Webpack的知識(shí)脈絡(luò)。

基礎(chǔ)篇

1. 為什么要使用Webpack?

在面對(duì)復(fù)雜的業(yè)務(wù)需求和前端性能優(yōu)化的場(chǎng)景時(shí),會(huì)存在以下需要解決的問題:

  • 前端模塊化工具
  • 異步模塊加載及管理
  • 樣式(Style)預(yù)處理:Scss、Less、Postcss等
  • 樣式(Style)后處理:autoprefixer、img資源內(nèi)聯(lián)、原子css、css module
  • ES6語(yǔ)法、TypeScript、CoffeeScript支持
  • 專有代碼處理:JSX、*.vue
  • 外部依賴模塊:Handlerbars等模板、gzip、text、json等
  • 公共模塊提取
  • 開模模式自動(dòng)刷新瀏覽器
  • 長(zhǎng)效緩存處理
  • 代碼壓縮混淆
  • ...

等等以上如果人肉處理則會(huì)相當(dāng)麻煩,按照Webpack的模塊打包思路,以上問題都能很好的解決。

2. 什么是Webpack?

Webpack是模塊打包器:它做的事情是,分析你的項(xiàng)目結(jié)構(gòu),找到JavaScript模塊以及其它的一些瀏覽器不能直接運(yùn)行的拓展語(yǔ)言(Scss,TypeScript等),并將其轉(zhuǎn)換和打包為合適的格式供瀏覽器使用。

3. Webpack和Browserify、Rollup、Gulp、Grunt、Seajs、Requirejs的區(qū)別?

以上工具的功能可分為兩類:

1. 任務(wù)管理器(Task Runner)

Gulp / Grunt是一種任務(wù)管理工具,能夠優(yōu)化前端工作流程。比如自動(dòng)刷新頁(yè)面、combo、壓縮css、js、編譯less等等。簡(jiǎn)單來(lái)說,就是使用Gulp/Grunt,然后配置你需要的插件,就可以把以前需要手工做的事情讓它幫你做了。

2. 模塊化解決方案

2.1 在線模塊方案

Seajs/Requirejs是一種在線"編譯" 模塊的方案,相當(dāng)于在頁(yè)面上加載一個(gè) CMD/AMD 解釋器。這樣瀏覽器就認(rèn)識(shí)了 define、exports、module 這些東西。也就實(shí)現(xiàn)了模塊化。

2.2 模塊打包器

Browserify / Webpack / Rollup是一個(gè)預(yù)編譯模塊的方案,相比于上面 ,這個(gè)方案更加智能。沒用過browserify,這里以webpack為例。首先,它是預(yù)編譯的,不需要在瀏覽器中加載解釋器。另外,你在本地直接寫JS,不管是 AMD / CMD / ES6 風(fēng)格的模塊化,它都能認(rèn)識(shí),并且編譯成瀏覽器認(rèn)識(shí)的JS。以上三個(gè)工具都能完成對(duì)lib庫(kù)的打包,React,Vue,Ember,Preact,D3,Three.js,Moment 以及其他許多知名的庫(kù)都使用 Rollup 。Rollup使用Tree Shaking技術(shù)精簡(jiǎn)Lib庫(kù),這個(gè)最新的Webpack也能做到,但是從最終的打包代碼中分析,Rollup做好的包會(huì)更純凈一些,性能更好一些。

4. Webpack的構(gòu)建思路?

一切都是模塊

就像JS文件可以視作“模塊”一樣,其他所有的一切(CSS,圖片,HTML)都可以被視作模塊。也就是說,你可以require("myJSfile.js")或者require("myCSSfile.css")。這意味著我們可以把任何靜態(tài)資源分割成可控的模塊,以供重復(fù)使用等不同的操作。

只加載“你需要的”和“你何時(shí)需要”的

典型的模塊加載器會(huì)把所有的模塊最終打包生成一個(gè)巨大的“bundle.js”文件。但在很多實(shí)際的項(xiàng)目當(dāng)中,這個(gè)“bundle.js”文件體積可能會(huì)達(dá)到10MB~15MB,并且會(huì)一直不停進(jìn)行加載!所以Webpack通過大量的特性去分割你的代碼,生成多個(gè)“bundle”片段,并且異步地加載項(xiàng)目的不同部分,因此只會(huì)為你加載“你需要的”和“你何時(shí)需要”的部分。

入門篇

入門主要是對(duì)Webpack配置的解讀及常見問題點(diǎn)解答。

1. 如何區(qū)分Webpack構(gòu)建形式(開發(fā)環(huán)境 VS 生產(chǎn)環(huán)境)?

按照Webpack文檔中說明的,使用配置對(duì)象編寫配置文檔,一般Webpack構(gòu)建分為兩個(gè)環(huán)境:開發(fā)、發(fā)布。因此需要三個(gè)配置:

  • webpack.base.config.js:基礎(chǔ)公共
  • webpack.dev.config.js:開發(fā)模式
  • webpack.prod.config.js:發(fā)布模式

通過webpack-merge將配置合并,使用pkg.script管理構(gòu)建任務(wù)。

為了在window和max上都能無(wú)縫兼容,這里使用了 cross-env 配置環(huán)境變量,例如:

"scripts": {
    "clear": "rm -rf build&& mkdir build",
    "start": "npm run clear&& cross-env NODE_ENV=development webpack-dev-server --host 0.0.0.0 --devtool eval --progress --color --profile",
    "deploy": "npm run pre&& npm run clear&& cross-env NODE_ENV=production webpack -p --progress"
}

代碼中可使用如下方式判斷:

if (process.env.NODE_ENV === 'development') {
    // ...
}

2. Entry在MPA下如何寫?

MPA是Muti Page Application的縮寫,指多頁(yè)應(yīng)用程序。這種需求常見于:

  1. 多頁(yè)靜態(tài)項(xiàng)目編寫
  2. 多頁(yè)后端渲染模板編寫

如果頁(yè)面不多,且模板路徑無(wú)規(guī)律的話,使用對(duì)象語(yǔ)法定義Entry配置,具體Entry配置參考文檔。

如果頁(yè)面結(jié)構(gòu)已約定,且頁(yè)面眾多,這里建議使用JS代碼獲取入口路徑,比如像這個(gè)項(xiàng)目express-hbs-webpack-demo,約定了chunkName和入口JSindex: .../views/index/main.js

|   ├── views        // 前端各個(gè)頁(yè)面
|   |   |── common   // 公共Partials
|   |   |── layout.hbs
|   |   |── index    // 主頁(yè)
|   |   |   |── partials
|   |   |   |   |── content.hbs    // 頁(yè)面Partials
|   |   |   |   |── xxxxxxxx.hbs   // 其余頁(yè)面Partials
|   |   |   |   |── resource.ejs   // webpack的 HTMLHtmlWebpackPlugin 插件需要這個(gè)模板
|   |   |   |   └── resource.hbs   // 最終生成的hbs資源片段
|   |   |   |── index.hbs    // 頁(yè)面主結(jié)構(gòu)
|   |   |   |── main.js      // 頁(yè)面入口js,約定只能是index.js或者main.js
|   |   |   └── style.less   // 當(dāng)前頁(yè)面的樣式,支持scss/less/css等
|   |   └── xxxxx    // 其余頁(yè)面
|   └── app.js       // 頁(yè)面公共部分,比如全局樣式及腳本,或者頁(yè)面初始化時(shí)的動(dòng)作

代碼示例如下(源碼)

/**
 * read path of js entry files(for webpack entry)
 * notice: the entry file name must be main.js or index.js, and only one exist
 */
exports.webpackEntry = function () {
  var webpackEntry = {} // for webpack entry
  glob.sync(`${config.viewsPath}/**/{main,index}.js`).forEach(function (entry) {
    var tmp = entry.split('/').splice(-3)
    var moduleName = tmp.slice(1, 2)[0]
    // eg: entry -> {about: '/Users/xxx/xxx/express-here/client/views/about/main.js'}
    webpackEntry[moduleName] = entry
  })
  return webpackEntry

4. 如何在移動(dòng)配置文件的同時(shí)不影響構(gòu)建結(jié)果?

因?yàn)樵谂渲弥行枰褂孟鄬?duì)路徑獲取某些文件/模塊的位置,因此當(dāng)移動(dòng)Webpack配置時(shí)會(huì)影響資源引用,這個(gè)很好處理,使用context即可,這使得你的配置獨(dú)立于 CWD(current working directory - 當(dāng)前執(zhí)行路徑)。

context: path.resolve(__dirname, "app")

4. 靜態(tài)資源托管到CDN時(shí),構(gòu)建時(shí)資源如何改寫?

需要看下文檔中對(duì)output.publicPath的說明。這里需要對(duì)三個(gè)常用屬性進(jìn)行解讀:

  • filename:決定了每個(gè)輸出 bundle 的名稱
  • path:這些 bundle 將寫入到 output.path 選項(xiàng)指定的目錄下。
  • publicPath:將上面兩個(gè)準(zhǔn)備的路徑字符串前面加上外部路徑,比如某個(gè)CDN:http://xx.xx.com/public/,此時(shí)原資源位置:js/xx.xxxxx.bundle.js --> http://xx.xx.com/public/js/xx.xxxxx.bundle.js

因此,增加publicPath屬性就可改寫資源名稱外部路徑。

5. 構(gòu)建Lib庫(kù)使用Rollup還是Webpack?

建議使用Rollup打包,編譯最終生成的模塊干凈清晰。

6. 引入模塊時(shí),rules的匹配順序是?

module是對(duì)import/require引入的資源進(jìn)行匹配處理的配置項(xiàng)。

module.noParse

首先,如果定義了module.noParse且匹配到時(shí),則不解析匹配的模塊,因此模塊內(nèi)部不應(yīng)該有import, require, define 的調(diào)用,或任何其他導(dǎo)入機(jī)制,這樣做可以忽略大型的 library 可以提高構(gòu)建性能。比如:

noParse: /jquery|lodash/

// 從 webpack 3.0.0 開始
noParse: function(content) {
  return /jquery|lodash/.test(content);
}

module.rules

其次,如果上述未匹配到,則進(jìn)行module.rules匹配。數(shù)組中對(duì)象可通過屬性:test, include, excluderesource對(duì)資源匹配。

這里需要注意,Webpack執(zhí)行最后一次匹配到的rule配置。因此,rule配置順序影響構(gòu)建,無(wú)用的rule全部去掉。

7. 代碼中import/require找模塊的過程?

這個(gè)問題也是對(duì)“模塊解析(Module Resolution)”過程的提問。

Webpack使用enhanced-resolve來(lái)解析文件路徑。

1. 絕對(duì)路徑

不需要解析

2. 相對(duì)路徑

由當(dāng)前文件的上下文生成絕對(duì)路徑,之后轉(zhuǎn)到第一種情況。

3. 模塊路徑

import "module"; // 文件
import "module/lib/file"; // 文件夾

模塊將在 resolve.modules 中指定的所有目錄內(nèi)搜索。

  1. resolve.alias查找有沒匹配到的別名,如果匹配到則替換路徑,繼續(xù)向下
  2. 如果路徑指向文件
    • 如果文件自帶拓展名,則直接引用
    • 否則,使用resolve.extensions作為拓展名解析
  3. 如果路徑指向文件夾
    • 如果文件夾中包含 package.json 文件,則按照順序查找 resolve.mainFields 配置選項(xiàng)中指定的字段(默認(rèn)為index)。并且 package.json 中的第一個(gè)這樣的字段確定文件路徑。
    • 如果 package.json 文件不存在或者 package.json 文件中的 main 字段沒有返回一個(gè)有效路徑,則按照順序查找 resolve.mainFields 配置選項(xiàng)中指定的文件名,看是否能在 import/require 目錄下匹配到一個(gè)存在的文件名。
    • 文件擴(kuò)展名通過 resolve.mainFields 選項(xiàng)采用類似的方法進(jìn)行解析。

8. 外部依賴如何處理?

比如公共模塊不希望打包到vendor中,而是通過CDN的方式引入的時(shí)候。這里參考下externals的配置。

配置:

externals: {
  jquery: 'jQuery'
}

代碼中:

import $ from 'jquery';

說明:

jQuery<script>中的引入全局變量window.jQueryjquery為代碼中的引入名稱。

9. Webpack管理的模塊如何掛載到window上?

參考這個(gè)loader:expose-loader

8. 如何選擇SourceMap生成類型?

SourceMap 在devtool 屬性下設(shè)置:

  • 開發(fā)模式時(shí),使用source-map,他提供全套支持
  • 發(fā)布模式時(shí),使用cheap-module-eval-source-map,不影響構(gòu)建速度

進(jìn)階篇

1. 長(zhǎng)效緩存怎么處理?

原則

一般來(lái)說,我們發(fā)布到線上的資源都會(huì)再次進(jìn)行迭代開發(fā),或者增加新需求。為了保證用戶瀏覽的流暢性,服務(wù)端會(huì)對(duì)靜態(tài)資源進(jìn)行長(zhǎng)效緩存。這里我們希望:

  • 新需求上線對(duì)緩存變更影響越小越好,最小化用戶本地更新,減少服務(wù)器請(qǐng)求帶寬
  • 用戶本地緩存不能和新上線的資源產(chǎn)生沖突
  • 資源和頁(yè)面在進(jìn)行升級(jí)時(shí),不能出現(xiàn)資源引用的問題(404)

傳統(tǒng)的方式使用資源名后面加query的方式處理緩存,比如:

var sourcePath = `http://xx.xx.com/public/js/index.js?time=${new Date().getTime()}`
var sourcePath = `http://xx.xx.com/public/js/index.js?v=${version()}`

類似的方式進(jìn)行防緩存處理,但是這樣的做法不便于管理,且頁(yè)面和資源部署先后會(huì)出現(xiàn)沖突。

因此,最暴力直接的方式是根據(jù)資源內(nèi)容改寫文件名的方式處理長(zhǎng)效緩存的問題。當(dāng)資源發(fā)生變化時(shí)修改資源名稱,不變則不處理。使用這種方式,可以先替換靜態(tài)資源,之后替換頁(yè)面,不會(huì)造成沖突。

以上的介紹建議閱讀這篇文章:用 webpack 實(shí)現(xiàn)持久化緩存。我這里總結(jié)最終執(zhí)行的部分。

Webpack解讀

Webpack使用內(nèi)建模塊系統(tǒng)管理模塊引用,為了保證模塊內(nèi)容穩(wěn)定,這里需要將webpack runtime/chunk清單(經(jīng)常改變)模塊(相對(duì)固定)分離。

因此,長(zhǎng)效緩存的核心就是:生成穩(wěn)定的模塊及模塊ID,只有模塊內(nèi)容變動(dòng)才會(huì)改變模塊及模塊ID,只進(jìn)行必要的模塊及模塊ID變更。

措施

  • 合理劃分公共模塊
    • 提取vendor模塊
    • 使用CommonsChunkPlugin提取公共模塊
    • 提取manifest,將模塊與webpack runtime/Manifest分離
  • 使用chunkhash改寫js模塊的文件名, 使模塊唯一化
  • 使用HashedModuleIdsPlugin穩(wěn)定模塊ID
  • 使用import()加載異步模塊
  • 使用inline-manifest-webpack-plugin將 manifest文件 inline到html中處理

2. 模塊熱替換的思路是?

參考文檔

3. 如何優(yōu)化構(gòu)建性能?

參考文檔

4. 如何在編譯后的代碼中添加作者信息, 在文件后面添加版權(quán)信息?

使用new webpack.BannerPlugin('版權(quán)所有,翻版必究')處理

(完)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • GitChat技術(shù)雜談 前言 本文較長(zhǎng),為了節(jié)省你的閱讀時(shí)間,在文前列寫作思路如下: 什么是 webpack,它要...
    蕭玄辭閱讀 12,914評(píng)論 7 110
  • 無(wú)意中看到zhangwnag大佬分享的webpack教程感覺受益匪淺,特此分享以備自己日后查看,也希望更多的人看到...
    小小字符閱讀 8,379評(píng)論 7 35
  • 最近在學(xué)習(xí) Webpack,網(wǎng)上大多數(shù)入門教程都是基于 Webpack 1.x 版本的,我學(xué)習(xí) Webpack 的...
    My_Oh_My閱讀 8,344評(píng)論 40 247
  • publicPath指定了一個(gè)在瀏覽器中被引用的URL地址。 對(duì)于使用 和 加載器,當(dāng)文件路徑不同于他們的本地磁盤...
    飛呀飛哥閱讀 1,760評(píng)論 0 0
  • 開場(chǎng)馬東說:講話是我們生活中最常用到的交流方式,但是會(huì)不會(huì)講話,工作和生活中的重要關(guān)鍵時(shí)刻,你能不能講好話,對(duì)你的...
    求上進(jìn)的少女婧閱讀 1,530評(píng)論 0 0

友情鏈接更多精彩內(nèi)容