Webpack 踩坑記 - 配置 externals 和 output

webpack 很強大,提供的能力選項配置也很多好讓你滿足各種不同的打包場景。

但學習成本也跟着上去了,其中一件讓人頭疼的是輸出時的配置,特別容易讓人迷惑;

平時用 webpack 可能不會有太大問題,可一旦你開發的包被別人引用的時候,就會存在問題;我最近遇到這麼一個場景耗費我很多時間去重新學習 webpack 的打包輸出。

本文總結了自己開發庫時依賴共同第三方包時的 externals 和 output 配置項。

1、場景

當你開發的包依賴較大的第三方包(reactreact-dom) 的時候,你一般是把這些大的第三方包 externals 出去:

1{
2  externals: {
3    'react''React',
4    'react-dom''ReactDOM'
5  }
6}

假如你開發了 A、B 兩個插件

  • A 依賴 B 、reactreact-dom

  • B 只依賴 reactreact-dom

deps

如果你想發佈 A 的話,有兩種策略,要麼直接依賴(將 B 寫到 dependencies 中),要麼像 reactreact-dom 一樣 externals 掉 B 包(有可能 B 的包也很大)。

請問此時該如何 webpack 配置來支持常用的兩種模式?

2、如何解決?

先看一下 webpack 官方在進行打包時候,是根據 package.json 中的 mainFields 字段指定依據哪個字段中的 路徑 找到第三方包的,而 externals 字段則是指定以何種方式引入第三方包

客觀情況如下:

  • 我們通常的是會把打出來的包放在 browser 字段中;

  • webpack 打包時,選擇依賴包是根據 resolve.mainFields 字段找到指定的路徑把代碼打進去的;默認配置是 ['browser', 'module', 'main'],也就是說會優先找 browser 的字段指定的路徑

  • libraryTarget 配置如何暴露 library。如果不設置 library, 那這個library就不暴露。就相當於一個自執行函數

  • libraryTarget 決定了你的 library 運行在哪個環境,哪個環境也就決定了你哪種模式去加載所引入的額外的包。也就是說,externals 應該和 libraryTarget 保持一致。library 運行在瀏覽器中的,你設置 externals 的模式爲 commonjs,那代碼肯定就運行不了了。

我們一般容易混淆的是 externals 的使用,比如對 react-dom 的 externals,經常會看到兩種寫法:

  • {'react-dom': 'reactDom'}

  • {'react-dom': 'react-dom'}

這兩種的區別,其實是和你想將第三方以怎樣的方式打入到你最終代碼有關

還有一種 對象形式,那種是隻應用在 umd 的打包方式中

2.1、externals 資料

  • externals:官方文檔

  • webpack externals 深入理解:偏向總結

  • 深入淺出webpack之externals的使用:以打包之後的源碼拆解開來講解

2.2、mainFields 參考資料

先通讀一下官方文檔中的 resolve.mainFields,想看中文的可以看這個鏈接 解析(resolve)

附其他參考資料:

  • 深入淺出webpack學習(5)--Resolve:詳細解讀 webpack 中的這個 resolve 字段用法,相比官方文檔多了舉例

  • package-browser-field-spec:package.json 中 browser 字段的標準寫法。

  • 深入理解webpack如何解析代碼路徑:掘金上的文章,講解 webpack 的代碼路徑解析規則

順帶收集幾個相關 issue,看看別人遇到的問題現在你是否可以解決:

  • `browser` vs `module` fields in `package.json`:回答了當 package.json 同時包含 browser, module & main fields 字段時候,如何指定我們 webpack 不用默認的 browser 字段

3、解決方案

使用兩份輸出配置項,主要更改 webpack 的打包的配置項中的 externalsoutput 這兩個字段。

3.1、B 包的配置如下

第一份配置是針對 .umd.js 文件的(別人用於 externals ,然後通過 script 腳本標籤)

 1{
2    externals: {
3      'react''React',
4      'react-dom''ReactDOM'
5    },
6    output: {
7      filename'index.umd.js',
8      libraryTarget'umd',
9      library'extB',
10      path: path.resolve(__dirname, 'dist'),
11      umdNamedDefinetrue
12    }
13}

第二份配置是針對 .browser.js 文件的(別人使用的時候直接放在 dependencies中,也會最終打包進去)

 1{
2    externals: {
3      'react''react'// 這裏更改了
4      'react-dom''react-dom' // 這裏更改了
5    },
6    output: {
7      filename'index.browser.js'// 這裏更改了
8      libraryTarget: 'umd',
9      library'extB',
10      path: path.resolve(__dirname, 'dist'),
11      umdNamedDefinetrue
12    }
13}

同時還得更改當前文件夾的 package.json 的對應字段如下:

1{
2  ...
3  "main""dist/index.umd.js",
4  "module""dist/index.umd.js",
5  "browser""dist/index.browser.js",
6  ...
7}

3.2、A 包的配置

首先,無論是否 externals,都需要在 package.json 中填完對 B 依賴的信息(可以根據實際情況放在 dependencies 字段或者 peerDependencies 字段)

情況 1 :A 包最終要把 B 包打入到最終代碼中去,那麼和 B 包的 webpack 配置是一樣的;

情況 2:A 包最終要把 B 包 externals 掉,基本配置是一樣的,只不過有額外的兩部需要操作:

  1. 在上述的 externals 中新增 B 包的 externals 配置項(需要區別 {'B': 'B'} 和 {'B': 'extB'})

  2. 在頁面中引入 cdn 資源 http://xxx/index.umd.js (注意不是 http://xxx/index.browser.js

4、簡化的寫法

我們看到上述這麼寫是能成功的,官方考慮到了這種情況,所以針對 umd 的打包方式,推出 對象形式,讓你統一上述兩種配置文件(但這種配置只能應用在 umd 的打包方式中);

最終我們把上述兩份打包配置合併成一份:

 1{
2    externals: {
3      'react''react'
4      'react-dom': {
5        commonjs'react-dom'// 這裏更改了
6        commonjs2: 'react-dom'// 這裏更改了
7        amd: 'react-dom'// 這裏更改了
8        root: 'reactDom'
9      }
10    },
11    output: {
12      filename'index.umd.js'// 這裏更改了
13      libraryTarget: 'umd',
14      library'extB',
15      path: path.resolve(__dirname, 'dist'),
16      umdNamedDefinetrue
17    }
18}

這樣就省去了 index.browser.js 這個文件,增加了兼容性。

5、總結

這篇文章是我一整天掉在 webpack 坑裏,來回調試了半天所得出的經驗總結,特此總結形成文章,方便以後查找。

閱讀原文獲取更多的信息

本文分享自微信公衆號 - JSCON簡時空(iJSCON)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章