Help us understand the problem. What is going on with this article?

VSCodeでTypeScript/Node.jsの開発環境を作る(UT・カバレッジ・ログ出力・リリース手順含む)

More than 1 year has passed since last update.

Visual Studio CodeでTypeScriptの開発環境を作る手順を調べてみました。
Node.jsで動く簡単なCLIツールを作る前提で進めていきますが、他の開発にも応用できるかと思います。

なお、Visual Studio CodeNode.jsはインストール済みの前提です。
Windows 10環境で以下のバージョンで確認しています。

C:\>node -v
v8.10.0

C:\>npm -v
3.10.6

C:\>code -v
1.21.1
79b44aa704ce542d8ca4a3cc44cfca566e7720f1
x64

(ショートカット)

以降で使っていくコマンドです。(一度にインストールしたい人向け)

mkdir ts-sample
cd ts-sample

npm init -y
mkdir .vscode src test config

npm i -SEB config log4js
npm i -DE typescript ts-node tslint
npm i -DE @types/node @types/config
npm i -DE espower-typescript power-assert mocha @types/mocha
npm i -DE nyc
npm i -DE rimraf cpx

node_modules\.bin\tsc --init
npm i -g npm-run
npm-run tslint --init

code --install-extension eg2.tslint
code --install-extension fabiospampinato.vscode-commands

TypeScriptプロジェクトの作成

プロジェクトフォルダー(例:ts-sample)をまず作成して、npm initでpackage.jsonを出力します。

mkdir ts-sample
cd ts-sample
npm init -y
(初回生成)package.json
{
  "name": "ts-sample",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": ""
}

公開しない限り、ほとんどの項目は不要なので適当に削除しておきます。

package.json
{
  "name": "ts-sample",
  "version": "1.0.0",
  "private": true
}

ただし、以下のようなWARNがnpmコマンドを打つたびに出るのもアレなので"private": trueで非公開設定にしておきます。

npm WARN ts-sample@1.0.0 No description
npm WARN ts-sample@1.0.0 No repository field.
npm WARN ts-sample@1.0.0 No license field.

続いて、プロジェクトに必要なディレクトリを作成します。

mkdir .vscode src test config

なお、今回のプロジェクト構成は以下のようになっています。

├─.vscode
├─bin          # リリース後のjsソース配置先
├─build        # ビルド用の一時ディレクトリ
│  ├─src       # jsソース出力先
│  └─test      # jsテスト出力先
├─config   
├─coverage
├─logs
├─node_modules
├─src          # tsソース
└─test         # tsテスト

TypeScriptトランスパイラ(tsc)とTS実行ツール(ts-node)のインストール

以下のコマンドでインストールします。
npm inpm installと同じです。-D--save-dev-E--save-exactと同じで、package.jsonのdevDependenciesにバージョン固定で記録されます)

npm i -DE typescript ts-node 

インストール後、tsc --initでtsconfig.json(トランスパイル設定ファイル)を出力します。

node_modules\.bin\tsc --init 
(初回生成[コメント省略])tsconifg.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true
  }
}

今回はNode.jsで動くCLIツールを作る前提なので、targetはより新しいES2017にしておきます。
また、デバッグ用に"sourceMap": trueと、js出力先outDir、tsソース配置先includeを追加します。

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2017",
    "module": "commonjs",
    "sourceMap": true,
    "outDir": "build",
    "strict": true
  },
  "include": [
    "src/**/*",
    "test/**/*"
  ]
}

なお、esModuleInteropは削除しています。これがtrueになっているとconfigモジュールを使ったときにTypeError: config.get is not a functionというエラーが出たので、現時点では削除しておいたほうがよさそうです。

簡単なソースでお試し実行

src/main.ts
const message: string = "world";
console.log(`Hello ${message}`);

src内に上記のようなtsを作って、tscでトランスパイル(コンパイル)すると

node_modules\.bin\tsc

以下のようなjsが出力されます。
(test内が空だとbuild直下、空でなければbuild/srcに出力されます)

build/main.js
"use strict";
const message = "world";
console.log(`Hello ${message}`);
//# sourceMappingURL=main.js.map

以下のコマンドで実行するとHello worldと出力されると思います。

node build/main.js

なお、ts-nodeを使うとコンパイルせずに実行できます。

node_modules\.bin\ts-node src/main.ts

また、毎回node_modules\.bin\と打つのも大変なので、npm-runをインストールすると楽できます。
(以降の説明でもnpm-runはインストール済みとしています)

npm i -g npm-run
npm-run ts-node src/main.ts

注意点として、Windowsの場合、npm-runは拡張子が.exeのものを先に見てしまうようで、tscはC:\Program Files (x86)\Microsoft SDKs\TypeScript\1.0\tsc.exeが先に見つかってしまうとnpm-runでは使えません。
(コマンドプロンプトからだとwhere tscでtscの場所が見れます)

TSLintのインストール・設定

まずはtslintをインストールします。

npm i -DE tslint

さらにVSCodeでTSLint拡張を画面で選択するか、以下のコマンドでインストールします。

code --install-extension eg2.tslint

以下のコマンドでtslint.jsonを生成すると、lint(コードの静的解析)が有効になります。

npm-run tslint --init
(初回生成)tslint.json
{
    "defaultSeverity": "error",
    "extends": [
        "tslint:recommended"
    ],
    "jsRules": {},
    "rules": {},
    "rulesDirectory": []
}

"defaultSeverity": "error"だとコンパイルエラーとlintのエラーの区別がつかなくなるのでwarnに変更しておきます。
console.logで怒られないようにしたい場合は"no-console": falseを設定します。

tslint.json
{
    "defaultSeverity": "warn",
    "extends": [
        "tslint:recommended"
    ],
    "jsRules": {},
    "rules": {
        "no-console": false
    },
    "rulesDirectory": []
}

VSCodeでコード実行タスクの設定

以下のようにコマンドパレットで選択すると、tasks.jsonが出力されます。

task.gif

(初回生成).vscode/tasks.json
{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "echo",
            "type": "shell",
            "command": "echo Hello"
        }
    ]
}

以下のようにするとタスク経由でtsコードの実行ができます。

vscode/tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "ts-node",
            "type": "shell",
            "command": "npm-run ts-node ${relativeFile}"
        }
    ]
}

ユニットテストの実行

Mochapower-assertを使ったユニットテストの設定をしていきます。
まずは以下のコマンドで必要なツールをインストールします。

npm i -DE @types/node
npm i -DE espower-typescript power-assert mocha @types/mocha

以下のようなソースで実行してみます。
if (require.main === module)の中はソースが(テスト経由でなく)直接実行されたときのみ動きます)

src/main.ts
export function main(message: string) {
    console.log(`${message}`);
    return `Hello ${message}`;
}

if (require.main === module) {
    main(process.argv[2]);
}
test/main.test.ts
import assert = require("assert");
import {main} from "../src/main";

describe("main()", () => {
    const message = "world!";
    it("hello world!", () => {
        assert(main(message) === "Hello world");
    });
});

以下のようにmochaのタスクを作成して実行すると

vscode/tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "ts-node",
            "type": "shell",
            "command": "npm-run ts-node ${relativeFile}"
        },
        {
            "label": "mocha",
            "type": "shell",
            "command": "npm-run mocha -r espower-typescript/guess ${relativeFile}"
        }
    ]
}

以下のように詳細なエラー表示がされます。
(上記で${relativeFile}の代わりに${file}を使うとpower-assertが効かなくなるので注意です)

mocha_error.png

(比較対象の右辺の最後に!が足りないのでエラーになっています)

(オプション)ステータスバーにボタン設置

VSCodeにCommands拡張を入れるとステータスバーのボタンから各種実行ができて便利です。
VSCodeの拡張画面からインストールするか、以下のコマンドでインストールできます。

code --install-extension fabiospampinato.vscode-commands

インストール後、コマンドパレットからCommands: Edit Configurationを実行するとcommands.jsonが出力されます。

(初回生成).vscode/commands.json
{
  "commands": [
    {
      "command": "commands.refresh",
      "text": "$(sync)",
      "tooltip": "Refresh commands",
      "color": "#FFCC00"
    }
  ]
}

以下のように設定すると

vscode/commands.json
{
  "commands": [
    {
      "command": "commands.refresh",
      "text": "$(sync)",
      "color": "#FFCC00"
    },
    {
      "command": "workbench.action.tasks.runTask",
      "arguments": ["ts-node"],
      "text": "$(triangle-right) ts-node",
      "color": "#CCFFCC",
      "filterFileRegex": "\\.ts"
    },
    {
      "command": "workbench.action.tasks.runTask",
      "arguments": ["mocha"],
      "text": "$(octoface) mocha",
      "color": "#EEBBAA",
      "filterFileRegex": "\\.ts"
    },
    {
      "command": "workbench.action.terminal.toggleTerminal",
      "text": "$(terminal)"
    }
  ]
}

以下のような表示になります。

statusbar.png

なおtextにはOcticonsが使えます。

テストカバレッジの出力

nycを使うと簡単にテストカバレッジが出力できます。

npm i -DE nyc
(抜粋)package.json
  "scripts": {
    "test": "nyc -i ts-node/register --temp-directory coverage/.nyc -r text -r html -n test/**/*.ts -n src/**/*.ts -e .ts mocha test/**/*.ts"
  },

上記のようにpackage.jsonのscriptsにtestを設定すると、npm testで以下のような出力がされます。

cover_text.png

以下はcoverageディレクトリ内にHTMLで出力されたものです。

cover_html.png

(オプション)デバッグ実行の設定

デバッグ画面で歯車のようなボタンを押すとlaunch.jsonが出力されます。

(初回生成).vscode/launch.json
{
    // IntelliSense を使用して利用可能な属性を学べます。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "プログラムの起動",
            "program": "${workspaceFolder}/src\\main.ts",
            "outFiles": [
                "${workspaceFolder}/**/*.js"
            ]
        }
    ]
}

以下の設定でデバッグ実行ができてブレークポイントなどが使えます。
デバッグコンソールよりもターミナルのほうが、標準出力の表示がされるなど便利なので"console": "integratedTerminal"の設定をしています。

vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "ts-node[debug]",
            "program": "${workspaceFolder}/node_modules/ts-node/dist/bin.js",
            "args": [
                "${relativeFile}"
            ],
            "console": "integratedTerminal"
        },
        {
            "type": "node",
            "request": "launch",
            "name": "mocha[debug]",
            "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
            "args": [
                "--no-timeouts",
                "--colors",
                "--require",
                "espower-typescript/guess",
                "${relativeFile}"
            ],
            "console": "integratedTerminal"
        }
    ]
}

(VSCode 1.21.1にはバグがあってブレークポイントが効かないことがあります。VSCode Insiders 1.20で上記の動作確認しています)

(オプション)設定ファイルの読み込みとログ出力設定

configで設定ファイルを読み込んで、log4jsでログ出力するサンプルを作ってみました。
以下のコマンドで必要なものをインストールします。
-S--saveと同じでpackage.jsonのdependenciesに記録されます。-B--save-bundleと同じでbundleDependenciesに記録され、パッケージ化した際に内部にライブラリが含まれるようになります)
(なお、@types/log4jsは不要です。log4jsに既に含まれていますので)

npm i -SEB config log4js
npm i -DE @types/config

設定ファイルは標準のもの(default.json)と開発時に値が上書きされるもの(development.json)を作成しています。(詳しくはライブラリのドキュメントを見てください)

config/default.json
{
    "log4js": {
        "appenders": {
            "out": {
                "type": "stdout"
            },
            "error": {
                "type": "dateFile",
                "filename": "logs/error.log",
                "daysToKeep": 31
            }
        },
        "categories": {
            "default": {
                "appenders": [
                    "error"
                ],
                "level": "INFO"
            },
            "console": {
                "appenders": [
                    "out",
                    "error"
                ],
                "level": "INFO"
            }
        }
    }
}
config/development.json
{
    "log4js": {
        "categories": {
            "default": {
                "level": "DEBUG"
            },
            "console": {
                "level": "DEBUG"
            }
        }
    }
}

ソースは以下のとおりです。
#!/usr/bin/env nodeはパッケージ化した後、そのパッケージのインストール時に自動でnode_modules/.binに実行スクリプトが作られるようにするためのものです。

src/main.ts
#!/usr/bin/env node
process.env.NODE_CONFIG_DIR = __dirname + "/../config";
import * as config from "config";
import * as log4js from "log4js";

log4js.configure(config.get("log4js"));
const log = log4js.getLogger();
const consoleLog = log4js.getLogger("console");

export function main(argv: string[]) {
    try {
        consoleLog.debug(`argv: ${argv}`);
        const message: string = argv[2];
        return `Hello ${message.charAt(0)}`;
    } catch (e) {
        log.error(e);
    } finally {
        log4js.shutdown((e) => e && console.log(e));
    }
}

if (require.main === module) {
    main(process.argv);
}

リリース

リリース用に、rm -rf相当のrimrafとcp -R相当のことができるcpxをインストールします。

npm i -DE rimraf cpx

package.jsonにはリリース用のスクリプト(release)と、パッケージ内に含めるファイル(files)と、node_modules/.binに生成される実行スクリプトの設定(ファイル名:sample、起動ソース:bin/main.js)を記載します。

(抜粋)package.json
  "scripts": {
    "test": "nyc -i ts-node/register --temp-directory coverage/.nyc -r text -r html -n test/**/*.ts -n src/**/*.ts -e .ts mocha test/**/*.ts",
    "release": "rimraf build bin && tsc && cpx build/src/** bin && cd build && npm pack -cwd .."
  },
  "files": [
    "bin",
    "config/default.json"
  ],
  "bin": {
    "sample": "bin/main.js"
  },

npm run releaseを実行すると、スクリプトが走ってbuild内にts-sample-1.0.0.tgzが出力されます。
このファイルを別のところに持っていって、npm i ts-sample-1.0.0.tgzとするとインストールができます。

これでプロジェクトの作成からリリースまで、ひととおりの作業ができるかと思います。

(その他)package.jsonの最終形

package.json
{
  "name": "ts-sample",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "test": "nyc -i ts-node/register --temp-directory coverage/.nyc -r text -r html -n test/**/*.ts -n src/**/*.ts -e .ts mocha test/**/*.ts",
    "release": "rimraf build bin && tsc && cpx build/src/** bin && cd build && npm pack -cwd .."
  },
  "files": [
    "bin",
    "config/default.json"
  ],
  "bin": {
    "sample": "bin/main.js"
  },
  "devDependencies": {
    "@types/config": "0.0.34",
    "@types/mocha": "2.2.48",
    "@types/node": "9.4.7",
    "cpx": "1.5.0",
    "espower-typescript": "8.1.3",
    "mocha": "5.0.4",
    "nyc": "11.6.0",
    "power-assert": "1.4.4",
    "rimraf": "2.6.2",
    "ts-node": "5.0.1",
    "tslint": "5.9.1",
    "typescript": "2.7.2"
  },
  "dependencies": {
    "config": "1.30.0",
    "log4js": "2.5.3"
  },
  "bundledDependencies": [
    "config",
    "log4js"
  ]
}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした