esl icon indicating copy to clipboard operation
esl copied to clipboard

[❔build] proposal: CJS or ESM output for esl-utils module

Open fshovchko opened this issue 1 year ago • 1 comments

  • Can the consumer use from ESM Node project CJS modules
  • Trends in Node env
  • if we include support of ESM/CJS for esl-utils - > how do we need to extend our build process...

fshovchko avatar Jul 05 '23 15:07 fshovchko

@ala-n Upon the investigation based on the ESL project, I came to the following conclusion: I made an attempt to modify our build process using newly added package.json export maps (more details on that https://www.sensedeep.com/blog/posts/2021/how-to-create-single-source-npm-module.html https://habr.com/ru/articles/695482/)

In the end i got something like that:

"module": "modules/mjs/modules/all.js", "exports": { "./*": { "import": "./modules/mjs/modules/all.js", "require": "./modules/cjs/modules/all.js", "types": "./modules/types/modules/all.d.ts", "default": "./modules/mjs/modules/all.js" } },

To crate separate export folders, it involved adding additional typescript config files for each module type. These config files are extensions of our existing tsconfig.buid.json file. tsconfig-esm.json was added for esm and default module types:

{ "extends": "./tsconfig.build.json", "compilerOptions": { "module": "esnext", "outDir": "./modules/mjs", "target": "esnext" } }

tsconfig-cjs.json was added for commonjs modules:

{ "extends": "./tsconfig.build.json", "compilerOptions": { "module": "commonjs", "outDir": "./modules/cjs", "target": "es2015" } }

In order to prevent TypeScript from generating two type maps, it was separated into standalone process aswell: ` { "extends": "./tsconfig.build.json", "compilerOptions": { "outDir": "./modules/types", "declaration": true, "emitDeclarationOnly": true } }

`

The result package worked well in test repository, https://github.com/fshovchko/esl-monorepo-test throught webpack build process. Same goes for node environment that was using CommonJs module type. But using ESM modules in Node resulted in a simillar error: https://github.com/Esri/calcite-design-system/issues/5077. In order to fix the issue, the file extensions should be specified in import/export statements. And esl's package.json should have "type": "module" set. But using the statement will result in error in non-module environment. One of the workaround to it can be using a separate package.json just for esm modules that will only consist of one field {"type": "module"}.

Another workaround that I found was proposed by a TypeScript itself (https://www.typescriptlang.org/docs/handbook/esm-node.html). Changing our module to "module": "NodeNext" seems to resolve all of the issues. It works well with CommonJs and ESM, doesn't require specified extensions in package.json, or basically any other configuration from our side. Looking at its compiled code, it looks like cjs based coade, with probably some extra fetures added to it. The code was tested in Node v14 and everything worked fine. Some extra explanation about "NodeNext" here: https://stackoverflow.com/questions/71463698/why-we-need-nodenext-typescript-compiler-option-when-we-have-esnext

About the Node version.

  • Starting Node 12+, it has support for CJS and ESM side-by-side.
  • Node.js 14 introduced the ESMLoader, which is responsible for loading and interpreting ES Modules. It enables features such as top-level await, dynamic import(), and import.meta.
  • Node.js 17 introduced import assertions. (https://nodejs.org/api/esm.html). We don't use import assertions, so we should be good with any Node starting from v14.

In package json it can be specified as following: "engines": { "node": ">=14.0.0" },

fshovchko avatar Jul 05 '23 17:07 fshovchko