TypeScript と webpack で作る npm パッケージ

## はじめに

[TypeScript](https://www.typescriptlang.org/) と [webpack](https://webpack.js.org/) を使って npm パッケージを作成して publish する方法を紹介します。実際に npm パッケージを publish する際に考慮しておいたほうがよい TypeScript や webapck の設定については省略しているので悪しからず。

## 環境

- Node.js 10.13.0
- npm 6.4.1
- TypeScript: 3.3.4000
- ts-loader 5.3.3
- webpack 4.29.6

## package.json ファイルの作成

以下のコマンドで package.json ファイルを作成します。

```
npm init
```

次の項目の入力を求められますが、すべて後で変更可能なので気軽に決めて OK です。 ただしパッケージ名は重複不可なので、既に同じ名前のものが存在しないか確認しておいた方がよいでしょう。

- package name
- version
- description
- entry point
- test command
- git repository
- keywords
- license

## パッケージのディレクトリ構成

特に決まりはありませんが、ここでは例として次のような構成で説明します。

```
.
├── dist // ビルド後の成果物の出力先
├── package.json
└── src // ソースコード
```

## webpack を使ったビルド処理

まず [webpack](https://github.com/webpack/webpack) と [webpack-cli](https://github.com/webpack/webpack-cli) をインストールします。

```
npm install --save-dev webpack webpack-cli
```

次に webpack の設定ファイルを作ります。

モジュールの定義方法によって設定内容は異なりますが、今回は [UMD](https://github.com/umdjs/umd) で作ることにします。 `library` に指定した名前はブラウザ上で実行する際のグローバル変数名となります。ここでは `Calc` としましたが任意です。なお `globalObject` については注釈を見てください。[^注1]

```javascript:webpack.config.js
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'index.js',
    path: path.resolve(__dirname, 'dist'),
    library: 'Calc',
    libraryTarget: 'umd',
    globalObject: 'typeof self !== \'undefined\' ? self : this'
  }
};
```

次に src/index.js を用意します。ここでは例として次のような内容にします。

```javascript:src/index.js
export function sum(a, b) {
  return a + b;
}
```

ビルド処理を package.json に記述します。

```diff
diff --git a/package.json b/package.json
index 77904e2..9810380 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,8 @@
   "description": "",
   "main": "index.js",
   "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "build": "npx webpack --config webpack.config.js"
   },
   "repository": {
     "type": "git",
```

試しにビルドしてみましょう。正常にビルドできれば次のような表示がされるはずです。

```
npm run build

...

> npx webpack --config webpack.config.js

Hash: e08c8cfaa6ee7d9b3aae
Version: webpack 4.29.6
Time: 94ms
Built at: 2019-03-23 08:57:52
   Asset       Size  Chunks             Chunk Names
index.js  956 bytes       0  [emitted]  main
Entrypoint main = index.js
[0] ./src/index.js 28 bytes {0} [built]
```

## ソースコードを TypeScript で書けるようにする

[TypeScript](https://github.com/Microsoft/TypeScript) と [ts-loader](https://github.com/TypeStrong/ts-loader) をインストールします。

```
npm install --save-dev typescript ts-loader
```

次に tsconfig.json ファイルを用意します。ここで設定内容を変更しても構いませんが、ひとまずはデフォルト設定のままでも OK です。

```
npx tsc --init
```

次に webpack の設定を修正します。

```diff
diff --git a/webpack.config.js b/webpack.config.js
index 8cd9058..9d35ec9 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -2,7 +2,15 @@ const path = require('path');

 module.exports = {
   mode: 'development',
-  entry: './src/index.js',
+  entry: './src/index.ts',
+  resolve: {
+    extensions: [".ts", ".js"],
+  },
+  module: {
+    rules: [
+      {test: /\.ts$/, loader: 'ts-loader'},
+    ],
+  },
   output: {
     filename: 'index.js',
     path: path.resolve(__dirname, 'dist')
```

entry に指定するファイルの拡張子を変更しているので、ファイルもリネームしておきます。

```
mv src/index.js src/index.ts
```

これでソースコードを TypeScript で書くことができるようになっているはずです。試しに src/index.ts を次のように書き換えてみます。

```typescript:src/index.ts
export function sum(a: number, b: number) {
  return a + b;
}
```

再度ビルドして出力されたファイルを確認すると src/index.ts に書いた内容が出力されていることがわかります。

```
npm run build
```

```

tail -n 10 dist/index.js
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

"use strict";
eval("\nObject.defineProperty(exports, \"__esModule\", { value: true });\nfunction sum(a, b) {\n    return a + b;\n}\nexports.sum = sum;\n\n\n//# sourceURL=webpack://Calc/./src/index.ts?");

/***/ })

/******/ });
});
```

## npm パッケージのエントリーポイントとなるファイルを更新する

ここまでで dist/index.js にビルドされた JavaScript が出力されるようになりました。このファイルが npm パッケージのエントリポイントとなるように package.json を修正します。

```diff
diff --git a/package.json b/package.json
index 1b982a9..593a3d5 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "version": "1.0.0",
   "description": "",
-  "main": "index.js",
+  "main": "dist/index.js",
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",
     "build": "npx webpack --config webpack.config.js",
```

## パッケージの公開前のビルド処理を定義

用意したビルド処理を npm パッケージとして publish する前に行う処理として定義します。 npm-scripts の prepare として定義することで publish する前にビルド処理を行い、生成された成果物をパッケージに含めることができます。

```diff
diff --git a/package.json b/package.json
index 1a5ce7e..1b982a9 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,8 @@
   "main": "index.js",
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",
-    "build": "npx webpack --config webpack.config.js"
+    "build": "npx webpack --config webpack.config.js",
+    "prepare": "npm run build"
   },
   "repository": {
     "type": "git",
```

## パッケージの公開

まだ npm のアカウントを持っていなければ[ココ](https://www.npmjs.com/signup)から作ります。

作成したアカウントでログインします。

```
npm login
```

あとは以下のコマンドでパッケージを公開することができます。テストや Linter などを用意している場合は事前に実行するなどして正常に動作することを確認しましょう。

```
npm publish
```

## バージョンの更新

[Semantic Versioning](https://semver.org/) に従って更新するのが一般的です。

- API に後方互換性のない変更がある場合にはメジャーバーションを更新
- 後方互換性のある機能追加の場合にはマイナーバーションを更新
- 後方互換性のあるバグ修正の場合にはパッチバーションを更新

npm のサブコマンドとしてそれぞれ以下のコマンドが用意されているので、変更内容に合うものを使います。

```
npm version major
npm version minor
npm version patch
```

実行すると package.json, package-lock.json 内に記述された version プロパティが更新されます。また Git リポジトリであるならばバージョンと同名のタグが作成されます。

パッケージの公開時と同様に、再度 npm publish することでバージョンを更新することができます。

```
npm publish
```

[^注1]: 2019-03-23 現在、 [UMD で出力する際に起きる問題の回避措置](https://github.com/webpack/webpack/issues/6522#issuecomment-371120689)です。将来的には必要なくなっている可能性があります。