Skip to content

fix: marko virtual file hmr#266

Merged
rturnq merged 2 commits intomainfrom
virtual-file-hmr
Apr 14, 2026
Merged

fix: marko virtual file hmr#266
rturnq merged 2 commits intomainfrom
virtual-file-hmr

Conversation

@rturnq
Copy link
Copy Markdown
Contributor

@rturnq rturnq commented Apr 10, 2026

No description provided.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 10, 2026

🦋 Changeset detected

Latest commit: d4fae7c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@marko/vite Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 10, 2026

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Adds a changeset marking @marko/vite for a patch. Modifies the plugin to track template→virtual module mappings (virtualFilesForTemplate), add an ssrTransformCache, and refactor HMR: hotUpdate is now async, includes virtual dependents in module invalidation, resets virtual entries, and can trigger SSR full-reload when cached SSR output changes. Normalizes transform id/fileName handling and narrows dev HMR injection. Introduces an inject-hmr-events Vite plugin and numerous test fixtures (dev servers, production servers, templates, components, and test.configs) for isomorphic/SSR virtual-file HMR scenarios.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess relevance to the changeset. Add a pull request description explaining the fix, its motivation, and any relevant implementation details or test scenarios.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: marko virtual file hmr' clearly describes the main change—fixing HMR (hot module replacement) for Marko virtual files, which aligns with the added test fixtures and core plugin modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch virtual-file-hmr

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/__tests__/fixtures/isomorphic-tags-api-virtual-file-hmr/test.config.ts`:
- Around line 2-8: The hmr fixture only replaces the stylesheet but provides no
post-update steps to make the CSS change observable; update the exported hmr
array (export const hmr) to include a steps array for each change that triggers
a UI reflow/read (e.g., simulate a click on the page element with id
"#clickable") after the stylesheet swap so the test snapshot in main.test.ts
observes the new computed color; specifically add a steps entry alongside
changes in the hmr object so the test runner executes hmrStep.steps (simulate
click "#clickable" after each update).

In `@src/__tests__/fixtures/isomorphic-virtual-file-hmr/server.mjs`:
- Around line 12-16: The request listener passed to createServer should catch
and handle errors and must not rely solely on res.headersSent to know if the
handler finished; wrap the body of the async callback in try/catch, call await
handler(req, res) and treat its return value as an explicit completion flag
(e.g., handler returns true when it fully handled the response or a Promise that
resolves when template.render completes), and only call await serve(req, res,
serveOpts) when handler indicates it did not complete the response; on any
caught error send a 500 response (or use res.writeHead(500)/res.end) and log the
error so rejections from handler or serve are not unhandled.

In `@src/__tests__/main.test.ts`:
- Around line 414-424: Replace the risky use of the possibly already-resolved
window.__nextHmr by installing a fresh deferred promise in the page context
before awaiting HMR: evaluate in the page to create a new deferred and assign
its promise to window.__nextHmr so Promise.any races the newly-created HMR
promise and the DOM-change watcher (referencing page.evaluate, window.__nextHmr,
page.waitForFunction, prevRawHtml, Promise.any). Also remove the broad .catch(()
=> {}) that swallows all errors—only treat the DOM wait timeout as optional (so
DOM timeout can be handled locally) but let HMR promise rejections surface so
test failures are detectable.

In `@src/index.ts`:
- Around line 150-160: The virtual file isn't being registered in
virtualFilesForTemplate on the first compilation because the current logic only
adds id when prev exists and is not a deferred promise; update the handler so
that for the given normalizedFrom you always ensure a Set exists and add id to
virtualFilesForTemplate (i.e., move/create the files Set and files.add(id)
unconditionally for this normalizedFrom), while preserving the existing behavior
of resolving deferred promises via isDeferredPromise(prev) (call
prev.resolve(virtualFile) when applicable).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4091f4cb-f9d7-4178-b92a-2af69aadc27e

📥 Commits

Reviewing files that changed from the base of the PR and between 7390837 and 8204698.

⛔ Files ignored due to path filters (6)
  • src/__tests__/fixtures/isomorphic-tags-api-virtual-file-hmr/__snapshots__/build.expected.md is excluded by !**/__snapshots__/** and included by **
  • src/__tests__/fixtures/isomorphic-tags-api-virtual-file-hmr/__snapshots__/dev-hmr.expected.md is excluded by !**/__snapshots__/** and included by **
  • src/__tests__/fixtures/isomorphic-tags-api-virtual-file-hmr/__snapshots__/dev.expected.md is excluded by !**/__snapshots__/** and included by **
  • src/__tests__/fixtures/isomorphic-virtual-file-hmr/__snapshots__/build.expected.md is excluded by !**/__snapshots__/** and included by **
  • src/__tests__/fixtures/isomorphic-virtual-file-hmr/__snapshots__/dev-hmr.expected.md is excluded by !**/__snapshots__/** and included by **
  • src/__tests__/fixtures/isomorphic-virtual-file-hmr/__snapshots__/dev.expected.md is excluded by !**/__snapshots__/** and included by **
📒 Files selected for processing (19)
  • .changeset/happy-rooms-burn.md
  • src/__tests__/fixtures/isomorphic-tags-api-virtual-file-hmr/dev-server.mjs
  • src/__tests__/fixtures/isomorphic-tags-api-virtual-file-hmr/server.mjs
  • src/__tests__/fixtures/isomorphic-tags-api-virtual-file-hmr/src/index.js
  • src/__tests__/fixtures/isomorphic-tags-api-virtual-file-hmr/src/tags/child-tag.marko
  • src/__tests__/fixtures/isomorphic-tags-api-virtual-file-hmr/src/tags/layout.marko
  • src/__tests__/fixtures/isomorphic-tags-api-virtual-file-hmr/src/template.marko
  • src/__tests__/fixtures/isomorphic-tags-api-virtual-file-hmr/test.config.ts
  • src/__tests__/fixtures/isomorphic-virtual-file-hmr/dev-server.mjs
  • src/__tests__/fixtures/isomorphic-virtual-file-hmr/server.mjs
  • src/__tests__/fixtures/isomorphic-virtual-file-hmr/src/components/class-component.marko
  • src/__tests__/fixtures/isomorphic-virtual-file-hmr/src/components/implicit-component.marko
  • src/__tests__/fixtures/isomorphic-virtual-file-hmr/src/components/layout-component.marko
  • src/__tests__/fixtures/isomorphic-virtual-file-hmr/src/index.js
  • src/__tests__/fixtures/isomorphic-virtual-file-hmr/src/template.marko
  • src/__tests__/fixtures/isomorphic-virtual-file-hmr/test.config.ts
  • src/__tests__/main.test.ts
  • src/__tests__/utils/inject-hmr-events-plugin.ts
  • src/index.ts

Comment thread src/__tests__/fixtures/isomorphic-virtual-file-hmr/server.mjs
Comment thread src/__tests__/main.test.ts Outdated
Comment thread src/index.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/__tests__/main.test.ts (1)

421-435: ⚠️ Potential issue | 🟠 Major

Install a fresh HMR promise before waiting on the next update.

This still reads whichever deferred is currently sitting on window.__nextHmr. If the previous cycle already resolved it, Promise.any(...) can return immediately and the snapshot gets taken before the current HMR cycle finishes. The DOM fallback is not enough for the new server-only HMR cases, so this remains flaky. Create a new deferred in the page before mutating files, then await that specific promise here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/main.test.ts` around lines 421 - 435, The test is racing on a
possibly-stale window.__nextHmr deferred; before mutating files, inject a fresh
deferred into the page (e.g. create and assign a new Promise resolver to
window.__nextHmr via page.evaluate) and capture a handle/reference to that
specific promise, then replace the Promise.any(...) block so it awaits that
captured promise plus the existing DOM fallback (page.waitForFunction using
prevRawHtml) instead of relying on whatever deferred happens to be on
window.__nextHmr; update the code locations referencing window.__nextHmr,
Promise.any, page.evaluate and prevRawHtml accordingly to await the newly
created promise.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/index.ts`:
- Around line 151-157: virtualFilesForTemplate currently only appends with
files.add(id), causing stale ids to accumulate; instead, during a single compile
pass collect the current compile's virtual ids for a given normalizedFrom into a
fresh Set and replace the stored entry (use
virtualFilesForTemplate.set(normalizedFrom, currentSet)) so the map reflects
only ids emitted this compile; if no ids were emitted, remove the entry
(virtualFilesForTemplate.delete(normalizedFrom)); make this change around the
code that currently reads virtualFilesForTemplate.get(normalizedFrom) and calls
files.add(id) (leave the isDeferredPromise(prev) handling intact).

---

Duplicate comments:
In `@src/__tests__/main.test.ts`:
- Around line 421-435: The test is racing on a possibly-stale window.__nextHmr
deferred; before mutating files, inject a fresh deferred into the page (e.g.
create and assign a new Promise resolver to window.__nextHmr via page.evaluate)
and capture a handle/reference to that specific promise, then replace the
Promise.any(...) block so it awaits that captured promise plus the existing DOM
fallback (page.waitForFunction using prevRawHtml) instead of relying on whatever
deferred happens to be on window.__nextHmr; update the code locations
referencing window.__nextHmr, Promise.any, page.evaluate and prevRawHtml
accordingly to await the newly created promise.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ee9dc67f-62c2-4f9c-a840-77a615975466

📥 Commits

Reviewing files that changed from the base of the PR and between 8204698 and 0e6e843.

⛔ Files ignored due to path filters (7)
  • src/__tests__/fixtures/isomorphic-basic-hmr/__snapshots__/dev-hmr.expected.md is excluded by !**/__snapshots__/** and included by **
  • src/__tests__/fixtures/isomorphic-server-only-hmr/__snapshots__/build.expected.md is excluded by !**/__snapshots__/** and included by **
  • src/__tests__/fixtures/isomorphic-server-only-hmr/__snapshots__/dev-hmr.expected.md is excluded by !**/__snapshots__/** and included by **
  • src/__tests__/fixtures/isomorphic-server-only-hmr/__snapshots__/dev.expected.md is excluded by !**/__snapshots__/** and included by **
  • src/__tests__/fixtures/isomorphic-tags-api-hmr/__snapshots__/dev-hmr.expected.md is excluded by !**/__snapshots__/** and included by **
  • src/__tests__/fixtures/isomorphic-tags-api-virtual-file-hmr/__snapshots__/dev-hmr.expected.md is excluded by !**/__snapshots__/** and included by **
  • src/__tests__/fixtures/isomorphic-virtual-file-hmr/__snapshots__/dev-hmr.expected.md is excluded by !**/__snapshots__/** and included by **
📒 Files selected for processing (10)
  • src/__tests__/fixtures/isomorphic-server-only-hmr/dev-server.mjs
  • src/__tests__/fixtures/isomorphic-server-only-hmr/server.mjs
  • src/__tests__/fixtures/isomorphic-server-only-hmr/src/components/class-component.marko
  • src/__tests__/fixtures/isomorphic-server-only-hmr/src/components/implicit-component.marko
  • src/__tests__/fixtures/isomorphic-server-only-hmr/src/components/layout-component.marko
  • src/__tests__/fixtures/isomorphic-server-only-hmr/src/index.js
  • src/__tests__/fixtures/isomorphic-server-only-hmr/src/template.marko
  • src/__tests__/fixtures/isomorphic-server-only-hmr/test.config.ts
  • src/__tests__/main.test.ts
  • src/index.ts
✅ Files skipped from review due to trivial changes (3)
  • src/tests/fixtures/isomorphic-server-only-hmr/src/components/implicit-component.marko
  • src/tests/fixtures/isomorphic-server-only-hmr/src/template.marko
  • src/tests/fixtures/isomorphic-server-only-hmr/src/components/class-component.marko

Comment thread src/index.ts Outdated
@rturnq rturnq force-pushed the virtual-file-hmr branch from 0e6e843 to d4fae7c Compare April 14, 2026 17:11
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 14, 2026

Codecov Report

❌ Patch coverage is 98.24561% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 81.41%. Comparing base (750540c) to head (d4fae7c).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
src/index.ts 98.24% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #266      +/-   ##
==========================================
+ Coverage   77.53%   81.41%   +3.88%     
==========================================
  Files          14       15       +1     
  Lines         819      931     +112     
  Branches      221      246      +25     
==========================================
+ Hits          635      758     +123     
+ Misses        142      127      -15     
- Partials       42       46       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@rturnq rturnq merged commit 1e2bf28 into main Apr 14, 2026
11 checks passed
@rturnq rturnq deleted the virtual-file-hmr branch April 14, 2026 17:18
@github-actions github-actions bot mentioned this pull request Apr 14, 2026
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.

1 participant