Skip to content

Improve documentation on Dual Module Packages #34515

@MicahZoltu

Description

@MicahZoltu
  • Version: v14.4.0
  • Platform: Windows 10
  • Subsystem: Windows

What steps will reproduce the bug?

git clone [email protected]:MicahZoltu/dual-module-package-repro.git
cd dual-module-package-repro/app-package
npm install
node index.mjs
# notice it works
node index.cjs
# notice it does not work

Then remove "type": "module" from library-package/package.json and repeat the process:

npm install
node index.mjs
# notice it doesn't work
node index.cjs
# notice it does work

How often does it reproduce? Is there a required condition?

Always.

What is the expected behavior?

The ability to ship an NPM package that can be used by either CJS users (any version of NodeJS) or ESM users (running NodeJS 14+)

What do you see instead?

I can either define type: module and it will work as an ESM, or I can not define it or set it to CommonJS and it will work as a CJS module, but I cannot define the package.json such that the package works in both environments.

Additional information

The error indicates that NodeJS is correctly following the exports path and locating the right module in each situation, however it proceeds to load the module using a loader picked by the presence of the type property in package.json. My expectation is that if it loads via the exports: { import: ... } entrypoint then that entire call stack from there down should be ESM, and if it loads via exports: { require: ... } then the entire call stack from there down should be CJS.

Error when type: module is not set:

dual-module-package-repro\library-package\index-esm.js:1
export const apple = 'apple'
^^^^^^

SyntaxError: Unexpected token 'export'
    at wrapSafe (internal/modules/cjs/loader.js:1116:16)

Error when type: module is set:

internal/modules/cjs/loader.js:1216
      throw new ERR_REQUIRE_ESM(filename, parentPath, packageJsonPath);
      ^

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: ...\dual-module-package-repro\library-package\index-cjs.js
require() of ES modules is not supported.
require() of ...\dual-module-package-repro\library-package\index-cjs.js from ...\dual-module-package-repro\app-package\index.cjs is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename index-cjs.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from ...\dual-module-package-repro\library-package\package.json.

    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1216:13)

The documentation makes it sound like NodeJS 14 supports packages that can be used as either ESM or CJS, but thus far I have not figured out how to actually accomplish this.

I believe this specific problem could be worked around by renaming files to .cjs and .mjs in the library-package. However, in the real world this solution is far less tenable because I'm compiling from TypeScript which does not do import rewrites during emit (and they maintain a very strong position that they have no intent to ever change this policy). This means that I cannot have TypeScript emit files where the import statements have different extensions depending on the module type being targeted. So in order to have everything be mjs in the ESM build output and cjs in the CJS build output I would need to run my code through a transformer that rewrites all import statements (both static and dynamic) and also rename all of the files to have extensions by output folder.

Metadata

Metadata

Assignees

No one assigned

    Labels

    docIssues and PRs related to the documentations.moduleIssues and PRs related to the module subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions