藻ログ

都会でOLをしています

がんばらないTypeScript環境ほしい

色々あってシンプルなtypescriptの開発環境をつくろうとして消耗した話です

小規模なプロジェクトをシュって書けるシンプルな環境がほしい

  • でもナウくなっててほしい
  • そもそもナウいとは...
  • 最近のフロントエンドの人は何を言ってるのか全然わからないし依存パッケージが多すぎて混乱する
  • でもちょっとはナウくなっててほしい

試行錯誤した結果

  • npm scripts + browserify + tsify + watchify で構成することにきめた.
  • 本体を1行も書かないまま日付が変わっていた.
  • もうナウくなくていいから本体が書きたい

構成

とりあえずbuildすると色々なものがdistに送られる構成にした

├── dist (static-assets)
│   ├── bundle.js
│   ├── bundle.css
│   └── index.html
├── src
│   ├── ts
│   │   ├── app.ts
│   │   ├── ui.ts
│   │   └── renderer.ts
│   ├── css
│   │   └── custom.css
│   └── html
│   │   └── index.html
├── package.json
└── tsconfig.json 

package.json

まず npm init して package.json を作る.

dependencies と devDependencies

必要なパッケージを入れる.
npm install ${package} --save すれば package.json に自動追記される.
--savedependencies(実行に必要なパッケージ群), --save-devdevDependencies(開発に必要なパッケージ群) に入る.

$ npm install browserify tsify typescript watchify catw http-server --save-dev
$ npm install jquery @types/jquery skeleton-css --save

今回は開発まわりのパッケージ以外に jquery(+型定義ファイル) と cssフレームワークとして skeleton をいっしょにいれた.

tsconfig.json

これはtypescriptをトランスパイルするときのオプションがまとまった設定ファイルで,後述の tsify もこいつを参照する.
tsconfig.json · TypeScript
さっき typescript をinstallする時に入れた tscを使って node_modules/.bin/tsc --initすれば以下のような tsconfig.json が自動生成される.できたjsはコメント消えてシュッとなっててほしいのでremoveCommentsは追加した*1

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "noImplicitAny": false,
        "sourceMap": false,
        "removeComments": true
    }
}

ビルドツール

最初はgulp - npmでひととおり書いてみたのだが,色々試行錯誤してるうちに

なんかもうmakefileいやシェルスクリプトでもいいかも...

という気持ちになってきたことと,gulp.watchwatchify まわりでモヤモヤしたため結局 npm scripts で直に書くことにした.種々のツールは廃れてもnpmが死ぬことはまずないだろう.
npm で依存もタスクも一元化する - Qiita
[意訳]私がGulpとGruntを手放した理由 - Qiita

npm scripts

package.json の scripts: では,npm run ${task} で走らせるタスクを定義することができる.タスクの中身はシェルスクリプトをぶち書いて良い.たとえば

package.json
"scripts": {
  "clean": "rm -rf dist/",
  "build:ts": "browserify ./src/ts/app.ts -p [tsify] | uglifyjs -c > dist/bundle.js"
}

のように書いて npm run clean とかnpm run build:tsすると書いたままのコマンドが実行される.また,node_modules/.bin/browserify のようなパスを書かなくても--save--save-dev で入れたツールは直に呼び出せる.

  • シェルコマンド直書きしてしまうとWinどうするの
  • ただの文字列になってしまうため管理がしづらい
  • 冗長な記述が増える
  • 可読性が低い etc.

のような問題は確かにあるのだが,今回小規模用ということでscriptsだけでやってみることにした.

開発ツール

browserify

CommonJS仕様で書かれたrequire('module')をブラウザ上で動かせるようにするモジュールシステム.モジュール間の依存関係を解決してバンドルしてくれる.

$ browserify main.js > bundle.js

のようにすると,main.js(CommonJS仕様で書かれたjs)をブラウザで動くjsに変換してstdoutにぶち吐いてくれるのでそのままbundle.jsに書き出す感じになる.しかし,今回はtypescriptをまずjsにしなければならない*2ので,後述のtsifyを合わせて使う.

tsify

TypeScriptをトランスパイルするためのbrowserifyプラグイン.これと合わせて使うことで,TypeScriptをjsにトランスパイルしてCommonJS仕様のjsをブラウザで動くjsにするのを一度にやってくれる.*3

$ browserify main.ts -p [tsify] > bundle.js

のように,tsifyをプラグイン指定したbrowserifyにかけるとmain.ts(typescript)をブラウザで動くjsに変換してstdoutにぶち吐いてくれるのでそのままbundle.jsに書き出す感じになる.
[] は tsifyにオプションを渡すとき必要で[tsify --noImplicitAny] みたいにできる.オプションが無いときは省略可能.オプションは基本的にtscと同じものが渡せる.

watchify

ファイル監視して差分browserifyするためのツール(だと思ってる).

watchify main.js -o 'bundle.js'

のようにすると,main.jsが変更されたとき,bundle.jsを勝手にbrowserifyしてくれる*4.また,出力先にはコマンドを設定することも可能.

watchify main.js -o 'uglifyjs -cm > static/bundle.min.js'

www.npmjs.com

catw

監視機能付きのcatで,今回はcss連結したりするのに使う.cssとhtml周りは色んなnpmパッケージを眺めてもいまいちどうするべきなのかよくわからなかったがとりあえずcssも変更したら連結してdistにぶち送る感じにした

catw main.css custom.css -v -o 'dist/bundle.css'

みたいにすると mainとcustomを監視し,変更があれば連結してしてbundle.cssにぶち吐いてくれる.
www.npmjs.com

型定義ファイル

typescriptではjsのライブラリをそのまま読めないので型定義ファイルが必要なのだが,今年リリースされたtypescript2.0からはnpmだけでその型定義ファイルが管理できるようになった.たとえば

$ npm install jquery @types/jquery jqueryui @types/jqueryui --save

のように 型定義ファイルをインストールするだけで,特別なツールや宣言無しにtsでjquery, jquery-uiのライブラリが使える*5.typingsとかtsdはもういらないっぽい.

src/ts/app.ts
import * as $ from "jquery"; //=> node_modules/@types/jquery/index.d.ts を見に行ってくれる

The Future of Declaration Files | TypeScript
npmでインストール可能なtypesパッケージはここから検索できる.
TypeScript Types Search

tsconfig.json の typeRootsでハマってた

By default all visible “@types” packages are included in your compilation. Packages in node_modules/@types of any enclosing folder are considered visible; specifically, that means packages within ./node_modules/@types/, ../node_modules/@types/, ../../node_modules/@types/, and so on.

typeRootsは以下のように明示的に指定できるが,node_modules/@typesは指定しなくても勝手に見に行ってくれる.

tsconfig.json
{
   "compilerOptions": {
       "typeRoots" : ["./mytypes"]
   }
}

また,typeRootsを指定するとここに記述した以外のパスは見に行かなくなってしまう.上の例では./mytypesのみを見に行き,node_modules/@typesは見に行かなくなるので注意する.
tsconfig.json · TypeScript

types

@typesに型定義ファイルを入れたら後は import * as hoge from fuga すれば簡単に読み込めて便利だけど,意図しないパッケージを読み込んでしまったら困ると思う*6.その場合,

tsconfig.json
{
   "compilerOptions": {
       "types" : ["jquery", "jqueryui"]
   }
}

のように明示的に読み込みたい型定義を指定すれば,そのパッケージのみ読み込みを許可することができる.types:[]とすれば@types/* を自動で見に行くのを無効化することになる.

いまのpackage.json

こんな感じになった.
基本的には npm run dev して開発することにしてる.
typescriptソース, css, html が変更されるとdistが更新される.

{
  "name": "kirakira-ol",
  "version": "0.0.1",
  "description": "marunouchi building",
  "scripts": {
    "clean": "rm -rf dist/",
    "prebuild": "npm run clean && mkdir dist",
    "build:css": "cat ./node_modules/skeleton-css/css/* ./src/css/custom.css > dist/bundle.css",
    "build:html": "cat ./src/html/index.html > dist/index.html",
    "build:ts": "browserify ./src/ts/app.ts -p [tsify] > dist/bundle.js",
    "build": "npm run prebuild && npm run build:css && npm run build:html && npm run build:ts",
    "watch:ts": "watchify ./src/ts/app.ts -p [tsify] -v --debug -o dist/bundle.js",
    "watch:css": "catw ./src/css/custom.css ./node_modules/skeleton-css/css/* -v -o dist/bundle.css",
    "watch:html": "catw ./src/html/index.html -v -o 'dist/index.html'",
    "watch": "npm run watch:ts & npm run watch:css & npm run watch:html",
    "serve": "./node_modules/.bin/http-server dist/ -p 8000",
    "dev": "npm run serve & npm run watch"
  },
  "devDependencies": {
    "browserify": "^13.1.1",
    "catw": "^1.0.1",
    "http-server": "^0.9.0",
    "tsify": "^2.0.7",
    "typescript": "^2.1.4",
    "watchify": "^3.8.0"
  },
  "dependencies": {
    "@types/jquery": "^2.0.34",
    "jquery": "^3.1.1",
    "skeleton-css": "^2.0.4"
  }
}

追記

いいのがあった.
www.npmjs.com
npm-run-all を使って build,watch をまとめてみた.

$ npm install --save-dev npm-run-all
package.json
  "scripts": {
    "clean": "rm -rf dist/",
    "prebuild": "npm run clean && mkdir dist",
    "build:css": "cat ./node_modules/skeleton-css/css/* ./src/css/custom.css > dist/bundle.css",
    "build:html": "cat ./src/html/index.html > dist/index.html",
    "build:ts": "browserify ./src/ts/app.ts -p [tsify] | uglifyjs -c > dist/bundle.js",
    "build": "npm run prebuild && npm-run-all build:*",
    "watch:ts": "watchify ./src/ts/app.ts -p [tsify] -v --debug -o dist/bundle.js",
    "watch:css": "catw ./src/css/custom.css ./node_modules/skeleton-css/css/* -v -o dist/bundle.css",
    "watch:html": "catw ./src/html/index.html -v -o 'dist/index.html'",
    "watch": "npm-run-all --parallel watch:*",
    "serve": "http-server dist/ -p 8000",
    "dev": "npm run serve & npm run watch"
  }

感想

  • 果たしてこれはシンプルでシュッと書ける環境なのか
  • npm scriptsもなんだかんだ段々色々やりたくなってきてやめろ〜感してくる
  • cssとhtmlはどうするのがいい感じなのかいまいちわからない
  • 小規模ならtsを選択せずともES6+babelで良いのではないか. 小規模で型は必要なのか
  • 色々な情報が氾濫して混乱したので,結局地道にmanしたり公式読んだりソース読むことになった
  • フロントエンドおばあちゃんだったので勉強になった

*1:あとでuglifyjsとかにかけたい

*2:変換例 · GitHub

*3:tscでトランスパイルした結果のjsをbrowserifyにかけてもいい気もする.

*4:なに言ってるかわかんなくなってきた

*5:本体は必要. あと jquery plugins の jquery依存を解決するには別途 browserify-shim が必要だが,今回は省略する.

*6:グローバル空間が汚されることを危惧している