Repository navigation
Conversation
The v7-to-v8 migration reference told sites to send full-reload from a hotUpdate hook on .deco/blocks.gen.ts. That fires before the worker re-imports the content, so the reloaded page renders the previous save and the site editor's local preview stays one edit behind. Send the reload from cms.ts's accept callback after createCMS takes the new module instead (demo-storefront be699ee). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WNwbSEePYNcY5YCgqZURig
Contributor
Author
tlgimenes
added a commit
that referenced
this pull request
Oct 14, 2026
…he saved content (#690) Vite sites no longer need template code to show a block saved with `deco serve`. Today every v8 Vite site carries two pieces: an `import.meta.hot.accept("../.deco/blocks.gen", ...)` block in `src/cms.ts` that calls `createCMS` again and sends `site:content-updated`, and a `site-content-reload` plugin in `vite.config.ts` that turns that event into `full-reload`. This PR ships that as one opt-in Vite plugin: ```ts // vite.config.ts import { decoContent } from "@decocms/blocks/vite"; export default defineConfig({ plugins: [/* cloudflare(), tanstackStart(), react(), */ decoContent()] }); ``` Only Vite sites add it. Next.js sites never import it, so nothing changes for them. The generated `.deco/blocks.gen.ts` on disk is unchanged (`deco content` is untouched). **Supersedes #676.** That PR fixed only the timing, in the docs (reload after `createCMS` takes the new module, not on the file change). This PR makes the plugin do that and rewrites the same "Editing locally" section, so #676 can be closed. ## How it works - **`@decocms/blocks/vite`, `decoContent()`**: `apply: "serve"`. In a server environment only (`this.environment.config.consumer === "server"`, or `ssr` before the Environment API), its `transform` hook finds each module that statically imports the site's `.deco/blocks.gen.ts`. It reads the module's static imports and re-exports and resolves each one with `this.resolve`, so any specifier form counts: `"../.deco/blocks.gen"`, an alias like `"~/.deco/blocks.gen"`, with or without `.ts`. Modules under `node_modules`, non-JS files and the content module itself are skipped. To each importer (usually `src/cms.ts`) it appends the block sites used to write by hand: ```js import { adoptContent as __decoAdoptContent } from "@decocms/blocks/vite/hot"; if (import.meta.hot) { import.meta.hot.accept("../.deco/blocks.gen", (next) => { if (next) __decoAdoptContent(next.default); import.meta.hot?.send("decocms:content-updated"); }); } ``` The block is appended, so no line moves and the source map holds (`map: null`). The importer is the update's HMR boundary, so neither it nor anything above it (the router entry, `routeTree.gen`) is invalidated or runs again. JSON block edits bubble up through `blocks.gen.ts` to the same boundary. - **Already accepted by hand:** if the importer already has an `….accept(<specifier>)` (string or array) that resolves to the content module, the plugin adds nothing to it. - **Pre-bundled hot module:** `configEnvironment` adds `@decocms/blocks/vite/hot` to `optimizeDeps.include` in the server environments. The appended import appears only after the transform, so the dependency scan can't see it. Discovered on the first request instead, the worker re-optimizes its dependencies and reloads the whole program once. - **One reload per save:** `configureServer` listens for the event on every server environment (the `@cloudflare/vite-plugin` worker whatever it's named, `rsc`, plain `ssr`) and sends `{ type: "full-reload" }` to the client environment. It waits 100 ms because a save swaps the module twice (the block's JSON, then `blocks.gen.ts`), so open pages reload once. - **`adoptContent(content)`** (`src/v8/cms.ts`, exposed only on `@decocms/blocks/vite/hot`): hands the module to every CMS instance keyed for that content's `.deco` root (`module:<root>`, with or without `|site:…`), the same adopt path a second `createCMS` call takes. It doesn't need the site's config: each instance re-adopts with its own stored config, so a hosted CMS keeps its `site`/`dev`. It returns how many instances took it. With none (no CMS yet, or a module without `root`) it does nothing. - `vite` is an optional peer (`>=6.0.0`, for the Environment API) and a devDependency. The plugin uses `import type` only, so nothing is imported from vite at runtime. New `exports` entries are `./vite` and `./vite/hot` (types/source/default, like the others), and `bun run build` emits `dist/v8/vite/*`. **Sites that still have the old code:** they keep working. - Without the plugin, nothing changes. - With the plugin and the old `cms.ts` accept block both in place, the plugin leaves that module alone. The old callback re-creates the CMS and the old `site-content-reload` plugin reloads the page, as before. If you delete only the old Vite plugin, pages don't reload, so the migration reference says to delete both together. - The older docs recipe's reload plugin is a `hotUpdate` hook on the file change. That hook fires early, so the reference says to delete it. ## Review fixes - **`optimizeDeps` (041a9b4):** the server environments pre-bundle `@decocms/blocks/vite/hot`. Without that, the first request found the new import and re-optimized the worker, which reloaded the whole program once. - **Importer boundary instead of a self-accepting content module (ea8618c):** the first version made `blocks.gen.ts` accept its own updates. Vite still invalidates every importer of an updated module unless the importer accepts it. TanStack Start imports its server entry again on each request, so with the cloudflare plugin `src/cms.ts` and the route tree re-ran on every save. On the demo store, bursts of saves gave 500s (`Cannot read properties of undefined (reading 'method')` in `handleServerAction`, `Route` undefined in `routeTree.gen`), and the client then stopped reloading. The plugin now writes the accept that worked before (the hand-written `cms.ts` block had 14/14 clean bursts), on the importer. ## Tests - `src/v8/vite/vite.test.ts` (unit tests): - `adoptContent`: the same instance, another `.deco` root left alone, a hosted instance keeps its options, nothing before a CMS exists or for a root-less module. - The import reader: static imports and re-exports, not dynamic `import()` or `import.meta`. It also finds accepted dependencies in string and array forms. - The transform: relative, alias, `.ts` and bare-alias specifiers resolving to the content module get the block with their own specifier. Nothing is added in the client, to the content module itself, under `node_modules`, to non-JS files, to modules that don't import it, or to a module that accepts it by hand. An unrelated accept doesn't count. The `ssr` flag works without the Environment API. - `optimizeDeps.include` only in server environments; `configureServer` listens on every server environment, and two events make one `full-reload`. - `src/v8/vite/devServer.test.ts` (integration): a real Vite dev server (`createServer`, SSR `RunnableDevEnvironment`). Like TanStack Start, every "request" imports the site entry again (`entry.ts → cms.ts`, which imports `~/.deco/blocks.gen` through an alias). - Three saves: each gives a `full-reload`, and a request at that moment already resolves the new content. - **Regression:** the test counts module runs. `cms.ts` and the entry each ran once, and the transformed `cms.ts` has exactly one accept while `blocks.gen.ts` has none. With the previous self-accepting design the same test fails with `{ cms: 4, entry: 4 }`. - A `cms.ts` with its own accept block keeps exactly one accept, its callback runs, and the plugin sends no reload. - End to end with `@cloudflare/vite-plugin` (Node `vite dev`; 10 bursts of 3 saves 120 ms apart, each save writing the block JSON and then `blocks.gen.ts`; two request loops running throughout; a `vite-hmr` WebSocket counting reloads; `cms.ts` logging each run): - **Demo store** (deco-sites/demo-storefront#133 with this build, Vite 7, TanStack Start): - This PR: 52/52 responses 200, `cms.ts` ran once in all, `hot updated: /.deco/blocks.gen.ts via /src/cms.ts`, and the page had each burst's last save 10/10. - Previous self-accepting design: 12 of 54 responses were 500, `cms.ts` ran 16 times, and 1 burst's page lacked the last save. - **`examples/tanstack-smoke`:** 276/276 responses 200, `cms.ts` ran once, and 10/10 bursts showed the last save. Saves more than 100 ms apart each get their own reload, so a burst gave 1 to 3 reloads, never more than its saves. - `bun run check` (typecheck, biome, knip), `bun run build` and `skills:check` pass. In `bun run test`, 1926 tests pass. The failures are not from this PR: - `delivery.cli.test.ts` DS-2: it greps `protocol/storage/fs/git.ts` for `"reset"`, which #689 added. It fails on the base too. - `schema.test.ts` (`is deterministic`, and one more): they hit the 5 s timeout under full-suite load and pass when run alone (58/58). ## Also in this PR - `examples/tanstack-smoke` and `examples/tanstack-rsc-smoke` add `decoContent()`. The `ts-09` guide conformance test now expects that one `@decocms/blocks/vite` import. - `.agents/skills/deco-v7-to-v8-migration/reference/content.md`, "Editing locally": add the plugin; drop the old snippet and plugin together when you do; and why: the "client is not a function" and one-edit-behind history from #676, and why the boundary is the importer. - `README.md` mentions the plugin and the `/vite` subpath. Follow-ups opened alongside: - The TanStack guide snippet becomes the plugin line: deco-sites/docs-tanstack#25. Merge it after the release. - The demo store drops the template code: deco-sites/demo-storefront#133 (draft; it needs the release containing this fix). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WNwbSEePYNcY5YCgqZURig --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Superseded by decoContent() in @decocms/blocks/vite (released in 8.2.2). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The
deco-v7-to-v8-migrationreference (reference/content.md, "Editing locally") told sites to sendfull-reloadfrom a VitehotUpdatehook on.deco/blocks.gen.ts. That hook fires before the worker re-imports the content, so the reloaded page renders the previous save, and the site editor's local preview (Localhost and/site-editor) stays one edit behind.The bullet now says to send the reload from
cms.ts'simport.meta.hot.acceptcallback, aftercreateCMStakes the new module (import.meta.hot.send("site:content-updated")), and to have the plugin listen inconfigureServer(server.environments.ssr.hot.on(...)) and sendfull-reloadfrom there.The same fix is in the demo store (deco-sites/demo-storefront#131, be699ee). With
deco serve+vite dev: before, 1 of 4 saves showed on the reload; after, 5/5 single saves and 4/4 save bursts did.Docs only.
bun run skills:checkandskills:readme:checkpass.🤖 Generated with Claude Code
https://claude.ai/code/session_01WNwbSEePYNcY5YCgqZURig
Summary by cubic
Updates the
deco-v7-to-v8-migrationreference to reload open pages only after the server has the saved content. The old instruction sentfull-reloadfrom ahotUpdatehook on.deco/blocks.gen.ts, which fires before the worker re-imports the content and leaves the site editor preview one save behind. The reference now directs sending the reload fromcms.ts'simport.meta.hot.acceptcallback aftercreateCMStakes the new module, with the plugin listening inconfigureServer.Written for commit fad48d8. Summary will update on new commits.