Add build output manifest for static caching#262
Conversation
Coverage Report for CI Build 27524398777Coverage increased (+0.3%) to 92.204%Details
Uncovered Changes
Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
|
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. |
31311d7 to
620a4bc
Compare
fb90546 to
e8c79b8
Compare
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
e8c79b8 to
f665681
Compare
a3079c4 to
260af7a
Compare
0fe8310 to
714e90d
Compare
| * @param {string[]} exclude | ||
| */ | ||
| function applyExclude (entries, exclude) { | ||
| if (exclude.length === 0) return entries |
There was a problem hiding this comment.
Why is manifest dealing with this. Shouldnt it be totally downstream from ignores?
There was a problem hiding this comment.
Manifest excludes are separate from source/build ignores. ignore/copy filtering decides what gets discovered or copied upstream; domstackManifest.exclude and includeEntry filter the reconciled public output entries after all build steps have reported outputs, so this logic belongs at manifest reconciliation time.
2576a7f to
01f6d43
Compare
| const serviceWorkerStat = await stat(path.join(dest, 'service-worker.js')) | ||
| assert.ok(serviceWorkerStat.isFile(), 'watch mode builds site service-worker entries') | ||
| const serviceWorkerContent = await readFile(path.join(dest, 'service-worker.js'), 'utf8') | ||
| assert.ok(!serviceWorkerContent.includes('process.env.DOMSTACK_MANIFEST_ENABLED'), 'watch service worker receives the domstack manifest enabled define') |
| { | ||
| "name": "@domstack/static-mpa-workbox-offline-example", | ||
| "version": "0.0.0", | ||
| "description": "Static MPA offline Workbox service worker example for domstack", | ||
| "type": "module", |
| @@ -0,0 +1,33 @@ | |||
| import type { HtmlResult } from 'fragtml/types.ts' | |||
| import type { LayoutFunction } from '@domstack/static/types.ts' | |||
| defineServiceWorkerConstant: (identifier, value) => { | ||
| serviceWorkerDefines[identifier] = JSON.stringify(value) | ||
| }, | ||
| writeFile: (outputRelname, contents) => writeGeneratedManifestFile(dest, outputRelname, contents), |
01f6d43 to
f35b11d
Compare
Adds the unstable Domstack manifest preview, service-worker build support, documentation, migration notes, tests, and workspace-aware example infrastructure without the PWA example implementations. Address manifest review feedback Keep service worker build single file Revert "Keep service worker build single file" This reverts commit 3c6beff. Working static PWA cache example PWA
f35b11d to
43de599
Compare
| page/frontmatter vars > page.vars.* > layout vars > global.data/global.vars > domstack defaults | ||
| ``` | ||
|
|
||
| This is additive for most sites. If a layout module already exported a named `vars` value for another purpose, that value will now participate in page variable resolution. Rename that export if it was not intended as layout defaults. |
There was a problem hiding this comment.
We should also acknowledge the previous strategy of just mixing in variables at the layout level. Those can be moved to the cars cascade explicitly now. We should sk mention why it was added (manifest policy defsults)
| }) | ||
| ``` | ||
|
|
||
| If `domstackManifest` is unset, Domstack skips the manifest pipeline unless a `domstack-manifest.settings.*` file exists. |
There was a problem hiding this comment.
The settings file is the primary interface the programatic one should come second or only mentioned in the docs.
| Apps can add one `domstack-manifest.settings.*` file anywhere under `src`: | ||
|
|
||
| ```txt | ||
| domstack-manifest.settings.js |
There was a problem hiding this comment.
We need to find a convention not to have to write every file type out.
| ``` | ||
|
|
||
| Use `manifestVars` to explicitly expose selected page, layout, global, or default vars on manifest entries. | ||
| Use `policy` for root-level application policy. |
There was a problem hiding this comment.
Explain what this is and why you would use it.
| ### Prefer manifest hooks over runtime manifest fetches | ||
|
|
||
| The primary service-worker integration point is `hooks.manifestBuilt`. | ||
| This hook receives the finalized manifest before the final `/service-worker.js` bundle is emitted. |
There was a problem hiding this comment.
Service worker support is new too. We should introduce that before the manifet since you need that before you set this up.
| } | ||
| ``` | ||
|
|
||
| This avoids shipping a runtime manifest file just so a service worker can discover the build output list. |
There was a problem hiding this comment.
This copy is too reflective of our desingn path. Getting the manifest into the service worker is standard practice.
| interface Window { | ||
| domstackUtils: DomstackUtils; | ||
| const globalWindow = window as Window & { | ||
| domstackUtils?: DomstackUtils |
| * | ||
| * This module owns browser registration, waiting-worker prompts, and the | ||
| * one-time reload after a user-approved update takes control. It receives a UI | ||
| * interface from the entrypoint instead of importing a banner implementation. |
There was a problem hiding this comment.
This comment needs to be cleaned up.
| const registration = await navigator.serviceWorker.register(url, { | ||
| scope, | ||
| type: 'module', | ||
| updateViaCache: 'none', |
There was a problem hiding this comment.
Need to look up what this does.
| * | ||
| * See MDN Client.postMessage(): https://developer.mozilla.org/en-US/docs/Web/API/Client/postMessage | ||
| */ | ||
| export async function postToWindowClients (message: Record<string, unknown>): Promise<void> { |
|
|
||
| if (request.method !== 'GET') return | ||
|
|
||
| const url = new URL(request.url) |
There was a problem hiding this comment.
Is this not already a url object?
| preloadResponsePromise: Promise<Response | undefined>, | ||
| event: FetchEvent | ||
| ): Promise<Response> { | ||
| event.waitUntil(preloadResponsePromise.catch(() => undefined)) |
| withoutIgnoredParams.hash = '' | ||
| for (const param of Array.from(withoutIgnoredParams.searchParams.keys())) { | ||
| if (/^utm_/.test(param) || param === 'fbclid') { | ||
| withoutIgnoredParams.searchParams.delete(param) |
There was a problem hiding this comment.
What is this about I don't think we need this.
| return policyEntry ? getRuntimeStrategy(config, policyEntry) === 'runtime' : false | ||
| } | ||
|
|
||
| function getRequestPolicyEntry (config: ActiveServiceWorkerConfig, request: Request): StaticMpaOfflineServiceWorkerPolicyEntry | undefined { |
There was a problem hiding this comment.
Need JSDoc explaining all of these functions.
Summary
This adds an unstable-preview Domstack manifest pipeline for static caching and first-class site service-worker builds.
The manifest pipeline produces a normalized, revisioned view of build outputs for manifest hooks and optional public JSON output.
The main service-worker integration point is
hooks.manifestBuilt, where applications can derive cache policy from the finalized manifest and inject it into the final/service-worker.jsbundle withcontext.defineServiceWorkerConstant().Public
domstack-manifest.jsonwriting is opt-in.Apps that only need the manifest for service-worker policy can consume it through the hook without shipping a runtime manifest file.
Warning
The domstack manifest,
domstack-manifest.settings.*, first-classservice-worker.*entries, manifest hooks, generated browserprocess.env.DOMSTACK_*defines, and service-worker define injection 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/staticto an exact version before relying on this contract.Goals
The goal is to let a domstack app build a fully static client and derive cache policy from a finalized, revisioned list of emitted files.
Domstack provides build facts and service-worker build plumbing.
The application still owns service-worker registration, update UX, route filtering, offline behavior, runtime caching decisions, and cache policy.
What Changed
results.domstackManifestfrom one-shot programmatic builds when a manifest consumer exists.domstack-manifest.jsonwriting opt-in with--domstackManifest,domstackManifest: true, or{ write: true }.report.outputsrecords.$schemaURL.domstack-manifest.settings.{js,mjs,cjs,ts,mts,cts}for manifest filtering, manifest vars, root policy, and manifest lifecycle hooks.hooks.manifestBuiltwithcontext.manifest,context.writeFile(), andcontext.defineServiceWorkerConstant().service-worker.{js,mjs,cjs,ts,mts,cts}anywhere undersrcbuilds to stable root/service-worker.js./service-worker.jswithout a circular hash dependency.domstack --servefor one-shot build plus static serving, which is useful for service-worker and PWA lifecycle testing..npmignorewith apackage.json#filesallowlist.New Preview APIs
Build Results
Programmatic one-shot builds return
results.domstackManifestwhen a manifest consumer exists.A manifest consumer is either explicit
domstackManifestconfiguration or adomstack-manifest.settings.*file.Watch mode does not return a domstack manifest.
CLI Options
--domstackManifestwrites the standarddomstack-manifest.jsonfile.--serveruns a one-shot build and serves the destination directory without watch mode or live reload snippets.Use
--servefor testing service-worker cache lifecycle behavior against stable build bytes.There is intentionally no custom manifest filename CLI shortcut.
Custom public manifest-like files should be written from a
manifestBuilthook withcontext.writeFile().Programmatic Options
Supported shape:
Use
domstackManifest: truewhen you only want to write the standard publicdomstack-manifest.jsonfile.Use the object form when you need
exclude,manifestVars,policy, orhooks.Add
write: trueto the object form only when you also want Domstack to writedomstack-manifest.json.Leaving
domstackManifestunset skips the manifest pipeline unless adomstack-manifest.settings.*file exists.A settings file enables the pipeline, returns
results.domstackManifest, and runs manifest hooks, but it does not write a public JSON file unless writing is explicitly enabled.domstack-manifest.settings.*Apps can configure the generated manifest from a dedicated settings file anywhere under
src:The default export can be an object, a sync function, or an async function returning an object.
domstackManifest.excludefrom programmatic options anddomstack-manifest.settings.*excludevalues are combined.Exclude patterns are checked against both
entry.urlandentry.outputRelname.Excludes run before
includeEntry(entry).The
includeEntry(entry)hook receives the public manifest entry shape, not local filesystem paths.Manifest Built Hooks
hooks.manifestBuiltruns after the manifest is finalized and before the final service-worker bundle is emitted.defineServiceWorkerConstant()serializesvaluewithJSON.stringify()and passes it to esbuild'sdefineoption for the final service-worker build.Use it for precomputed service-worker policy, such as a vanilla precache list or a Workbox-shaped
precacheManifest, when you do not want a runtime fetch or generated global script.writeFile()writes generated artifacts underdest.Use it for custom public integration data when a runtime file is intentionally desired.
Service Worker Support
Domstack reserves one site service-worker source filename:
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:
The final service-worker build receives manifest hook defines and
process.env.DOMSTACK_MANIFEST_VERSION.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
process.env.DOMSTACK_MANIFEST_URL/domstack-manifest.jsonprocess.env.DOMSTACK_MANIFEST_VERSION/service-worker.js;""in other bundlesprocess.env.DOMSTACK_MANIFEST_ENABLED"true"for one-shot builds with an enabled manifest pipeline,"false"when disabled or in watch modeprocess.env.DOMSTACK_SERVICE_WORKER_URL/service-worker.js, or""when no service worker is presentprocess.env.DOMSTACK_SERVICE_WORKER_SCOPE/, or""when no service worker is presentCompatibility Notes
domstack-manifest.jsononly when--domstackManifestis set.domstack-manifest.jsononly withdomstackManifest: trueor{ write: true }.entries.manifest.versioncan be embedded into/service-worker.jswithout a circular hash dependency.copyis the output kind for files copied by the generic copy step.{ url, revision }precache shape fromcontext.manifestinside a manifest hook.Testing
npm testnpm run test:tscnpm run test:neostandardnode --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.jsgit diff --check