【譯】關於Webpack中一些讓人困惑的地方的解答

Jrain發表於2018-12-19

寫於 2016.08.03

原文連線:Webpack — The Confusing Parts


Webpack是React和Redux專案的主要模組載入器。我認為使用Angular2和其他的框架的人在如今也大量使用Webpack進行開發。

當我第一次檢視Webpack的配置檔案時,我是懵逼的。在使用過一段時間以後,我覺得這是因為Webpack有著獨一無二的語法和標新立異的哲學思想,所以在剛開始使用的時候可能會造成一定的困惑。湊巧的是,這些哲學思想也是讓它如此受歡迎的原因。

正因為Webpack的起步比較容易產生困惑,所以我希望寫一些什麼出來,好讓更多人更容易上手並且體驗它強大的特性。

接下來是第一部分。

Webpack的核心哲學思想

兩個核心哲學思想是:

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

  2. 只載入“你需要的”和“你何時需要”的——典型的模組載入器會把所有的模組最終打包生成一個巨大的“bundle.js”檔案。但在很多實際的專案當中,這個“bundle.js”檔案體積可能會達到10MB~15MB,並且會一直不停進行載入!所以Webpack通過大量的特性去分割你的程式碼,生成多個“bundle”片段,並且非同步地載入專案的不同部分,因此只會為你載入“你需要的”和“你何時需要”的部分。

OK,讓我們一起來看看那些“讓人困惑”的部分吧。

1. 開發環境 VS 生產環境

第一件需要意識到的事情是Webpack擁有著大量的特性。有一些是“開發環境專用”的,一些是“生產環境專用”的,還有一些是“通用”的。

【譯】關於Webpack中一些讓人困惑的地方的解答

一般來說,大部分的專案都使用了許多Webpack的特性,所以它們通常有兩個大的webpack config檔案,用於區分開發環境和生產環境。

2. webpack CLI Vs webpack-dev-server

明白Webpack這個模組載入器擁有兩個介面是非常重要的:

  1. Webpack CLI tool ——預設的介面(和Webpack一併被安裝)
  2. webpack-dev-server tool ——這個工具通過來自CLI和配置檔案(預設:webpack.config.js)的配置項來控制Webpack的打包動作。

你剛開始學習Webpack的時候可能是從CLI入手的,但你接下來很可能只會用它去建立生產環境的專案。

使用方法:

OPTION 1: 
//全域性安裝
npm install webpack --g

//在終端使用
$ webpack //<--通過webpack.bundle.js進行打包

OPTION 2 :
//本地安裝並寫入package.json依賴
npm install webpack --save

//新增到package.json的script內
“scripts”: {
 “build”: “webpack --config webpack.config.prod.js -p”,
 ...
 }

//開始構建
npm run build
複製程式碼

Webpack-dev-server (有利於開發環境使用)

這是一個執行在8080埠的基於Express的node.js伺服器。這個伺服器會在內部呼叫Webpack。它的優勢是提供了額外的能力——類似可以重新整理瀏覽器的“Live Reloading”,以及(或者)區域性更新模組的**“模組熱過載”功能(HMR)**。

使用方法:

OPTION 1:
//全域性安裝
npm install webpack-dev-server --save

//終端使用
$ webpack-dev-server --inline --hot

OPTION 2:
//新增到package.json的script內
“scripts”: {
 “start”: “webpack-dev-server --inline --hot”,
 ...
 }

//輸入下列命令列進行使用
$ npm start

瀏覽器開啟下列地址
http://localhost:8080
複製程式碼

Webpack Vs webpack-dev-server options

值得注意的是,有一些選項比如“inline”和“hot”僅用於webpack-dev-server,而比如“hide-modules”僅用於CLI。

webpack-dev-server CLI options Vs config options

另外一件需要知道的事情是你可以通過兩種方式對webpack-dev-server進行配置:

  1. 通過webpack.config.js的“devServer”物件。
  2. 通過CLI選項。
//使用CLI
webpack-dev-server --hot --inline

//使用webpack.config.js
devServer: {
 inline: true,
 hot:true
 }
複製程式碼

我發現有時候devServer的配置並不管用!所以我更傾向於把這些選項以CLI的方式寫入package.json裡面:

//package.json
{
scripts: 
   {“start”: “webpack-dev-server --hot --inline”}
}
複製程式碼

注意:確保你木有把hot:true-hot寫在一塊兒。

“hot” Vs “inline” webpack-dev-server options

“inline”選項為整個頁面提供了“Live reloading”功能。“hot”選項提供了“模組熱過載”功能,它會嘗試僅僅更新元件被改變的部分(而不是整個頁面)。如果我們把這兩個選項都寫上,那麼當檔案被改動時,webpack-dev-server會先嚐試HMR,如果這不管用,它就會重新載入整個頁面。

//當檔案被改動後,下面的三個選項都會生成新的bundle,但是,
 
//1. 頁面不會重新整理
$ webpack-dev-server

//2. 重新整理整個頁面
$ webpack-dev-server --inline

//3. 僅僅重新整理被改動的部分(HMR),如果HMR失敗則重新整理整個頁面
$ webpack-dev-server  --inline --hot
複製程式碼

“entry”——字串VS陣列VS物件

entry告訴Webpack入口檔案或者起點在哪裡。它可以是一個字串,一個陣列或者一個物件。這可能會使你感到困惑,但不同的型別適用於不同的場合。

如果你使用的是單個起點(大部分專案都是如此),那麼你可以使用任意的型別,它們的結果都會是一樣的。

【譯】關於Webpack中一些讓人困惑的地方的解答

entry——陣列

但是,如果你想要新增互不依賴的多個檔案,你可以使用陣列的格式。

舉個例子,你的HTML可能需要“googleAnalytics.js”。你可以告訴Webpack在bundle.js的後面把它新增進去:

【譯】關於Webpack中一些讓人困惑的地方的解答

entry——物件

現在,當你有一個包含多個HTML檔案的多頁應用,而不是單頁應用的專案的時候(index.html和profile.html),你可以通過物件格式告訴Webpack去一次性生成多個bundle檔案。

下面的配置會生成兩個JS檔案:indexEntry.jsprofileEntry.js,你可以在index.htmlprofile.html分別使用它們

【譯】關於Webpack中一些讓人困惑的地方的解答

使用方法:

//profile.html
<script src=”dist/profileEntry.js”></script>

//index.html
<script src=”dist/indexEntry.js”></script>
複製程式碼

注意:檔名來自“entry”物件的key。

entry——組合格式

你也可以在entry物件中使用陣列。下面的例子會生成三個檔案:一個包含三個檔案的vendor.js,一個index.js和一個profile.js

【譯】關於Webpack中一些讓人困惑的地方的解答

4. output — “path” Vs “publicPath”

output告訴Webpack應該在哪裡以怎樣的方式去放置打包好的檔案。它有兩個屬性:“path”和“publicPath”,這也許會對使用者造成一定的困惑。

“path”會簡單地告訴Webpack生成檔案輸出位置。“publicPath”多被一些Webpack的外掛使用,在HTML檔案以生產環境方式被構建的時候,更新CSS檔案內的URL地址。

【譯】關於Webpack中一些讓人困惑的地方的解答

舉個例子,在你的CSS檔案裡面,你可能會在URL裡面載入./test.png。但是在生產環境中,test.png很可能放在CDN內——比如當你的node.js伺服器執行在Heroku的時候。這意味著,你可能在生產環境內不得不手動更新檔案內的URL指向

相反,你可以使用Webpack的publicPath以及其他適用於這個屬性的外掛在生產環境中自動地更新檔案內部的URL指向。

【譯】關於Webpack中一些讓人困惑的地方的解答

//開發環境:伺服器和圖片都放在本地
.image { 
  background-image: url(‘./test.png’);
 }

//生產環境:伺服器在Heroku而圖片在CDN
.image { 
  background-image: url(‘https://someCDN/test.png’);
 }
複製程式碼

5. 載入器和鏈式載入器

載入器是額外的node模組,用於“載入”或者“引入”不同型別的檔案,並把他們轉化成瀏覽器能夠識別的格式——比如JS檔案、內聯樣式表或其他格式。另外,載入器也允許以“require”或者ES6的“import”的方式把這些檔案引入到JS檔案當中。

例如,你可以使用babel-loader把ES6程式碼轉化成ES5程式碼:

module: {
 loaders: [{
  test: /\.js$/, // 判斷檔案格式,若為“.js”檔案則呼叫loader
  exclude: /node_modules/, // 排除node_modules資料夾
  loader: ‘babel’ // 使用babel(babel-loader的縮寫)
 }]
複製程式碼

鏈式載入器(從右到左的順序進行工作)

不同的載入器可以鏈式地在針對同一個檔案型別進行工作。鏈式載入器的工作順序是從右到左的,並且通過“!”分割

舉個例子,我們有一個叫做myCssFile.css的CSS檔案,現在想把它以<style></style>的方式在我們的HTML檔案中使用,可以通過兩個載入器去完成這個需求:css-loaderstyle-loader

module: {
 loaders: [{
  test: /\.css$/,
  loader: ‘style!css’ // style-loader!css-loader的縮寫
 }]
複製程式碼

這是執行原理:

【譯】關於Webpack中一些讓人困惑的地方的解答

  1. Webpack搜尋被模組所引用的CSS檔案。意思是Webpack會檢查一個JS檔案內是否有require(myCssFile.css),如果有這句話並且找到了這個依賴,它會首先把這個檔案交給css-loader
  2. css-loader載入所有的CSS檔案及其依賴包(例如通過@import引入的其他CSS檔案)到一個JSON檔案中。隨後Webpack會把結果交給style-loader
  3. style-loader拿到這個JSON檔案並把它注入到<style></style>標籤當中,並把這個標籤新增到index.html檔案內。

6.載入器自身是可配置的

載入器其自身可以通過配置不同的引數實現不同的功能。

在下面的例子中,我們配置了url-loader,當圖片小於1024byte的時候使用DataURL,當圖片大雨1024byte的時候使用URL。我們通過下面兩個傳入limit引數的方法來實現這個功能:

【譯】關於Webpack中一些讓人困惑的地方的解答

7. babelrc檔案

babel-loader使用presets去規定如何把ES6程式碼轉化為ES5程式碼,以及如何把React的JSX轉化為JS。我們可以通過query方法進行配置:

module: {
  loaders: [
    {
      test: /\.jsx?$/,
      exclude: /(node_modules|bower_components)/,
      loader: 'babel',
      query: {
        presets: ['react', 'es2015']
      }
    }
  ]
}
複製程式碼

然而,在許多專案中babel的配置項會非常巨大。所以作為替代,你可以把這些配置項寫入一個叫做.babelrc的檔案中。如果這個檔案存在的話bable-loader會自動的載入這個檔案。

所以在許多例子中,你會看到:

//webpack.config.js 
module: {
  loaders: [
    {
      test: /\.jsx?$/,
      exclude: /(node_modules|bower_components)/,
      loader: 'babel'
    }
  ]
}

//.bablerc
{
 “presets”: [“react”, “es2015”]
}
複製程式碼

8. 外掛

外掛是額外的node模組,多用於處理輸出檔案。

例如,uglifyJSPlugin會壓縮並混淆JS程式碼,使其體積減小。

同樣的,extract-text-webpack-plugin會在內部使用css-loaderstyle-loader去把所有的CSS合併為一個檔案,並且最終把結果提取到一個分離在外部的style.css檔案中,最後在index.html中引用這個CSS檔案。

//webpack.config.js
//Take all the .css files, combine their contents and it extract them to a single "styles.css"
var ETP = require("extract-text-webpack-plugin");

module: {
 loaders: [
  {test: /\.css$/, loader:ETP.extract("style-loader","css-loader") }
  ]
},
plugins: [
    new ExtractTextPlugin("styles.css") //Extract to styles.css file
  ]
}
複製程式碼

注意,如果你只打算把CSS以行內樣式的形式在HTML中引用,你可以僅僅使用css-loaderstyle-loader,像下面的例子:

module: {
 loaders: [{
  test: /\.css$/,
  loader: ‘style!css’ // style-loader!css-loader的縮寫
 }]
複製程式碼

9. 載入器 VS 外掛

正如你可能已經弄明白的,載入器在單個檔案的程度上,在打包結束之前或者打包的過程中執行

外掛是在打包或者資料塊的程度上,在輸出打包檔案的過程中進行運作。一些外掛比如commonsChunksPlugins甚至在更早的階段開始運作,可以用來修改打包的方式。

10. 解析副檔名

一些Webpack配置檔案帶有解析擴充套件檔名的屬性,它們像下面的例子一樣,包含了一些空字串。這些空字串被用於幫助載入一些沒有副檔名的檔案,比如require("./myJSFile")或者import myJSFile from './myJSFile'

{
 resolve: {
   extensions: [‘’, ‘.js’, ‘.jsx’]
 }
}
複製程式碼

全文完。

感謝Tobias Koppers(Webpack的作者)幫我審閱這篇文章!


感謝你的閱讀。我是Jrain,歡迎關注我的專欄,將不定期分享自己的學習體驗,開發心得,搬運牆外的乾貨。下次見啦!

相關文章