Skip to content

Commit 2c8b0e1

Browse files
chore(repo): add ESM-only upgrade playbook (#1934)
* chore(repo): add ESM-only upgrade playbook for plugin packages\n\nRefs #1933 * docs(playbook): map exports via '.' and add tsconfig symlink step - Use explicit . export with types/import (+default) for compatibility - Replace tsconfig JSON example with symlink to .config/tsconfig.plugin.json - Update verify jq path and add symlink check - Clarify package.json files note and prefer .npmignore for maps * docs(playbook): clarify files negation and simplify rollback\n\n- Instruct to remove '!dist/**/*.map' if present in package.json (negation unsupported)\n- Keep only modern 'git restore -SW ' in rollback section * docs(playbook): add JS→TS and AVA→Vitest conversion order\n\n- Always convert JS in src to TS\n- Never convert fixtures to TS\n- Always convert JS in test to TS\n- Run AVA after JS→TS + package.json changes; then convert to Vitest and run Vitest thereafter\n\nRefs #1933 * docs(playbook): prefer import.meta.dirname/filename over URL utilities\n\n- Node >=20.11 supports import.meta.dirname and import.meta.filename; prefer them\n- Keep URL utilities as a fallback for non-file URLs or special cases * docs(playbook): always publish source maps; drop .npmignore/disable-map guidance - Remove suggestion to exclude dist/**/*.map via .npmignore - Remove suggestion to disable sourceMap/declarationMap via tsconfig - Add explicit note to keep maps published and remove any existing exclusions Refs #1934 --------- Co-authored-by: CharlieHelps <charlie@charlielabs.ai>
1 parent 18a4e38 commit 2c8b0e1

File tree

1 file changed

+149
-0
lines changed

1 file changed

+149
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Upgrade a plugin package to ESM-only (packages/<name>)
2+
3+
Upgrade a single plugin under `packages/<name>` to publish ESM-only output with TypeScript-emitted JS and declarations.
4+
5+
## Prerequisites
6+
7+
- Repo already contains shared config at `.config/tsconfig.base.json` and `.config/tsconfig.plugin.json` and (optionally) `.config/vitest.config.mts` from prior migrations.
8+
- Scope constraint: make changes only inside the target package directory (e.g., `packages/alias`). Do not add or edit files outside `packages/<name>`.
9+
- Local Node 20.19+ to run builds and tests.
10+
11+
## Steps
12+
13+
1. Identify the target package
14+
15+
- Set a shell variable for reuse: `PKG=packages/<name>`.
16+
17+
2. Package metadata: ESM-only and minimums
18+
19+
- Edit `$PKG/package.json`:
20+
21+
- Set `"type": "module"`.
22+
- Replace legacy `main/module/exports.require` with an ESM-only export mapped via the explicit `"."` entry for broad tooling compatibility:
23+
```json
24+
{
25+
"exports": {
26+
".": {
27+
"types": "./dist/index.d.ts",
28+
"import": "./dist/index.js",
29+
"default": "./dist/index.js"
30+
}
31+
},
32+
"types": "./dist/index.d.ts"
33+
}
34+
```
35+
- Set minimums: `"engines": { "node": ">=20.19.0" }` and `"peerDependencies": { "rollup": ">=4.0.0" }`.
36+
- Keep `rollup` as a devDependency only if tests use it. Otherwise remove it.
37+
- Ensure published files include build output and standard docs:
38+
```json
39+
"files": ["dist", "README.md", "LICENSE"]
40+
```
41+
Notes:
42+
- `package.json` `files` does not support negation patterns. If an existing `package.json` contains "files": [ ..., "!dist/**/*.map", ... ], remove the negated entry—negation is not supported and will be ignored.
43+
- Always publish source maps. Do not exclude `dist/**/*.map` via `.npmignore`, and do not disable `sourceMap`/`declarationMap` for published packages. If a package currently excludes maps (via `.npmignore` or tsconfig), remove those exclusions so maps are included.
44+
45+
3. Build scripts: TypeScript emit to dist
46+
47+
- Prefer a tsc-only build for packages that do not need bundling:
48+
- In `$PKG/package.json`, set scripts:
49+
```json
50+
"prebuild": "del-cli dist",
51+
"build": "tsc --project tsconfig.json",
52+
"pretest": "pnpm build",
53+
"prerelease": "pnpm build",
54+
"prepare": "if [ ! -d 'dist' ]; then pnpm build; fi"
55+
```
56+
- If this package still needs bundling for tests/examples, keep its Rollup config but point inputs at the TypeScript output in `dist/` instead of sources.
57+
58+
4. TypeScript config: use the shared plugin config (symlink)
59+
60+
- Replace any existing `$PKG/tsconfig.json` with a symlink to the shared plugin config (`.config/tsconfig.plugin.json`), which already enables emit to `dist/` and declaration maps:
61+
```bash
62+
# from repo root
63+
ln -snf ../../.config/tsconfig.plugin.json "$PKG/tsconfig.json"
64+
git add "$PKG/tsconfig.json"
65+
```
66+
On Windows PowerShell, you can run:
67+
```powershell
68+
# from repo root
69+
$pkg = 'packages/<name>'
70+
New-Item -ItemType SymbolicLink -Path "$pkg/tsconfig.json" -Target (Resolve-Path ".config/tsconfig.plugin.json") -Force
71+
git add "$pkg/tsconfig.json"
72+
```
73+
The shared config content lives at `.config/tsconfig.plugin.json`.
74+
- Delete any package-local `rollup` build scripts that produced CJS, and remove any `types/` folder if declarations were hand-authored (they will now be generated).
75+
76+
5. Source: convert to pure ESM and modern Node APIs
77+
78+
- Replace `require`, `module.exports`, and `__dirname` patterns with ESM equivalents.
79+
- Use `node:` specifiers for built-ins (e.g., `import path from 'node:path'`).
80+
- Prefer the latest ES APIs: use `import.meta.dirname` and `import.meta.filename` (Node ≥20.11) instead of re‑creating them via `fileURLToPath`.
81+
82+
```ts
83+
import path from 'node:path';
84+
85+
const here = import.meta.dirname;
86+
// const file = import.meta.filename;
87+
const pkgJson = path.join(here, 'package.json');
88+
```
89+
90+
Use URL utilities only when specifically needed (e.g., for non‑file module URLs): `fileURLToPath(new URL('.', import.meta.url))`.
91+
92+
- Inline and export public types from `src/index.ts`; avoid separate `types/` unless unavoidable.
93+
- Conversion rules for file types:
94+
- Always convert any `.js` in `src/` to `.ts`.
95+
- Never convert files in test `fixtures/` to TypeScript—keep fixtures exactly as authored.
96+
- Always convert any `.js` in `test/` to `.ts` (test sources only; exclude `test/**/fixtures/**`).
97+
98+
6. Tests: order of operations (AVA → Vitest) and ESM
99+
100+
- Remove CJS-specific branches/assertions from tests.
101+
- Follow this sequence for reliability:
102+
1. After JS→TS conversion in `src/` and `test/` and after any `package.json` changes, always run the AVA test suite.
103+
2. Only after verifying AVA tests run without modifications, convert the AVA tests to Vitest.
104+
3. After converting tests to Vitest, always run the Vitest suite after any subsequent `src/` change.
105+
- Note: If a package already uses Vitest, start at step 3 and skip AVA‑specific steps.
106+
- Ensure Rollup bundles created in tests are `await bundle.close()`-d to avoid leaks.
107+
108+
7. Clean up package artifacts
109+
- Remove obsolete files that are no longer used by ESM-only publishing (examples):
110+
- `$PKG/rollup.config.*` if switching to tsc-only.
111+
- `$PKG/types/**` once declarations are generated to `dist/`.
112+
113+
## Verify
114+
115+
- Build succeeds and emits JS and d.ts to `dist/`:
116+
```bash
117+
pnpm -C $PKG build
118+
tree $PKG/dist | sed -n '1,80p'
119+
```
120+
- Symlink exists and points at the shared config:
121+
```bash
122+
test -L "$PKG/tsconfig.json" && ls -l "$PKG/tsconfig.json" || (echo "tsconfig.json symlink missing" && exit 1)
123+
```
124+
- Type declarations resolve for consumers:
125+
```bash
126+
jq -r '.types, .exports["."].types, .exports["."].import' $PKG/package.json
127+
```
128+
- Runtime smoke (Node ESM import works):
129+
```bash
130+
node -e "import('file://$PWD/$PKG/dist/index.js').then(() => console.log('ok'))"
131+
```
132+
- Tests pass for the package (runner may be AVA or Vitest depending on the package):
133+
```bash
134+
pnpm -C $PKG test
135+
```
136+
137+
## Rollback
138+
139+
- Revert the package directory to the previous commit (modern Git):
140+
```bash
141+
git restore -SW $PKG
142+
```
143+
- If needed, `git reset --hard HEAD~1` when this package’s change is isolated on a feature branch.
144+
145+
## References
146+
147+
- Alias migration (ESM-only) — PR #1926: feat(alias)!: ESM only. Update Node and Rollup minimum versions
148+
- Task spec used for alias — Issue #1925
149+
- Shared TS configs used by packages — `.config/tsconfig.base.json`, `.config/tsconfig.plugin.json`

0 commit comments

Comments
 (0)