【TypeScript】はじめてのnpm package公開

パッケージを自作したら、思いのほか簡単だったので共有。typeScriptでurlを生成する便利関数をつくります。

typeScriptでつくらないよ〜って方も特に手順は変わらないのでみていただけると幸いです

最終的なディレクトリはこんな感じ

<pre>
📦url-generator
 ┣ 📂__tests__
 ┣ 📂lib
 ┃ ┣ 📜index.d.ts
 ┃ ┗ 📜index.js
 ┣ 📂node_modules
 ┣ 📜.gitignore
 ┣ 📜.npmignore
 ┣ 📜LICENSE
 ┣ 📜README.md
 ┣ 📜index.ts
 ┣ 📜package-lock.json
 ┣ 📜package.json
 ┗ 📜tsconfig.json
</pre>

## フォルダを作成
`$ mkdir url-generator`

## TypeScriptをインストール
devDependenciesに追加

`$ npm install -D typescript`

## tsconfig.jsonを生成、編集
``$ tsc --init``

中身はこんな感じ。

```Json:tsconfig.json
{
  "compilerOptions": {
    "target": "es2015",
    "module": "commonJS",
    "outDir": "./lib",
    "declaration": true,
    "strict": true,
  },
  "include": ["index.ts"]
}
```

### "target"
どのバージョンでjsを出力するか。

### "module"
出力するjsのモジュールの仕組みとして何を使用するか。

### "outDir"
出力するディレクトリーを指定<br/>
何も指定しない場合、roodディレクトリーに出力される

### "declaration"
タイプ定義ファイルを生成するか。<br/>

こんな感じで、1ファイルにつき定義ファイルが生成される

<pre>
📦lib
┣ 📂utils
┃ ┣ 📜foo.d.ts
┃ ┗ 📜foo.js
┣ 📜hoo.d.ts
┣ 📜hoo.js
┣ 📜index.d.ts
┗ 📜index.js
</pre>

### "strict"

<pre>
--noImplicitAny
--noImplicitThis
--alwaysStrict
--strictBindCallApply
--strictNullChecks
--strictFunctionTypes
--strictPropertyInitialization
</pre>

が全てtrueになる。

### "include"
コンパイルする対象のファイルを指定。<br/>
他にオプションを詳しく知りたい方は、こちらを[参照](https://qiita.com/ryokkkke/items/390647a7c26933940470#jsx "tsconfig.jsonの全オプションを理解する（随時追加中）")

## package.jsonを生成、編集
`$ npm init`

中身がこんな感じ

```Json:package.json
{
  "name": "パッケージの名前",
  "version": "1.0.0",
  "description": "パッケージの説明",
  "main": "lib/index.js",
  "types": "lib/index.d.ts",
  "keywords": [
    "typescipt",
    "url",
    "url-generator",
    "params"
  ],
  "files": [
    "lib"
  ],
  "repository": {
    "type": "git",
    "url": "githubレポjと"
  },
  "dependencies": {},
  "devDependencies": {
    "typescript": "^4.1.3"
  },
  "scripts": {
    "build": "tsc",
    "prepublishOnly": "npm run build"
  },
  "author": "taichi-t",
  "license": "MIT",
}

```

### "name"
モジュールの名前<br/>
他のモジュールと名前が被ってはいけないから、npmのウェブサイトで検索してから決めるとよい
![npmの検索バー](//images.ctfassets.net/nwdyo7rxqvgg/2Zxd52kyeihk2meAIuPTiF/53bec23d83505b387486df816ab6aeb4/Screen_Shot_2021-01-26_at_19.41.44.png)
＠taichi-t/moduleNameのようにスコープをつけることもできる。

ただしスコープをつけた場合、自動でprivateパッケージになってしまい公開する際に金を払えとエラーが出る。その対処法は後述。

### "version"
モジュールのバージョン<br/>
パッケージを変更する際は、バージョンを書き換えないと公開する時にエラーを吐く。

### "description"
npm searchした際や検索エンジンの説明欄に表示される。
![descriptionの表示場所１](//images.ctfassets.net/nwdyo7rxqvgg/7zyaOVmuxtoIPmNHayQiR9/2cb404eaa6bb72d735f549479b3c13b4/Screen_Shot_2021-01-26_at_19.34.45.png)

![descriptionの表示場所２](//images.ctfassets.net/nwdyo7rxqvgg/L25XlYbSlG8ZN6fSPa97b/35c40f929d6e6bac37f5719f5e482b7a/Screen_Shot_2021-01-26_at_19.37.40.png)

### "main"
モジュールをimportした際、一番初めに呼ばれるファイルを指定。

### "types"
型定義ファイルを指定。

### "keywords"
npmのpackageページのkeyword欄に表示される
![keywordsの表示場所](//images.ctfassets.net/nwdyo7rxqvgg/1DewH2HAvYCIzujNkDZ739/09282eaa610b0cee0c5bb0b33bfbfe92/Screen_Shot_2021-01-26_at_20.03.12.png)

### "repository"
ソースコードが管理されているURLを指定。

### "author"
複数の場合配列で指定。

### "license"
よく使われる寛容なライセンスはMIT、Apache2.0、ISC、BSD。
MITをよくみる気がする。<br/>
オープンソースライセンスについて詳しく知りたい方は、こちらを[参考](https://licenses.opensource.jp/ "オープンソースライセンスの日本語参考訳")に

### "files"
公開する際に、ここで指定したファイルは.npmignore、.gitignoreに記載されていても除外されることはない。
詳しくはこちらを[参照](https://docs.npmjs.com/cli/v6/configuring-npm/package-json#files "Specifics of npm's package.json handling")

ほかにもプロパティを知りたい方は[こちら](https://qiita.com/dondoko-susumu/items/cf252bd6494412ed7847 "package.jsonの中身を理解する")にまとめられています。

## index.tsに関数を定義

```JavaScript:index.ts
const isNumber = (value: number | string): value is number =>
  typeof value === 'number';

const generateUrl = (
  baseUrl: string,
  params: { [key: string]: string | number }
): string => {
  const url = new URL(baseUrl);
  Object.keys(params).forEach((key) => {
    const value = params[key];
    if (isNumber(value)) {
      return url.searchParams.set(key, String(value));
    } else {
      url.searchParams.set(key, value);
    }
  });
  return url.toString();
};

export default generateUrl;
```

## コンパイルしてみる

`＄ tsc`

tscofigで指定したディレクトリーにファイルが生成されてる。
![libファイルの生成の確認](//images.ctfassets.net/nwdyo7rxqvgg/6F9W0So7wnO97yxAkPz4vs/5f3821389192fed593be2de4e022ed52/Screen_Shot_2021-01-27_at_1.09.46.png)

## .npmignore生成、編集
パッケージを公開する際に除外するファイルを指定

`$ touch .npmignore`

中身はこんな感じ

```npmignore:.npmignore
.gitignore
package-lock.json
node_modules
index.ts
tsconfig.json
__tests__
```

パッケージを公開する際、.gitignoreと.npmignoreがある場合
.npmignoreが適用される。つまりfiles>.npmignore>.gitignoreの順で優先される。

## README.mdの追加
少なくともタイトル、インストール方法、使い方、ライセンスを記述するとよい

こんな感じ。

```md:README.md
# <img src="https://raw.githubusercontent.com/devicons/devicon/master/icons/typescript/typescript-original.svg" alt="typescript" width="35" height="35"/> @taichi-t/url-generator
Generate urls with params.
## Installaion
`$ npm i @taichi-t/url-generator`
## Usage
```javascript
import urlGenerator from '@taichi-t/url-generator';
urlGenerator('http://www.example.com', { foo: 1, hoo: '2' });
// http://www.example.com/?foo=1&hoo=2
```

## LICENCEをGitリポジトリのメインページから作成

[ここ](https://licenses.opensource.jp/ "オープンソースライセンスの日本語参考訳")からコピペできるが、Gitリポジトリのメインページから自動生成する方法を紹介。

1. Gitリポジトリのメインページから__Add file__を選択
2. __Creat new file__を選択
3. 入力欄に__LICENCE__と記入すると画面右側に__Choose a licence template__が現れるので選択。
4. 好みのライセンスを選んぶと自動生成されるので、画面上でコミットしマージ。

これでLICENCEファイル作成完了、メインブランチにPullするのを忘れずに！！

これでコードを書く部分は終了

## npmアカウントを作成
[こちら](https://www.npmjs.com/ "npm - Build amazing things")からアカウントを作成

## ターミナルからログインしてpublish
プロジェクトルートで下記コマンドを実行。すると__Username__、__Password__、__Email__を尋ねられるので先ほど登録した情報を入力。

```shell:shell
$ npm login
Username:
Password：
Email:
```

ログインした状態で、
`$ npm publish`

スコープネーム（＠yourname／modulename）を使っている場合は、デフォルトでprivateになっている為公開できない。なので `$ npm publish --access=public`を実行

__これで終わり！簡単ですね！__

## おまけ
人に使ってもらうものだからテストをしてしておきたい！今回はjestを使用

### 各パッケージをインストール
devDependenciesに追加<br/>
`$ npm install --D jest @types/jest ts-jest`

### package.jsonを編集

- jestのconfigを記述する

```Json:package.json
  "jest": {
    "moduleFileExtensions": [
      "ts",
      "js"//jsがないとエラーになる
    ],
    "transform": {
      "^.+\\.ts$": "ts-jest"
    },
    "globals": {
      "ts-jest": {
        "tsconfig": "tsconfig.json"
      }
    }
  }
```
jest.config.jsに書いてもOK

- scriptにtestを追加

```Json:package.json
  "scripts": {
    "test": "jest",
    "build": "tsc",
    "prepublishOnly": "npm run test && npm run build"
  },

```
毎回公開する前にテストが走るようになってる。

## テストを作成

デフォルトで`__tests__`フォルダー内の.js、.jsx、.ts、および.tsxファイルと、サフィックスが.testまたは.specのファイルを検索するようになってるので、`__tests__`フォルダ内に、index.test.tsファイルをつくる。

```TypeScript:__tests__/index.test.ts
import generateUrl from '../index';

describe('generateUrl', () => {
  it('receives invalid url', () => {
    const baseUrl = 'this is not url';
    const params = { foo: '1', hoo: 2 };
    expect(() => generateUrl(baseUrl, params)).toThrow();
  });
});
```

## まとめ
作ってみると意外と簡単なnpm package。公開して使ってもらえると嬉しい。毎日weekly Downloadsみちゃう。

## リソース
- [tsconfig.jsonの全オプションを理解する（随時追加中）](https://qiita.com/ryokkkke/items/390647a7c26933940470#jsx "tsconfig.jsonの全オプションを理解する（随時追加中）")
- [オープンソースライセンスの日本語参考訳](https://licenses.opensource.jp/ "オープンソースライセンスの日本語参考訳")
- [Specifics of npm's package.json handling](https://docs.npmjs.com/cli/v6/configuring-npm/package-json#files "Specifics of npm's package.json handling")
- [package.jsonの中身を理解する](https://qiita.com/dondoko-susumu/items/cf252bd6494412ed7847 "package.jsonの中身を理解する")
- [npm - Build amazing things](https://www.npmjs.com/ "npm - Build amazing things")