内容
以下の仕様のCLIプログラムをNPM
に公開してみます。
GitHub Repository
本記事で作成するPackageはGitHubで公開しています。
仕様
プログラム名は、test-pair-checker
とします。
テスト対象ファイルに対応するテストファイルを検索し、存在有無をチェックします。これにより、ファイルの存在有無レベルですが、簡単にテスト不足を検知できます。
以下の構成の場合、src/components/custom-button.tsx
は対応するテストファイルが存在する、src/components/custom-input.tsx
は対応するテストファイルが存在しないと検知できればOKとなります。
.
├── src
│ ├── components
│ ├── custom-button.tsx
│ ├── custom-input.tsx
│ ├── tests
│ ├── components
│ ├── custom-button.test.tsx
入力
test-pair-checker.config.js
ファイルに以下の入力を準備。
・テスト対象フォルダ(string[])
・除外するフォルダパターン(string[])
・テストフォルダ(string)
ファイル同士の照会
config
ファイルからから、*.test.*
が存在するかチェックする。
そして下記記載の出力を行う。
出力
------------------------------------------------------
The number of test target files 対象ファイル数
The number of test files テストファイル数
The number of matched test files 対応するテストファイル数
------------------------------------------------------
You must implement corresponding test files for the following files.
[ テストファイルが存在しない対象ファイル一覧 ]
使用方法
・CLI
プロジェクト作成
# 既にnpm公開済みの名称なので、もしテスト公開したい場合は以下の名前以外を付けてください。
$ mkdir test-pair-checker && cd ./test-pair-cherk
$ npm init -y
# srcフォルダ配下にコードを記載していきます。
$ mkdir src
※Package Manageはpnpmで記載しておりますが、こちらも適宜、npm
やyarn
で読み替えてください。
実装
以下の順番で進めていきます。
シンプルに公開までさっさと進めたいという方は、*
がついている手順をスキップしてください。
-
Packageファイル出力
dist
フォルダ配下にPackageファイルを出力することを目指します。
バンドラーにはRollup
、コーディングはTypeScript
を使用します。
Rollupを選択した理由は、この記事のUse webpack for apps, and Rollup for librariesという記載を見かけたのと、最小限の構成からスタートし、必要分だけPlugin
を追加していくという方式が分かりやすかったためです。 -
ローカルテスト
NPM Packageに公開してから、テストをおこなっていては効率、信頼性がともに損なわれます。
便利なPackage manageのlink機能を使い、ローカルテストで正常性を確認します。 -
*Linter、Formatter、Testing
継続性のある開発を行えるように設定を行います。 -
Description
READMEに使用方法などを記載します。 -
NPMにPackageを公開
npm login
を行い、Packageを公開します。
1.Packageファイル出力
必要なPackageをインストールします。
RollupのPluginは実装内容に応じてというのが前提ではありますが、
今回使用しているものはよく使う部類に入るので、知っておいて損はないと思います。
# 開発時に使用するので、devDependenciesに入れます。
$ pnpm add -D \
# Rollup バンドラー本体です。
rollup \
# TypeScriptをコンパイルする際に使用します。
typescript \
# TypeScriptファイルをバンドルする際に使用します。
# typescriptとtslibはpeer dependenciesにも記載されており、必須パッケージとなります。
@rollup/plugin-typescript \
tslib \
# サードパーティ製(Package.jsonのdependencies、peerDependencies、nodeのビルトインモジュール)をそのままパッケージとして保持してくれます。
# 実際にライブラリをインストールして使うときに、本体プロジェクト側で解決されることを前提とした仕組みです。
rollup-plugin-node-externals \
# コメントや空白などの無駄な行を削除します。
@rollup/plugin-terser \
# processを使用するためです。
@types/node
また、ファイル検索のコアとなるモジュールをインストールします。
# globパッケージはライブラリインストール時に、本体プロジェクト側でインストールされます。
$ pnpm add glob
-
package.json
を編集
特に、NPM Packageに関連してくる内容を説明します。
name
:your-scope
に関しては、NPM
アカウント取得後に確定するので一旦は何でもOKです。これがインストール時の名称となります。
version
:Packageの初版Verです。完成版ではないので、0.9.0
にしています。
main
:例えば、test-pair-checkerフォルダ直下で、node ./
とコマンドしたときに、記載されているファイルが実行されます。
type
:ESMモジュールを前提とします。commonjs
の記載を行う場合は、拡張子にcjs
が必要となります。
bin
:このPackageをインストール後に、pnpm pair-check
というコマンドで、指定されたファイルが実行されます。CLIプログラムの重要なポイントです。
files
:Packageインストールに付帯するファイルを指定します。package.json
はデフォルトで含まれます。
完成形は以下です。
{
"name": "@your-scope/your-file-name",
"version": "0.9.0",
"description": "This checks corresponding test file existance based on those names.",
"main": "./dist/bin/index.js",
"type": "module",
"bin": {
"pair-check": "./dist/bin/index.js"
},
"files": [
"dist"
],
"scripts": {
"build": "rollup -c -w"
},
"author": "Your name",
"license": "MIT",
"dependencies": {
"glob": "^10.3.10"
},
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.5",
"@types/node": "^20.10.4",
"rollup": "^4.7.0",
"rollup-plugin-node-externals": "^6.1.2",
"tslib": "^2.6.2",
"typescript": "^5.3.3"
}
}
-
tsconfig.json
を追加
{
"compilerOptions": {
"esModuleInterop": true
},
"include": [
"src"
],
"exclude": [
"node_modules",
"dist"
]
}
- コアロジック
以下より、コピーしてください。
また、デフォルト値として取得されるconfig
ファイルを作成します。
※このPackageのインストールを行うプロジェクトのRootフォルダにtest-pair-checker.config.{js,cjs}
が存在する場合は、使用されません。
const config = {
targetDirs: ['src'],
ignorePatterns: [],
testDir: 'tests',
};
export default config;
- Rollup設定
import typescript from '@rollup/plugin-typescript';
import nodeExternals from 'rollup-plugin-node-externals';
import terser from '@rollup/plugin-terser';
const plugins = [
// This helps Rollup understands typescript.
typescript(),
// This excludes packages under peer-dep, dep and built-in node modules.
nodeExternals(),
// This minifies bundled file to eliminate empty line and comments.
terser({ format: { comments: false } }),
];
/** @type {import('rollup').RollupOptions} */
const cliConfig = {
input: 'src/index.ts',
output: {
dir: 'dist/bin',
strict: true,
},
plugins,
};
export default cliConfig;
- Packageファイルを出力
dist/bin/index.js
にバンドルファイルが生成されていればOKです。
$ pnpm build
rollup v4.7.0
bundles src/index.ts → dist/bin...
created dist/bin in 743ms
[xxxx-xx-xx xx:xx:xx] waiting for changes...
2.ローカルテスト
- ローカルで公開
npm
、yarn
も同様の方法があるので、適宜調整してください。
test-pair-checker
配下で以下のコマンドを実行します。
$ pnpm link -g
WARN xxx/test-pair-checker has no binaries
xxx/.local/share/pnpm/global/5:
+ @[your-scope]/[your-file-name] 0.9.0 <- ../../../../../xxx/test-pair-checker
- インストールを行うテストプロジェクトを作成
# test-pair-checkerとは別フォルダに移動し、新プロジェクトを生成
$ mkdir package-install
$ cd package-install
$ npm init -y
config
ファイルを作成します。拡張子はcjs
です。
※簡単のためcjs
にしています。js
にしたい場合は、package-install
のpackage.json
に"type":"module"
を追加してください。
module.exports = {
targetDirs: ['src'],
ignorePatterns: [],
testDir: 'tests',
};
テストを行えるように、テスト対象ファイルとテストファイルを適当に作成します。以下のようなフォルダ構成になるようにファイルを生成してください。中身は空でOKです。
.
├── src
│ ├── components
│ ├── custom-button.tsx
│ ├── custom-input.tsx
│ ├── tests
│ ├── components
│ ├── custom-button.test.tsx
├── dummy
│ ├── components
│ ├── dummy-button.tsx
│
├── test-pair-checker.config.cjs
├── package.json
- ローカル公開されたPackageをインストール
package-install
配下で以下のコマンドを実行します。
すると、node_modules
配下にインストールされます。
※正確にはSymlinkが張られた状態となります。
$ pnpm link -g @[your-scope]/[your-file-name]
xxx/.local/share/pnpm/global/5:
+ @[your-scope]/[your-file-name] 0.9.0 <- node_modules/@[your-scope]/[your-file-name]
-
pair-check
コマンド
テスト対象ファイルが2つ見つかり、そのうちcustom-button.tsx
はテストファイルが存在し、custom-input.tsx
には存在していないことが分かります。また、対象外のdummy
フォルダからは特に影響を受けていないことが分かります。
$ pnpm pair-check
------------------------------------------------------
The number of test target files 2
The number of test files 3
The number of matched test files 1
------------------------------------------------------
You must implement corresponding test files for the following files.
custom-input.tsx
ローカルテストはOKとします。
3.*Linter、Formatter、Testing
継続的な開発に欠かせないPackageを導入していきます。
- Linter
以下のコマンドでインストールします。基本的なルールが導入された.eslintrc.cjs
が生成されますので、それをそのまま使用します。package.json
には、eslint
用のスクリプトを追加します。
$ npm init @eslint/config
Need to install the following packages:
@eslint/create-config@0.4.6
Ok to proceed? (y)
✔ How would you like to use ESLint? · problems
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · none
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
✔ What format do you want your config file to be in? · JavaScript
Local ESLint installation not found.
The config that you've selected requires the following dependencies:
@typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest eslint@latest
✔ Would you like to install them now? · No / Yes
✔ Which package manager do you want to use? · pnpm
"scripts": {
"build": "rollup -c -w",
"lint": "eslint --ext js,jsx,ts,tsx src",
},
eslint
を起動し、問題なければOKです。
$ pnpm lint
> @[your-scope]/[your-file-name]@0.9.0 lint /xxx/test-pair-checker
> eslint --ext js,jsx,ts,tsx src
- Formatter
Prettier
を導入します。prettierrc.config.js
も設定します。
$ pnpm add -D -E prettier
/** @type {import("prettier").Config} */
const config = {
printWidth: 120,
trailingComma: "es5",
tabWidth: 2,
semi: true,
singleQuote: true,
endOfLine: "lf",
};
export default config;
-
husky
とlint-staged
これらを使用し、Git Commit
時にstaging
されたファイルに対し、eslint
とprettier
を実行します。これにより、ソース保全を強制させることができます。
関連Packageをインストール後、ファイルを調整します。
# husky
$ pnpm dlx husky-init && pnpm install
# lint-staged
$ pnpm add -D lint-staged
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm lint-staged
"scripts": {
"build": "rollup -c -w",
"lint": "eslint --ext js,jsx,ts,tsx src",
"lint-staged": "lint-staged",
"prepare": "husky install"
},
,
,
,
"lint-staged": {
"*.*": "npx prettier --write",
"*.{js,cjs,ts}": "npx eslint"
}
動作チェックしてみます。以下のように、不使用のconst test
を定義し、Commitをしようとすると、、
import { globSync } from 'glob';
import matcher from './matcher';
import { cwd } from 'process';
const test = 'test';
// 省略
以下のようにエラーが発生し、git commit
が失敗します。
$ git commit -a -m "test commit"
> @[your-scope]/[your-file-name]@0.9.0 lint-staged /xxx/test-pair-checker
> lint-staged
✔ Preparing lint-staged...
⚠ Running tasks for staged files...
❯ package.json — 2 files
✔ *.* — 2 files
❯ *.{js,cjs,ts} — 1 file
✖ npx eslint [FAILED]
↓ Skipped because of errors from tasks.
✔ Reverting to original state because of errors...
✔ Cleaning up temporary files...
✖ npx eslint:
/xxx/test-pair-checker/src/index.ts
5:7 error 'test' is assigned a value but never used @typescript-eslint/no-unused-vars
✖ 1 problem (1 error, 0 warnings)
ELIFECYCLE Command failed with exit code 1.
- Testing
NPM Packageの信頼性に大きく寄与するUnitテストを導入します。フレームワークはjest
を使用します。
typescript
に対応するためのPackageも導入します。
$ pnpm add -D jest @types/jest ts-jest
テスト詳細は、Repoを確認ください。
4.Description
README.md
にPackage詳細を記載していきます。
Repoを確認ください。
5.NPMにPackageを公開
- アカウント作成
NPMでアカウントを作成します。
作成したアカウント名がScopeとなります。これまで@[your-scope]
としてきたところに格納され、私の場合は以下のようになります。
{
"name":"@i2i3i/test-pair-checker"
,
,
}
npm login
$ npm login
Login at:
https://www.npmjs.com/login?next=/login/cli/xxxxx
Press ENTER to open in the browser...
## Browserでログインが完了すると、、
Logged in on https://registry.npmjs.org/.
- 公開
公開対象のPackage配下で以下のコマンドを実行します。
$ npm publish --access public
こちらに公開されています。
最後に
想定よりも大幅に時間がかかりました。OSS活動をされている方や、NPM Packageを公開されている方達は本当に凄いです。
ただ、今回の記事のように、普通の開発者がちょっとした便利ツールを公開することにも使えるので、どんどん作ってみると良いですね。
興味がある方は、ぜひインストールしてみてください。