chore: packaging polish for 0.1.0
- ship `src/` in the npm tarball alongside `dist/` — the emitted `.d.ts.map`
reference `../../src/*.ts`, so "go to definition" only lands on real code
if the source is there; also gives npm/JSR source parity. (+16 kB packed.)
- `sideEffects` becomes an explicit allowlist (`dist/index.js`,
`dist/index.npm.js`, `dist/components/*.js`) instead of `true`, so bundlers
can tree-shake the side-effect-free `elements/*` and `core/*`.
- add `scripts/sync-version.mjs`: copies `package.json` version into
`jsr.json`. Wired into the `version` npm lifecycle hook (so `npm version`
bumps and stages both) and re-run in `prepublishOnly`.
- add `repository` / `bugs` / `homepage`; `author` -> "Buddy Sandidge" (was
"buddy", now matches LICENSE); a few more `keywords`.
- note in README/CLAUDE/docs that per-component deep imports
(`./elements/*.js`, `./components/*.js`) are npm-only — JSR has no subpath
patterns, so `jsr.json` exposes just `.` and `./elements`.
- exclude `scripts/` and `CLAUDE.md` from the JSR tarball.
Verified: `typecheck`, `lint`, `test` (76), `build`, `jsr publish --dry-run`
("Success"), and a fresh `npm pack` consumer typecheck all pass.
This commit is contained in:
parent
c03eb77d5b
commit
ebd6f20cdf
21
CLAUDE.md
21
CLAUDE.md
@ -12,7 +12,7 @@ stays a terse working cheat-sheet; `docs/` is the "why".
|
||||
```bash
|
||||
npm run build # tsc emits individual ESM modules to dist/ (no bundling)
|
||||
npm run typecheck # tsc --noEmit, then tsc -p tsconfig.test.json for test/
|
||||
npm run lint # oxlint over src/ and test/
|
||||
npm run lint # oxlint over src/, test/, scripts/
|
||||
npm run format # oxfmt formatting
|
||||
npm run test # vitest run
|
||||
npm run test:watch # vitest, watch mode
|
||||
@ -45,7 +45,7 @@ Every component is split across two files with the same basename:
|
||||
- **`src/elements/leaflet-foo.ts`** — a `const PROPS`, a `const Base = WithProps(PROPS)` (explicitly typed — see below), then `export default class LeafletFooElement extends Base {...}`. The class only; **no `customElements.define`**, no side effects. `src/elements/index.ts` is a barrel re-exporting all of them by name (`LeafletFooElement`), order-free because nothing here registers a tag.
|
||||
- **`src/components/leaflet-foo.ts`** — three lines: `import LeafletFooElement from '../elements/leaflet-foo.ts'`, `customElements.define('leaflet-foo', LeafletFooElement)`, `export { LeafletFooElement }`. Importing this module (or `src/index.ts` / `src/index.npm.ts`) is what actually registers the tag.
|
||||
|
||||
Most consumers just import the package root (or a `components/*` module) and get the tags defined. The `elements/*` classes exist for plugin authors who want to subclass an element or register it under a different tag name without triggering the built-in `define`. Package subpath exports (`leaflet-web-components/elements`, `.../elements/leaflet-foo.js`, `.../components/leaflet-foo.js`) map straight onto `dist/`.
|
||||
Most consumers just import the package root (or a `components/*` module) and get the tags defined. The `elements/*` classes exist for plugin authors who want to subclass an element or register it under a different tag name without triggering the built-in `define`. Package subpath exports (`leaflet-web-components/elements`, `.../elements/leaflet-foo.js`, `.../components/leaflet-foo.js`) map straight onto `dist/` — the per-file ones are npm-only, since JSR (`jsr.json` `exports`) has no subpath patterns and exposes just `.` and `./elements`.
|
||||
|
||||
### Two package entry points: `src/index.ts` vs `src/index.npm.ts`
|
||||
|
||||
@ -113,13 +113,18 @@ If you add or change what events a component fires, keep `#forwardEvents`'s `det
|
||||
|
||||
### Output & dual publish
|
||||
|
||||
`tsc` compiles `src/` → `dist/` as individual ESM modules (`.js` + `.d.ts` + `.d.ts.map`) — no bundling step, so `dist/elements/` and `dist/components/` mirror `src/`. There is no CJS or UMD build. Leaflet is always external (never bundled). Imports within source use `.ts` extensions; `rewriteRelativeImportExtensions` in tsconfig strips them to `.js` in the emitted `.js` (the `.d.ts` keep `.ts` specifiers, which TS resolves to the sibling `.d.ts`).
|
||||
`tsc` compiles `src/` → `dist/` as individual ESM modules (`.js` + `.d.ts` + `.d.ts.map`) — no bundling step, so `dist/elements/` and `dist/components/` mirror `src/`. There is no CJS or UMD build. Imports within source use `.ts` extensions; `rewriteRelativeImportExtensions` strips them to `.js` in the emitted `.js` (the `.d.ts` keep `.ts` specifiers, which TS resolves to the sibling `.d.ts`).
|
||||
|
||||
Two registries, different entry files (both pinned to the same version — keep them in step):
|
||||
- **`leaflet` is a `peerDependency`** (`^1.9.4`), also kept in `devDependencies` for local dev/tests — a second copy would break the `instanceof`-based nesting protocol. **`@types/leaflet` + `@types/geojson` are regular `dependencies`** (the emitted `.d.ts` reference them; `leaflet` ships no types).
|
||||
- **`files` ships `dist/` and `src/`** — the `.d.ts.map` point at `../../src/*.ts`, so source has to be there for "go to definition" (and it matches JSR).
|
||||
- **`sideEffects`** is the explicit list `dist/index.js`, `dist/index.npm.js`, `dist/components/*.js` — the only modules that `customElements.define()` on import; everything else is tree-shakeable.
|
||||
- **No `./dist/*` export.** The per-file `./elements/*.js` / `./components/*.js` subpaths are **npm-only** (JSR has no subpath patterns).
|
||||
|
||||
| Registry | Package name | Entry | Ships |
|
||||
| -------- | --------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| npm | `leaflet-web-components` | `package.json` `.`/`main`/`module`/`types` → `dist/index.npm.*` | compiled `dist/` + `README.md` + `LICENSE` (`files` field); `prepublishOnly` runs `typecheck` + `test` + `build` |
|
||||
| JSR | `@buddy/leaflet-components` | `jsr.json` `exports` → `./src/index.ts` (+ `./elements`) | TypeScript source directly, minus `jsr.json`'s `publish.exclude` (which drops `src/index.npm.ts`, `src/core/globals.ts`, `test/`, `docs/`, `dist/`, configs) |
|
||||
Two registries, different entry files. Both carry a `version`; `scripts/sync-version.mjs` (the `version` lifecycle hook, and re-run in `prepublishOnly`) copies `package.json`'s version into `jsr.json`.
|
||||
|
||||
| Registry | Package name | Entry | Ships |
|
||||
| -------- | --------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| npm | `leaflet-web-components` | `package.json` `.`/`main`/`module`/`types` → `dist/index.npm.*` | `dist/` + `src/` + `README.md` + `LICENSE` (`files` field); `prepublishOnly` runs `sync-version` + `typecheck` + `test` + `build` |
|
||||
| JSR | `@buddy/leaflet-components` | `jsr.json` `exports` → `./src/index.ts` (+ `./elements`) | TypeScript source directly, minus `jsr.json`'s `publish.exclude` (`src/index.npm.ts`, `src/core/globals.ts`, `test/`, `docs/`, `scripts/`, `dist/`, configs, `CLAUDE.md`) |
|
||||
|
||||
The npm entry (`src/index.npm.ts`) adds the `declare global` augmentations via `src/core/globals.ts`; the JSR entry (`src/index.ts`) can't, because JSR's "no slow types" check forbids `declare global` in its published graph. That constraint is also why every element file uses the explicitly-typed `const PROPS` / `const Base` shape (see the `WithProps` section). `npx jsr publish --dry-run` is the authoritative check; it must print "Success" with no slow-type errors.
|
||||
|
||||
@ -17,7 +17,7 @@ Also published to JSR as [`@buddy/leaflet-components`](https://jsr.io/@buddy/lea
|
||||
|
||||
## Import options
|
||||
|
||||
The package is ESM-only — `tsc` emits `dist/` as individual modules, one per component, with no bundling step. Deep imports work for registering only what you use.
|
||||
The package is ESM-only — `tsc` emits `dist/` as individual modules, one per component, with no bundling step. Deep imports let you register only what you use.
|
||||
|
||||
```js
|
||||
// Registers all components
|
||||
@ -27,6 +27,8 @@ import 'leaflet-web-components';
|
||||
import 'leaflet-web-components/components/leaflet-marker.js';
|
||||
```
|
||||
|
||||
Per-component deep imports (`./components/*.js`, `./elements/*.js`) are an **npm-only** convenience — JSR doesn't support subpath patterns, so from JSR the entry points are just the root and `@buddy/leaflet-components/elements` (the class barrel). JSR consumers who want selective registration import the class and call `customElements.define()` themselves.
|
||||
|
||||
Each component is two modules with the same basename: `components/leaflet-marker.js` is the side-effecting one that calls `customElements.define()`, and `elements/leaflet-marker.js` is just the class (`default` export, **no** `define`). Import from `elements/` when you want to subclass a component or register it under a different tag name:
|
||||
|
||||
```js
|
||||
|
||||
@ -33,7 +33,8 @@ extends Base` (where `const Base = WithProps(PROPS)`) — the class alone, no
|
||||
|
||||
`package.json` maps `leaflet-web-components/elements`,
|
||||
`.../elements/leaflet-foo.js` and `.../components/leaflet-foo.js` onto the
|
||||
matching `dist/` files.
|
||||
matching `dist/` files. The per-file subpaths are npm-only — `jsr.json`
|
||||
exposes just `.` and `./elements` (JSR has no subpath patterns).
|
||||
|
||||
### Two package entry points
|
||||
|
||||
|
||||
@ -2,13 +2,14 @@
|
||||
|
||||
## Scripts
|
||||
|
||||
| `npm run …` | Does |
|
||||
| --------------------- | ----------------------------------------------------------------------- |
|
||||
| `build` | `rm -rf dist && tsc --outDir dist` |
|
||||
| `typecheck` | `tsc --noEmit`, then `tsc -p tsconfig.test.json` |
|
||||
| `lint` | `oxlint src test` |
|
||||
| `format` | `oxfmt '*.md' 'docs/**/*.md' 'src/**/*.{ts,js,json,md}' 'test/**/*.ts'` |
|
||||
| `test` / `test:watch` | `vitest run` / `vitest` |
|
||||
| `npm run …` | Does |
|
||||
| --------------------- | ---------------------------------------------------------------------------- |
|
||||
| `build` | `rm -rf dist && tsc --outDir dist` |
|
||||
| `typecheck` | `tsc --noEmit`, then `tsc -p tsconfig.test.json` |
|
||||
| `lint` | `oxlint src test scripts` |
|
||||
| `format` | `oxfmt` over `*.md`, `docs/`, `src/`, `test/`, `scripts/` |
|
||||
| `test` / `test:watch` | `vitest run` / `vitest` |
|
||||
| `sync-version` | copy `package.json` version → `jsr.json` (also the `version` lifecycle hook) |
|
||||
|
||||
## TypeScript 7
|
||||
|
||||
@ -109,6 +110,10 @@ export default defineConfig({
|
||||
`src/index.ts` on JSR — see "Publishing" below) or any single module
|
||||
directly.
|
||||
- **No CJS, no UMD** build.
|
||||
- The npm tarball ships **`src/` as well as `dist/`** (`files` field) — the
|
||||
`.d.ts.map` files reference `../../src/*.ts`, so shipping the source is what
|
||||
makes "go to definition" land on real code, and it matches what JSR
|
||||
publishes.
|
||||
- **Leaflet is always external** — never bundled, a bare `import` in the
|
||||
output. It's a **`peerDependency`** (`^1.9.4`), not a regular dependency:
|
||||
the nesting protocol keys off `instanceof` against Leaflet's own `Layer` /
|
||||
@ -122,20 +127,28 @@ export default defineConfig({
|
||||
`./elements` → `dist/elements/index.js` (the class barrel, no `define`);
|
||||
`./elements/*.js` and `./components/*.js` → the matching `dist/` module.
|
||||
There is deliberately **no `./dist/*`** wildcard — `core/*` is not a stable
|
||||
contract, and the root export already surfaces the toolkit.
|
||||
- `sideEffects: true` — the `components/*` modules (and both `index*.js`
|
||||
entries) call `customElements.define()` at import time, so a bundler must
|
||||
not tree-shake them away. The `elements/*` modules have no side effect.
|
||||
contract, and the root export already surfaces the toolkit. The per-file
|
||||
`./elements/*.js` / `./components/*.js` patterns are **npm-only**: JSR has
|
||||
no subpath-pattern support, so `jsr.json` `exports` is just `.` +
|
||||
`./elements`.
|
||||
- `sideEffects` is an explicit allowlist — `dist/index.js`,
|
||||
`dist/index.npm.js`, `dist/components/*.js` — the only modules that run
|
||||
`customElements.define()` at import. Everything else (`elements/*`,
|
||||
`core/*`, and `core/globals.js`, whose augmentation is type-only) is
|
||||
side-effect-free, so a bundler may drop it when unused.
|
||||
|
||||
## Publishing to two registries
|
||||
|
||||
| Registry | Package name | Entry | Ships |
|
||||
| -------- | --------------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| npm | `leaflet-web-components` | `package.json` `.`/`main`/`module`/`types` → `dist/index.npm.*` | compiled `dist/` + `README.md` + `LICENSE` (`files` field); `prepublishOnly` runs `typecheck` + `test` + `build` |
|
||||
| JSR | `@buddy/leaflet-components` | `jsr.json` `exports` → `./src/index.ts` (+ `./elements`) | TypeScript source directly (JSR compiles per-consumer), minus `jsr.json`'s `publish.exclude` |
|
||||
| Registry | Package name | Entry | Ships |
|
||||
| -------- | --------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| npm | `leaflet-web-components` | `package.json` `.`/`main`/`module`/`types` → `dist/index.npm.*` | `dist/` + `src/` + `README.md` + `LICENSE` (`files` field); `prepublishOnly` runs `sync-version` + `typecheck` + `test` + `build` |
|
||||
| JSR | `@buddy/leaflet-components` | `jsr.json` `exports` → `./src/index.ts` (+ `./elements`) | TypeScript source directly (JSR compiles per-consumer), minus `jsr.json`'s `publish.exclude` |
|
||||
|
||||
Both are pinned to the same version; keep them in step. JSR publishes `src/`
|
||||
with its `.ts` import extensions intact, which JSR supports natively.
|
||||
Both carry a `version` and must stay in step. `scripts/sync-version.mjs`
|
||||
copies `package.json`'s version into `jsr.json`; it's wired into the `version`
|
||||
npm lifecycle script (so `npm version <bump>` updates and stages both) and
|
||||
run again from `prepublishOnly`. JSR publishes `src/` with its `.ts` import
|
||||
extensions intact, which JSR supports natively.
|
||||
|
||||
### The npm/JSR entry split
|
||||
|
||||
|
||||
2
jsr.json
2
jsr.json
@ -14,9 +14,11 @@
|
||||
"docs/",
|
||||
"demos/",
|
||||
"dist/",
|
||||
"scripts/",
|
||||
"index.html",
|
||||
"*.config.ts",
|
||||
"tsconfig*.json",
|
||||
"CLAUDE.md",
|
||||
".idea/",
|
||||
".claude/"
|
||||
]
|
||||
|
||||
31
package.json
31
package.json
@ -24,28 +24,47 @@
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"sideEffects": true,
|
||||
"sideEffects": [
|
||||
"./dist/index.js",
|
||||
"./dist/index.npm.js",
|
||||
"./dist/components/*.js"
|
||||
],
|
||||
"files": [
|
||||
"dist",
|
||||
"src",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rm -rf dist && tsc --outDir dist",
|
||||
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
|
||||
"lint": "oxlint src test",
|
||||
"format": "oxfmt '*.md' 'docs/**/*.md' 'src/**/*.{ts,js,json,md}' 'test/**/*.ts'",
|
||||
"lint": "oxlint src test scripts",
|
||||
"format": "oxfmt '*.md' 'docs/**/*.md' 'src/**/*.{ts,js,json,md}' 'test/**/*.ts' 'scripts/**/*.mjs'",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"prepublishOnly": "npm run typecheck && npm run test && npm run build"
|
||||
"sync-version": "node scripts/sync-version.mjs",
|
||||
"version": "npm run sync-version && git add jsr.json",
|
||||
"prepublishOnly": "npm run sync-version && npm run typecheck && npm run test && npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
"leaflet",
|
||||
"leafletjs",
|
||||
"web-components",
|
||||
"maps"
|
||||
"custom-elements",
|
||||
"webcomponents",
|
||||
"maps",
|
||||
"map"
|
||||
],
|
||||
"author": "buddy",
|
||||
"author": "Buddy Sandidge",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://git.buddy.wtf/lib/leaflet-components.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://git.buddy.wtf/lib/leaflet-components/issues"
|
||||
},
|
||||
"homepage": "https://git.buddy.wtf/lib/leaflet-components#readme",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@types/geojson": "^7946.0.16",
|
||||
|
||||
22
scripts/sync-version.mjs
Normal file
22
scripts/sync-version.mjs
Normal file
@ -0,0 +1,22 @@
|
||||
// Keeps `jsr.json`'s version in step with `package.json`.
|
||||
//
|
||||
// Wired into the `version` npm lifecycle script, so `npm version <bump>`
|
||||
// updates both files and stages `jsr.json` into the same commit/tag. Also
|
||||
// runs from `prepublishOnly` as a guard, and can be run by hand
|
||||
// (`npm run sync-version`). Exits non-zero only on a real filesystem error;
|
||||
// a no-op when the versions already match.
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
|
||||
const pkgUrl = new URL('../package.json', import.meta.url);
|
||||
const jsrUrl = new URL('../jsr.json', import.meta.url);
|
||||
|
||||
const pkg = JSON.parse(readFileSync(pkgUrl, 'utf8'));
|
||||
const jsr = JSON.parse(readFileSync(jsrUrl, 'utf8'));
|
||||
|
||||
if (jsr.version === pkg.version) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
jsr.version = pkg.version;
|
||||
writeFileSync(jsrUrl, `${JSON.stringify(jsr, null, 2)}\n`);
|
||||
console.log(`sync-version: jsr.json ${jsr.version} (from package.json)`);
|
||||
Loading…
Reference in New Issue
Block a user