How to easily create JS libraries compatible with ES/AMD/UMD/CJS module systems using Nx
Iulian Preda
Posted on December 28, 2021
Problem description
Authoring libraries is always a pain if you try to maximize the number of projects that can incorporate yours.
In a perfect world, we would have to use only one module system, most probably ES modules as that is the standard in Javascript specifications, but as we might know we don't live in a perfect world and transitioning projects takes a lot of time. So, even though now, NodeJS already has pretty good support for ES modules and all modern browsers are compatible with it a lot of projects still use CJS or UMD or even AMD modules.
In this article I'll show you a quick and easy way on how to create a new library publishable to NPM, YARN, PNPM, whatever other package managers will exist in the future, that will target all these module systems.
In my example, I'll use Typescript and NPM but the solution is overall agnostic of these so you could use YARN and Javascript for example.
The entire solution will also be powered up by NX, a free mono repository solution that does the entire heavy lifting for us.
Please be aware that some package names might change in the future, if that happens let me know in a comment so I can update the article.
I will be using the latest NX version available, which at the time of writing is V13, which brings a lot of improvements and a simplified process.
Prerequisites for this example
NodeJs
Npm
-
VsCode
or any other code editor - Any terminal - Personally I recommend
Windows Terminal
Creating the library
- Create a folder and open a terminal in it
- Run
npx create-nx-workspace@latest LibrarySolutionName
- Run
cd LibrarySolutionName
- Run
npm i -D @nrwl/web
- it will install the addon that will package our library - Run
nx generate @nrwl/js:library --name=LibraryName --importPath=LibraryName --buildable
- Open the newly created folder in your code editor
- Go to
packages/LibraryName/tsconfig.json
and changeCommonJs
toEsNext
orES6
. - Create in
packages/LibraryName
a json calledbabel.config.json
that will contain{}
. You can alternatively create it in the root folder and it will work as a global babel file for each ulterior library you might create. - Go to
packages/LibraryName/project.json
and add in thetargets
the property
"package": {
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"project": "packages/LibraryName/package.json",
"outputPath": "dist/packages/LibraryName",
"entryFile": "packages/LibraryName/src/index.ts",
"tsConfig": "packages/LibraryName/tsconfig.lib.json"
}
}
There are other interesting options
you might consider, like:
- assets
- different compiler (only babel and swc are available)
- different UMD name
- CJS output
- external libraries included in the bundle
- adding dependencies I will present you a configuration that lists all these options
! To copy the Readme.md created please move it to the packages/LibraryName/src
! To use 'swc' as a compiler you will need to add it to the project using
npm i @swc/core
"package": {
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"project": "packages/LibraryName/package.json",
"outputPath": "dist/packages/LibraryName",
"entryFile": "packages/LibraryName/src/index.ts",
"tsConfig": "packages/LibraryName/tsconfig.lib.json",
"compiler": "babel",
"umdName": "OtherName",
"external": ["typescript"],
"format": ["cjs", "esm", "umd"],
"assets": ["packages/LibraryName/src/README.md"]
}
}
At this point you are pretty much done, all you need to do is run nx package LibraryName
and a few seconds later you will see a dist/LibraryName
folder appeared with all the files needed for publishing. If you will open the package.json you will notice a few extra properties added
"main": "./index.umd.js",
"module": "./index.esm.js",
"typings": "./index.d.ts"
These will instruct any library user from where to import each library type based on the module system they use.
Minify the bundles
If you would like to have your code minify you can take advantage of babel for that.
Run npm install babel-preset-minify --save-dev
Then in babel.config.json
add "presets": ["minify"]
Publishing
- Add in
packages/LibraryName/package.json
the property
"files": [
"**/*"
],
This needs to be done in order to get these files inside your npm package.
- Run
cd dist/packages/LibraryName
- Run
npm publish --tag=latest --access public
and login
For a more advanced publishing way, you can run
nx g @nrwl/workspace:run-commands publish --project LibraryName --command 'cd dist/packages/LibraryName && npm publish --tag=latest --access public'
This will add a publishing executor to the packages/LibraryName/project.json
that will run the publishing npm command. Then all you need to do is update the version of the package and run nx publish LibraryName
and it will automatically be published.
Extra details
- Usually the
importPath
is used with the following naming scheme@LibrarySolutionName/LibraryName
- If you use it for Node do not forget to install
@types/node
and add them to thetsconfig.base.json
and thepackages/LibraryName/tsconfig.json
- After you publish to npm your library is automatically available on
unpkgr
atunpkg.com/:package@:version/:file
so you can import your bundled scripts directly. Imported like this the UMD can be used in the web browser as a global object with the name of the library in PascalCase. - Opposed to webpack this type of bundling does not include external dependencies so your libraries are kept to a minimum. Please do not forget to add all of your dependencies to
packages/LibraryName/package.json
You can check this repo as an example of this approach.
Thanks for reading! Feel free to suggest any other interesting topics to be covered in a different article.
Posted on December 28, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
December 28, 2021