Skip to content

feat!: switch to ESM and native TypeScript type-stripping - #166

Open
Tony133 wants to merge 7 commits into
mainfrom
feat/esm-strip-types
Open

feat!: switch to ESM and native TypeScript type-stripping#166
Tony133 wants to merge 7 commits into
mainfrom
feat/esm-strip-types

Conversation

@Tony133

@Tony133 Tony133 commented Aug 13, 2026

Copy link
Copy Markdown
Member

Proposal:

Follow-up to #159, as discussed with @mcollina:

I would recommend we switch to strip types and esm

Changed:

  • src/index.ts rewritten to ESM (export default), the old export = / import = require() syntax isn't "erasable" and
    breaks Node's native type-stripping.
  • tsconfig.json updated per Node's official recommended settings for type-stripping (module: nodenext, verbatimModuleSyntax, erasableSyntaxOnly, allowImportingTsExtensions, rewriteRelativeImportExtensions).
  • Relative imports now use explicit .ts extensions in source; tsc rewrites them to .js in the compiled lib/ output.
  • test/index.test.ts: __dirnameimport.meta.dirname, type-only imports (TestContext, Session, SessionData) marked with type per verbatimModuleSyntax.
  • package.json: "type": "module", added "exports" map, bumped engines to >=20.19.0 <21 || >=22.12.0.
  • Update README.md

Breaking change:

This is now an ESM-only package. require() from CommonJS consumers still works, since it relies on Node's require(esm) support, stable as of Node 20.19.0 / 22.12.0. Consumers on older Node versions will need to upgrade.

Note:

  • Requires a major version bump ( v7.0.0 )

- rewrite src/index.ts to use `export default` / `import` instead of
  TS-specific `export =` / `import = require()`
- update tsconfig.json for `module: nodenext`, `verbatimModuleSyntax`,
  `erasableSyntaxOnly`, `allowImportingTsExtensions`,
  `rewriteRelativeImportExtensions`
- add explicit `.ts` extensions to relative imports
- replace `__dirname` with `import.meta.dirname` in tests
- set "type": "module" in package.json, add "exports" map
- bump engines to require Node >=20.19.0 <21 || >=22.12.0
  (needed for require(esm) interop with CJS consumers)

BREAKING CHANGE: this package is now ESM-only. CJS consumers can
still `require()` it thanks to Node's require(esm) support
(stable since Node 20.19.0 / 22.12.0); older Node versions are
no longer supported.
@Tony133
Tony133 force-pushed the feat/esm-strip-types branch from 3ce9c0b to 6e4ef80 Compare August 13, 2026 11:02
@Tony133
Tony133 marked this pull request as ready for review August 13, 2026 11:03
@Tony133

Tony133 commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

cc @fastify/typescript

@Tony133
Tony133 force-pushed the feat/esm-strip-types branch from b53aff1 to af1000c Compare August 13, 2026 11:16

@mcollina mcollina left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

likely there is something to fix in the tsconfig.build.json

Comment thread README.md Outdated
Comment thread package.json Outdated
@Tony133
Tony133 force-pushed the feat/esm-strip-types branch 2 times, most recently from fefd2ce to e75bb6a Compare August 13, 2026 16:16
@Tony133
Tony133 requested a review from mcollina August 13, 2026 16:18
@Tony133
Tony133 force-pushed the feat/esm-strip-types branch 2 times, most recently from dd37d8b to 217ee9a Compare August 13, 2026 16:31
@Tony133
Tony133 force-pushed the feat/esm-strip-types branch from 217ee9a to 6c2b2e3 Compare August 13, 2026 16:32
Signed-off-by: Antonio Tripodi <Tony133@users.noreply.github.com>
Comment thread .github/workflows/ci.yml Outdated
Comment thread package.json
"types": "./lib/index.d.ts",
"default": "./lib/index.js"
}
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm, you mean main/types are redundant now that exports is present, right? 😅

-  "main": "./lib/index.js",
   "type": "module",
-  "types": "./lib/index.d.ts",
   "exports": {
     ".": {
       "types": "./lib/index.d.ts",
       "default": "./lib/index.js"
     }
   },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why using exports when you only have single entrypoint?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why using exports when you only have single entrypoint?

The only advantage, now that I think about it is: "encapsulation".
The exports field explicitly defines the package's public interface. Without it, nothing prevents a user from running require('@fastify/flash/lib/flash.js') and depending on an internal file. With exports, that import throws ERR_PACKAGE_PATH_NOT_EXPORTED, so we're free to restructure the internals of lib/ later without it being a breaking change.

That said, we could also just drop it and keep this instead:

   "main": "./lib/index.js",
   "type": "module",
   "types": "./lib/index.d.ts",
-  "exports": {
-    ".": {
-      "types": "./lib/index.d.ts",
-      "default": "./lib/index.js"
-    }
-  },

Happy to go either way, let me know which you'd prefer 😉

Comment thread README.md Outdated
Comment thread package.json
}
},
"scripts": {
"build": "npm run clean-build && npm run lint && tsc -p ./tsconfig.build.json",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this building correctly for release? is tsconfig.build.json correct?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, verified locally on Node 24.18.0. Commands to reproduce:

npm run build
ls -la lib/
node -e "require('./lib/index.js'); console.log('require(esm) works')"

Output:

  • lib/index.js, lib/index.d.ts, lib/flash.js, lib/flash.d.ts produced
  • lib/index.js matches exports["."].default
  • lib/index.d.ts matches exports["."].types
  • require('./lib/index.js') resolves and logs "require(esm) works" confirms the exports map works for CJS consumers too

tsconfig.build.json is correct: its include: ["src"] overrides(not merges with) the base config's broader include, so test/ is correctly excluded and only the paths declared in exports get produced.


test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants