authoring-modules-in-node
authoring-modules-in-node copied to clipboard
Repo to document all my findings about authoring, publishing and working with commonjs and esm in Node
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
.cjsfile endings without explicitly sending a application/javascript header (assumption: they do) - Does webpack and other bundlers find
.cjsfiles without explicit configuration (assumption: as long as themainproperty in package.json points to a.cjsfile, 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
importparticular ESModules without complaining
Findings
- Node's experimental implementation of ESModules had breaking changes/different behavior between versions
12.11.0→12.11.1,12.12.0,12.13.0→12.13.1whentypeis set tomodulein package.json. - Native ESModules can only be imported in Node if the file that is importing the ESModule is ending on
.mjsor if it is inside of a project that has"type":"module"defined in its package.json. In this case.jsalso works. - When importing from a relative ESModule the ~~import path~~ import identifier must contain the file suffix (
import add from './add.js'works whileimport add from './add'does not). This is according to the current spec and matches Browser behavior as far as I can tell. - On the other side, if you do have
"type":"module"in your package.json, you can use.cjsfor commonjs modules. - LOL: when importing
./example/es/index.jswith ESLint witheslint-plugin-unicornand autofix enabled, theunicorn/import-indexrule automatically shortens the path to./example/esmaking the script fail. - Unless
"type":"module"is set in package.json, files need to be named.mjsto 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-extensionoption tobabel-clibut it hasn't been merged yet. Update: will probably be released with Babel 7.8! - 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.mjsas output file extension. - There is an open issue in TypeScript to support writing compiled files with a
.mjsfile-extension. Until this is done, there's also no safe support for.mjsas indicator for ES Modules. - @karlhorky pointed me to babel-esm-plugin which looks helpful until #9144 is merged. Will investigate.
- VSCode 1.40.2 (and probably other editors and IDEs) do not treat
.cjsfiles as JavaScript but useplaintextinstead. This can be configured by setting:"files.associations": { "*.cjs": "javascript" }.mjshowever, is correctly detected as JavaScript. - Once
"type":"module"is set, you can't use.babelrc.jsorwebpack.config.jsanymore but you must stricly use.cjsand rename them.babelrc.cjsandwebpack.config.cjs. That is because@babel/coreis still usingrequire()to load config files. However, Babel looks for the existence of a.babelrc.cjsfile automatically (source). Webpack does not. You have to add--config webppack.config.cjsexplicitly. - There is a new
exportsproperty inpackage.jsonfor 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:
- https://twitter.com/MylesBorins/status/1202686414896300033
- https://twitter.com/mjackson/status/1202650812159184896
- https://github.com/graphql/graphql-js/pull/2277
- https://medium.com/@nodejs/announcing-a-new-experimental-modules-1be8d2d6c2ff
- https://nodejs.org/api/esm.html
- https://github.com/rollup/rollup/wiki/pkg.module
- https://github.com/parcel-bundler/parcel/pull/3545
- https://twitter.com/wesbos/status/1205159427491414017
- https://github.com/vanillaes
Credits
Setup
If you wanna try it out yourself:
yarn
npx lerna bootstrap --force-local
More docs on that coming soon
Dictionary
tbd.