Skip to content

[Bug]: usage of import in Node is broken because of export maps #26176

@layershifter

Description

@layershifter

Library

React Components / v9 (@fluentui/react-components)

System Info

N/A

Are you reporting Accessibility issue?

no

Reproduction

https://stackblitz.com/edit/node-b7xc87

Bug Description

Actual Behavior

Native ES modules don't work:

❯ node index.mjs
(node:11) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/home/projects/node-b7xc87/node_modules/@fluentui/react-tabster/lib/index.js:1
export { useArrowNavigationGroup, useFocusableGroup, useFocusFinders, useFocusVisible, useFocusWithin, useKeyboardNavAttribute, useModalAttributes, useTabsterAttributes } from './hooks/index';
^^^^^^

Expected Behavior

ES Modules work:

❯ node index.cjs
function

ESM in Node

Node.js has two module systems: CommonJS modules and ECMAScript modules.

Authors can tell Node.js to use the ECMAScript modules loader via the .mjs file extension, the package.json "type" field, or the --input-type flag. Outside of those cases, Node.js will use the CommonJS module loader. See Determining module system for more details.

https://nodejs.org/api/esm.html

Another good summary is there: https://www.typescriptlang.org/docs/handbook/esm-node.html

Problem

We ship CommonJS and ESM code currently, the problem is that our packaging does not follow best practices from Node (https://nodejs.org/api/packages.html#dual-commonjses-module-packages):

  • we don't specify type: module or type: commonjs so Node does not know how to handle .js files
  • we don't ship .mjs or .cjs files to indicate contents of them
  • our export maps are invalid

Mid-solution: update export maps

  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
+     "node": "./lib-commonjs/index.js"
      "import": "./lib/index.js",
      "require": "./lib-commonjs/index.js"
    },
    "./package.json": "./package.json"
  }

We can add node field (order is important) to export maps, so Node will always use CommonJS output. That's not ideal, but things will start to work as expected:

  • Node uses CommonJS always
  • Bundlers will still use ESM output based on import entry

Ideal solution: ship dual packages

  • Upgrade TypeScript to support new module resolution and apply it (moduleResolution: node16)
  • Add type: module to package.json files
  • Ship ESM in .js file, ship CommonJS in .cjs files
  • ✅ 🍾

Note: While this sounds easy, the first step will be the most time consuming:

relative import paths need full extensions
https://www.typescriptlang.org/docs/handbook/esm-node.html

Logs

N/A

Requested priority

High

Products/sites affected

No response

Are you willing to submit a PR to fix?

no

Validations

  • Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
  • The provided reproduction is a minimal reproducible example of the bug.

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions