-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Description
Following up #5441
Details
- xterm.js version: 5.6.0-beta.140
Seems like #5442 won't fix the core problem and ESM imports would still fail in NodeJs.
Investigation
TL;DR: packages should use package.json exports field, otherwise named imports may fail in Node runtime and tree-shaking might work unpredictable.
I've tried the same fix from #5442 and my code still failed by running it with npx tsx ./src/snippet.ts:
import {Terminal} from "@xterm/headless" // no typescript error here
const terminal = new Terminal()
console.log("cols:", terminal.cols)That seem to be a module resolution problem inside tsx I thought, and tried to run the same script like this:
node -e 'import {Terminal} from "@xterm/headless"
const terminal = new Terminal()
console.log("cols:", terminal.cols)'
output:
import {Terminal} from "@xterm/headless"
^^^^^^^^
SyntaxError: Named export 'Terminal' not found. The requested module '@xterm/headless' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from '@xterm/headless';
const {Terminal} = pkg;
Turns out the "module" field in package.json isn't supported by NodeJs and won't be in the future. That means module resolution silently falls back to CommonJs version.
As per my research all popular bundlers seem to support "module" field right now, however webpack uses https://github.com/webpack/enhanced-resolve which by default doesn't respect it (webpack overrides this defaults).
Why it's bad
If a runtime or a bundler doesn't support "module" field in package.json and:
- module compiled to
commonjs-> typescript won't raise any error for named imports -> Runtime error - module compiled to
umd(like other add-ons) -> Tree-shaking silently skipped - ...probably more uncertainties?
Proposed solution
We should adopt de-facto standard exports field in package.json. More specifically Conditional exports. This won't introduce any compatibility issues, exports and main+module fields can live together:
// ...
"main": "lib-headless/xterm-headless.js",
"module": "lib-headless/xterm-headless.mjs",
"types": "typings/xterm-headless.d.ts",
"exports": {
"types": "./typings/xterm-headless.d.ts", // goes first
"import": "./lib-headless/xterm-headless.mjs",
"require": "./lib-headless/xterm-headless.js"
}
// ...
This approach can provide further benefits like precise exports for browser/node or test/production environments etc.. More: https://nodejs.org/api/packages.html#resolving-user-conditions