Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions docs/release-1.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# @knighted/module 1.0.0 release notes

## Status

- Current candidate: 1.0.0-rc.0 (tag `latest` until final 1.0.0).
- Stability: API surface locked for 1.0.x; only bug fixes expected before GA.

## Requirements

- Node >= 22.21.1 (tested primarily on 22.x). Older LTS are not guaranteed; add matrix coverage if support is desired.

## Highlights

- Symmetric ESM↔CJS transforms with optioned behaviors:
- `topLevelAwait`: `error` (default), `wrap`, `preserve` for CJS targets.
- `importMetaMain`: `shim` (default), `warn`, `error` with Node version gate.
- `liveBindings`: `strict` (default), `loose`, `off` for ESM→CJS.
- `cjsDefault`: `auto` (default), `module-exports`, `none` for default import interop.
- `rewriteSpecifier`: extension or callback-based specifier rewriting for both directions.
- `requireSource`: `builtin` (default) or `create-require` fallback.
- Defensive handling: reject `with` / unshadowed `eval` when raising to ESM; reject shadowed `module`/`exports` in CJS lowering.
- Runtime coverage: fixtures and integration tests for **filename/**dirname/import.meta globals, `require.main` rewrites, TLA wrapping, namespace/default interop, export hoisting, and project-level conversions.

## Behavior guarantees

- Live bindings are strict by default for ESM→CJS; CJS→ESM rewrites avoid unsound cases by throwing on unsupported constructs.
- `import.meta` properties shimmed when targeting CJS; `import.meta.main` guarded by version-aware warnings/errors.
- Top-level await in CJS targets defaults to `error`; wrapping/preserve are opt-in and keep exports intact.

## Packaging and metadata

- Confirm `package.json` exports/fields (`main`, `exports`, `types`) match the build outputs.
- Dependencies: `oxc-parser`, `@babel/parser/traverse`, `magic-string`, `@knighted/specifier`, `@knighted/walk`, `node-module-type`.
- Consider source maps (not emitted today); document absence if deferring.

## Testing

- Lint: `npm run lint` (oxlint) — clean.
- Tests: `npm test` — ~98% statements, ~93% branches; format.ts 100% line coverage, remaining branches are defensive/unreachable.
- Add Node matrix if supporting additional runtimes before GA.

## Release steps to GA

1. Publish 1.0.0-rc.0 as `latest` (per plan); keep `next` if needed for future prereleases.
2. Monitor for regressions; if none, tag 1.0.0 with the same artifacts.
3. Update README and docs links to reference this release note; remove any “remaining gaps” language now that coverage is closed.
4. Add changelog entry summarizing changes since 1.0.0-alpha.8/1.0.0-beta.5.

## Open questions

- Do we want official support for Node 20/18? If yes, run matrix and adjust shims accordingly.
- CLI packaging: still library-only; document intentionally if not providing a CLI.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@knighted/module",
"version": "1.0.0-beta.5",
"version": "1.0.0-rc.0",
"description": "Transforms differences between ES modules and CommonJS.",
"type": "module",
"main": "dist/module.js",
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/arrayValue.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = [1, 2, 3]
2 changes: 2 additions & 0 deletions test/fixtures/bareModuleExports.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module.exports
exports.foo = 1
3 changes: 3 additions & 0 deletions test/fixtures/cjsDefaultModuleExports.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import foo from './values.cjs'

export const value = foo.foo
1 change: 1 addition & 0 deletions test/fixtures/evalOnly.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eval('exports.ok = true')
3 changes: 3 additions & 0 deletions test/fixtures/exportDefaultAnon.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function () {
return 'anon'
}
5 changes: 5 additions & 0 deletions test/fixtures/exportDefaultAnonTla.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
await Promise.resolve('tla')

export default function () {
return 'tla-anon'
}
3 changes: 3 additions & 0 deletions test/fixtures/exportDefaultNamed.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function namedNoTla() {
return 'named-no-tla'
}
4 changes: 4 additions & 0 deletions test/fixtures/exportDefaultTlaNamed.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
await Promise.resolve()
export default function named() {
return 'tla-named'
}
7 changes: 7 additions & 0 deletions test/fixtures/exportNamedFunction.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function greet() {
return 'greet'
}

export class Box {
value = 1
}
1 change: 1 addition & 0 deletions test/fixtures/exportNamespaceAll.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as bag from './values.mjs'
1 change: 1 addition & 0 deletions test/fixtures/exportNamespaceSpecifier.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as ns from './exportDefaultAnon.mjs'
4 changes: 4 additions & 0 deletions test/fixtures/exportReexportDefault.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { default as anon } from './exportDefaultAnon.mjs'
export { default as named } from './exportDefaultNamed.mjs'

export const label = 'reexport'
3 changes: 3 additions & 0 deletions test/fixtures/importSideEffect.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import './values.cjs'

export const loaded = true
4 changes: 4 additions & 0 deletions test/fixtures/liveBindingsOff.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const foo = 1
export function inc(x) {
return x + 1
}
15 changes: 15 additions & 0 deletions test/fixtures/projects/cjs-app/index.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { basename, bump, value } = require('./lib.cjs')
const { join } = require('node:path')

const here = __dirname

module.exports.main = () => ({
name: basename(__filename),
bumped: bump(2),
value,
path: join(here, 'lib.cjs'),
})

if (require.main === module) {
console.log(JSON.stringify(module.exports.main()))
}
10 changes: 10 additions & 0 deletions test/fixtures/projects/cjs-app/lib.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const path = require('node:path')

exports.value = 1
exports.basename = file => path.basename(file)
exports.bump = v => v + exports.value
exports.dynamicLoad = id => require(id).value

if (require.main === module) {
console.log(exports.bump(2))
}
13 changes: 13 additions & 0 deletions test/fixtures/projects/esm-app/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { add, inc } from './lib.mjs'

const boot = await Promise.resolve(2)

export const ready = boot
export const run = async () => {
const next = inc()
return { sum: add(boot, 0), after: next, main: import.meta.main ?? false }
}

if (import.meta.main) {
run().then(res => console.log(JSON.stringify(res)))
}
6 changes: 6 additions & 0 deletions test/fixtures/projects/esm-app/lib.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const add = (a, b) => a + b
export let counter = 0
export const inc = () => {
counter += 1
return counter
}
6 changes: 6 additions & 0 deletions test/fixtures/requireArray.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const [first] = require('./arrayValue.cjs')
const path = './arrayValue.cjs'
require(path)

// keep a runtime observable side effect
globalThis.__requireArrayFirst = first
5 changes: 5 additions & 0 deletions test/fixtures/requireMainNotEq.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
if (require.main !== module) {
module.exports = { main: false }
} else {
module.exports = { main: true }
}
5 changes: 5 additions & 0 deletions test/fixtures/requireMainReversed.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
if (module === require.main) {
module.exports = { main: true }
} else {
module.exports = { main: false }
}
2 changes: 2 additions & 0 deletions test/fixtures/tlaError.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
await Promise.resolve(1)
export const foo = 1
3 changes: 3 additions & 0 deletions test/fixtures/weirdExport.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
exports['foo-bar'] = 1
module.exports['baz+qux'] = 2
exports['123num'] = 3
Loading