Skip to content

Repo to document all my findings about authoring, publishing and working with commonjs and esm in Node

Notifications You must be signed in to change notification settings

manuelbieh/authoring-modules-in-node

Repository files navigation

Authoring and publishing packages in Node

I am not sure yet if it is realistic, I'm not even sure if this all makes sense, but I want to find a way to build and publish a JavaScript package that works both in the Browser and in Node with the minimal possible effort. With commonjs and esm. With support for treeshaking. Without going crazy on configuration or build steps and without changing the way you're working right now (or at least without changing it too much).

The main reason for this investigation is a (still unresolved) issue I got in an open source project of mine. At the time of writing this, I have absolutely no idea how an ideal solution to that issue could look like.

Specific goals

  • browser support by directly embedding a transpiled/bundled UMD version via <script src></script>
  • browser support for <script type="module" src></script>
  • full backwards-compatibilty with commonjs and require() in Node
  • tree-shaking support for bundlers like Parcel.js, Rollup or Webpack
  • work effortlessly with the current state of Node's experimental support for ECMAScript Modules (import x from 'x', without additional build step required)
  • stick with Babel + Webpack for transpiling and bundling the library that is published to npm
  • do not require users to make changes to their existing build tools

Things to find out

  • Do browsers understand .cjs file endings without explicitly sending a application/javascript header (assumption: they do)
  • Does webpack and other bundlers find .cjs files without explicit configuration (assumption: as long as the main property in package.json points to a .cjs file, they do)
  • How do source files need to be built/transpiled so that they can be tree-shaken by webpack et al.?
  • What must happen so that Node can import particular ESModules without complaining

Findings

  1. Node's experimental implementation of ESModules had breaking changes/different behavior between versions 12.11.012.11.1, 12.12.0, 12.13.012.13.1 when type is set to module in package.json.
  2. Native ESModules can only be imported in Node if the file that is importing the ESModule is ending on .mjs or if it is inside of a project that has "type":"module" defined in its package.json. In this case .js also works.
  3. When importing from a relative ESModule the import path import identifier must contain the file suffix (import add from './add.js' works while import add from './add' does not). This is according to the current spec and matches Browser behavior as far as I can tell.
  4. On the other side, if you do have "type":"module" in your package.json, you can use .cjs for commonjs modules.
  5. LOL: when importing ./example/es/index.js with ESLint with eslint-plugin-unicorn and autofix enabled, the unicorn/import-index rule automatically shortens the path to ./example/es making the script fail.
  6. Unless "type":"module" is set in package.json, files need to be named .mjs to tell Node it's an ESModule. Babel, however, does not yet support writing file extentions other than .js. There's been an open PR to add a new --out-file-extension option to babel-cli but it hasn't been merged yet. Update: will probably be released with Babel 7.8!
  7. That also means that at the time of writing there seems to be no way to safely generate ES Modules in Babel without setting "type":"modules" for the complete package due to the lack of support for .mjs as output file extension.
  8. There is an open issue in TypeScript to support writing compiled files with a .mjs file-extension. Until this is done, there's also no safe support for .mjs as indicator for ES Modules.
  9. @karlhorky pointed me to babel-esm-plugin which looks helpful until #9144 is merged. Will investigate.
  10. VSCode 1.40.2 (and probably other editors and IDEs) do not treat .cjs files as JavaScript but use plaintext instead. This can be configured by setting:
    "files.associations": {
      "*.cjs": "javascript"
    }
    
    .mjs however, is correctly detected as JavaScript.
  11. Once "type":"module" is set, you can't use .babelrc.js or webpack.config.js anymore but you must stricly use .cjs and rename them .babelrc.cjs and webpack.config.cjs. That is because @babel/core is still using require() to load config files. However, Babel looks for the existence of a .babelrc.cjs file automatically (source). Webpack does not. You have to add --config webppack.config.cjs explicitly.
  12. There is a new exports property in package.json for Node. It is a map containing aliases to tell Node where to look for imports. See Node docs on ECMAScript Modules for more info.

Related links:

Credits

Setup

If you wanna try it out yourself:

yarn
npx lerna bootstrap --force-local

More docs on that coming soon

Dictionary

tbd.

About

Repo to document all my findings about authoring, publishing and working with commonjs and esm in Node

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published