Code to NPM Packages

Introduction
A how-to guide about the philosophy, design, repositories, building, and deployment of component code Modules, Command Line Interface (CLI), or Cloud Functions (like AWS Lambda) via npm packages.
During my exploration into the Nuxt Static S3 issues and Nuxt Static S3 fixes, I identified custom CICD requirements to implement the fix I had identified.
- Create a list of paths from a sitemap.xml file
- Compare the list of paths of a new build sitemap.xml
- Perform custom AWS S3 commands based on the differences between the prod and new build.
I knew that each component of functionality should be modular and could be a useful in other contexts and code bases as a node module (NPM) package. Further we might want to create a regular NPM module, a standalone Command-line Interface (CLI), or even a cloud function.
- sitemapParse
- sitemapCompare
- updateS3NuxtSSG
Node Package Manager
I have always relied on Node Package Manager (NPM) and its registry of modular packages at npmjs.com. And even though my startup was an early sponsor of NPM, I had only ever contributed code to private package manager like JFrog Artifactory for my employers. It's time to contribute some open source code to the public via NPMjs.org.
NPM Best Practices
How should my code be organized, tested, for a good NPM package that others might find useful?
- Use a package manager like rollup.js
- Use TypeScript so you can share types
package.json- main npm package configuration.tsconfig.json- TypeScript configuration.rollup.config.js- package manager rollup.js config- Support both ESM (default for NPM) and CJS (CommonJs) for use in the client and node servers.
- use git branch protection rules and use PR process to protect main branch
Configuration
| config file | key | value | system |
|---|---|---|---|
package.json | type | module | ESM |
package.json | type | commonjs | CJS |
tsconfig.json | module | es6 | ESM |
tsconfig.json | module | commonjs | CJS |
see NPM Cheat Sheet
Module Systems
JavaScript Module Systems
- CJS - CommonJS
- AMD - Asynchronous Module Definition
- UMD - Universal Module Definition = CJS + AMD
- ESM - ECMA Script Modules
ESM is modern and can use CJS modules. CJS is older and can't use ESM modules1
| ESM key words | CJS key words | |
|---|---|---|
import | ---------> | require |
export | <---XX--- | module exports |
Support ESM and CJS for greatest compatibility for client, server, and operation code bases.
TypeScript
Typescript is recommended for NPM package due to its support for typing and assisting in documentation and usage.
TypeScript options necessary
- compilerOptions.declaration - true/false, omit declaration files while builds
- compilerOptions.declarationDir - path, where declaration files will be
- compilerOptions.target es6 - trans pile code to ECMA script 6
- compilerOptions.moduleResultation - use "node"
- include: - array of source code directories, glob patter src/**/*
- exclude: - ignore the array of directories for source code
Rollup Bundler
RollupJs is a package bundler which will place all key files and build artifacts into a bundle for distribution. For greatest compatibility we will support both module types
- ESM Bundle
- CJS Bundle
Update package.json to to allow for rollup bundling
package.json
{
// ...
"name": "<npm_user_name>/<npm-package-name>",
"type": "module",
"main": "lib/index.cjs",
"exports": {
"import": {
"default": "./lib/index.esm.js",
"types": "./lib/types/index.d.ts"
},
"require": {
"default": "./lib/index.cjs",
"types": "./lib/types/index.d.ts"
}
},
"files": [
"lib"
],
"scripts": {
"build": "rollup -c",
"release-package": "npm run build && npx changeset publish"
}
// ...
}
Changesets Automatic Publishing
- Changesets is an automatic package versioning CLI and GitHub Action toolset.
References
Footnotes
- CJS can now load ESM. This is no longer true for newer Node versions with some restrictions. Version 23 of Node already have support for loading ES modules using require() under certain conditions (the main condition currently is that it only supports loading synchronous ES modules). This support was also added to v22.0.0 and v20.17.0. You can read more about it in the official docs ↩