Skip to content

Commit 2d5d359

Browse files
WIP: initial content for article
1 parent b20dcbf commit 2d5d359

File tree

1 file changed

+176
-0
lines changed

1 file changed

+176
-0
lines changed

apps/site/pages/en/learn/typescript/publishing.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,179 @@ authors: JakobJingleheimer
55
---
66

77
# Publishing a TypeScript package
8+
9+
This article augments TypeScript's [Publishing guide](https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html) with specifics for native node support.
10+
11+
Some important things to note:
12+
13+
- Node runs typescript via a process called "type stripping", wherein node (via SWC under the hood of [Amaro](https://github.com/nodejs/amaro)) removes TypeScript-specific syntax, leaving behind vanilla JavaScript (which node already understands). This behaviour is enabled by default of node version 23.6.0.
14+
15+
- Node does **not** strip types in `node_modules`. This decision was at the request of TypeScript maintainers because it can cause significant performance issues for the official compiler (`tsc`).
16+
17+
- TypeScript-specific features like `enum` still require a flag ([`--experimental-transform-types`](https://nodejs.org/api/typescript.html#typescript-features))
18+
19+
## What to do with your types
20+
21+
### Treat them like a test
22+
23+
The purpose of types are to warn an implementation will not work:
24+
25+
```ts
26+
const foo = 'a';
27+
const bar: number = 1 + foo;
28+
// ^^^ Type 'string' is not assignable to type 'number'.
29+
```
30+
31+
TypeScript has warned you that the above code will not behave as intended, just like a unit test warns you code does not behave as intended.
32+
33+
Your IDE (ex VS Code) likely has built-in support for TypeScript, displaying errors as you work. If not, and/or you missed those, CI will have your back.
34+
35+
```yaml displayName=".github/workflows/ci.yml"
36+
name: Tests
37+
38+
on:
39+
push:
40+
branches: ['main']
41+
pull_request:
42+
branches: ['main']
43+
44+
jobs:
45+
lint-and-check-types:
46+
# Separate these from tests because
47+
# they are platform and node-version independent.
48+
49+
runs-on: ubuntu-latest
50+
51+
steps:
52+
- uses: actions/checkout@v4
53+
- uses: actions/setup-node@v4
54+
with:
55+
node-version-file: '.nvmrc'
56+
cache: 'npm'
57+
- name: npm clean install
58+
run: npm ci
59+
- run: node --run lint
60+
- run: node --run types:check
61+
62+
test:
63+
runs-on: ubuntu-latest
64+
65+
strategy:
66+
matrix:
67+
node:
68+
- version: 23.x
69+
- version: 22.x
70+
# glob is not backported below 22.x
71+
fail-fast: false # prevent a failure in one version cancelling other runs
72+
73+
steps:
74+
- uses: actions/checkout@v4
75+
- name: Use Node.js ${{ matrix.node.version }}
76+
uses: actions/setup-node@v4
77+
with:
78+
node-version: ${{ matrix.node.version }}
79+
cache: 'npm'
80+
- name: npm clean install
81+
run: npm ci
82+
- run: node --run test
83+
```
84+
85+
```json displayName="package.json"
86+
{
87+
"version": "0.0.0",
88+
"name": "example-ts-pkg",
89+
"scripts": {
90+
"lint": "",
91+
"types:check": "tsc --noEmit"
92+
},
93+
"optionalDependencies": {
94+
// This is used only in CI.
95+
// Marking it 'optional' avoids installing on your local
96+
// (where you probably won't use it).
97+
"typescript": "^5.7.2"
98+
}
99+
}
100+
```
101+
102+
```json displayName="tsconfig.json"
103+
{
104+
"compilerOptions": {
105+
"declarationMap": true,
106+
"declaration": true,
107+
"emitDeclarationOnly": true,
108+
"esModuleInterop": true, // Flux Capacitor: The universe breaks without it, but nobody knows what it does.
109+
"module": "NodeNext",
110+
"moduleResolution": "NodeNext",
111+
"target": "ESNext"
112+
},
113+
// These may be different for your repo:
114+
"include": "./src",
115+
"exclude": ["**/*/*.test.*"]
116+
}
117+
```
118+
119+
### Generating type declarations
120+
121+
Type declarations (`.d.ts` and friends) provide type information as a sidecar file, allowing the execution code to be vanilla JavaScript whilst still having types.
122+
123+
Since these are generated based on source code, they can be built as part of your publication process and do not need to be checked into your repository.
124+
125+
Take the following example (a [GitHub Action](https://github.com/features/actions)), where the type declarations are generated just before publishing to the NPM registry.
126+
127+
```yaml displayName=".github/workflows/publish.yml"
128+
name: Publish to NPM
129+
on:
130+
push:
131+
tags:
132+
- '**@*'
133+
134+
jobs:
135+
build:
136+
runs-on: ubuntu-latest
137+
138+
permissions:
139+
contents: read
140+
id-token: write
141+
steps:
142+
- uses: actions/checkout@v4
143+
- uses: actions/setup-node@v4
144+
with:
145+
node-version-file: '.nvmrc'
146+
registry-url: 'https://registry.npmjs.org'
147+
- run: npm ci
148+
149+
# You can probably ignore the boilerplate config above
150+
151+
- name: Generate types
152+
run: node --run types:generate
153+
154+
- name: Publish with provenance
155+
env:
156+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
157+
run: npm publish --access public --provenance
158+
```
159+
160+
```diff displayName="package.json"
161+
{
162+
"name": "example-ts-pkg",
163+
"scripts": {
164+
"types:check": "tsc --noEmit",
165+
+ "types:generate": "tsc"
166+
}
167+
}
168+
```
169+
170+
```text displayName=".npmignore"
171+
*.test.*
172+
*.fixture.*
173+
fixture.*
174+
fixtures
175+
```
176+
177+
#### Breaking this down
178+
179+
Generating type declarations is deterministic: you'll get the same output from the same input, every time. So there is no need to commit these to git.
180+
181+
[`npm publish`](https://docs.npmjs.com/cli/v11/commands/npm-publish) grabs everything applicable and available at the moment the command is run; so generating type declarations immediately before means those are available and will get picked up.
182+
183+
By default, `npm publish` grabs (almost) everything (see [Files included in package](https://docs.npmjs.com/cli/v11/commands/npm-publish#files-included-in-package)). In order to keep your published package minimal (see the "Heaviest Objects in the Universe" meme about `node_modules`), you want to exclude certain files (like tests and test fixtures) from from packaging.

0 commit comments

Comments
 (0)