jspmからWebpackに移行した
とりあえず、Webpackを導入したがそのままだと色々問題が多かったので以下の事をやった。
まあ今更感あるが。
- typescriptのallowSyntheticDefaultImportsをfalseにする。
- production用とdev用のconfigをいい感じにわける
- node_modulesのdllを生成する。
typescriptのallowSyntheticDefaultImportsをfalse
今回はtypescriptとwebpackの組み合わせだったのだが、webapckに移行する前にはjspmを利用していたので、
allowSyntheticDefaultImports: true, module: system,
で運用していた。ちなみにこのallowSyntheticDefaultImports
が何かというと、
本来typescriptのES6 Moduleはexport default ...
の構文しか、直接importできない。
// a.js module.exports = function() {}
// b.ts import fn from './a'; // Error import * as fn from './a'; // OK
しかし、allowSyntheticDefaultImports
をtrue
にすると、commonjsスタイルのmoduleも直接importできるようになる。
のだが、webpackはsystemjsを取り扱えず、module: commonjs
にしたところ、このallowSyntheticDefaultImports
がうまく動作しなくなった。
// a.js module.exports = function() {}
// b.ts import fn from './a'; fn(); // Error
ので、allowSyntheticDefaultImports
はfalse
にして、すべてimport * as ...
の方式で読み込むことにした。
production用とdev用のconfigをいい感じにわける
まあ、webpack.config.js自体がnodejsのモジュールだったので、webpack.config.jsに設定を書いてもいいのだが、
凄くごちゃごちゃするので、webpack.dev.config.jsを作って、そこでwebpack.config.jsを読み込み、
_.clone
で設定をコピーしてdev用の設定を追加するようにした。
node_modulesのdllを生成する。
コンパイルがあまりに遅かったので、DllPlugin
を利用して、変更されない外部モジュールを全てまとめておいた。
dll側
output: { filename: '[name].dll.js', library: 'vendor_library' // Bundleが定義されるグローバル変数名。参照する時に使用する。 }, plugins: [ new webpack.DllPlugin({ path: path.join(__dirname, 'dll', '[name]-manifest.json'), // manifestファイルを出力する。とりあえず-manifestにしとけばok name: 'vendor_library' // output.libraryで指定した名前 }) ]
使う側
plugins: [ ... new webpack.DllReferencePlugin({ // Dllを参照する context: __dirname, manifest: require('./dll/vendor.production-manifest.json') // 上のmanifestファイルへのパス }) ]
こうすることでnode_modulesとかのあまり変化しないファイルをコンパイルしておくことができ、
そのコンパイルしたファイルを参照することでコンパイル時間を短縮できる。
ただし、DllReferencePluginはconcatしてくれないので、dllは手動でconcatするか、別途scriptで読み込む必要がある。
今回はconcatした。
gulp.task('minify', done => { const webpack = require('webpack'); const config = _.clone(require('./webpack.config.js')); config.output = _.clone(config.output); config.output.path = path.join(__dirname, DIST); const compiler = webpack(config); compiler.run((err, stats) => { if (err) { throw err; } console.log(stats.toString('minimal')); try { const main = `${DIST}/main.js`; const app = fs.readFileSync(main, 'utf8'); const vendor = fs.readFileSync(`dll/vendor.production.dll.js`, 'utf8'); fs.writeFileSync(main, `!function(){${vendor};\n${app}}();`, 'utf8'); //ここでconcatしてる。 } catch(e) { throw e; } done(); }); });
さらに新たにnode_modulesがロードされても更新されるように、package.jsonにも以下の記述をした。
"scripts": { "postinstall": "node ./node_modules/.bin/gulp bundle-dll" }
これでnpm install/yarn add
してもbundleが更新される。
あと、はまった点として、予めbundleしておく中にreactとかも入れていたので、process.env.NODE_ENVの値に困ってしまった。
なので、dllファイルをdev用、production用と2つ用意して対処した。
function bundleDll(env, done) { const webpack = require('webpack'); const config = _.clone(require('./webpack.dll.config.js')); config.entry = _.clone(config.entry); config.output = _.clone(config.output); config.output.path = path.join(__dirname, 'dll'); config.plugins = config.plugins.slice(); const vendor = config.entry.vendor; const keyName = `vendor.${env}`; config.entry = {}; config.entry[keyName] = vendor; if (env === 'production') { config.plugins.push(new webpack.optimize.UglifyJsPlugin()); } config.plugins.push(new webpack.DefinePlugin({ 'process.env.NODE_ENV': `'${env}'` })); const compiler = webpack(config); compiler.run((err, stats) => { if (err) { throw err; } console.log(stats.toString('minimal')); done(); }); } /** * dev dll */ gulp.task('bundle-dev-dll', done => { bundleDll('development', done); }); /** * production dll */ gulp.task('bundle-prod-dll', done => { bundleDll('production', done); }); gulp.task('bundle-dll', () => { const rs = require('run-sequence'); return rs( 'clean-dll', 'bundle-dev-dll', 'bundle-prod-dll' ); });
まとめ
jspmよりwebpackの方が快適でした。
さよならjspm