Skip to content

Conversation

@huang-julien
Copy link
Member

🔗 Linked issue

fix #33788

📚 Description

This PR re-executes callOnce during HMR.

@bolt-new-by-stackblitz
Copy link

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 3, 2025

Open in StackBlitz

@nuxt/kit

npm i https://pkg.pr.new/@nuxt/kit@33810

@nuxt/nitro-server

npm i https://pkg.pr.new/@nuxt/nitro-server@33810

nuxt

npm i https://pkg.pr.new/nuxt@33810

@nuxt/rspack-builder

npm i https://pkg.pr.new/@nuxt/rspack-builder@33810

@nuxt/schema

npm i https://pkg.pr.new/@nuxt/schema@33810

@nuxt/vite-builder

npm i https://pkg.pr.new/@nuxt/vite-builder@33810

@nuxt/webpack-builder

npm i https://pkg.pr.new/@nuxt/webpack-builder@33810

commit: 62ec27a

@codspeed-hq
Copy link

codspeed-hq bot commented Dec 3, 2025

CodSpeed Performance Report

Merging #33810 will not alter performance

Comparing fix/callOnceHMR (62ec27a) with main (9a62708)

Summary

✅ 10 untouched

@huang-julien huang-julien marked this pull request as ready for review December 3, 2025 21:52
@coderabbitai
Copy link

coderabbitai bot commented Dec 3, 2025

Walkthrough

This change adds a module-scoped boolean flag to callOnce to track Hot Module Replacement (HMR) state and updates the early-return logic so keys that have already run will re-execute during HMR in development. HMR integration is implemented via import.meta.hot listeners (vite:beforeUpdate and vite:afterUpdate) that set and reset the flag based on js-update events. Exported signatures are unchanged.

Possibly related PRs

  • nuxt/nuxt PR 32827 — Modifies the same packages/nuxt/src/app/composables/once.ts callOnce logic to re-run callbacks on client-side navigation (router/beforeResolve and page:start hooks), touching the same code paths for re-execution behaviour.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and concisely describes the main change: re-executing callOnce during HMR, which aligns with the core modification in the code.
Description check ✅ Passed The description references the linked issue and explains that the PR re-executes callOnce during HMR, which is directly related to the changeset.
Linked Issues check ✅ Passed The code changes implement HMR re-execution of callOnce by tracking HMR state and forcing re-execution when HMR updates occur, directly addressing issue #33788's requirement.
Out of Scope Changes check ✅ Passed All changes are scoped to the callOnce composable and directly address the HMR re-execution requirement; no unrelated modifications are present.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/callOnceHMR

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f518e76 and 62ec27a.

📒 Files selected for processing (1)
  • packages/nuxt/src/app/composables/once.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Follow standard TypeScript conventions and best practices

Files:

  • packages/nuxt/src/app/composables/once.ts
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx,js,jsx,vue}: Use clear, descriptive variable and function names
Add comments only to explain complex logic or non-obvious implementations
Keep functions focused and manageable (generally under 50 lines), and extract complex logic into separate domain-specific files
Remove code that is not used or needed
Use error handling patterns consistently

Files:

  • packages/nuxt/src/app/composables/once.ts
🧠 Learnings (2)
📚 Learning: 2024-12-12T12:36:34.871Z
Learnt from: huang-julien
Repo: nuxt/nuxt PR: 29366
File: packages/nuxt/src/app/components/nuxt-root.vue:16-19
Timestamp: 2024-12-12T12:36:34.871Z
Learning: In `packages/nuxt/src/app/components/nuxt-root.vue`, when optimizing bundle size by conditionally importing components based on route metadata, prefer using inline conditional imports like:

```js
const IsolatedPage = route?.meta?.isolate ? defineAsyncComponent(() => import('#build/isolated-page.mjs')) : null
```

instead of wrapping the import in a computed property or importing the component unconditionally.

Applied to files:

  • packages/nuxt/src/app/composables/once.ts
📚 Learning: 2025-11-25T11:42:16.132Z
Learnt from: CR
Repo: nuxt/nuxt PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-25T11:42:16.132Z
Learning: Applies to **/*.{ts,tsx,js,jsx,vue} : Remove code that is not used or needed

Applied to files:

  • packages/nuxt/src/app/composables/once.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: code
🔇 Additional comments (3)
packages/nuxt/src/app/composables/once.ts (3)

8-9: LGTM! Clean solution for tracking HMR state.

The module-scoped flag is an appropriate way to track ongoing HMR updates and coordinate with the early-return logic below.


45-48: Past review feedback has been addressed.

The control flow now matches the suggestion from danielroe. The logic correctly allows re-execution during HMR whilst preserving the early return in all other cases.


58-70: No issues found—the Vite HMR event names and payload structure are correct.

The events vite:beforeUpdate and vite:afterUpdate with the payload structure (updates array containing objects with type field) are documented Vite HMR API features. The code correctly filters for 'js-update' type updates and appropriately uses the any type for Vite's internal payload structure.

Tip

✨ Issue Enrichment is now available for GitHub issues!

CodeRabbit can now help you manage issues more effectively:

  • Duplicate Detection — Identify similar or duplicate issues
  • Related Issues & PRs — Find relevant issues and PR's from your repository
  • Suggested Assignees — Find the best person to work on the issue
  • Implementation Planning — Generate detailed coding plans for engineers and agents
Disable automatic issue enrichment

To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

issue_enrichment:
  auto_enrich:
    enabled: false

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

@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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a62708 and f518e76.

📒 Files selected for processing (1)
  • packages/nuxt/src/app/composables/once.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Follow standard TypeScript conventions and best practices

Files:

  • packages/nuxt/src/app/composables/once.ts
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx,js,jsx,vue}: Use clear, descriptive variable and function names
Add comments only to explain complex logic or non-obvious implementations
Keep functions focused and manageable (generally under 50 lines), and extract complex logic into separate domain-specific files
Remove code that is not used or needed
Use error handling patterns consistently

Files:

  • packages/nuxt/src/app/composables/once.ts
🧠 Learnings (1)
📚 Learning: 2024-12-12T12:36:34.871Z
Learnt from: huang-julien
Repo: nuxt/nuxt PR: 29366
File: packages/nuxt/src/app/components/nuxt-root.vue:16-19
Timestamp: 2024-12-12T12:36:34.871Z
Learning: In `packages/nuxt/src/app/components/nuxt-root.vue`, when optimizing bundle size by conditionally importing components based on route metadata, prefer using inline conditional imports like:

```js
const IsolatedPage = route?.meta?.isolate ? defineAsyncComponent(() => import('#build/isolated-page.mjs')) : null
```

instead of wrapping the import in a computed property or importing the component unconditionally.

Applied to files:

  • packages/nuxt/src/app/composables/once.ts
🔇 Additional comments (2)
packages/nuxt/src/app/composables/once.ts (2)

8-9: LGTM: Module-scoped HMR flag is appropriate.

The module-scoped flag is a reasonable approach for tracking HMR state in development mode.


59-71: This HMR event listener implementation appears sound and does not have the timing issues described.

The concerns raised in the original review conflate the intended behavior with potential problems. Here's the verification:

  1. Timing window is intentional: The vite:beforeUpdatevite:afterUpdate window where _isHmrUpdating = true exists specifically to allow callOnce to re-execute when a module is hot-replaced. When Vite replaces a module containing a callOnce call, the developer expects it to run again during development. This is not a race condition but a feature.

  2. Listener lifecycle is safe: Vite's HMR API manages the lifecycle of import.meta.hot. These listeners are part of the module's hot context and do not leak when the module is replaced. The guard if (import.meta.hot) is the standard pattern.

  3. Event filtering is correct: Filtering for 'js-update' type is appropriate. CSS and other non-JS updates should not reset callOnce behavior; only JavaScript module changes should trigger re-execution.

The implementation follows Vite's documented HMR patterns correctly and is protected by the import.meta.dev check (line 45), ensuring this re-execution only occurs in development.

@cernymatej
Copy link
Member

Wouldn't this re-execute on any javascript HMR update, though?
If so, I'm not sure that's desired as it could cause various problems depending on what people use the function for.

Co-authored-by: Daniel Roe <daniel@roe.dev>
@huang-julien
Copy link
Member Author

Wouldn't this re-execute on any javascript HMR update, though?
If so, I'm not sure that's desired as it could cause various problems depending on what people use the function for.

If Component A with callOnce is updated, callOnce will re-execute. If there's a component B in the dependency tree with another callOnce, it will re-execute too.
But if B is not a dependency of A, callOnce friom B will not re-execute

@danielroe danielroe merged commit 2a91221 into main Dec 3, 2025
55 checks passed
@danielroe danielroe deleted the fix/callOnceHMR branch December 3, 2025 22:42
This was referenced Dec 3, 2025
danielroe pushed a commit that referenced this pull request Dec 9, 2025
@github-actions github-actions bot mentioned this pull request Dec 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

callOnce does not hot reload when the inner function is changed during development

4 participants