In this section, we'll create a new git repository, set up a .gitignore, create an initial commit, create a new repository on GitHub, and push our code to GitHub.
name is the name by which people will install your package. It must be unique on npm. You can create organization scopes (such as @total-typescript/demo) for free, these can help make it unique.
version is the version of your package. It should follow semantic versioning: the 0.0.1 format. Each time you publish a new version, you should increment this number.
description and keywords are short descriptions of your package. They're listed in searches in the npm registry.
homepage is the URL of your package's homepage. The GitHub repo is a good default, or a docs site if you have one.
bugs is the URL where people can report issues with your package.
author is you! You can add optionally add your email and website. If you have multiple contributors, you can specify them as an array of contributors with the same formatting.
repository is the URL of your package's repository. This creates a link on the npm registry to your GitHub repo.
files is an array of files that should be included when people install your package. In this case, we're including the dist folder. README.md, package.json and LICENSE are included by default.
type is set to module to indicate that your package uses ECMAScript modules, not CommonJS modules.
Create a file called LICENSE (no extension) containing the text of your license. For MIT, this is:
MIT LicenseCopyright (c) [year] [fullname]Permission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in allcopies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THESOFTWARE.
Change the [year] and [fullname] placeholders to the current year and your name.
In this section, we'll install TypeScript, set up a tsconfig.json, create a source file, create an index file, set up a build script, run our build, add dist to .gitignore, set up a ci script, and configure our tsconfig.json for the DOM.
{ "compilerOptions": { /* Base Options: */ "esModuleInterop": true, "skipLibCheck": true, "target": "es2022", "allowJs": true, "resolveJsonModule": true, "moduleDetection": "force", "isolatedModules": true, "verbatimModuleSyntax": true, /* Strictness */ "strict": true, "noUncheckedIndexedAccess": true, "noImplicitOverride": true, /* If transpiling with TypeScript: */ "module": "NodeNext", "outDir": "dist", "rootDir": "src", "sourceMap": true, /* AND if you're building for a library: */ "declaration": true, /* AND if you're building for a library in a monorepo: */ "declarationMap": true }}
In this section, we'll install Prettier, set up a .prettierrc, set up a format script, run the format script, set up a check-format script, add the check-format script to our CI script, and run the CI script.
Prettier is a code formatter that automatically formats your code to a consistent style. This makes your code easier to read and maintain.
In this section, we'll install @arethetypeswrong/cli, set up a check-exports script, run the check-exports script, set up a main field, run the check-exports script again, set up a ci script, and run the ci script.
@arethetypeswrong/cli is a tool that checks if your package exports are correct. This is important because these are easy to get wrong, and can cause issues for people using your package.
This is telling us that our package is compatible with systems running ESM. People using CJS (often in legacy systems) will need to import it using a dynamic import.
If you want to publish both CJS and ESM code, you can use tsup. This is a tool built on top of esbuild that compiles your TypeScript code into both formats.
My personal recommendation would be to skip this step, and only ship ES Modules. This makes your setup significantly simpler, and avoids many of the pitfalls of dual publishing, like Dual Package Hazard.
The exports field tells programs consuming your package how to find the CJS and ESM versions of your package. In this case, we're pointing folks using import to dist/index.js and folks using require to dist/index.cjs.
It's also recommended to add ./package.json to the exports field. This is because certain tools need easy access to your package.json file.
In this section, we'll install vitest, create a test, set up a test script, run the test script, set up a dev script, and add the test script to our CI script.
vitest is a modern test runner for ESM and TypeScript. It's like Jest, but better.
In this section, we'll install @changesets/cli, initialize Changesets, make changeset releases public, set commit to true, set up a local-release script, add a changeset, commit your changes, run the local-release script, and finally see your package on npm.
Changesets is a tool that helps you version and publish your package. It's an incredible tool that I recommend to anyone publishing packages to npm.
Add a local-release script to your package.json with the following content:
{ "scripts": { "local-release": "changeset version && changeset publish" }}
This script will run your CI process and then publish your package to npm. This will be the command you run when you want to release a new version of your package from your local machine.
Add a prepublishOnly script to your package.json with the following content:
{ "scripts": { "prepublishOnly": "npm run ci" }}
This will automatically run your CI process before publishing your package to npm.
This is useful to separate from the local-release script in case a user accidentally runs npm publish without running local-release. Thanks to Jordan Harband for the suggestion!
You now have a fully set up package. You've set up:
A TypeScript project with the latest settings
Prettier, which both formats your code and checks that it's formatted correctly
@arethetypeswrong/cli, which checks that your package exports are correct
tsup, which compiles your TypeScript code to JavaScript
vitest, which runs your tests
GitHub Actions, which runs your CI process
Changesets, which versions and publishes your package
For further reading, I'd recommend setting up the Changesets GitHub action and PR bot to automatically recommend contributors add changesets to their PR's. They are both phenomenal.
And if you've got any more questions, let me know!