typescript + vitest で npm パッケージの開発 2024 の履歴(No.4)
更新2024 年の記事です†
prettier + eslint + typescript + vitest + publint で npm パッケージを作る環境を整えます。
出来上がったコードはこちら:
https://github.com/osamutake/typescript-vitest-skeleton
目次†
vite を使わない場合にも jest より vitest を使った方がいいそうです†
- jest を typescript で使うとトランスパイルに時間がかかり設定も大変?
- TypeScript: JestでES Modulesは問題なくテストできるのか?
https://zenn.dev/suin/scraps/126d311493a9a1
など参照すればできそうだけど 
 - TypeScript: JestでES Modulesは問題なくテストできるのか?
 
- vitest は簡単に動かせる
- Vite は使ってないけど Jest を Vitest に移行する
https://zenn.dev/sa2knight/articles/migrating_vitest_from_jest 
 - Vite は使ってないけど Jest を Vitest に移行する
 
手順をメモ†
プロジェクトフォルダを作成†
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
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
できちゃった。
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 ブランチを削除した。
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
のようになる。
コメント・質問†
Counter: 1958 (from 2010/06/03), 
today: 1,
yesterday: 1

