前回まででTypeScriptを使ってフロントエンドJavaScriptをビルドする話を書きましたが、TypeScrptに慣れてしまうとES2015で書く気にはなれません。
当然のようにサーバーサイドもTypeScriptでコードを書き始めたのですが、ビルドにフロントエンドでも使っていたWebPackを採用して少しハマったので共有します。
やりたかったこと
- TypeScriptでsocket.ioを使ったソケットサーバーを書きたい
- 変更検知でビルド自動化したい
- glup/grunt抜きでWebPackでやりたい
環境
- Mac OS X El Capitan
- node.js v5.7.1 / npm v3.6.0
- TypeScript v1.9.0-dev.20160312
- typings v0.7.9
- webpack v1.12.14
上3つのモジュールは npm でグローバルインストール済み。
- ts-loader@0.8.1
WebPackでTypeScriptをビルドするためのローダーをローカルインストールしています。
忙しい人のために
先に結論を書くと、nodeランタイム用にWebPackを使ってビルドするとき、単に node_modules をexcludeするだけではダメで、代わりに webpack-node-externals を使いましょう、という話。
やったこと
ソースコードの修正
このシンプルな socket.io をつかったサーバー用コードをTypeScriptに移植していました。
|
1 2 3 4 5 6 7 8 9 10 |
var server = require('http').createServer(); var io = require('socket.io')(server); io.on('connection', function(socket){ socket.on('event', function(data){}); socket.on('disconnect', function(){}); console.log('new socket connected'); }); server.listen(4000); |
もちろんそのままでは動かなくて、まずはTypeScriptで使う型定義ファイルを typings を使ってインストールします。
|
1 2 |
$ typings install --save-dev --ambient node $ typings install --save-dev --ambient socket.io |
この node 定義ファイルは require するために必要です。
TypeScript用に上のJSを書き換えます。具体的には require 周りを import してから使うようにします。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/// <reference path="../typings/main.d.ts" /> import * as http from 'http'; import * as sio from 'socket.io'; var server = http.createServer(); var io = sio(server); io.on('connection', function(socket){ socket.on('event', function(data){}); socket.on('disconnect', function(){}); console.log('new socket connected'); }); server.listen(4000); |
設定ファイルの準備
TypeScriptコンパイル用の設定ファイルを作ります。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "compilerOptions": { "module": "commonjs", "target": "es5", "noImplicitAny": false, "sourceMap": false }, "exclude": [ "node_modules", "typings/browser.d.ts", "typings/browser" ] } |
WebPack用設定ファイルを作ります。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
module.exports = [{ entry: { app: "./src/server.ts" }, output: { path: __dirname + '/dist', filename: "server.js" }, module: { loaders: [ { test: /\.ts$/, loader: "ts-loader", exclude: /node_modules/ } ] } }]; |
ビルドします。
|
1 2 3 4 5 6 7 8 9 10 |
$ webpack ..(略).. ERROR in ./~/socket.io/lib/index.js Module not found: Error: Cannot resolve module 'fs' in /path/to/src/node_modules/socket.io/lib @ ./~/socket.io/lib/index.js 7:11-24 ERROR in ./~/socket.io/lib/index.js Module not found: Error: Cannot resolve module 'socket.io-client/package' in /path/to/src/node_modules/socket.io/lib @ ./~/socket.io/lib/index.js 10:20-55 ..(略).. |
解決策
原因切り分けのために、 tsc コマンドでコンパイルすると成功したことから、WebPackに起因するものだと特定できました。
どうやらWebPackで node ランタイム用にビルドする時は、 webpack-node-externals が必要とのことで、これを使います。
|
1 |
$ npm install --save-dev webpack-node-externals |
WebPackの設定ファイルに、ドキュメントの通りに変更を加えます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var dist_dir = __dirname + '/dist'; var nodeExternals = require('webpack-node-externals'); module.exports = [{ target: 'node', externals: [nodeExternals()], entry: { app: "./src/server.ts" }, output: { path: dist_dir, filename: "server.js" }, resolve: { extensions: ['', '.js', '.ts'] }, module: { loaders: [ { test: /\.ts$/, loader: "ts-loader", exclude: /node_modules/ } ] } }]; |
ビルドして、実行してみます。
|
1 2 |
$ webpack $ node dist/server.js |
うまく動いてくれました!
所感
ちゃんと exclude しているし、フロントエンドではエラーにならなかったので、除外設定には問題ないと思って数時間ハマりました。。
あまりサーバーサイド側のビルドにWebPackというのは聞かなくて、サーバー用にTypeScriptをビルドする際のベストプラクティス的なものを探しています。
軽く調べた感じだと gulp を使っているケースが多いようでした。
参考
- Webpack: How to don’t bundle node_modules, but use them normally in node.js? #603
https://github.com/webpack/webpack/issues/603


