An investigation into publishing native ECMA Modules via npm and consuming them in browser and node applications.
This project is a lerna monorepo which contains 2 libraries, and some apps. The apps consume one of the libraries, which has a dependency on the other library.
The libraries are written using ECMA Module semantics (import
, export
and full file paths including the extension) which should theoretically work with Node, modern browsers, and various bundling systems.
Ideally in the future we will have "Import Maps" in browsers, but until then (and without wanting to any build-time / runtime dependencies) I have forgone using those purely in favour of specifying full paths where necessary.
The point of this project is to understand whether it is practical to publish Ecma Modules to NPM, and whether there are any tangible costs or benefits to doing so.
lib-date
, a date formatting library which provides two date-formatting functions, both of which depend onlib-string
lib-string
, a string formatting library which can "pad" or "quote" stringsapp-browser
, a vanilla JS browser application that requires no compilation, assuming the presence ofnode_modules
and a suitable browser.app-browser-angular
, an angular app (created withnpx -p @angular/cli ng new app-browser-angular
)app-browser-cra
, a react app (created withnpx create-react-app app-browser-cra
)app-node
, a node applicationapp-parcel
, a vanilla web application bundled using Parcelapp-rollup
, a vanilla js application bundled using Rollupapp-webpack
, a vanilla js application bundled using Webpack
You can run npm run bootstrap
at the root to make sure that lerna
has created all the symlinks for you.
Then run npm run build
to build all of the projects that require it (all the app-*
projects except app-browser
and app-node
). That will run the build scripts in each package and you can inspect the results (usually in either a dist/
or build/
folder).
Project Name | Path | Import Style | Tree Shaking |
---|---|---|---|
Native Browser | app-browser |
Full Path | ❌ |
Angular | app-browser-angular |
Node | ❌ |
React (CRA) | app-browser-cra |
Node | ✅ |
Node | app-node |
Node | ❌ |
Parcel | app-parcel |
Node | ❌ |
Rollup + Plugin | app-rollup |
Node | ✅ |
Webpack | app-webpack |
Node | ✅ |
See ./packages/app-browser/index.html.
Works fine! Browsers (until import-map
is supported) need the exact file loaded. Since the published libraries are written in a browser-supporting manner (i.e. they import
full file paths including the extension) the user can either import
the root module, or a specific file that they require. In either case, a full file path is required, and there can be no tree-shaking (if you import the root file of a module that itself imports 2 things, but you only use one, everything will be loaded by the browser).
See ./packages/app-browser-angular/src/app/app.component.ts.
Angular's default setup does not do any tree shaking, but everything works out of the box just by referencing the package name.
See ./packages/app-browser-cra/src/App.js.
create-react-app
s webpack setup tree-shakes the loaded modules when building. Only the imported functions (from the direct dependency and the transitive one) are loaded. You can see this in the built output (the code will be in the main.{HASH}.chunk.js
file).
See ./packages/app-node/index.js.
This requires Node 13+, and you will still see a warning about "ESM Modules" being experimental, which is true. You don't need to add the --experimental-modules
flag as long as you include "type": "module"
in the appropriate package.json
files.
Since this is node, we can rely on package.json
in the library to point us to the right actual file, and we don't need to include the full file path.
Note that when you have set type as "module", you can no longer use require()
!
See ./packages/app-parcel/index.js.
Doesn't tree shake by default, but does let you use Node style module resolution.
See ./packages/app-rollup/index.js.
Does tree shake, and assuming use of @rollup/plugin-node-resolve
we can just reference the package name.
See ./packages/app-webpack/src/index.js.
Does tree shaking (no loaders or any other config except mode: "production"
used in webpack.config.js
) anfd lets us reference the package name.
- ESM Modules in Node
- ESM Module loader warning in Node, suggests that "you should not use it in production yet (or publish any node module that uses es6 modules that is meant to be used with node)".
- Investigate
"exports": ...
in library package.json files, see Node ESM docs - Collate metrics for all apps into a table