Webpackのexternalsについて

webpack 4.19.1 を使っています。

背景

Webpackの設定でexternalsを使うと、指定したモジュールをバンドル対象から外して外部依存のままにできる。 典型的には、ブラウザの<script>タグで別途CDNからjQueryをロードする前提で、かつソース内でjQueryimportしていると、webpack実行時にはモジュール解決できずエラーになってしまうが、externals指定することでエラーを回避できる。

たとえば

/* webpack.config.js */
module.exports = [{
  // ...
  entry: 'main.js',
  externals: {
    hoge: 'fuga'
  },
  // ...
}]

こんな設定で

/* main.js */
import {app} from 'electron';
import hoge from 'hoge';

// ...

をバンドルすると、hogenode_modules/とかに存在しなくてもwebpackはエラーにならず、バンドル後のファイルでは単に

module.exports = fuga;

となる。つまり、変数fugaが定義されていることを前提に、それをhogeモジュールとして使うようにバンドルしてくれる。

問題

公式ドキュメントを読んだが、いまいちexternalsの書き方がよくわからなかった。 stringarray, objectfunctionregexの5通りの記法があるらしい。 stringは上に書いた説明で良さそうだけど、後ろ4つについて理解したい。

結果

まだわかってない部分が多い。何かわかったら追記する...

array

externals: {
  hoge: ['fuga', 'piyo']
}

と書くと、モジュールの定義は

module.exports = fuga["piyo"];

と出力されていた。定義済みオブジェクトの子孫プロパティをモジュールと見なしたいときに使うと良さそう。

object

正確に言うと、stringの書き方を包含している模様。なのでstring以外にあたる書き方について。 結論から言うと、どう使うのかよくわからない。 ドキュメント同様

externals : {
  hoge : {
    root: ['fuga', 'piyo']
  }
}

と書くと、module.exports = fuga["piyo"]になるのだろうと思いきや

module.exports = undefined;

となりまともに実行できないバンドルができる。 ソースを見れば何かわかるのかもしれないが。。

function

これを使うと、ソースでimportしていたモジュールを、バンドルではCommonJS requireされるようにできる模様。 理解できていないけど、

externals: [
  function(context, request, callback) {
    if (request === 'hoge') {
      return callback(null, 'commonjs ' + request);
    }
    callback();
  }
]

と設定することで

module.exports = require("hoge");

なるバンドルができる。callbackの第2引数に'commonjs hoge'を渡している。第1引数は謎。 他にもできることがありそうではある。

実は個人的に一番知りたかったのがこれだった。

2018-09-23 1:00 追記

  • commonjs以外に指定できるキーワードには、this,window,self,global,commonjs2,amd,umd,umd2がある模様(ソース
  • callbackの第2引数に'commonjs ' + requestを指定する代わりに、callback(null, request, 'commonjs')と指定することもできる(ソース

regex

stringと同様。モジュール名と同名の変数が定義されていることが前提のバンドルができる。

(おまけ)予備調査

Webpackの吐くJSファイルはどんな構造になってるのか、そもそもわかってなかったので少し見た。 きっと設定で変わる部分もありそうだけど、自分の手元で見たものは以下のような感じになっていた。

(function(modules) {
  // 必要な関数の定義 + エントリポイントの実行
})({
  "モジュール1の名前": (function(...) {
    // モジュール1の定義
  }),
  "モジュール2の名前": (function(...) {
    // モジュール2の定義
  }),
  // ...
});
  • 全体は一つの匿名関数の定義となっていて、即時実行される
  • その際の引数として、全てのモジュールを持つオブジェクトが渡されている
  • エントリポイントもモジュールの一つとして扱われており、匿名関数の末尾でロード(実行)される

という感じ。