abcdefGets

ゲッツ!

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

しかし、allowSyntheticDefaultImportstrueにすると、commonjsスタイルのmoduleも直接importできるようになる。
のだが、webpackはsystemjsを取り扱えず、module: commonjsにしたところ、このallowSyntheticDefaultImportsがうまく動作しなくなった。

// a.js
module.exports = function() {}
// b.ts
import fn from './a';

fn(); // Error

ので、allowSyntheticDefaultImportsfalseにして、すべて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