webpack配置

寒青發表於2018-05-22

webpack配置

檢視所有文件頁面:全棧開發,獲取更多資訊。

原文連結:第2章 配置,原文廣告模態框遮擋,閱讀體驗不好,所以整理成本文,方便查詢。

配置 Webpack 的方式有兩種:

  1. 透過一個 JavaScript 檔案描述配置,例如使用 webpack.config.js 檔案裡的配置;
  2. 執行 Webpack 可執行檔案時透過命令列引數傳入,例如 webpack --devtool source-map

這兩種方式可以相互搭配,例如執行 Webpack 時透過命令 webpack --config webpack-dev.config.js 指定配置檔案,再去 webpack-dev.config.js 檔案裡描述部分配置。

按照配置所影響的功能來劃分,可分為:

  • Entry 配置模組的入口;
  • Output 配置如何輸出最終想要的程式碼;
  • Module 配置處理模組的規則;
  • Resolve 配置尋找模組的規則;
  • Plugins 配置擴充套件外掛;
  • DevServer 配置 DevServer;
  • 其它配置項 其它零散的配置項;
  • 整體配置結構 整體地描述各配置項的結構;
  • 多種配置型別 配置檔案不止可以返回一個 Object,還有其他返回形式;
  • 配置總結 尋找配置 Webpack 的規律,減少思維負擔。

Entry

Webpack 在尋找相對路徑的檔案時會以 context 為根目錄,context 預設為執行啟動 Webpack 時所在的當前工作目錄。

如果想改變 context 的預設配置,可以在配置檔案裡設定:

module.exports = {
  context: path.resolve(__dirname, 'app')
}

注意, context 必須是一個絕對路徑的字串。 除此之外,還可以透過在啟動 Webpack 時帶上引數 webpack --context 來設定 context。

Chunk 名稱

Webpack 會為每個生成的 Chunk 取一個名稱,Chunk 的名稱和 Entry 的配置有關:

  • 如果 entry 是一個 stringarray,就只會生成一個 Chunk,這時 Chunk 的名稱是 main
  • 如果 entry 是一個 object,就可能會出現多個 Chunk,這時 Chunk 的名稱是 object 鍵值對裡鍵的名稱。

配置動態 Entry

假如專案裡有多個頁面需要為每個頁面的入口配置一個 Entry ,但這些頁面的數量可能會不斷增長,則這時 Entry 的配置會受到到其他因素的影響導致不能寫成靜態的值。其解決方法是把 Entry 設定成一個函式去動態返回上面所說的配置,程式碼如下:

// 同步函式
entry: () => {
  return {
    a:'./pages/a',
    b:'./pages/b',
  }
};
// 非同步函式
entry: () => {
  return new Promise((resolve)=>{
    resolve({
       a:'./pages/a',
       b:'./pages/b',
    });
  });
};

Output

output 配置如何輸出最終想要的程式碼。output 是一個 object,裡面包含一系列配置項:

filename

output.filename 配置輸出檔案的名稱,為 string 型別。 如果只有一個輸出檔案,則可以把它寫成靜態不變的:

filename: 'bundle.js'

但是在有多個 Chunk 要輸出時,就需要藉助模版和變數了。前面說到 Webpack 會為每個 Chunk取一個名稱,可以根據 Chunk 的名稱來區分輸出的檔名:

filename: '[name].js'

程式碼裡的 [name] 代表用內建的 name 變數去替換[name],這時你可以把它看作一個字串模組函式, 每個要輸出的 Chunk 都會透過這個函式去拼接出輸出的檔名稱。

變數名 含義
id Chunk 的唯一標識,從0開始
name Chunk 的名稱
hash Chunk 的唯一標識的 Hash 值
chunkhash Chunk 內容的 Hash 值

其中 hashchunkhash 的長度是可指定的,[hash:8] 代表取8位 Hash 值,預設是20位。

注意 ExtractTextWebpackPlugin 外掛是使用 contenthash 來代表雜湊值而不是 chunkhash, 原因在於 ExtractTextWebpackPlugin 提取出來的內容是程式碼內容本身而不是由一組模組組成的 Chunk。

chunkFilename

output.chunkFilename 配置無入口的 Chunk 在輸出時的檔名稱。 chunkFilename 和上面的 filename 非常類似,但 chunkFilename 只用於指定在執行過程中生成的 Chunk 在輸出時的檔名稱。 常見的會在執行時生成 Chunk 場景有在使用 CommonChunkPlugin、使用 import('path/to/module') 動態載入等時。 chunkFilename 支援和 filename 一致的內建變數。

path

output.path 配置輸出檔案存放在本地的目錄,必須是 string 型別的絕對路徑。通常透過 Node.js 的 path 模組去獲取絕對路徑:

path: path.resolve(__dirname, 'dist_[hash]')

publicPath

在複雜的專案裡可能會有一些構建出的資源需要非同步載入,載入這些非同步資源需要對應的 URL 地址。

output.publicPath 配置釋出到線上資源的 URL 字首,為string 型別。 預設值是空字串 '',即使用相對路徑。

把構建出的資原始檔上傳到 CDN 服務上,以利於加快頁面的開啟速度。配置程式碼如下:

filename:'[name]_[chunkhash:8].js'
publicPath: 'https://cdn.example.com/assets/'

這時釋出到線上的 HTML 在引入 JavaScript 檔案時就需要:

<script src='https://cdn.example.com/assets/a_12345678.js'></script>

使用該配置項時要小心,稍有不慎將導致資源載入404錯誤。

output.pathoutput.publicPath 都支援字串模版,內建變數只有一個:hash 代表一次編譯操作的 Hash 值。

crossOriginLoading

Webpack 輸出的部分程式碼塊可能需要非同步載入,而非同步載入是透過 JSONP 方式實現的。 JSONP 的原理是動態地向 HTML 中插入一個 <script src="url"></script> 標籤去載入非同步資源。

output.crossOriginLoading 則是用於配置這個非同步插入的標籤的 crossorigin 值。

script 標籤的 crossorigin 屬性可以取以下值:

  • false(預設) 在載入此指令碼資源時不會帶上使用者的 Cookies;
  • use-credentials 在載入此指令碼資源時會帶上使用者的 Cookies。

通常用設定 crossorigin 來獲取非同步載入的指令碼執行時的詳細錯誤資訊。

libraryTarget 和 library

當用 Webpack 去構建一個可以被其他模組匯入使用的庫時需要用到它們。

  • output.libraryTarget 配置以何種方式匯出庫。
  • output.library 配置匯出庫的名稱。

假如配置了 output.library='LibraryName',則輸出和使用的程式碼如下:

// Webpack 輸出的程式碼
var LibraryName = lib_code;

// 使用庫的方法
LibraryName.doSomething();

假如 output.library 為空,則將直接輸出:lib_code

其中 lib_code 代指匯出庫的程式碼內容,是有返回值的一個自執行函式。

它們通常搭配在一起使用。

output.libraryTarget 是字串的列舉型別,支援以下配置。

var (預設)

編寫的庫將透過 var 被賦值給透過 library 指定名稱的變數。

commonjs

編寫的庫將透過 CommonJS2 規範匯出,輸出和使用的程式碼如下:

// Webpack 輸出的程式碼
module.exports = lib_code;

// 使用庫的方法
require('library-name-in-npm').doSomething();
CommonJS2 和 CommonJS 規範很相似,差別在於 CommonJS 只能用 exports 匯出,而 CommonJS2 在 CommonJS 的基礎上增加了 module.exports 的匯出方式。

output.libraryTarget 為 commonjs2 時,配置 output.library 將沒有意義。

this

編寫的庫將透過 this 被賦值給透過 library 指定的名稱,輸出和使用的程式碼如下:

// Webpack 輸出的程式碼
this['LibraryName'] = lib_code;

// 使用庫的方法
this.LibraryName.doSomething();

window

編寫的庫將透過 window 被賦值給透過 library 指定的名稱,即把庫掛載到 window 上,輸出和使用的程式碼如下:

// Webpack 輸出的程式碼
window['LibraryName'] = lib_code;

// 使用庫的方法
window.LibraryName.doSomething();

global

編寫的庫將透過 global 被賦值給透過 library 指定的名稱,即把庫掛載到 global 上,輸出和使用的程式碼如下:


// Webpack 輸出的程式碼
global['LibraryName'] = lib_code;

// 使用庫的方法
global.LibraryName.doSomething();

libraryExport

output.libraryExport 配置要匯出的模組中哪些子模組需要被匯出。 它只有在 output.libraryTarget 被設定成 commonjs 或者 commonjs2 時使用才有意義。

假如要匯出的模組原始碼是:

export const a=1;
export default b=2;

現在想讓構建輸出的程式碼只匯出其中的 a,可以把 output.libraryExport 設定成 a,那麼構建輸出的程式碼和使用方法將變成如下:

// Webpack 輸出的程式碼
module.exports = lib_code['a'];

// 使用庫的方法
require('library-name-in-npm')===1;

Module

配置 Loader

rules 配置模組的讀取和解析規則,通常用來配置 Loader。其型別是一個陣列,陣列裡每一項都描述瞭如何去處理部分檔案。 配置一項 rules 時大致透過以下方式:

  1. 條件匹配:透過 testincludeexclude 三個配置項來命中 Loader 要應用規則的檔案。
  2. 應用規則:對選中後的檔案透過 use 配置項來應用 Loader,可以只應用一個 Loader 或者按照從後往前的順序應用一組 Loader,同時還可以分別給 Loader 傳入引數。
  3. 重置順序:一組 Loader 的執行順序預設是從右到左執行,透過 enforce 選項可以讓其中一個 Loader 的執行順序放到最前或者最後。
module: {
  rules: [
    {
      // 命中 JavaScript 檔案
      test: /\.js$/,
      // 用 babel-loader 轉換 JavaScript 檔案
      // ?cacheDirectory 表示傳給 babel-loader 的引數,用於快取 babel 編譯結果加快重新編譯速度
      use: ['babel-loader?cacheDirectory'],
      // 只命中src目錄裡的js檔案,加快 Webpack 搜尋速度
      include: path.resolve(__dirname, 'src')
    },
    {
      // 命中 SCSS 檔案
      test: /\.scss$/,
      // 使用一組 Loader 去處理 SCSS 檔案。
      // 處理順序為從後到前,即先交給 sass-loader 處理,再把結果交給 css-loader 最後再給 style-loader。
      use: ['style-loader', 'css-loader', 'sass-loader'],
      // 排除 node_modules 目錄下的檔案
      exclude: path.resolve(__dirname, 'node_modules'),
    },
    {
      // 對非文字檔案採用 file-loader 載入
      test: /\.(gif|png|jpe?g|eot|woff|ttf|svg|pdf)$/,
      use: ['file-loader'],
    },
  ]
}

在 Loader 需要傳入很多引數時,你還可以透過一個 Object 來描述,例如在上面的 babel-loader 配置中有如下程式碼:

use: [
  {
    loader:'babel-loader',
    options:{
      cacheDirectory:true,
    },
    // enforce:'post' 的含義是把該 Loader 的執行順序放到最後
    // enforce 的值還可以是 pre,代表把 Loader 的執行順序放到最前面
    enforce:'post'
  },
  // 省略其它 Loader
]

上面的例子中 test include exclude 這三個命中檔案的配置項只傳入了一個字串或正則,其實它們還都支援陣列型別,使用如下:

{
  test:[
    /\.jsx?$/,
    /\.tsx?$/
  ],
  include:[
    path.resolve(__dirname, 'src'),
    path.resolve(__dirname, 'tests'),
  ],
  exclude:[
    path.resolve(__dirname, 'node_modules'),
    path.resolve(__dirname, 'bower_modules'),
  ]
}

陣列裡的每項之間是的關係,即檔案路徑符合陣列中的任何一個條件就會被命中。

noParse

noParse 配置項可以讓 Webpack 忽略對部分沒采用模組化的檔案的遞迴解析和處理,這樣做的好處是能提高構建效能。 原因是一些庫例如 jQuery 、ChartJS 它們龐大又沒有采用模組化標準,讓 Webpack 去解析這些檔案耗時又沒有意義。

noParse 是可選配置項,型別需要是 RegExp[RegExp]function 其中一個。

例如想要忽略掉 jQuery 、ChartJS,可以使用如下程式碼:

// 使用正規表示式
noParse: /jquery|chartjs/

// 使用函式,從 Webpack 3.0.0 開始支援
noParse: (content)=> {
  // content 代表一個模組的檔案路徑
  // 返回 true or false
  return /jquery|chartjs/.test(content);
}
注意被忽略掉的檔案裡不應該包含 importrequiredefine 等模組化語句,不然會導致構建出的程式碼中包含無法在瀏覽器環境下執行的模組化語句。

parser

因為 Webpack 是以模組化的 JavaScript 檔案為入口,所以內建了對模組化 JavaScript 的解析功能,支援 AMDCommonJSSystemJSES6

parser 屬性可以更細粒度的配置哪些模組語法要解析哪些不解析,和 noParse 配置項的區別在於 parser 可以精確到語法層面, 而 noParse 只能控制哪些檔案不被解析。 parser 使用如下:

module: {
  rules: [
    {
      test: /\.js$/,
      use: ['babel-loader'],
      parser: {
      amd: false, // 停用 AMD
      commonjs: false, // 停用 CommonJS
      system: false, // 停用 SystemJS
      harmony: false, // 停用 ES6 import/export
      requireInclude: false, // 停用 require.include
      requireEnsure: false, // 停用 require.ensure
      requireContext: false, // 停用 require.context
      browserify: false, // 停用 browserify
      requireJs: false, // 停用 requirejs
      }
    },
  ]
}

Resolve

Webpack 在啟動後會從配置的入口模組出發找出所有依賴的模組,Resolve 配置 Webpack 如何尋找模組所對應的檔案。 Webpack 內建 JavaScript 模組化語法解析功能,預設會採用模組化標準里約定好的規則去尋找,但你也可以根據自己的需要修改預設的規則。

alias

resolve.alias 配置項透過別名來把原匯入路徑對映成一個新的匯入路徑。例如使用以下配置:

// Webpack alias 配置
resolve:{
  alias:{
    components: './src/components/'
  }
}

當你透過 import Button from 'components/button' 匯入時,實際上被 alias 等價替換成了 import Button from './src/components/button'

以上 alias 配置的含義是把匯入語句裡的 components 關鍵字替換成 ./src/components/

這樣做可能會命中太多的匯入語句,alias 還支援 $ 符號來縮小範圍到只命中以關鍵字結尾的匯入語句:

resolve:{
  alias:{
    'react$': '/path/to/react.min.js'
  }
}

react$ 只會命中以 react 結尾的匯入語句,即只會把 import 'react' 關鍵字替換成 import '/path/to/react.min.js'

mainFields

有一些第三方模組會針對不同環境提供幾分程式碼。 例如分別提供採用 ES5 和 ES6 的2份程式碼,這2份程式碼的位置寫在 package.json 檔案裡,如下:

{
  "jsnext:main": "es/index.js",// 採用 ES6 語法的程式碼入口檔案
  "main": "lib/index.js" // 採用 ES5 語法的程式碼入口檔案
}

Webpack 會根據 mainFields 的配置去決定優先採用哪份程式碼,mainFields 預設如下:

mainFields: ['browser', 'main']

Webpack 會按照陣列裡的順序去 package.json 檔案裡尋找,只會使用找到的第一個。

假如你想優先採用 ES6 的那份程式碼,可以這樣配置:

mainFields: ['jsnext:main', 'browser', 'main']

extensions

在匯入語句沒帶檔案字尾時,Webpack 會自動帶上字尾後去嘗試訪問檔案是否存在。 resolve.extensions 用於配置在嘗試過程中用到的字尾列表,預設是:

extensions: ['.js', '.json']

modules

resolve.modules 配置 Webpack 去哪些目錄下尋找第三方模組,預設是隻會去 node_modules 目錄下尋找。

有時你的專案裡會有一些模組會大量被其它模組依賴和匯入,由於其它模組的位置分佈不定,針對不同的檔案都要去計算被匯入模組檔案的相對路徑, 這個路徑有時候會很長,就像這樣 import '../../../components/button' 這時你可以利用 modules 配置項最佳化,假如那些被大量匯入的模組都在 ./src/components 目錄下,把 modules 配置成:

modules:['./src/components','node_modules']

後,你可以簡單透過 import 'button' 匯入。

descriptionFiles

resolve.descriptionFiles 配置描述第三方模組的檔名稱,也就是 package.json 檔案。預設如下:

descriptionFiles: ['package.json']

enforceExtension

resolve.enforceExtension 如果配置為 true 所有匯入語句都必須要帶檔案字尾, 例如開啟前 import './foo' 能正常工作,開啟後就必須寫成 import './foo.js'

enforceModuleExtension

enforceModuleExtensionenforceExtension 作用類似,但 enforceModuleExtension 只對 node_modules 下的模組生效。

enforceModuleExtension 通常搭配 enforceExtension 使用,在 enforceExtension:true 時,因為安裝的第三方模組中大多數匯入語句沒帶檔案字尾, 所以這時透過配置 enforceModuleExtension:false 來相容第三方模組。

Plugins

Plugin 用於擴充套件 Webpack 功能,各種各樣的 Plugin 幾乎讓 Webpack 可以做任何構建相關的事情。

配置 Plugin

Plugin 的配置很簡單,plugins 配置項接受一個陣列,陣列裡每一項都是一個要使用的 Plugin 的例項,Plugin 需要的引數透過建構函式傳入。

const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');

module.exports = {
  plugins: [
    // 所有頁面都會用到的公共程式碼提取到 common 程式碼塊中
    new CommonsChunkPlugin({
      name: 'common',
      chunks: ['a', 'b']
    }),
  ]
};

使用 Plugin 的難點在於掌握 Plugin 本身提供的配置項,而不是如何在 Webpack 中接入 Plugin。

DevServer

要配置 DevServer ,除了在配置檔案裡透過 devServer 傳入引數外,還可以透過命令列引數傳入。 注意只有在透過 DevServer 去啟動 Webpack 時配置檔案裡 devServer 才會生效,因為這些引數所對應的功能都是 DevServer 提供的,Webpack 本身並不認識 devServer 配置項。

hot

devServer.hot 配置是否啟用模組熱替換功能。

DevServer 預設的行為是在發現原始碼被更新後會透過自動重新整理整個頁面來做到實時預覽,開啟模組熱替換功能後將在不重新整理整個頁面的情況下透過用新模組替換老模組來做到實時預覽。

inline

DevServer 的實時預覽功能依賴一個注入到頁面裡的代理客戶端去接受來自 DevServer 的命令和負責重新整理網頁的工作。

devServer.inline 用於配置是否自動注入這個代理客戶端到將執行在頁面裡的 Chunk 裡去,預設是會自動注入。 DevServer 會根據你是否開啟 inline 來調整它的自動重新整理策略:

  • 如果開啟 inline,DevServer 會在構建完變化後的程式碼時透過代理客戶端控制網頁重新整理。
  • 如果關閉 inline,DevServer 將無法直接控制要開發的網頁。這時它會透過 iframe 的方式去執行要開發的網頁,當構建完變化後的程式碼時透過重新整理 iframe 來實現實時預覽。

如果你想使用 DevServer 去自動重新整理網頁實現實時預覽,最方便的方法是直接開啟 inline

historyApiFallback

devServer.historyApiFallback 用於方便的開發使用了 HTML5 History API 的單頁應用。

這類單頁應用要求伺服器在針對任何命中的路由時都返回一個對應的 HTML 檔案,例如在訪問 http://localhost/userhttp://localhost/home 時都返回 index.html 檔案, 瀏覽器端的 JavaScript 程式碼會從 URL 裡解析出當前頁面的狀態,顯示出對應的介面。

配置 historyApiFallback 最簡單的做法是:

historyApiFallback: true

這會導致任何請求都會返回 index.html 檔案,這隻能用於只有一個 HTML 檔案的應用。

如果你的應用由多個單頁應用組成,這就需要 DevServer 根據不同的請求來返回不同的 HTML 檔案,配置如下:

historyApiFallback: {
  // 使用正則匹配命中路由
  rewrites: [
    // /user 開頭的都返回 user.html
    { from: /^\/user/, to: '/user.html' },
    { from: /^\/game/, to: '/game.html' },
    // 其它的都返回 index.html
    { from: /./, to: '/index.html' },
  ]
}

contentBase

devServer.contentBase 配置 DevServer HTTP 伺服器的檔案根目錄。 預設情況下為當前執行目錄,通常是專案根目錄,所有一般情況下你不必設定它,除非你有額外的檔案需要被 DevServer 服務。 例如你想把專案根目錄下的 public 目錄設定成 DevServer 伺服器的檔案根目錄,你可以這樣配置:

devServer:{
  contentBase: path.join(__dirname, 'public')
}

這裡需要指出可能會讓你疑惑的地方,DevServer 伺服器透過 HTTP 服務暴露出的檔案分為兩類:

  • 暴露本地檔案。
  • 暴露 Webpack 構建出的結果,由於構建出的結果交給了 DevServer,所以你在使用了 DevServer 時在本地找不到構建出的檔案。

contentBase 只能用來配置暴露本地檔案的規則,你可以透過 contentBase:false 來關閉暴露本地檔案。

headers

devServer.headers 配置項可以在 HTTP 響應中注入一些 HTTP 響應頭,使用如下:

devServer:{
  headers: {
    'X-foo':'bar'
  }
}

host

devServer.host 配置項用於配置 DevServer 服務監聽的地址。

例如你想要區域網中的其它裝置訪問你本地的服務,可以在啟動 DevServer 時帶上 --host 0.0.0.0host 的預設值是 127.0.0.1 即只有本地可以訪問 DevServer 的 HTTP 服務。

port

devServer.port 配置項用於配置 DevServer 服務監聽的埠,預設使用 8080 埠。 如果 8080 埠已經被其它程式佔有就使用 8081,如果 8081 還是被佔用就使用 8082,以此類推。

allowedHosts

devServer.allowedHosts 配置一個白名單列表,只有 HTTP 請求的 HOST 在列表裡才正常返回,使用如下:

allowedHosts: [
  // 匹配單個域名
  'host.com',
  'sub.host.com',
  // host2.com 和所有的子域名 *.host2.com 都將匹配
  '.host2.com'
]

disableHostCheck

devServer.disableHostCheck 配置項用於配置是否關閉用於 DNS 重繫結的 HTTP 請求的 HOST 檢查。

DevServer 預設只接受來自本地的請求,關閉後可以接受來自任何 HOST 的請求。 它通常用於搭配 --host 0.0.0.0 使用,因為你想要其它裝置訪問你本地的服務,但訪問時是直接透過 IP 地址訪問而不是 HOST 訪問,所以需要關閉 HOST 檢查。

https

DevServer 預設使用 HTTP 協議服務,它也能透過 HTTPS 協議服務。 有些情況下你必須使用 HTTPS,例如 HTTP2 和 Service Worker 就必須執行在 HTTPS 之上。 要切換成 HTTPS 服務,最簡單的方式是:

devServer:{
  https: true
}

DevServer 會自動的為你生成一份 HTTPS 證書。

如果你想用自己的證書可以這樣配置:

devServer:{
  https: {
    key: fs.readFileSync('path/to/server.key'),
    cert: fs.readFileSync('path/to/server.crt'),
    ca: fs.readFileSync('path/to/ca.pem')
  }
}

clientLogLevel

devServer.clientLogLevel 配置在客戶端的日誌等級,這會影響到你在瀏覽器開發者工具控制檯裡看到的日誌內容。

clientLogLevel列舉型別,可取如下之一的值 none | error | warning | info。 預設為 info 級別,即輸出所有型別的日誌,設定成 none 可以不輸出任何日誌。

compress

devServer.compress 配置是否啟用 gzip 壓縮。boolean 為型別,預設為 false

open

devServer.open 用於在 DevServer 啟動且第一次構建完時自動用你係統上預設的瀏覽器去開啟要開發的網頁。 同時還提供 devServer.openPage 配置項用於開啟指定 URL 的網頁。

其它配置項

Target

target 配置項可以讓 Webpack 構建出針對不同執行環境的程式碼。 target 可以是以下之一:

target值 描述
web 針對瀏覽器 (預設),所有程式碼都集中在一個檔案裡
node 針對 Node.js,使用 require 語句載入 Chunk 程式碼
async-node 針對 Node.js,非同步載入 Chunk 程式碼
webworker 針對 WebWorker
electron-main 針對 Electron 主執行緒
electron-renderer 針對 Electron 渲染執行緒

例如當你設定 target:'node' 時,原始碼中匯入 Node.js 原生模組的語句 require('fs') 將會被保留,fs 模組的內容不會打包進 Chunk 裡。

Devtool

devtool 配置 Webpack 如何生成 Source Map,預設值是 false 即不生成 Source Map,想為構建出的程式碼生成 Source Map 以方便除錯,可以這樣配置:

module.export = {
  devtool: 'source-map'
}

Watch 和 WatchOptions

前面介紹過 Webpack 的監聽模式,它支援監聽檔案更新,在檔案發生變化時重新編譯。在使用 Webpack 時監聽模式預設是關閉的,想開啟需要如下配置:

module.export = {
  watch: true
}

在使用 DevServer 時,監聽模式預設是開啟的。

除此之外,Webpack 還提供了 watchOptions 配置項去更靈活的控制監聽模式,使用如下:

module.export = {
  // 只有在開啟監聽模式時,watchOptions 才有意義
  // 預設為 false,也就是不開啟
  watch: true,
  // 監聽模式執行時的引數
  // 在開啟監聽模式時,才有意義
  watchOptions: {
    // 不監聽的檔案或資料夾,支援正則匹配
    // 預設為空
    ignored: /node_modules/,
    // 監聽到變化發生後會等300ms再去執行動作,防止檔案更新太快導致重新編譯頻率太高
    // 預設為 300ms  
    aggregateTimeout: 300,
    // 判斷檔案是否發生變化是透過不停的去詢問系統指定檔案有沒有變化實現的
    // 預設每1000豪秒去問1次
    poll: 1000
  }
}

Externals

Externals 用來告訴 Webpack 要構建的程式碼中使用了哪些不用被打包的模組,也就是說這些模版是外部環境提供的,Webpack 在打包時可以忽略它們。

有些 JavaScript 執行環境可能內建了一些全域性變數或者模組,例如在你的 HTML HEAD 標籤裡透過以下程式碼:

<script src="path/to/jquery.js"></script>

引入 jQuery 後,全域性變數 jQuery 就會被注入到網頁的 JavaScript 執行環境裡。

如果想在使用模組化的原始碼裡匯入和使用 jQuery,可能需要這樣:

import $ from 'jquery';
$('.my-element');

構建後你會發現輸出的 Chunk 裡包含的 jQuery 庫的內容,這導致 jQuery 庫出現了2次,浪費載入流量,最好是 Chunk 裡不會包含 jQuery 庫的內容。

Externals 配置項就是為了解決這個問題。

透過 externals 可以告訴 Webpack JavaScript 執行環境已經內建了那些全域性變數,針對這些全域性變數不用打包進程式碼中而是直接使用全域性變數。 要解決以上問題,可以這樣配置 externals

module.export = {
  externals: {
    // 把匯入語句裡的 jquery 替換成執行環境裡的全域性變數 jQuery
    jquery: 'jQuery'
  }
}

ResolveLoader

ResolveLoader 用來告訴 Webpack 如何去尋找 Loader,因為在使用 Loader 時是透過其包名稱去引用的, Webpack 需要根據配置的 Loader 包名去找到 Loader 的實際程式碼,以呼叫 Loader 去處理原始檔。

ResolveLoader 的預設配置如下:

module.exports = {
  resolveLoader:{
    // 去哪個目錄下尋找 Loader
    modules: ['node_modules'],
    // 入口檔案的字尾
    extensions: ['.js', '.json'],
    // 指明入口檔案位置的欄位
    mainFields: ['loader', 'main']
  }
}

該配置項常用於載入本地的 Loader。

整體配置結構

之前的章節分別講述了每個配置項的具體含義,但沒有描述它們所處的位置和資料結構,下面透過一份程式碼來描述清楚:

const path = require('path');

module.exports = {
    // entry 表示 入口,Webpack 執行構建的第一步將從 Entry 開始,可抽象成輸入。
    // 型別可以是 string | object | array
    entry: './app/entry', // 只有1個入口,入口只有1個檔案
    entry: ['./app/entry1', './app/entry2'], // 只有1個入口,入口有2個檔案
    entry: { // 有2個入口
        a: './app/entry-a',
        b: ['./app/entry-b1', './app/entry-b2']
    },

    // 如何輸出結果:在 Webpack 經過一系列處理後,如何輸出最終想要的程式碼。
    output: {
        // 輸出檔案存放的目錄,必須是 string 型別的絕對路徑。
        path: path.resolve(__dirname, 'dist'),

        // 輸出檔案的名稱
        filename: 'bundle.js', // 完整的名稱
        filename: '[name].js', // 當配置了多個 entry 時,透過名稱模版為不同的 entry 生成不同的檔名稱
        filename: '[chunkhash].js', // 根據檔案內容 hash 值生成檔名稱,用於瀏覽器長時間快取檔案

        // 釋出到線上的所有資源的 URL 字首,string 型別
        publicPath: '/assets/', // 放到指定目錄下
        publicPath: '', // 放到根目錄下
        publicPath: 'https://cdn.example.com/', // 放到 CDN 上去

        // 匯出庫的名稱,string 型別
        // 不填它時,預設輸出格式是匿名的立即執行函式
        library: 'MyLibrary',

        // 匯出庫的型別,列舉型別,預設是 var
        // 可以是 umd | umd2 | commonjs2 | commonjs | amd | this | var | assign | window | global | jsonp ,
        libraryTarget: 'umd',

        // 是否包含有用的檔案路徑資訊到生成的程式碼裡去,boolean 型別
        pathinfo: true,

        // 附加 Chunk 的檔名稱
        chunkFilename: '[id].js',
        chunkFilename: '[chunkhash].js',

        // JSONP 非同步載入資源時的回撥函式名稱,需要和服務端搭配使用
        jsonpFunction: 'myWebpackJsonp',

        // 生成的 Source Map 檔名稱
        sourceMapFilename: '[file].map',

        // 瀏覽器開發者工具裡顯示的原始碼模組名稱
        devtoolModuleFilenameTemplate: 'webpack:///[resource-path]',

        // 非同步載入跨域的資源時使用的方式
        crossOriginLoading: 'use-credentials',
        crossOriginLoading: 'anonymous',
        crossOriginLoading: false,
    },

    // 配置模組相關
    module: {
        rules: [ // 配置 Loader
            {
                test: /\.jsx?$/, // 正則匹配命中要使用 Loader 的檔案
                include: [ // 只會命中這裡面的檔案
                    path.resolve(__dirname, 'app')
                ],
                exclude: [ // 忽略這裡面的檔案
                    path.resolve(__dirname, 'app/demo-files')
                ],
                use: [ // 使用那些 Loader,有先後次序,從後往前執行
                    'style-loader', // 直接使用 Loader 的名稱
                    {
                        loader: 'css-loader',
                        options: { // 給 html-loader 傳一些引數
                        }
                    }
                ]
            },
        ],
        noParse: [ // 不用解析和處理的模組
            /special-library\.js$/  // 用正則匹配
        ],
    },

    // 配置外掛
    plugins: [],

    // 配置尋找模組的規則
    resolve: {
        modules: [ // 尋找模組的根目錄,array 型別,預設以 node_modules 為根目錄
            'node_modules',
            path.resolve(__dirname, 'app')
        ],
        extensions: ['.js', '.json', '.jsx', '.css'], // 模組的字尾名
        alias: { // 模組別名配置,用於對映模組
            // 把 'module' 對映 'new-module',同樣的 'module/path/file' 也會被對映成 'new-module/path/file'
            'module': 'new-module',
            // 使用結尾符號 $ 後,把 'only-module' 對映成 'new-module',
            // 但是不像上面的,'module/path/file' 不會被對映成 'new-module/path/file'
            'only-module$': 'new-module',
        },
        alias: [ // alias 還支援使用陣列來更詳細的配置
            {
                name: 'module', // 老的模組
                alias: 'new-module', // 新的模組
                // 是否是隻對映模組,如果是 true 只有 'module' 會被對映,如果是 false 'module/inner/path' 也會被對映
                onlyModule: true,
            }
        ],
        symlinks: true, // 是否跟隨檔案軟連結去搜尋模組的路徑
        descriptionFiles: ['package.json'], // 模組的描述檔案
        mainFields: ['main'], // 模組的描述檔案裡的描述入口的檔案的欄位名稱
        enforceExtension: false, // 是否強制匯入語句必須要寫明檔案字尾
    },

    // 輸出檔案效能檢查配置
    performance: {
        hints: 'warning', // 有效能問題時輸出警告
        hints: 'error', // 有效能問題時輸出錯誤
        hints: false, // 關閉效能檢查
        maxAssetSize: 200000, // 最大檔案大小 (單位 bytes)
        maxEntrypointSize: 400000, // 最大入口檔案大小 (單位 bytes)
        assetFilter: function (assetFilename) { // 過濾要檢查的檔案
            return assetFilename.endsWith('.css') || assetFilename.endsWith('.js');
        }
    },

    devtool: 'source-map', // 配置 source-map 型別

    context: __dirname, // Webpack 使用的根目錄,string 型別必須是絕對路徑

    // 配置輸出程式碼的執行環境
    target: 'web', // 瀏覽器,預設
    target: 'webworker', // WebWorker
    target: 'node', // Node.js,使用 `require` 語句載入 Chunk 程式碼
    target: 'async-node', // Node.js,非同步載入 Chunk 程式碼
    target: 'node-webkit', // nw.js
    target: 'electron-main', // electron, 主執行緒
    target: 'electron-renderer', // electron, 渲染執行緒

    externals: { // 使用來自 JavaScript 執行環境提供的全域性變數
        jquery: 'jQuery'
    },

    stats: { // 控制檯輸出日誌控制
        assets: true,
        colors: true,
        errors: true,
        errorDetails: true,
        hash: true,
    },

    devServer: { // DevServer 相關的配置
        proxy: { // 代理到後端服務介面
            '/api': 'http://localhost:3000'
        },
        contentBase: path.join(__dirname, 'public'), // 配置 DevServer HTTP 伺服器的檔案根目錄
        compress: true, // 是否開啟 gzip 壓縮
        historyApiFallback: true, // 是否開發 HTML5 History API 網頁
        hot: true, // 是否開啟模組熱替換功能
        https: false, // 是否開啟 HTTPS 模式
    },

    profile: true, // 是否捕捉 Webpack 構建的效能資訊,用於分析什麼原因導致構建效能不佳

    cache: false, // 是否啟用快取提升構建速度

    watch: true, // 是否開始
    watchOptions: { // 監聽模式選項
        // 不監聽的檔案或資料夾,支援正則匹配。預設為空
        ignored: /node_modules/,
        // 監聽到變化發生後會等300ms再去執行動作,防止檔案更新太快導致重新編譯頻率太高
        // 預設為300ms
        aggregateTimeout: 300,
        // 判斷檔案是否發生變化是不停的去詢問系統指定檔案有沒有變化,預設每秒問 1000 次
        poll: 1000
    },
};

多種配置型別

除了透過匯出一個 Object 來描述 Webpack 所需的配置外,還有其它更靈活的方式,以簡化不同場景的配置。

匯出一個 Function

在大多數時候你需要從同一份原始碼中構建出多份程式碼,例如一份用於開發時,一份用於釋出到線上。

如果採用匯出一個 Object 來描述 Webpack 所需的配置的方法,需要寫兩個檔案。 一個用於開發環境,一個用於線上環境。再在啟動時透過 webpack --config webpack.config.js 指定使用哪個配置檔案。

採用匯出一個 Function 的方式,能透過 JavaScript 靈活的控制配置,做到只寫一個配置檔案就能完成以上要求。

匯出一個 Function 的使用方式如下:

const path = require('path');
const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');

module.exports = function (env = {}, argv) {
    const plugins = [];

    const isProduction = env['production'];

    // 在生成環境才壓縮
    if (isProduction) {
        plugins.push(
            // 壓縮輸出的 JS 程式碼
            new UglifyJsPlugin()
        )
    }

    return {
        plugins: plugins,
        // 在生成環境不輸出 Source Map
        devtool: isProduction ? undefined : 'source-map',
    };
};

在執行 Webpack 時,會給這個函式傳入2個引數,分別是:

  1. env:當前執行時的 Webpack 專屬環境變數,env 是一個 Object。讀取時直接訪問 Object 的屬性,設定它需要在啟動 Webpack 時帶上引數。例如啟動命令是 webpack --env.production --env.bao=foo 時,則 env 的值是 {"production":"true","bao":"foo"}
  2. argv:代表在啟動 Webpack 時所有透過命令列傳入的引數,例如 --config、--env、--devtool,可以透過 webpack -h 列出所有 Webpack 支援的命令列引數。

就以上配置檔案而言,在開發時執行命令 webpack 構建出方便除錯的程式碼,在需要構建出釋出到線上的程式碼時執行 webpack --env.production 構建出壓縮的程式碼。

匯出一個返回 Promise 的函式

在有些情況下你不能以同步的方式返回一個描述配置的 Object,Webpack 還支援匯出一個返回 Promise 的函式,使用如下:

module.exports = function(env = {}, argv) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        // ...
      })
    }, 5000)
  })
}

匯出多份配置

除了只匯出一份配置外,Webpack 還支援匯出一個陣列,陣列中可以包含每份配置,並且每份配置都會執行一遍構建。

使用如下:

module.exports = [
  // 採用 Object 描述的一份配置
  {
    // ...
  },
  // 採用函式描述的一份配置
  function() {
    return {
      // ...
    }
  },
  // 採用非同步函式描述的一份配置
  function() {
    return Promise();
  }
]

以上配置會導致 Webpack 針對這三份配置執行三次不同的構建。

這特別適合於用 Webpack 構建一個要上傳到 Npm 倉庫的庫,因為庫中可能需要包含多種模組化格式的程式碼,例如 CommonJS、UMD。

配置總結

從前面的配置看來選項很多,Webpack 內建了很多功能。

你不必都記住它們,只需要大概明白 Webpack 原理和核心概念去判斷選項大致屬於哪個大模組下,再去查詳細的使用文件。

通常你可用如下經驗去判斷如何配置 Webpack:

  • 想讓原始檔加入到構建流程中去被 Webpack 控制,配置 entry
  • 想自定義輸出檔案的位置和名稱,配置 output
  • 想自定義尋找依賴模組時的策略,配置 resolve
  • 想自定義解析和轉換檔案的策略,配置 module,通常是配置 module.rules 裡的 Loader。
  • 其它的大部分需求可能要透過 Plugin 去實現,配置 plugin

相關文章