1
1

npmパッケージをScope付きで公開してみよう!

Last updated at Posted at 2023-12-19

内容

以下の仕様のCLIプログラムをNPMに公開してみます。

GitHub Repository

本記事で作成するPackageはGitHubで公開しています。

仕様

プログラム名は、test-pair-checkerとします。

テスト対象ファイルに対応するテストファイルを検索し、存在有無をチェックします。これにより、ファイルの存在有無レベルですが、簡単にテスト不足を検知できます。

以下の構成の場合、src/components/custom-button.tsxは対応するテストファイルが存在するsrc/components/custom-input.tsxは対応するテストファイルが存在しないと検知できればOKとなります。

Exapmle
.
├── src
│   ├── components
│       ├── custom-button.tsx
│       ├── custom-input.tsx
│   ├── tests
│       ├── components
│           ├── custom-button.test.tsx

入力

test-pair-checker.config.jsファイルに以下の入力を準備。
・テスト対象フォルダ(string[])
・除外するフォルダパターン(string[])
・テストフォルダ(string)

ファイル同士の照会

configファイルからから、*.test.*が存在するかチェックする。
そして下記記載の出力を行う。

出力

shell
------------------------------------------------------
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

プロジェクト作成

shell
# 既にnpm公開済みの名称なので、もしテスト公開したい場合は以下の名前以外を付けてください。
$ mkdir test-pair-checker && cd ./test-pair-cherk
$ npm init -y

# srcフォルダ配下にコードを記載していきます。
$ mkdir src

※Package Manageはpnpmで記載しておりますが、こちらも適宜、npmyarnで読み替えてください。

実装

以下の順番で進めていきます。
シンプルに公開までさっさと進めたいという方は、*がついている手順をスキップしてください。

  1. Packageファイル出力
    distフォルダ配下にPackageファイルを出力することを目指します。
    バンドラーにはRollup、コーディングはTypeScriptを使用します。
    Rollupを選択した理由は、この記事Use webpack for apps, and Rollup for librariesという記載を見かけたのと、最小限の構成からスタートし、必要分だけPluginを追加していくという方式が分かりやすかったためです。

  2. ローカルテスト
    NPM Packageに公開してから、テストをおこなっていては効率、信頼性がともに損なわれます。
    便利なPackage manageのlink機能を使い、ローカルテストで正常性を確認します。

  3. *Linter、Formatter、Testing
    継続性のある開発を行えるように設定を行います。

  4. Description
    READMEに使用方法などを記載します。

  5. NPMにPackageを公開
    npm loginを行い、Packageを公開します。


1.Packageファイル出力

必要なPackageをインストールします。

RollupのPluginは実装内容に応じてというのが前提ではありますが、
今回使用しているものはよく使う部類に入るので、知っておいて損はないと思います。

shell
# 開発時に使用するので、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

また、ファイル検索のコアとなるモジュールをインストールします。

shell
# 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はデフォルトで含まれます。

完成形は以下です。

/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を追加
tsconfig.json
{
  "compilerOptions": {
    "esModuleInterop": true
  },
  "include": [
    "src"
  ],
  "exclude": [
    "node_modules",
    "dist"
  ]
}
  • コアロジック

以下より、コピーしてください。

src/index.ts

src/matcher.ts

src/utils.ts

また、デフォルト値として取得されるconfigファイルを作成します。
※このPackageのインストールを行うプロジェクトのRootフォルダにtest-pair-checker.config.{js,cjs}が存在する場合は、使用されません。

src/test-pair-checker.config.js
const config = {
  targetDirs: ['src'],
  ignorePatterns: [],
  testDir: 'tests',
};

export default config;
  • Rollup設定
/rollup.config.js
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です。

shell
$ 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.ローカルテスト

  • ローカルで公開

npmyarnも同様の方法があるので、適宜調整してください。

test-pair-checker配下で以下のコマンドを実行します。

shell
$ 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
  • インストールを行うテストプロジェクトを作成
shell
# test-pair-checkerとは別フォルダに移動し、新プロジェクトを生成
$ mkdir package-install
$ cd package-install
$ npm init -y

configファイルを作成します。拡張子はcjsです。
※簡単のためcjsにしています。jsにしたい場合は、package-installpackage.json"type":"module"を追加してください。

/test-pair-checker.config.cjs
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が張られた状態となります。

shell
$ 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フォルダからは特に影響を受けていないことが分かります。

shell
$ 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用のスクリプトを追加します。

shell
$ 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
/package.json
 "scripts": {
    "build": "rollup -c -w",
    "lint": "eslint --ext js,jsx,ts,tsx src",
  },

eslintを起動し、問題なければOKです。

shell
$ 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も設定します。

shell
$ pnpm add -D -E prettier
/prettierrc.config.js
/** @type {import("prettier").Config} */
const config = {
  printWidth: 120,
  trailingComma: "es5",
  tabWidth: 2,
  semi: true,
  singleQuote: true,
  endOfLine: "lf",
};

export default config;
  • huskylint-staged

これらを使用し、Git Commit時にstagingされたファイルに対し、eslintprettierを実行します。これにより、ソース保全を強制させることができます。

関連Packageをインストール後、ファイルを調整します。

shell
# husky
$ pnpm dlx husky-init && pnpm install

# lint-staged
$ pnpm add -D lint-staged
/.husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

pnpm lint-staged
/package.json
  "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をしようとすると、、

/src/index.ts
import { globSync } from 'glob';
import matcher from './matcher';
import { cwd } from 'process';

const test = 'test';

// 省略

以下のようにエラーが発生し、git commitが失敗します。

shell
$ 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も導入します。

script
$ pnpm add -D jest @types/jest ts-jest

テスト詳細は、Repoを確認ください。


4.Description

README.mdにPackage詳細を記載していきます。

Repoを確認ください。


5.NPMにPackageを公開

  • アカウント作成

NPMでアカウントを作成します。

作成したアカウント名がScopeとなります。これまで@[your-scope]としてきたところに格納され、私の場合は以下のようになります。

package.json
{
    "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を公開されている方達は本当に凄いです。

ただ、今回の記事のように、普通の開発者がちょっとした便利ツールを公開することにも使えるので、どんどん作ってみると良いですね。

興味がある方は、ぜひインストールしてみてください。

参考

eslint
prettier
husky
lint-staged

1
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1