Skip to main content
Dublin Library

The Publishing Project

Publishing NPM modules

 

Publishing NPM modules has become progessively more complex over time (go figure). We now have to contend with ESM versus Common JS, Typescript versus Javascript, setting a default module type, and other considerations.

The first decision is to use Typescript as my development language to ensure type safety.

There is additional items that need to be added to package.json in order for it to work with common.js and ESM.

Finally, we'll discuss different tools available to make the process easier.

Setting Up And Writing The Code #

The first step is to initialize an empty package.json file:

npm init --yes

Next we install the necessary packages. It's a two-step process. One to install regular packages and another to install developer dependencies.

npm i lit
npm i -D typescsript tsup

Nex, we handle the Typescript configuration in a JSON file (tsconfig.json). We can either create one from scratch or run tsc --init and then edit it to make sure the values below are included.

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "useDefineForClassFields": false,
  }
}

tsup.config.js

import { defineConfig } from 'tsup'

export default defineConfig({
  entry: [
    'src/index.ts'
  ],
  splitting: false,
  sourcemap: true,
  clean: true,
})

lit component example

import {
  LitElement,
  css,
  html
} from 'lit';

import {
  customElement,
  property
} from 'lit/decorators.js';

@customElement('simple-greeting')
export class SimpleGreeting extends LitElement {
  // Define scoped styles right with your component, in plain CSS
  static styles = css`
    :host {
      color: blue;
    }
  `;

  // Declare reactive properties
  @property()
  name?: string = 'World';

  // Render the UI as a function of component state
  render() {
    return html`<p>Hello, ${this.name}!</p>`;
  }
}

Instead of using the Typescript compiler, we'll use Tsup

 npx tsup src/index.mts --format cjs,esm --dts --clean --sourcemap

Updating and Linting package.json #

The hardest part is making sure that we set up the package.json correctly to handle both CJS and ESM modules at the same time.

This is the correct package.json block with a src/index.ts root file that supports both ESM and CJS modules.

{
  "type": "module",
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    "import": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    },
    "require": {
      "types": "./dist/index.d.cts",
      "require": "./dist/index.cjs"
    }
  }
}

This gets complicated so we'll break it down in sections

  • The type (singular) field indicates that the package supports ESM modules
  • The main field shows the entry point for CJS modules
  • The module field indicates the entry point for ESM modules
  • The types (plural) field points to the type definitions file for ESM modules

We then set up the exports field to support both ESM and CJS modules. The import field is set up to support ESM modules and the require field is set up to support CJS modules. In each, the types field is set to the relevant type definitions file — the types comes first.

This is complicated so we'll use a third-party linter, Are the Types Wrong to lint the package.json file. This tool checks your package.json file to ensure that it's set up correctly for publishing a package that supports both ESM and CJS modules.

npx --yes @arethetypeswrong/cli --pack .

Setting a Git Repo #

I normally leave the Github repository creation and pushing content to it

Initialize the Git repository.

git init

Run the following command to create a .gitignore file and append node_modules to it.

echo "node_modules" >> .gitignore

Add all files to the repository and commit the files to the new repository with a message.

git add .
git commit -m "Initial commit"

Use the Github CLI to create the remote repository under your Github account.

gh repo create rw-greeting --source=. --public

Lastly we push the local repo to Github making sure we set the remote repository using --set-upstream.

git push --set-upstream origin main

I will not cover how to publish to NPM. We can also install Node packages from github or download it and link to it locally.

Edit on Github