webpackでサーバサイドでもTypeScriptしようぜ

webpackでサーバサイドでもTypeScriptしようぜ

主はおっしゃいました webpack はフロントエンドの javascript にだけ与えられたものではないと。よろしい!ではサーバサイドも TypeScript で webpack で es5(or es6 or es2015(まさかり対策))にしようではないか。

  • 今回のサンプルはこちらに保存してあります m(_ _)m

サーバサイド nodejs にも型が欲しかった

そもそも、サーバサイドなら別に他の言語を使いなさいというのはもっともですが、IO レイヤーの処理のひとつにスクレイピングでデータを取得するという部分があって(もちろん selenium でもいいんだが)、そいつをnightmareで書いていたので、これはもう wrap するレイヤーも nodejs で行くしかないなっという背景であります。

ただせっかくですし、サーバサイドですし、割りときっちりした型がほしいなぁと思いまして。それで TypeScript をサーバサイドで使って webpack で build しようという試みです。

Building with webpack in server side

TypeScript に進む前にまずはサーバサイド nodejs を webpack でひとつの index.js としましょう。適当にnpm initして、npm install --save-dev webpack(ぼくは global install を推奨したくないので、local install で path を解決する方向でいきます。) src/index.jsに簡単な関数を指定してmodule.exportsしただけのものが、こちら。まだこの段階ではただの webpack を使った build です。

Using nodejs modules

次に適当な nodejs の module を使ってみます。ブラウザで動かせない且つ、今回の model に合わせるために、nightmare を使用します。npm installe nightmare --saveします。んでもって、model 層の処理として簡単な data を取得する処理を書きます。

//src/models/scraper.js
'use strict';

const Nightmare = require('nightmare');

class HtmlContent {
  constructor(url) {
    this.url = url;
  }

  getContent() {
    new Nightmare()
      .goto(this.url)
      .wait(5000)
      .evaluate(() => {
        return document.querySelector('body').innerHTML;
      })
      .end()
      .then((result) => {
        return result;
      });
  }
}
module.exports = HtmlContent;

んでもって、exports された model をそのまま index.js にて exports します。

//src/index.js
'use strict';

const Scraper = require('./models/scraper');

module.exports = {
  Scraper: Scraper,
};

ここまでの処理がこちら

Building nodejs program by webpack

ここでおもむろに webpack で build してみる。

$(npm bin)/webpack
Hash: d86c13400a0572da64bc
Version: webpack 1.12.15
Time: 23ms
   Asset     Size  Chunks             Chunk Names
index.js  1.41 kB       0  [emitted]  main
    + 1 hidden modules

build 自体は通る。しかし、ここでこんな風に成果物を require してみると...

$node
> var index = require('./dist/index')
ReferenceError: nightmare is not defined

nightmare が undefined だと。ファッ??っと言いたい。require してるだけやんと。(実際ぼくはいいたくなった) この原因は module の解決が default だとvarという値が設定されていて global 変数から解決しようとするから起こっています(ということが調べていたらわかった)。っというわけで、module の解決に commonjs を使ってみましょう。(やっと webpack.config.js に辿りつけた(;^ω^))

//webpack.config.js
'use strict';

const path = require('path');

module.exports = {
  target: 'node',
  externals: /^(?!^\.\/)/,
  context: path.join(__dirname, 'src'),
  entry: './index.js',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'index.js',
    libraryTarget: 'commonjs2',
  },
  resolve: {
    extensions: ['', '.js'],
  },
};

ここで特に抑えておきたいのはlibraryTarget: 'commonjs2'と、externals: /^(?!^\.\/)/でありんす。

libraryTarget

こいつは、さっきの default の var を commonjs に差し替えるってやつで、module の解決を commonjs で解決してくれます。なのでこのオプションを設定するとnightmare is not definedがでなくなるます。

externals

externals は除外対象なので、build の対象外って感じ。今回は node_modules を npm によって解決したいので、./で始まってない moduleを exclude してます。

さて、ここまでの成果物がこちら

TypeScript

ふぅー、やっと typescript にたどりつけましたね。まずは、typescript 環境が欲しいのでnpm install --save-dev typescript webpack ts-loader typingsしましょう。

----------  TypeScript 化 -----------

  1. $(npm bin)/typings initで d.ts を get できる環境を用意しときましょう。
  2. $(npm bin)/typings install nightmare --save --ambientnightmare の d.ts を取得。
  3. mv src/index.js src/index.ts && mv src/models/scraper.js src/models/scraper.ts
  4. ごにょごにょっと typescript します w

まぁ、この TypeScript 化には深い意味はないですw

webpack.config.js + ts-loader

ts-loader から.ts ファイルを読み込みます。単純にtsc --initして ts-loader を使うとバラバラバラっとたくさんエラーが出ます。

$(npm bin)/webpack
ERROR in /Users/JumpeiArashi/tmp/serverside-typescript-with-webpack/typings/main/ambient/nightmare/index.d.ts
(121,3): error TS2300: Duplicate identifier 'export='.

ERROR in /Users/JumpeiArashi/tmp/serverside-typescript-with-webpack/typings/browser/ambient/nightmare/index.d.ts
(121,3): error TS2300: Duplicate identifier 'export='.

ERROR in ./models/scraper.ts
(17,8): error TS2339: Property 'end' does not exist on type 'Nightmare'.

nightmae.end が定義されてないのは残念ですが、typings/browser 配下の index.d.ts も build 対象になってますねぇ。。。ts-loader 側に exclude したいところですが、ts-loader の READMEを見ると、tsconfig.json に書けと書いてありますねぇ。っというわけで、tsconfig.json に

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5",
    "noImplicitAny": false,
    "sourceMap": false
  },
  "files": ["src/index.ts", "typings/main.d.ts"]
}

files を指定する。ブラウザで動くようには作れてませんが、nightmare をブラウザで動かそうって人はいないと思うってのと、今回はサーバサイド TypeScript + webpack が目的なのでこれにて完了。.。o○(Nightmare の ambient module に PR 出そうかな)

octobot: タコ・ソ・ノモノ