Skip to content

perf: deallocate out-of-scope references #32162

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: main
Choose a base branch
from

Conversation

GalacticHypernova
Copy link
Contributor

@GalacticHypernova GalacticHypernova commented May 20, 2025

🔗 Linked issue

A reiteration of #29661 on the latest main branch.
This iteration goes further to optimize related internally-used functions in core functions that execute only once, and reverts previously "optimized" parts that do not benefit from the optimization due to global reference dependencies/closures (hooks, watchers, plugins, etc)

📚 Description

Copy link

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

Copy link

pkg-pr-new bot commented May 20, 2025

Open in StackBlitz

@nuxt/kit

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

nuxt

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

@nuxt/rspack-builder

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

@nuxt/schema

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

@nuxt/vite-builder

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

@nuxt/webpack-builder

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

commit: 27f32cd

Copy link

codspeed-hq bot commented May 20, 2025

CodSpeed Performance Report

Merging #32162 will not alter performance

Comparing GalacticHypernova:patch-29 (27f32cd) with main (8d65b94)

Summary

✅ 10 untouched benchmarks

@GalacticHypernova
Copy link
Contributor Author

GalacticHypernova commented May 23, 2025

Hey @danielroe !
I wanted to get your opinion on this because it is a trade-off:

This PR would optimize memory utilization of the dev server and the prod build process, because it will allow references to be GC'ed before the process exits (as only then the modules (files) would be unloaded). Especially for the dev server, which runs indefinitely until explicitly shut down, this can be significant for constrained environments, as otherwise the data would be held in memory indefinitely.

However, in case of a nuxt.config.ts change in dev mode, which triggers a full dev server restart, it will re-allocate the references for the repeated execution and then once again free them. This includes initial setup of infrastructure (nuxt/nitro/vite/etc), internal modules, and more. It's worth noting that this is the only case affected by this PR. It's also worth noting that the impact is barely visible.

Realistically speaking, a nuxt.config change in development is relatively infrequent compared to regular HMR, which doesn't appear to be affected by it, but if it happens it might have a little slowdown in dev server re-starts.

I was wondering what your thoughts are on this trade-off? Would you still like this PR to be pushed?

@GalacticHypernova GalacticHypernova marked this pull request as ready for review May 23, 2025 17:59
Copy link

coderabbitai bot commented May 23, 2025

Walkthrough

This set of changes primarily refactors multiple modules by relocating utility functions, constants, and regular expressions from the module or file top-level scope into the more local scopes of the functions or methods where they are used. This includes moving helper functions and constants into the bodies of setup functions, template generators, or main logic functions in various Nuxt and Vite packages. No changes are made to the external APIs or exported function signatures; all modifications are internal, affecting only code organisation and scope. The core logic and behaviour of the affected modules remain unchanged.

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 0

🧹 Nitpick comments (1)
packages/nuxt/src/core/app.ts (1)

64-80: Consider replacing delete with undefined assignment for better performance.

The function relocation is good for memory optimization. However, the delete operator can impact performance.

Apply this diff to improve performance:

-  async function compileTemplate<T> (template: NuxtTemplate<T>, ctx: { nuxt: Nuxt, app: NuxtApp, utils?: unknown }) {
-    delete ctx.utils
+  async function compileTemplate<T> (template: NuxtTemplate<T>, ctx: { nuxt: Nuxt, app: NuxtApp, utils?: unknown }) {
+    ctx.utils = undefined
🧰 Tools
🪛 Biome (1.9.4)

[error] 65-65: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d65b94 and 27f32cd.

📒 Files selected for processing (14)
  • packages/kit/src/template.ts (3 hunks)
  • packages/nuxt/src/components/module.ts (1 hunks)
  • packages/nuxt/src/components/scan.ts (1 hunks)
  • packages/nuxt/src/components/templates.ts (3 hunks)
  • packages/nuxt/src/core/app.ts (3 hunks)
  • packages/nuxt/src/core/external-config-files.ts (1 hunks)
  • packages/nuxt/src/core/nitro.ts (1 hunks)
  • packages/nuxt/src/core/nuxt.ts (6 hunks)
  • packages/nuxt/src/core/templates.ts (4 hunks)
  • packages/nuxt/src/head/module.ts (1 hunks)
  • packages/nuxt/src/imports/module.ts (1 hunks)
  • packages/nuxt/src/pages/module.ts (2 hunks)
  • packages/vite/src/css.ts (1 hunks)
  • packages/vite/src/vite.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
packages/nuxt/src/components/module.ts (1)
packages/nuxt/src/utils.ts (1)
  • isDirectory (9-11)
packages/nuxt/src/components/scan.ts (1)
packages/nuxt/src/utils.ts (1)
  • logger (13-13)
packages/nuxt/src/imports/module.ts (6)
packages/schema/src/types/imports.ts (1)
  • ImportsOptions (3-41)
packages/kit/src/index.ts (5)
  • useNuxt (21-21)
  • directoryToURL (35-35)
  • resolveAlias (28-28)
  • tryResolveModule (35-35)
  • addTypeTemplate (31-31)
packages/kit/src/internal/esm.ts (2)
  • directoryToURL (13-15)
  • tryResolveModule (26-32)
packages/kit/src/resolve.ts (1)
  • resolveAlias (74-77)
packages/nuxt/src/utils.ts (1)
  • isDirectory (9-11)
packages/kit/src/template.ts (1)
  • addTypeTemplate (71-93)
packages/kit/src/template.ts (1)
packages/nuxt/src/core/utils/index.ts (1)
  • EXTENSION_RE (19-19)
🪛 Biome (1.9.4)
packages/nuxt/src/core/app.ts

[error] 65-65: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

🔇 Additional comments (31)
packages/vite/src/vite.ts (1)

240-247: LGTM: Function relocation for memory optimization.

Moving the withLogs helper function into the bundle function scope aligns well with the PR's memory optimization objective. Since this utility is only used within bundle, localising its scope will allow proper garbage collection when the function execution completes.

packages/vite/src/css.ts (1)

17-20: LGTM: Good encapsulation and memory optimization.

Relocating sortPlugins into the resolveCSSOptions function scope improves encapsulation whilst supporting the memory optimization goals. The function is only used within this context, making the scope reduction appropriate.

packages/kit/src/template.ts (4)

150-153: LGTM: Utility function relocation improves locality.

Moving relativeWithDot into _generateTypes is appropriate since it's only used within this function. The implementation correctly handles relative path formatting with proper dot prefixing.


277-279: LGTM: Constants relocated appropriately.

The EXTENSION_RE constant and excludedAlias array are correctly moved into local scope. Note that whilst there's a simpler EXTENSION_RE in packages/nuxt/src/core/utils/index.ts, this more specific version for TypeScript declaration files is appropriate for this context.


360-369: LGTM: Helper function properly encapsulated.

The sortTsPaths function relocation is well-placed within _generateTypes where it's exclusively used. The function correctly handles the reordering of TypeScript paths to ensure #build keys are positioned at the end.


377-387: LGTM: Template rendering utilities appropriately scoped.

Moving renderAttrs and renderAttr into the _generateTypes function improves encapsulation. These utilities are only needed for generating TypeScript reference comments within this specific context.

packages/nuxt/src/components/module.ts (1)

35-43: LGTM: Utility functions appropriately relocated to local scope.

Moving these helper functions and constants into the setup function scope is excellent for memory optimization whilst maintaining functionality. All utilities are exclusively used within this context:

  • isPureObjectOrString, isDirectory, and regex constants are well-encapsulated
  • The synchronous isDirectory implementation using statSync is appropriate here (note there's an async version in packages/nuxt/src/utils.ts for different use cases)
  • compareDirByPathLength maintains proper component directory sorting logic
packages/nuxt/src/head/module.ts (1)

16-16: Scope relocation improves memory utilisation.

Moving the components array into the setup function scope allows it to be garbage collected after module initialisation completes, which aligns with the PR's memory optimisation goals.

packages/nuxt/src/components/scan.ts (1)

29-41: Excellent scope optimisation for memory management.

Moving the regex constants and helper function into the scanComponents function scope is a well-executed optimisation. These utilities are only used within this function, and localising them allows for proper garbage collection after component scanning completes.

packages/nuxt/src/imports/module.ts (1)

168-228: Beneficial function relocation for memory optimisation.

Moving addDeclarationTemplates into the setup function scope is an excellent optimisation. Since this function is only called once during module setup, localising it allows all its internal state and closures to be properly garbage collected after the setup phase completes.

packages/nuxt/src/core/templates.ts (4)

106-109: Good scope localisation for regex constants.

Moving these regex constants into the pluginsDeclaration template function where they're used allows for better memory management during the build process.


186-187: Appropriate scope relocation for template-specific constants.

Localising these regex constants within the schemaTemplate function aligns with the PR's memory optimisation goals and keeps related logic together.


329-339: Excellent helper function localisation.

Moving the renderAttr and renderAttrs helper functions into the nitroSchemaTemplate where they're exclusively used is a smart optimisation that will allow proper garbage collection after template generation.


595-596: Consistent pattern application for regex constants.

The relocation of these regex constants into the buildTypeTemplate function maintains consistency with the overall refactoring pattern and improves memory utilisation.

packages/nuxt/src/components/templates.ts (3)

81-88: Excellent memory optimisation.

Moving createImportMagicComments into the local scope of componentsIslandsTemplate.getContents allows for proper garbage collection after template generation. This aligns perfectly with the PR's goal of deallocating out-of-scope references.


23-30: Good refactoring for memory efficiency.

Inlining the emptyComponentsPlugin constant directly within the conditional improves memory usage by avoiding a module-level constant that would persist throughout the application lifecycle.


109-109: Proper localisation of regex constant.

Moving NON_VUE_RE into the local scope ensures it's only allocated when needed and can be garbage collected after the template generation completes.

packages/nuxt/src/pages/module.ts (2)

36-55: Excellent scope optimisation.

Moving OPTIONAL_PARAM_RE, runtimeDir, and resolveRouterOptions into the setup function scope allows for proper memory management. These utilities are only needed during module initialisation and can be garbage collected afterwards.


544-576: Good localisation of HMR utilities.

Moving ROUTES_HMR_CODE and handleHotUpdate into the local scope near their usage point improves memory efficiency whilst maintaining all HMR functionality. The code structure remains clear and maintainable.

packages/nuxt/src/core/external-config-files.ts (1)

5-59: Excellent memory optimisation refactoring.

Moving the interface definition and all helper functions into the scope of checkForExternalConfigurationFiles significantly improves memory efficiency. These utilities are only needed during the configuration check phase and can be properly garbage collected afterwards. The refactoring maintains all existing logic, error handling, and warning messages without any functional changes.

packages/nuxt/src/core/nitro.ts (3)

25-30: Excellent regex and utility localisation.

Moving NODE_MODULES_RE, PNPM_NODE_MODULES_RE, RELATIVE_RE, and relativeWithDot into the initNitro scope provides significant memory benefits. These utilities are only needed during Nitro initialisation and can be garbage collected afterwards.


32-36: Good scope optimisation for log level mapping.

Localising logLevelMapReverse within initNitro ensures this constant is only allocated when needed and can be properly garbage collected after initialisation.


38-70: Superb SPA template handling refactoring.

Moving spaLoadingTemplatePath and spaLoadingTemplate functions into the local scope provides excellent memory optimisation whilst maintaining all functionality:

  • Proper error handling for missing files
  • Correct fallback logic for different configuration options
  • Template caching and resolution logic preserved
  • Warning messages for invalid configurations remain intact

This refactoring exemplifies the PR's memory optimisation goals.

packages/nuxt/src/core/app.ts (2)

44-48: LGTM! Good memory optimization.

Moving the postTemplates set into the generateApp function scope allows it to be garbage collected after the function completes, which aligns with the PR's memory optimization objectives.


218-231: LGTM! Appropriate function scoping.

Moving the resolvePaths helper function into resolveApp where it's exclusively used is a good optimization. This allows the function to be garbage collected after resolveApp completes.

packages/nuxt/src/core/nuxt.ts (6)

158-160: LGTM! Appropriate constant scoping.

Moving the fallbackCompatibilityDate constant into the initNuxt function scope where it's used is consistent with the PR's memory optimization strategy.


209-216: LGTM! Good memory optimization for nightlies mapping.

The nightlies object is appropriately scoped within initNuxt where it's exclusively used for resolving nightly package paths.


422-510: LGTM! Well-structured module resolution helpers.

The resolveModule and resolveModules functions are appropriately scoped within initNuxt. This encapsulation improves code organization whilst allowing these helper functions to be garbage collected after initialization.


752-752: LGTM! Appropriate regex scoping.

Moving the RESTART_RE regex into initNuxt where it's used for watch pattern matching is consistent with the memory optimization approach.


828-841: LGTM! Effective array deduplication scoping.

The deduplicateArray function is appropriately scoped within loadNuxt where it's used for deduplicating configuration arrays.


908-934: LGTM! Well-scoped property portal creation.

The createPortalProperties function is correctly scoped within loadNuxt where it's used to create shared getter/setter properties between Nuxt and Nitro configurations.

@danielroe
Copy link
Member

I think we can optimise for a single start rather than frequent reload.


Having said that (and this isn't a reason not to merge this one) I think PRs like these are likely to lead to very little improvement to the actual performance of Nuxt. (while there is certainly low-hanging fruit that would make a much bigger difference)

more generally, some of these PRs lead to improvement but it's just a tiny amount in a function that's not rerun many times. it doesn't mean it's not worth doing but the risk of accidentally breaking something is higher than the benefit it can often bring

in future, would you be able to create more targeted benchmarks before creating the PR, which can validate the approach you want to take?

otherwise there's probably an unlimited amount of bikeshedding we could do, and I'd rather spend our time reviewing/coding on changes that will make a more significant difference. 🙏

please don't take this as a lack of appreciation - I really value your time on this, but want to make best use of it as well as the time of the rest of the team

@GalacticHypernova
Copy link
Contributor Author

GalacticHypernova commented May 26, 2025

Hey @danielroe !

I appreciate your response. First of all, I do not take it as lack of appreciation, I fully understand your point.

TL;DR if you could assist with making reliable memory utilization benchmarks that would be appreciated, because I am uncertain of how to do that. Additionally, the smaller changes would really have a compounding effect that in the long term could be much more significant than what can be seen in the short term.


In depth:

The main issue is that such changes are mainly tackling memory efficiency. Unlike runtime performance, which can be measured with performance.now() - start, it's a lot trickier to measure memory utilization. I talked about this in #29620 as well, there's no reliable way to make application-side benchmarks because the benchmark itself would change the results.

I can, however, explain this logically with an extreme pseudo-code.
Imagine we have 8GB of RAM:

// without deallocation, every top-level function/object is in memory until the module is unloaded, 
// which is usually the end of the process

// for the nuxt dev server, which runs indefinitely, this means until the user shuts it off

const functionOnly = { ...2GB worth of data }
function func() {
// something with functionOnly
}
func()
const largeObject = { ...7GB worth of data } // the program crashed, because we 
// ran out of memory, 2GB from `functionOnly` and 7GB from `largeObject`
// with deallocation, every function/object is cleared from memory by the garbage collector (GC) 
// when the scope in which it was defined is no longer reachable
// this means no memory footprint pollution (data isn't left in memory longer than it needs to be)

// for the nuxt dev server this is substantial, because it allows for far greater memory safety 
// and gives more room for the actual end-developer's application

function func() {
const functionOnly = { ...2GB worth of data }
// something with functionOnly

}
func()

// `functionOnly` is now GC'ed

const largeObject = { ...7GB worth of data } // the program doesn't crash, 
// because `functionOnly` was GC'ed, we now have 1GB of free memory for user code

As for the tiny amount of improvements in some of the PR's, I also agree that they may not be the most substantial, but there are a lot of such possible changes. What really makes it substantial is the compounding effect of these multiple small changes that in the long term lead to much bigger improvements.

Not to mention, there is definitely much more significant low hanging fruits, as you said, that I, and seemingly everyone else, are simply yet to reach, because some of them are just harder to spot. I can say about myself that the optimizations of reducing duplicate function calls that I made a couple months ago were much easier for me to spot and implement than the optimizations I am working on today, such as this one. Both because it's much less obvious and because it delves deeper into the fundamentals of concepts like memory and performance.

However, the fact that these low hanging fruits weren't tackled yet doesn't necessarily have to deter us from tackling the smaller changes that we do notice, because otherwise it could become technical debt, if we end up tackling all the significant fruits and being left with only the less significant ones.

@danielroe
Copy link
Member

I understand that this PR is about memory management. My comment (below the horizontal rule) was more general.

I am worried that it's possible for large PRs to have a larger likelihood of introducing bugs and taking up time reviewing them, than the potential benefit. I review PRs line-by-line and this takes a considerable amount of time for PRs that might make only a 0.1ms difference in a user's experience.

I'm proposing using benchmarks to make it more obvious which changes matter.

We can do the same for memory management, with profiling.

@GalacticHypernova
Copy link
Contributor Author

GalacticHypernova commented May 27, 2025

I appreciate your response!

I'll try to set up profiling similar to the one I made in #29620. That's the most reliable profiler I managed to find. I'll post the results below.

If you'd like to backlog this PR, that's perfectly fine with me, I understand that you may have other priorities, and I'm of course not trying to force any PR to get merged against someone's will. I'm just trying to help and optimize wherever I can because I believe it will, in the long term, benefit everybody 🙂

As for the bugs, I think it's safe to assume that since all the tests passed, it hasn't introduced any breakage

@danielroe
Copy link
Member

Just a quick note on profiling, I'd like to run it on a real nuxt server. We can, for example, trigger HMR or trigger restarts with Nuxt hooks. If you want, I'd advise checking vite out - they added profiling support and then used it to speed up the server significantly.

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.

2 participants