Skip to content

Add build output manifest for static caching#262

Open
bcomnes wants to merge 1 commit into
masterfrom
staic-client-cache
Open

Add build output manifest for static caching#262
bcomnes wants to merge 1 commit into
masterfrom
staic-client-cache

Conversation

@bcomnes

@bcomnes bcomnes commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Summary

This adds an unstable-preview domstack output manifest for static app caching and pairs it with first-class site service-worker support.

Warning

The domstack manifest, domstack-manifest.settings.*, first-class service-worker.* entries, and related browser process.env.DOMSTACK_* defines are preview APIs. Their names, option shapes, manifest schema, generated output, and runtime semantics may change outside of a major version while the API is validated with real PWA use cases. Consumers should pin @domstack/static to an exact version before relying on this contract.

The goal is to let a domstack app build a fully static client, then have its own service worker consume a normalized, revisioned list of emitted files. Domstack provides the build facts; the application still owns service-worker registration, update UX, route filtering, offline behavior, and cache policy.

What Changed

  • Add domstack-manifest.json, written by one-shot builds by default.
  • Return results.domstackManifest from programmatic builds.
  • Track outputs from esbuild, pages, templates, static copies, and configured copy dirs through shared report.outputs records.
  • Reconcile those records once at the end of the build into public manifest entries with URLs, revisions, byte sizes, kinds, and optional source/page metadata.
  • Add a committed JSON Schema for the manifest with a versioned unpkg $schema URL.
  • Derive/export public manifest types through @domstack/static/types.js / #types using json-schema-to-ts.
  • Add domstack-manifest.settings.{js,mjs,cjs,ts,mts,cts} for manifest filtering policy.
  • Add first-class site service-worker entries: one service-worker.{js,mjs,cjs,ts,mts,cts} anywhere under src builds to stable root /service-worker.js.
  • Add domstack-owned browser defines for service workers and client bundles.
  • Keep watch mode simple: service workers still rebundle in watch mode, but domstack manifests are not written or returned in watch mode.
  • Replace .npmignore with a package.json#files allowlist.
  • Add a PWA example showing app-owned registration, update UX, manifest filtering, and cache lifecycle handling.

New Preview APIs

Build Results

const results = await site.build()
console.log(results.domstackManifest)

results.domstackManifest is still returned when writing the JSON file is disabled with domstackManifest: false. Watch mode does not return a domstack manifest.

CLI Options

domstack --customDomstackManifestName custom-domstack-manifest.json
domstack --noDomstackManifest
domstack --serve

--customDomstackManifestName changes the written manifest filename from the default domstack-manifest.json. --noDomstackManifest disables writing the JSON file. --serve runs a one-shot build and then serves the output without watch mode, which is useful for testing service-worker cache lifecycle behavior with a manifest-enabled build.

Programmatic Options

const site = new DomStack('src', 'public', {
  domstackManifest: {
    filename: 'custom-domstack-manifest.json',
    exclude: ['blog/**', '**/*.map'],
  },
})

Supported shape:

type DomstackManifestOption = false | {
  write?: boolean
  filename?: string
  exclude?: string[]
}

domstack-manifest.settings.*

Apps can configure the generated manifest from a dedicated settings file anywhere under src:

domstack-manifest.settings.js
domstack-manifest.settings.mjs
domstack-manifest.settings.cjs
domstack-manifest.settings.ts
domstack-manifest.settings.mts
domstack-manifest.settings.cts

The default export can be an object, a sync function, or an async function returning an object:

export default {
  exclude: ['admin/**', 'blog/**', '**/*.map'],
  includeEntry (entry) {
    if (entry.kind === 'metadata') return false
    if (entry.kind === 'sourcemap') return false
    if (entry.page?.vars?.precache === false) return false
    if (entry.page?.vars?.offline === false) return false
    return true
  },
}

Filtering affects both the written domstack-manifest.json and results.domstackManifest, and it affects manifest.version because versioning is based on final cache-relevant entries.

Service Worker Support

Domstack reserves one site service-worker source filename:

service-worker.js
service-worker.mjs
service-worker.cjs
service-worker.ts
service-worker.mts
service-worker.cts

The source may live anywhere under src, but only one is allowed. Multiple matches fail with DOM_STACK_ERROR_DUPLICATE_SERVICE_WORKER.

The service worker is bundled through esbuild and emitted with a stable root output path:

/service-worker.js

Domstack does not inject registration into the default layout. Registration timing, update prompts, local-development opt-outs, poisoned-cache recovery, and offline route behavior remain application policy.

Browser Defines

Define Value
process.env.DOMSTACK_MANIFEST_URL Public URL of the written domstack manifest, usually /domstack-manifest.json
process.env.DOMSTACK_MANIFEST_ENABLED "true" for one-shot builds that write the manifest, "false" when disabled or in watch mode
process.env.DOMSTACK_SERVICE_WORKER_URL Public service-worker URL, usually /service-worker.js, or "" when absent
process.env.DOMSTACK_SERVICE_WORKER_SCOPE Registration scope, usually /, or "" when absent

Compatibility Notes

  • This is an unstable preview feature and can change outside a major version.
  • copy is now the output kind for files copied by the generic copy step.
  • Watch mode intentionally does not write domstack-manifest.json or return results.domstackManifest; it does still build and rebundle the site service worker.

Testing

  • npm test
  • npm run test:tsc
  • npm run test:neostandard
  • npm run build:declaration
  • npm run build:schema
  • node --test lib/identify-pages.test.js test-cases/general-features/index.test.js test-cases/type-exports/index.test.ts test-cases/watch/index.test.js test-cases/template-output-escape/index.test.js
  • git diff --check

@github-actions

Copy link
Copy Markdown

Coverage Report for CI Build 27524398777

Coverage increased (+0.3%) to 92.204%

Details

  • Coverage increased (+0.3%) from the base build.
  • Patch coverage: 47 uncovered changes across 2 files (939 of 986 lines covered, 95.23%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
lib/build-output-manifest/index.js 553 520 94.03%
index.js 63 49 77.78%
Total (12 files) 986 939 95.23%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 5066
Covered Lines: 4806
Line Coverage: 94.87%
Relevant Branches: 886
Covered Branches: 682
Branch Coverage: 76.98%
Branches in Coverage %: Yes
Coverage Strength: 106.32 hits per line

💛 - Coveralls

@socket-security

socket-security Bot commented Jun 15, 2026

Copy link
Copy Markdown

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

View full report

@bcomnes bcomnes marked this pull request as ready for review June 15, 2026 19:50
Copilot AI review requested due to automatic review settings June 15, 2026 19:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@bcomnes bcomnes force-pushed the staic-client-cache branch 2 times, most recently from 31311d7 to 620a4bc Compare June 15, 2026 19:52
@bcomnes bcomnes requested a review from Copilot June 15, 2026 19:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@bcomnes bcomnes force-pushed the staic-client-cache branch 3 times, most recently from fb90546 to e8c79b8 Compare June 16, 2026 18:02
@socket-security

socket-security Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​json-schema-to-ts@​3.1.110010010080100

View full report

@bcomnes bcomnes force-pushed the staic-client-cache branch from e8c79b8 to f665681 Compare June 17, 2026 03:31
@bcomnes bcomnes force-pushed the staic-client-cache branch 8 times, most recently from a3079c4 to 260af7a Compare June 29, 2026 03:50
Comment thread README.md
Notable examples:

- [`examples/pwa`](./examples/pwa) - A static PWA with a site service worker, domstack manifest filtering, update prompts, offline fallback, and cache recovery behavior.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dont need this callout here. We can link to it in the manifest docs.

Comment thread README.md
Alternatively, you could compose your layouts from re-usable template functions and strings.
If you find your layouts nesting more than one or two levels, perhaps composition would be a better strategy.

#### Layout composition pitfalls

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section should not be deleted.

Comment thread README.md

Copy folders must live **outside** of the `dest` directory. Copy directories can be in the src directory allowing for nested builds. In this case they are added to the ignore glob and ignored by the rest of `domstack`.

When using the programmatic `DomStack` constructor, `copy` entries may be relative or absolute paths. Relative `copy` paths are resolved to absolute paths from the current working directory, matching the CLI `--copy` behavior, before they are stored on `domstack.opts.copy` and passed to the copy build step.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This note should not be deleted.

Comment thread README.md
export default templateIterator
```

### Choosing a template return type

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section should not have been removed.

Comment thread README.md
**Caveats:**
### `domstack-manifest.settings.ts`

**`page.vars` is a cached, read-only computed getter.** The first access merges all variable sources and DomStack caches a shallow-frozen result. Direct access and destructuring are both fine. Treat the returned object as read-only; if you need derived values, create a new object instead of mutating `page.vars`.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The page vars caveats should not have been deleted.

@bcomnes bcomnes force-pushed the staic-client-cache branch from 260af7a to 0fe8310 Compare June 29, 2026 03:54
@bcomnes bcomnes force-pushed the staic-client-cache branch from 0fe8310 to 714e90d Compare June 29, 2026 03:58
Comment thread README.md
- Running `domstack` will result in a `build` by default.
- Running `domstack --watch` or `domstack -w` will build the site and start an auto-reloading development web-server that watches for changes (provided by [`@domstack/sync`][domstack-sync]).
- Running `domstack --serve` will run a normal one-shot build and then serve the destination directory without watching. This is useful for PWA testing because it writes `domstack-manifest.json`.
- Running `domstack --target=es2022,chrome120` or `domstack -t es2022,chrome120` forwards the comma-separated target list to esbuild. See [esbuild's target docs](https://esbuild.github.io/api/#target) for accepted values such as `es2020`, `es2022`, `chrome120`, `firefox121`, `safari17`, and `node22`.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I removed this in favor of the esbuild settings file. Maybe we should remove this in this upcoming v12 release.

Comment thread README.md
--drafts Build draft pages with the `.draft.{md,js,ts,html}` page suffix.
--target, -t comma separated esbuild targets, e.g. es2022,chrome120; see https://esbuild.github.io/api/#target
--noEsbuildMeta skip writing the esbuild metafile to disk
--customDomstackManifestName

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should go in the settings file as well.

Comment thread package.json
"domstack": "./bin.js",
"dom": "./bin.js"
},
"files": [

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to audit this file array

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants