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

TypeScriptでCLIツールを作って、npmパッケージにする

More than 1 year has passed since last update.

環境

  • node 9.4.0
  • TypeScript 2.9.2
  • npm 6.1.0
  • webpack 4.16.3
  • Visual Studio Code 1.25.1

予めtj/n: Node version management等を使って、Node.jsをインストールしておきましょう。

実践

適当な名前でプロジェクトディレクトリを作っておきます。ココではts-sampleとします。

$ mkdir ts-sample
$ cd ts-sample/
$ git init
$ curl https://raw.githubusercontent.com/github/gitignore/master/Node.gitignore > .gitignore

webpackでTypeScriptのBuild環境構築

まずpackage.jsonを作成します。

$ npm init

適当に答えていくと、こんな感じになります。

package.json
{
  "name": "ts-sample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT"
}

TypeScriptと、ビルドするために必要なパッケージを、devDependenciesでインストールします。
今回はwebpackを使うため、ts-loaderもインストールします。

$ npm install --save-dev webpack webpack-cli typescript ts-loader

TypeScriptの設定であるtsconfig.jsonを作成します。
手動で作成しても良いですが、$ tsc --initで自動生成することもできます。

$ ./node_modules/typescript/bin/tsc --init

webpackの設定であるwebpack.config.jsを作成します。

webpack.config.js
module.exports = {
  entry: './src/index.ts',
  target: 'node',
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: ['ts-loader'],
        exclude: /node_modules/
      }
    ]
  },
  resolve: {
    extensions: [ '.tsx', '.ts', '.js' ]
  }
};

npmパッケージとして公開しない場合の設定

もしnpmパッケージとして公開する予定がない場合は、package.jsonに"private": trueを付けておきましょう。誤って$ npm publishで公開してしまう事を回避できます。

また、このときdescriptionなど「公開しないならば不要な設定項目」は一緒に消してしまっても構いません。(privateでない場合、package.jsonが正しく設定されていないと各種警告が表示されます)

package.json
 {
-  "name": "ts-sample",
-  "version": "1.0.0",
-  "description": "",
+  "private": true,
   "main": "index.js",

参考: package.json | npm Documentation

ソースコードの記述

TypeScriptのコーディングを始める前に、Node.js自体の型定義ファイルをインストールしておきます。

$ npm install --save-dev @types/node

実際のソースコードを書いていきます。

$ mkdir src
$ code src/index.ts

今回は例として、コマンドライン引数から数字を受け取って、Fizz Buzzで返すだけのCLIプログラムを書いてみます。

src/index.ts
const num : number = +process.argv[2];
console.log(fizzbuzz(num));

function fizzbuzz(num : number) : string {
  if (num % 15 == 0) {
    return "FizzBuzz";
  } else if (num % 3 == 0) {
    return "Fizz";
  } else if (num % 5 == 0) {
    return "Buzz";
  }
  return num.toString();
}

ビルドして実行

webpackでビルドするnpm scriptを記述します。

package.json
   "scripts": {
+    "build": "webpack",

ビルドします。

$ npm run build

実行してみます

$ node dist/main.js 1
1
$ node dist/main.js 2
2
$ node dist/main.js 3
Fizz

うまく動きました。

npm経由でコマンドとして実行

作成したプログラムを、CLIアプリケーションとして実行できるように設定していきます。

まずpackage.jsonにbinの設定を追加します。

package.json
+  "bin": {
+    "ts-sample": "./bin/ts-sample.js"
+  },

参考: package.json | npm Documentation

設定したbin/ts-sample.jsファイルからプログラムを実行できるようにします。

bin/ts-sample.js
#!/usr/bin/env node
require('../dist/main.js');

$ npm linkコマンドを使うと、このプロジェクトに対してシンボリックリンクを貼り、ローカルでnpm moduleをglobal installしたかのように使えるようになります。

$ npm link

参考: link | npm Documentation

こうしてpackage.json内でbinに指定したキーで、コマンドが実行になります。

$ ts-sample 15
FizzBuzz

開発/本番の各ビルドステージの設定

現在出力されたjs:dist/main.jsは、プロダクションビルドの設定になっています。
中身はminifyされており単体では読むことができません。

dist/main.js
!function(e){var r={};function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=r,t.d=function(e,r,n){t.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:n})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,r){if(1&r&&(e=t(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(t.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var o in e)t.d(n,o,function(r){return e[r]}.bind(null,o));return n},t.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(r,"a",r),r},t.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},t.p="",t(t.s=0)}([function(e,r,t){"use strict";var n=+process.argv[2];console.log(function(e){if(e%15==0)return"FizzBuzz";if(e%3==0)return"Fizz";if(e%5==0)return"Buzz";return e.toString()}(n))}]);

また、開発時はビルド用になんどもコマンドを打つのも面倒です。

  • 開発時にはファイルの変更を監視したい
  • 開発時にはソースマップを有効にしたい
  • 本番ビルドではソースマップを無効にしたい
  • 後にnpm publishする直前に自動で本番ビルドしたい

これらの問題を解決するため、webpackの設定を開発用と本番用で分離します。
webpack.dev.jsを作成し、webpack.config.jsを読み込んで設定をmergeします。

まずmergeするためのライブラリを読み込みます。

$ npm install webpack-merge --save-dev

webpack.dev.jsを作成し、下記の記述をします。

webpack.dev.js
const merge = require('webpack-merge');
const common = require('./webpack.config.js');
module.exports = merge(common, {
  mode: 'development',
  devtool: 'inline-source-map',
});

package.jsonに、それぞれのビルド用のコマンドを追記します

package.json
   "scripts": {
+    "prepare": "webpack --config webpack.config.js",
+    "watch": "webpack --config webpack.dev.js --watch"
   },

参考: Production - webpack

こうして、開発時はnpm run watchすることでファイル変更時に自動でビルドされるようになりました。

$ npm run watch

このときはdist/main.jsの中身も読みやすいものになっています。

dist/main.js(抜粋)
var num = +process.argv[2];
console.log(fizzbuzz(num));
function fizzbuzz(num) {
    if (num % 15 == 0) {
        return "FizzBuzz";
    }
    else if (num % 3 == 0) {
        return "Fizz";
    }
    else if (num % 5 == 0) {
        return "Buzz";
    }
    return num.toString();
}

またprepareは、$ npm publishする前などに自動的に実行されます。なので、公開時には自動的に本番の設定でビルドされたものがpublishされます。もちろん、$ npm run prepareで手動実行することもできます。

参考: scripts | npm Documentation

VSCodeでWatchする

ここではVisual Studio Codeのnpm連携機能を使ってみます。

コマンドパレットから>Tasks: Configure Tasknpm: watchすると、下記JSONが生成されます。

tasks.json
{
  // See https://go.microsoft.com/fwlink/?LinkId=733558
  // for the documentation about the tasks.json format
  "version": "2.0.0",
  "tasks": [
    {
      "type": "npm",
      "script": "watch",
      "problemMatcher": []``
    }
  ]
}

コマンドパレットからtask npm: watchすると、ターミナルウィンドウがひとつ自動的に立ち上がり$ npm watchを実行します。

また、VSCodeの設定でnpm.enableScriptExploreを有効にすると、package.json内のnpm scriptsを読み取ってサイドバーに表示されます。

image.png
ここでwatchをクリックすることでも$ npm watchを実行できます。

npmignoreの設定

ここでは、npmパッケージとして公開したい場合を考えます。

  • gitリポジトリには、dist/以下のビルド済JavaScriptファイルは無視して、ビルド前のTypeScriptソースコードのみを登録したい
  • npmパッケージとしては、src/以下のTypeScriptソースコードは無視して、ビルド済のJavaScriptファイルのみを登録したい

これらを実現するため、.gitignoreに下記追記します

.gitignore
dist/

そして.npmignoreを作成し、下記追記します

.npmignore
src/

すると、gitリポジトリにはTypeScriptコードのみが含まれ、npmパッケージにはビルド済JavaScriptファイルのみが含まれるようになります。

参考: developers | npm Documentation

利用

GitHubに上がっていれば、npm installコマンドでインストールすることが可能です。

$ npm install --global github_user_name/project_name

そしてnpmパッケージとして$ npm publish済であれば、下記コマンドでnpmからインストールできます。

$ npm install --global project_name

その他の詳細は、公式ドキュメントが詳しいです。
https://docs.npmjs.com/cli/install

参考

また、こうしてできたCLIツールが下記になります。
s2terminal/i_read_u: Extracting commands from README markdown file

suzuki_sh
Windowsでコンピュータの世界が広がります
https://www.s2terminal.com
finergy-a-tm
大阪府大阪市北区角田町8番1号 梅田阪急ビル オフィスタワー35F
https://finergy.a-tm.co.jp/
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