Humanity

Edit the world by your favorite way

Express で書いてるコードを TypeScript 化した時に対処したこと

tyru.hatenablog.com

の続き。

今度は 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” を指定する

otiai10.hatenablog.com

module.exports = {
  // ...
  target: 'node',
  // ...
};

よく分かってないけど target には以下を指定できる。デフォルトは web。

configuration

  • “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",
    ...
  },
  ...
}

参考記事