Express で書いてるコードを TypeScript 化した時に対処したこと
の続き。
今度は express-generator で自動生成されたバックエンドのコードを TypeScript 化してみる。
tsconfig.json に “allowJs”: true を指定する
express-generator のコードは bin/www から app.js を require() する形になっているが、
app.ts から app.js を生成したところ、app.js で module.exports = app しているのに 空の Object {}
がエクスポートされてしまう。
おそらく bin/www も TypeScript の仕組みに乗っからないと(TypeScript でトランスパイルしないと)
require() できないのだと何となく予測したので、bin/www も .ts 化しようと思った…
けどめんどいので –allowJs(引数)または “allowJs”: true(tsconfig.json)というので .js ファイルもトランスパイルできるらしい。
あと bin/www は shebang も付いてるので外してやる必要がある。
{ "compilerOptions": { ... "allowJs": true, ... }, ... }
ちなみに resolve の extensions に .js を指定しないと require() の時に .js を module として扱ってくれないので注意。
module.exports = { // ... resolve: { // note if using webpack 1 you'd also need a '' in the array as well extensions: ['.ts', '.js'] }, // ... };
target: “node” を指定する
module.exports = { // ... target: 'node', // ... };
よく分かってないけど target には以下を指定できる。デフォルトは web。
- “web” Compile for usage in a browser-like environment (default)
- “webworker” Compile as WebWorker
- “node” Compile for usage in a node.js-like environment (use require to load chunks)
- “async-node” Compile for usage in a node.js-like environment (use fs and vm to load chunks async)
- “node-webkit” Compile for usage in webkit, uses jsonp chunk loading but also supports build in node.js modules plus require(“nw.gui”) (experimental)
- “electron” Compile for usage in Electron – supports require-ing Electron-specific modules.
- “electron-renderer” Compile for electron renderer process, provide a target using JsonpTemplatePlugin, FunctionModulePlugin for browser environment and NodeTargetPlugin and ExternalsPlugin for commonjs and electron bulit-in modules. Note: need webpack >= 1.12.15.
webpack-node-externals を使って require() を展開しないようにする
webpack config のプロパティに externals というものがある。 これは 見つけた require() の中でトランスパイル時に require() 先のモジュールを展開するか(埋め込むか)決めるプロパティ。 externals にファイル名が指定されていれば require() は展開されず、実行時に node によって require される。
しかし一つ一つ指定していくのは面倒。 そこで webpack-node-externals というプラグインがあり、これがエクスポートする nodeExternals() という関数を実行して次のように externals に指定してやれば node_modules 以下のライブラリは一括で展開しないよう指定できる。
WebPackでnodeランタイム向けにビルドする | // sakura note
結果
前回のも含めて以下のようになった。
var path = require('path'); var glob = require('glob'); var nodeExternals = require('webpack-node-externals'); function getFrontendEntries() { // Move init.ts to the first element var INIT_TS = './src/front/init.ts'; var entries = glob.sync("./src/front/**/*.ts").sort(function (a, b) { return a === INIT_TS ? -1 : b === INIT_TS ? 1 : (a > b ? 1 : a === b ? 0 : -1) }); return entries; } function getBackendEntries() { // Transpile './src/back/(.+).[jt]s' into '$1' var entries = {}; glob.sync("./src/back/**/*.?s").forEach(function (entry) { entries[entry.replace(/.*src\/back\/(.+)\.[jt]s$/, '$1')] = entry; }); return entries; } module.exports = [ // Frontend { // 前回の module.exports // http://tyru.hatenablog.com/entry/2017/02/19/210947 entry: getFrontendEntries(), output: { path: path.resolve("./public/javascripts"), // filename: '[name].js' filename: 'bundle.js' }, resolve: { // note if using webpack 1 you'd also need a '' in the array as well extensions: ['.ts', '.js'] }, module: { // loaders will work with webpack 1 or 2; but will be renamed "rules" in // future loaders: [ { exclude: /(node_modules)/, test: /\.ts$/, loader: 'ts-loader' }, { test: /\.html$/, loader: 'html-loader?minimize' }, { test: /\.scss$/, loaders: ['style-loader', 'css-loader', 'sass-loader'] } ] } }, // Backend { // 今回追加するもの target: 'node', externals: [nodeExternals()], entry: getBackendEntries(), output: { path: path.resolve("."), filename: '[name].js' }, resolve: { // note if using webpack 1 you'd also need a '' in the array as well extensions: ['.ts', '.js'] }, module: { // loaders will work with webpack 1 or 2; but will be renamed "rules" in // future loaders: [ { exclude: /(node_modules)/, test: /\.[jt]s$/, loader: 'ts-loader', options: { transpileOnly: true } } ] } } ];
出力されるコードは必ず .js がつくので、npm start で起動するスクリプトにも .js を付けてやる。
{ ... "scripts": { ... "start": "node ./bin/www.js", ... }, ... }
参考記事
- webpackでnpmのbinつくっててfs.readFileSync is not a functionと言われる問題 - DRYな備忘録
- WebPackでnodeランタイム向けにビルドする | // sakura note
- TypeScriptSamples/imageboard at master · Microsoft/TypeScriptSamples · GitHub
- Samples · TypeScript の Express のサンプルアプリ
- “ImageBoard: A Node.js + Express + MongoDB application built using TypeScript on the server”
TypeScript 入門 + Express 再入門した
モチベーション
以下の記事を見て作りたくなったからです(ありがとうございます)。
- TypeScript 入門するのは 私的TypeScriptとの関わり方ガイドライン - 角待ちは対空 を見たから。
- Express 再入門するのは Express入門 - ぺーぺーSEのブログ を見たから。
- そして 2017年はNode.jsの達人になる!いま知っておきたいベストプラクティス10 - WPJ を見たから。
久しぶりに Express 再入門して express-generator が生成したコードの middleware ググりながらふんふん言ってる
— tyru (@_tyru_) 2017年2月17日
つーか createServer() とか listen() とかないやんけと思ってたら module.exports = app; ってのがあるからこれかなぁ… node で module を実行したら特殊な扱いするのかなぁ…
— tyru (@_tyru_) 2017年2月17日
とか思ってたけどそういや express-generator は bin/www から起動されてるのでそっちにごっそり listen() やら createServer() あるのを見つけた
— tyru (@_tyru_) 2017年2月17日
あんまりジェネレータ系好きじゃないけどこれくらいの規模なら全然読めるな
— tyru (@_tyru_) 2017年2月17日
以下は入門した時のメモ。
Pug (旧 Jade)
Express
TypeScript
- 私的TypeScriptとの関わり方ガイドライン - 角待ちは対空
- 2016年から始めるTypescript - Qiita
- tsconfig.json · TypeScript
- TypeScript の型定義ファイルと仲良くなろう - Hatena Developer Blog
- TypeScript 1.6時代の.d.ts管理について意見を述べておく - Qiita
なんか TypeScript 対応してるモジュールなら未来じゃなくすでにそうなってた。下記参照node_modules
に型定義ファイルがあるから、即 TypeScript でも使えるような未来が待ってるらしい(超意訳)
- Angular/Angular-Resource/Lodash TS1316 errors 'TS1316:Global module exports may only appear at top level' after update last version. · Issue #13338 · DefinitelyTyped/DefinitelyTyped · GitHub
- この issue で
npm install @types/{name}
を知った
- この issue で
- The Future of Declaration Files | TypeScript
@_tyru_ 今は特に理由がなければ @type/foo みたいな npm パッケージで管理されているものを使うのが良いっぽいです. https://t.co/9hTf3E1lkV
— ドッグ (@Linda_pp) 2017年2月17日
WebPack
TypeScript + Webpack 使った時に出たエラーと対処したこと
loaders に loader を指定する時は test
プロパティのパターンで絞り込むこと
html-loader を使おうと思って .html のファイルを require() したら、ts-loader に渡って解釈されてしまったので、絶対つけること。 当たり前っぽいけどこれでハマったので。
ERROR in ./src/component/root-index.html Module build failed: Error: Could not find file: 'C:\msys64\home\tyru\git\scrader\src\component\root-index.html'. at getValidSourceFile (C:\msys64\home\tyru\git\scrader\node_modules\typescript\lib\typescript.js:79277:23) at Object.getEmitOutput (C:\msys64\home\tyru\git\scrader\node_modules\typescript\lib\typescript.js:79644:30) at getEmit (C:\msys64\home\tyru\git\scrader\node_modules\ts-loader\dist\index.js:99:43) at Object.loader (C:\msys64\home\tyru\git\scrader\node_modules\ts-loader\dist\index.js:27:11) @ ./src/component/root-index.ts 11:14-42 @ multi ./src/init.ts ./src/component/root-index.ts ./src/routes.ts
module.exports = { // ... module: { loaders: [ { test: /\.ts$/, // これを必ずつける exclude: /(node_modules)/, loader: 'ts-loader' }, { test: /\.html$/, loader: 'html-loader', query: { minimize: true } } ] } }
「error TS2304: Cannot find name ‘require'」と言われる時は require() の型宣言をしてやること
declare function require(x: string): any;
このような型宣言をしないと、以下のようなエラーが出る。
ERROR in ./src/component/root-index.ts (12,15): error TS2304: Cannot find name 'require'.
以下のような通常の require() をしてる時は必要ないみたいだけど、
import angular = require('angular'); import 'angular-ui-router'; angular.module('scrader', ['ui.router']);
このように式の中?で require() をすると型宣言が必要になる。 正直 TypeScript 力が低すぎてよく分かってない。
// 以下の1行がないとコンパイルエラー!! declare function require(x: string): any; class RootIndex { msg: string; constructor() { this.msg = 'Hello!'; } } angular.module('scrader') .component('rootIndex', { template: require('./root-index.html'), // template: `<div ng-bind='$ctrl.msg'></div>`, controller: [RootIndex] });
WebPack と html-loader 便利すぎる。 Gulp より「コード」っぽい設定を書かずに済むし、loader めっちゃ便利だし、WebPack 大好きになってきた。 require() でコードに事前に*1色々埋め込める仕組みというのが気に入った。
参考記事
- TypeScript と Node.js で hello, world をする http server を作る - あいつの日誌β
- webpackを使い倒す - Thujikun blog
- WebPackを使ってJavaScriptを効率的に書くチュートリアル【入門編】 | 株式会社LIG
- javascript - How to use Webpack with Angular + templateCache? - Stack Overflow
- これで html-loader を知った
Special Thanks
TS の test、最後の $ が抜けてるかな?https://t.co/l9XeEAirN7
— 鳥頭なフレンズ (@kariya_mitsuru) 2017年2月19日
*1:「静的に」と言っていいんだろうか?