DEV Community

Iulian Preda
Iulian Preda

Posted on • Edited on

How to easily create JS libraries compatible with ES/AMD/UMD/CJS module systems using Nx

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
    "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"
      }
    }
Enter fullscreen mode Exit fullscreen mode

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"]
      }
    }
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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": [
        "**/*"
    ],
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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 as an example of this approach.

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

Top comments (0)