概要
開発中、以下のような状況が発生した。
- Angular2 でアプリ作って
- Webpack でバンドルして
- Electron で動かした
- ら、値がバインドされなかった
このとき、Electron で動かす Angular のアプリには Canvas を使ったものやら NodeJS のモジュールを利用したものやらを実装していた。なので、Webpack の設定は下記のようになっていた。
let config = {
...
target: "electron-renderer",
externals: [
"canvas",
"jsdom"
],
...
}
このような設定をしておくと、Electron から NodeJS を利用しつつ、Canvas もきちんと動いてくれる。実は、件のアプリは元々 AngularJS 1.5 で作っていたもので、そのときは問題なく動いていた。Angular2 のリリースを受けて移行作業を行っていたとき、問題に遭遇したわけだ。
原因
色々試してみたら原因が分かった。Electron や Angular2 の問題ではなく、Webpack の設定のせいだった。上記の設定から target
の記述を除く(= target:'web'
の状態にする)と、値がバインドされる。
つまり、Web をターゲットにすると動き、Electron をターゲットにすると動かない。Web 向けと Electron 向けできっと差異があるんだろう、と思って Webpack のソースを弄って試してみても特に動いたりしなかった。何かしら違いはあるんだろうけど。。。
まぁでも、Web 向けにバンドルすれば Angular2 がちゃんと動くのだから、その設定にしておけばいいという話だろう。
……ところで、本稿で試したアプリには target:web
と相性の良い Canvas と target:electron
じゃないと動かない NodeJS モジュールを利用した機能が混在している。Angular2 は target:web
にしないと値をバインドしてくれない。
さてどうしようか。
とはいうものの、問題となっている部分は切り分けができるので、回答は至ってシンプルだ。
コンポーネントとサービスを別々にバンドルする
target
の違うものを組み合わせるなら、別々にバンドルしたものを Webpack を使って連携させればいい。幸いにも Webpack にはそういう機能があるし、探せば色々記事はあるはず。
NodeJS モジュールを利用している機能をまとめたサービスだけを target:electron
の設定で別にバンドルする。その際の設定は下記のようになる。
const path = require('path');
const webpack = require('webpack');
let config = {
// バンドル対象のファイル
entry: {
service: [
path.join(__dirname, 'service/external.service.ts')
]
},
// 出力先の指定
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].lib.js',
library: '[name]_lib'
},
// ビルド対象の拡張子
resolve: {
extensions: ['', '.ts', '.webpack.js', '.web.js', '.js', '.json']
},
target: 'electron',
externals: [
"canvas",
"jsdom"
],
module: {
loaders: [
{
test: /\.ts$/,
loaders: ['awesome-typescript-loader', 'angular2-template-loader'],
exclude: [/\.(spec|e2e)\.ts$/]
}
]
},
plugins: [
// manifest ファイルを作るプラグイン
new webpack.DllPlugin({
path: path.join(__dirname, '[name]-manifest.json'),
name: '[name]_lib'
})
]
};
module.exports = config;
この設定ファイルを webpck --config
で渡してやれば、指定したファイルがバンドルされる。
ちなみに、この設定だとメインのバンドルファイルとサービスのバンドルファイルに Angular2 などのベンダーモジュールが重複して入ってしまうので、それを避けるために npm install
などでインストールしたモジュールも別のバンドルにしておくことをおすすめする。
そうした場合、resolve 以下を消して、plugins に次のプラグインを追加する。
plugins: [
...
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./hoge-manifest.json')
})
...
]
同じように、元々のバンドルファイルにも作成したバンドルファイルのマニフェストファイルを読み込むように設定し、target:web
としてバンドルすればよい。
後は index.html で双方を読み込んでやれば動作するはずだ(ただしサービスのバンドルを先に読み込むこと・非同期で読み込まないこと)。
最後に
これ、結局 Webpack, Angular2, Electron のどれのせいなのかよく分からんし、バグなのか仕様なのかも分からないっすね……。
なんかちょっと泥臭いやり方になっちゃったように感じるし、もうちょっとスマートに解決する方法あったりしないかな。。。
あと、バンドルの分割の話、分かりにくかったらこちらの記事をどうぞ。私の説明よりもっとずっと分かりやすいです。