typescript + vitest で npm パッケージの開発 2024

更新


プログラミング/svelte

2024 年の記事です

prettier + eslint + typescript + vitest + publint で npm パッケージを作る環境を整えます。

出来上がったコードはこちら:
https://github.com/osamutake/typescript-vitest-skeleton

目次

vite を使わない場合にも jest より vitest を使った方がいいそうです

  • jest を typescript で使うとトランスパイルに時間がかかり設定も大変?

手順をメモ

プロジェクトフォルダを作成

LANG:console
$ mkdir -p mypackage/src/lib
$ mkdir -p mypackage/src/test
$ cd mypackage

その下は、

  • dist/ トランスパイル済みの publish するコードがここに入る
  • src/ ソースコードをここに入れる。
    • lib/ パッケージのコードをここに。index.ts ですべてを export する。
    • test/ テストコードをここに入れる。

とした。

package.json を作成

LANG: console
$ npm run prepare

でビルド&テストができるようにした。

  • eslint で静的チェックを行う
    • npm run format を忘れていればそれもここではじかれる
  • tcs でトランスパイルする
  • vitest で *.test.ts や *.spec.ts を実行する
    • vitest はトランスパイル後のコードに対して実行されるので build より前に呼んではいけない
  • publint でパッケージ内容をチェックする
LANG:json
{
   "name": "### @yourname/mypackage ###",
   "version": "0.0.1",
   "description": "### description of mypackage ###",
   "exports": "./dist/index.js",
   "type": "module",
   "scripts": {
     "build": "tsc",
     "format": "prettier --write .",
     "lint": "prettier --check . && eslint .",
     "test": "vitest",
     "prepare": "npm run lint && npm run build && vitest run && publint"
   },
   "files": [
     "dist",
     "!dist/**/*.test.*",
     "!dist/**/*.spec.*"
   ],
   "author": "### Your Name ###",
   "license": "### MIT ###"
 }

あーと、少し試しただけで prepare の使い方間違ってそうなことがわかるな。

https://docs.npmjs.com/cli/v10/using-npm/scripts

後でちゃんとこれ見て勉強しないと。

必須パッケージを導入

LANG:console
$ npm install -D typescript prettier \
  eslint @types/eslint eslint-config-prettier typescript-eslint \
  vitest globals @types/jest vite-tsconfig-paths

あとは個別設定をいくつか↓

.gitignore

  • /dist を除外
  • あとはいつも通り
node_modules
/dist

# OS
.DS_Store
Thumbs.db

# Env
.env
.env.*
!.env.example
!.env.test

# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

# WinMerge etc.
*.bak

.prettierignore

  • /dist を prettier が読まないようにする
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock

/dist

.prettierrc

これはお好みで。

LANG:json
{
  "useTabs": false,
  "singleQuote": true,
  "trailingComma": "es5",
  "printWidth": 100
}

eslint.config.js

  • globals.jest を入れることで、テストコードで vitest を import しなくて良いようにする
    • そうしておくと vitest でも jest でもどちらでも動くコードにできる
  • eslint は dist/ フォルダで動かないようにしておく
LANG:ts
import js from '@eslint/js';
import ts from 'typescript-eslint';
import prettier from 'eslint-config-prettier';
import globals from 'globals';

/** @type {import('eslint').Linter.Config[]} */
export default [
  js.configs.recommended,
  ...ts.configs.recommended,
  prettier,
  {
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
        ...globals.jest,
      },
    },
  },
  {
    languageOptions: {
      parserOptions: {
        parser: ts.parser,
      },
    },
  },
  {
    ignores: ['dist/'],
  },
];

tsconfig.json

  • vitest/globals を指定する
  • outDir を /dist に
  • *.test.ts や *.spec.ts は無視する (build 時)
  • import { ... } from "パッケージ名" でアクセスできるよう alias を張る
LANG:json
{
  "compilerOptions": {
    "target": "ES2015",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "types": ["vitest/globals"],
    "declaration": true,
    "sourceMap": true,
    "outDir": "./dist",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["**/*.test.ts", "**/*.spec.ts"],
  "alias": {
    "### @yourbane/mypackage ###": "src/lib/index.ts"
  }
}

vitest.config.ts

LANG:ts
import { defineConfig } from 'vitest/config';
import tsconfigPaths from 'vite-tsconfig-paths';

export default defineConfig({
  plugins: [tsconfigPaths()],
  test: {
    globals: true,
  },
});

src/lib/mypackage.ts

LANG:ts
export function hello() {
  return 'Hello!';
}

src/lib/index.ts

LANG:ts
export { hello } from './mypackage.js';  // this is not '.ts'

src/test/mypacage.test.ts

LANG:ts
import { hello } from '@yourname/mypackage';

describe('mypackage.hello', ()=>{
  it('says hello', ()=>{
    expect(hello()).toBe('Hello!');
  });
});

これでビルド&テスト可能

LANG:console
$ npm run prepare

prepare-result.png

LANG:console
$ ls dist/
index.d.ts  index.js  index.js.map  mypackage.d.ts  mypackage.js  mypackage.js.map

パッケージをパブリッシュする

npm publish --access=public

npm へ公開するならそのままこれでOK?

LANG:console
$ npm publish --access=public
> @osamu_takeuchi/mypackage@0.0.1 prepare
> npm run lint && npm run build && vitest run && publint

> @osamu_takeuchi/mypackage@0.0.1 lint
> prettier --check . && eslint .
 
Checking formatting...
All matched files use Prettier code style!
 
> @osamu_takeuchi/mypackage@0.0.1 build
> tsc

RUN  v2.0.5 C:/Users/osamu/Desktop/svelte/lib/mypackage
 
✓ src/test/mypackage.test.ts (1)
   ✓ mypackage.hello (1)
     ✓ says hello

Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  19:10:10
   Duration  1.29s (transform 45ms, setup 0ms, collect 39ms, tests 3ms, environment 0ms, prepare 419ms)
 
@osamu_takeuchi/mypackage lint results:
All good!

npm notice
npm notice 📦  @osamu_takeuchi/mypackage@0.0.1
npm notice Tarball Contents
npm notice 40B dist/index.d.ts
npm notice 73B dist/index.js
npm notice 147B dist/index.js.map
npm notice 41B dist/mypackage.d.ts
npm notice 86B dist/mypackage.js
npm notice 165B dist/mypackage.js.map
npm notice 893B package.json
npm notice Tarball Details
npm notice name: @osamu_takeuchi/mypackage
npm notice version: 0.0.1
npm notice filename: osamu_takeuchi-mypackage-0.0.1.tgz
npm notice package size: 891 B
npm notice unpacked size: 1.4 kB
npm notice shasum: 98564f270bbef1eb6c6625420a35620f7fad0dfd
npm notice integrity: sha512-4GGxrYblUAfL6[...]ubWYlCHO0tq5Q==
npm notice total files: 7
npm notice
npm error code ENEEDAUTH
npm error need auth This command requires you to be logged in to https://registry.npmjs.org/
npm error need auth You need to authorize this machine using `npm adduser`

publish するとその前に自動的に prepare を呼んでくれるので、 先の設定であれば lint + build + test が自動で行われる。

ここでは npm へログインしていなかったのでアクセス権の問題でコケている。

npm login

自動でブラウザが立ち上がるので、ブラウザ上でログインする。

LANG: console
$ npm login
npm notice Log in on https://registry.npmjs.org/
Login at:
https://www.npmjs.com/login?next=/login/cli/1bb69932-2d0f-4d99-9629-83c7becb9870
Press ENTER to open in the browser...

Logged in on https://registry.npmjs.org/.

npm publish --access=public

今度はうまく行く。

LANG:console
$ npm publish --access=public

publish-result.png

できちゃった。

https://www.npmjs.com/package/@osamu_takeuchi/mypackage

github に上げておく

うっかりして github 側で作ったリポジトリで main をデフォルトブランチにしてしまったのでちょっとややこしくなってしまった・・・

LANG: console
$ git init
$ git add .
$ git status
On branch master
No commits yet
Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   .gitignore
        new file:   .prettierignore
        new file:   .prettierrc
        new file:   eslint.config.js
        new file:   package-lock.json
        new file:   package.json
        new file:   src/lib/index.ts
        new file:   src/lib/mypackage.ts
        new file:   src/test/mypackage.test.ts
        new file:   tsconfig.json
        new file:   vitest.config.ts
$ git commit -m "skelton npm package with typescript + vitest"
$ git push origin --all -f
Enumerating objects: 16, done.
Counting objects: 100% (16/16), done.
Delta compression using up to 12 threads
Compressing objects: 100% (13/13), done.
Writing objects: 100% (16/16), 31.84 KiB | 3.18 MiB/s, done.
Total 16 (delta 0), reused 0 (delta 0), pack-reused 0
remote: 
remote: Create a pull request for 'master' on GitHub by visiting:
remote:      https://github.com/osamutake/typescript-vitest-skeleton/pull/new/master
remote:
To https://github.com/osamutake/typescript-vitest-skeleton.git
 * [new branch]      master -> master

その後、github.com 上で master をデフォルトブランチとし、main ブランチを削除した。

https://github.com/osamutake/typescript-vitest-skeleton

npm version でバージョン番号を増やす

元が 0.0.1 のとき、

LANG: console
$ npm version patch  # 0.0.2
$ npm version minor  # 0.1.0
$ npm version major  # 1.0.0

のようになる。

コメント・質問





添付ファイル: filepublish-result.png 26件 [詳細] fileprepare-result.png 24件 [詳細]

Counter: 254 (from 2010/06/03), today: 6, yesterday: 0