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.