How to easily create JS libraries compatible with ES/AMD/UMD/CJS module systems using Nx
Tags: javascript, npm, webdev, tutorial
Published: 2021-12-28 13:45:29

## 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 change `CommonJs` to `EsNext` or `ES6`.
- Create in `packages/LibraryName` a json called `babel.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 the `targets` the property 
```json
    "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](https://nx.dev/l/n/web/package) 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`
```json
    "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
```json
 "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](https://babeljs.io/docs/en/babel-preset-minify) 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
```json
    "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](https://github.com/IP-OpenSourceWeb/OpenSourceWeb) way, you can run 
```bash
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 the `tsconfig.base.json` and the `packages/LibraryName/tsconfig.json`
- After you publish to npm your library is automatically available on `unpkgr` at `unpkg.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](https://github.com/IP-OpenSourceWeb/OpenSourceWeb) as an example of this approach.

Thanks for reading! Feel free to suggest any other interesting topics to be covered in a different article.