Skip to content

Commit

Permalink
fix(ses): fix broken types for CJS consumers (#2260)
Browse files Browse the repository at this point in the history
Due to changes in TS v5.5, CJS consumers cannot import types from a
`type: module` package unless the declaration file has the `.d.cts`
extension. This may or may not work the other way.

See [the v5.5. beta
announcement](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5-beta/#respecting-file-extensions-and-packagejson-in-other-module-modes)
for further explanation.
  • Loading branch information
kriskowal authored May 6, 2024
2 parents 8911ba8 + 5f7e78e commit 59bfbd0
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 14 deletions.
1 change: 1 addition & 0 deletions packages/ses/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ User-visible changes in SES:
# Next release

- Adds `importNowHook` to the `Compartment` options. The compartment will invoke the hook whenever it encounters a missing dependency while running `compartmentInstance.importNow(specifier)`, which cannot use an asynchronous `importHook`.
- To support TypeScript v5.5, a CommonJS-specific type declaration file is now explicitly exported.

# v1.3.0 (2024-02-22)

Expand Down
26 changes: 18 additions & 8 deletions packages/ses/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,25 @@
"unpkg": "./dist/ses.umd.js",
"types": "./types.d.ts",
"exports": {
".": {
"types": "./types.d.ts",
"import": "./index.js",
"require": "./dist/ses.cjs"
".": {
"import": {
"types": "./types.d.ts",
"default": "./index.js"
},
"require": {
"types": "./dist/types.d.cts",
"default": "./dist/ses.cjs"
}
},
"./lockdown": {
"types": "./types.d.ts",
"import": "./index.js",
"require": "./dist/ses.cjs"
"./lockdown": {
"import": {
"types": "./types.d.ts",
"default": "./index.js"
},
"require": {
"types": "./dist/types.d.cts",
"default": "./dist/ses.cjs"
}
},
"./tools.js": "./tools.js",
"./assert-shim.js": "./assert-shim.js",
Expand Down
26 changes: 20 additions & 6 deletions packages/ses/package.json.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,24 +94,38 @@ However, `@web/dev-server` gets confused by the extension.

The variations differ only in file name extension.

## "import": "./index.js"
### "import": {

Node.js and TypeScript will use these when importing `ses` as an ESM.

#### "default": "./index.js"

Node.js and other tools will use this file when importing `ses` as an ESM.
(JavaScript module).
We have in the past experimented with using the precompiled bundle of SES here
(`./dist/ses.cjs` or `./dist/ses.umd.js`), but found that this interacted
poorly with Endo, because an Endo bundle contains identifiers that SES censors.

## "require": "./dist/ses.cjs"

Node.js and other tools will use this file when importing `ses` as an CommonJS module.

## "types": "./types.d.ts"
#### "types": "./types.d.ts"

Only applicable for TypeScript v4.7+ consumers configured with `node16` or
`nodenext` [module resolution][]. This serves the same purpose as the `types`
prop at the top level.

### "require": {

Node.js and TypesScript will use these when importing `ses` as a CommonJS module.

#### "default": "./dist/ses.cjs"

Node.js and other tools will use this file when importing `ses` as an CommonJS module.

#### "types": "./dist/types.d.cts"

As of TypeScript v5.5 beta, if a package is an ESM package (`"type": "module"`), any `.d.ts` file in the package is considered to be for ESM consumers only. If CommonJS is targeted, ESM packages must now export types in `.d.cts` files. Think of it this way: *`.d.ts` is to `.d.cts` as `.js` is to `.cjs`*.

At build time, `./types.d.ts` is copied to `./dist/types.d.ts`; it is otherwise identical.

## "./lockdown"

The most recent SES only provides one API, but a previous version
Expand Down
25 changes: 25 additions & 0 deletions packages/ses/scripts/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,31 @@ const main = async () => {
...bundleFilePaths.map(dest => write(dest, versionedBundle)),
...terseFilePaths.map(dest => write(dest, terse)),
]);

// When importing types from a CJS package, TS v5.5+ considers the "module"
// field in `ses`' `package.json`, so any .d.ts file is considered to be "ESM
// types".
// For CJS, we need to provide a `.d.cts` file instead.
// It's unclear if this file can be identical to the original in _all_ cases,
// or just ours.
// We imagine ES-specific types (e.g., `import.meta`) would break
// in CJS, but generally consumers have `skipLibCheck` enabled.

// Also: this operation is in this script for portability's sake.

/** The "ESM types" */
const sourceDTS = /** @type {string} */ (
packageJson.exports['.'].import.types
);
/** The "CJS types" */
const destDTS = /** @type {string} */ (
packageJson.exports['.'].require.types
);
await fs.promises.copyFile(
fileURLToPath(new URL(sourceDTS, root)),
fileURLToPath(new URL(destDTS, root)),
);
console.log(`Copied ${sourceDTS} to ${destDTS}`);
};

main().catch(err => {
Expand Down

0 comments on commit 59bfbd0

Please sign in to comment.