Skip to content

Conversation

@danielroe
Copy link
Member

🔗 Linked issue

📚 Description

this ensures that legacy modules which are still using installModule have access to any changes to options that occur through registering moduleDependencies

@bolt-new-by-stackblitz
Copy link

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

@coderabbitai
Copy link

coderabbitai bot commented Nov 10, 2025

Walkthrough

Adds a Nuxt instance-level Map nuxt._moduleOptionsFunctions to collect module option-merging callbacks during dependency resolution and installation. Replaces prior local per-call maps with reads/writes to this shared Map, stores dependency option callbacks when loading dependencies, merges defaults and overrides into Nuxt config (using defu) prior to lifecycle hooks and module execution, and passes the resulting mergedOptions into lifecycle and module calls. Cleans up nuxt._moduleOptionsFunctions after installation. Schema and tests updated to reflect these changes.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main change: adding support for moduleDependencies to work with installModule, which is the core objective of this pull request.
Description check ✅ Passed The description is directly related to the changeset, explaining the purpose of ensuring legacy modules using installModule can access option changes from moduleDependencies.
✨ 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/module-dep-install

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

🧹 Nitpick comments (2)
packages/schema/src/types/nuxt.ts (1)

98-102: Consider using the more specific ModuleToInstall type.

The type uses bare NuxtModule without type parameters, whereas the implementation in packages/kit/src/module/install.ts (line 21) defines ModuleToInstall = string | NuxtModule<ModuleOptions, Partial<ModuleOptions>, false>. This creates a minor type inconsistency where the schema type is less specific than the actual usage.

Consider importing and using ModuleToInstall from the kit module, or defining a matching type here for better type safety:

+import type { ModuleOptions } from './module'
+
 /**
  * Module options functions collected from moduleDependencies.
  * @internal
  */
-_moduleOptionsFunctions?: Map<string | NuxtModule, Array<() => { defaults?: Record<string, unknown>, overrides?: Record<string, unknown> }>>
+_moduleOptionsFunctions?: Map<string | NuxtModule<ModuleOptions, Partial<ModuleOptions>, false>, Array<() => { defaults?: Record<string, unknown>, overrides?: Record<string, unknown> }>>
packages/kit/src/module/install.ts (1)

173-196: Core fix implemented correctly, but consider extracting the duplication.

This addition successfully achieves the PR objective by ensuring that legacy modules using the deprecated installModule function receive merged options from moduleDependencies. The logic correctly:

  • Checks for the existence of _moduleOptionsFunctions before use
  • Merges options with correct precedence
  • Passes merged options to lifecycle hooks and module execution

However, the merging logic (lines 178-192) is substantially duplicated from installModules (lines 130-143). Consider extracting this into a shared helper function to reduce duplication and improve maintainability.

Example refactoring:

function mergeModuleOptions(
  moduleToInstall: ModuleToInstall,
  configKey: keyof NuxtOptions | undefined,
  inlineOptions: any,
  nuxt: Nuxt
): any {
  if (!configKey || !nuxt._moduleOptionsFunctions) {
    return inlineOptions
  }
  
  const optionsFns = [
    ...nuxt._moduleOptionsFunctions.get(moduleToInstall) || [],
    ...nuxt._moduleOptionsFunctions.get(configKey) || [],
  ]
  
  if (optionsFns.length === 0) {
    return inlineOptions
  }
  
  const overrides = [] as unknown as [Record<string, unknown> | undefined, ...Array<Record<string, unknown> | undefined>]
  const defaults: Array<Record<string, unknown> | undefined> = []
  
  for (const fn of optionsFns) {
    const options = fn()
    overrides.push(options.overrides)
    defaults.push(options.defaults)
  }
  
  const merged = defu(inlineOptions, ...overrides, nuxt.options[configKey], ...defaults)
  ;(nuxt.options[configKey] as any) = merged
  return merged
}

Then use it in both locations:

// In installModules:
const mergedOptions = mergeModuleOptions(moduleToInstall, configKey, inlineOptions, nuxt)
await callLifecycleHooks(nuxtModule, meta, mergedOptions, nuxt)
await callModule(nuxtModule, meta, mergedOptions, ...)

// In installModule:
const mergedOptions = mergeModuleOptions(moduleToInstall, configKey, inlineOptions, nuxt)
await callLifecycleHooks(nuxtModule, meta, mergedOptions, nuxt)
await callModule(nuxtModule, meta, mergedOptions, ...)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2c9711 and d2f895e.

📒 Files selected for processing (3)
  • packages/kit/src/module/install.ts (5 hunks)
  • packages/kit/test/module.test.ts (2 hunks)
  • packages/schema/src/types/nuxt.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

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

Follow standard TypeScript conventions and best practices

Files:

  • packages/schema/src/types/nuxt.ts
  • packages/kit/test/module.test.ts
  • packages/kit/src/module/install.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

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

Write unit tests for core functionality using vitest

Files:

  • packages/kit/test/module.test.ts
🧠 Learnings (2)
📚 Learning: 2024-11-05T15:22:54.759Z
Learnt from: GalacticHypernova
Repo: nuxt/nuxt PR: 26468
File: packages/nuxt/src/components/plugins/loader.ts:24-24
Timestamp: 2024-11-05T15:22:54.759Z
Learning: In `packages/nuxt/src/components/plugins/loader.ts`, the references to `resolve` and `distDir` are legacy code from before Nuxt used the new unplugin VFS and will be removed.

Applied to files:

  • packages/schema/src/types/nuxt.ts
📚 Learning: 2025-07-18T16:46:07.446Z
Learnt from: CR
Repo: nuxt/nuxt PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-18T16:46:07.446Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : Write unit tests for core functionality using `vitest`

Applied to files:

  • packages/kit/test/module.test.ts
🧬 Code graph analysis (3)
packages/schema/src/types/nuxt.ts (1)
packages/schema/src/index.ts (1)
  • NuxtModule (10-10)
packages/kit/test/module.test.ts (3)
packages/nuxt/src/core/nuxt.ts (1)
  • loadNuxt (753-871)
packages/kit/src/module/define.ts (1)
  • defineNuxtModule (23-35)
packages/kit/src/module/install.ts (1)
  • installModule (158-197)
packages/kit/src/module/install.ts (1)
packages/schema/src/types/config.ts (1)
  • NuxtOptions (82-94)
⏰ 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). (2)
  • GitHub Check: code
  • GitHub Check: build
🔇 Additional comments (7)
packages/kit/test/module.test.ts (3)

104-112: LGTM! Clean approach to module factory with metadata.

The use of Object.assign to attach getMeta as a static method to the module function is a clean pattern that satisfies the module interface whilst allowing test inspection of the loaded options via globalThis.someModuleOptions.


118-118: LGTM! Proper test cleanup.

The addition of cleanup for globalThis.someModuleOptions in beforeEach ensures proper test isolation.


305-370: Excellent test coverage for the PR objective.

This test comprehensively validates that moduleDependencies configuration is properly applied when installModule is called explicitly. It correctly verifies:

  • Module execution order
  • Single module instantiation (no duplicate loading)
  • Complete option merging precedence: inline options > module overrides > user configuration > module defaults

The test assertions at lines 358-369 thoroughly cover all the merging scenarios.

packages/kit/src/module/install.ts (4)

44-44: LGTM! Safe initialisation of shared storage.

The use of the nullish coalescing assignment operator (||=) ensures the Map is only created if it doesn't already exist, providing safe initialisation of the shared module options storage.


84-88: LGTM! Correct accumulation of module dependency options.

The code correctly accumulates multiple moduleDependencies configurations by preserving existing entries before adding new option functions. The lazy evaluation pattern (storing functions rather than values) is appropriate.


126-148: LGTM! Correct option merging for module dependencies.

The option merging logic correctly implements the precedence: inline options (highest) → module overrides → user configuration → module defaults (lowest). The use of defu with the spread operators properly applies this precedence, and updating nuxt.options[configKey] ensures downstream code sees the merged configuration.

Note: This merging logic is duplicated in the installModule function (lines 173-196). See separate comment about potential refactoring.


150-151: LGTM! Proper cleanup of temporary storage.

Cleaning up _moduleOptionsFunctions after all modules are processed is good practice. The deprecated installModule function correctly handles the case where this storage may not exist (line 177 check).

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 10, 2025

Open in StackBlitz

@nuxt/kit

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

@nuxt/nitro-server

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

nuxt

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

@nuxt/rspack-builder

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

@nuxt/schema

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

@nuxt/vite-builder

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

@nuxt/webpack-builder

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

commit: e758786

@codspeed-hq
Copy link

codspeed-hq bot commented Nov 10, 2025

CodSpeed Performance Report

Merging #33689 will not alter performance

Comparing fix/module-dep-install (e758786) with main (9728d9e)

Summary

✅ 10 untouched

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a5ae2e and e758786.

📒 Files selected for processing (1)
  • packages/kit/src/module/install.ts (5 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/kit/src/module/install.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/kit/src/module/install.ts
🧬 Code graph analysis (1)
packages/kit/src/module/install.ts (2)
packages/schema/src/index.ts (1)
  • NuxtOptions (4-4)
packages/schema/src/types/config.ts (1)
  • NuxtOptions (82-94)
⏰ 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). (3)
  • GitHub Check: code
  • GitHub Check: codeql (actions)
  • GitHub Check: build
🔇 Additional comments (4)
packages/kit/src/module/install.ts (4)

44-44: LGTM!

The initialisation of the shared _moduleOptionsFunctions Map is correctly placed and uses the appropriate nullish coalescing assignment operator.


83-89: LGTM!

The logic correctly accumulates module option merge functions, preserving existing entries and appending new callbacks.


152-153: LGTM!

The cleanup of the temporary Map after module installation is complete prevents memory leaks and is correctly placed after all modules have been processed.


192-192: Verify the merge order includes inlineOptions correctly.

The merge order here is defu(inlineOptions, ...overrides, nuxt.options[configKey], ...defaults), which gives inlineOptions the highest priority. However, in installModules (line 144), the merge is defu(...overrides, nuxt.options[configKey], ...defaults) without including inlineOptions in the defu call.

This difference may be intentional (since installModule is the legacy API and might need different semantics), but please confirm this is the desired behaviour. If inlineOptions should have highest priority in both paths, the fix suggested for lines 148-149 should address this.

Comment on lines 148 to 149
await callLifecycleHooks(nuxtModule, meta, inlineOptions, nuxt)
await callModule(nuxtModule, meta, inlineOptions, resolvedModulePath, moduleToInstall, localLayerModuleDirs, buildTimeModuleMeta, nuxt)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Inconsistent options handling between installModules and installModule.

These calls pass inlineOptions to lifecycle hooks and module execution, but the merged options (from lines 129-146) are only applied to nuxt.options[configKey] and not propagated here. In contrast, installModule (lines 197-198) passes mergedOptions to the same functions. This inconsistency means:

  • In installModules: modules receive inlineOptions as the parameter but must read merged options from nuxt.options[configKey]
  • In installModule: modules receive mergedOptions as the parameter

If modules rely on the options parameter (rather than reading from config), they won't see the merged dependency options when installed via installModules.

Apply this diff to pass merged options consistently:

+  let mergedOptions = inlineOptions
   if (optionsFns.length > 0) {
     const overrides = [] as unknown as [Record<string, unknown> | undefined, ...Array<Record<string, unknown> | undefined>]
     const defaults: Array<Record<string, unknown> | undefined> = []
     for (const fn of optionsFns) {
       const options = fn()
       overrides.push(options.overrides)
       defaults.push(options.defaults)
     }
     if (configKey) {
+      mergedOptions = defu(inlineOptions, ...overrides, nuxt.options[configKey], ...defaults)
-      ;(nuxt.options[configKey] as any) = defu(...overrides, nuxt.options[configKey], ...defaults)
+      ;(nuxt.options[configKey] as any) = mergedOptions
     }
   }

-  await callLifecycleHooks(nuxtModule, meta, inlineOptions, nuxt)
-  await callModule(nuxtModule, meta, inlineOptions, resolvedModulePath, moduleToInstall, localLayerModuleDirs, buildTimeModuleMeta, nuxt)
+  await callLifecycleHooks(nuxtModule, meta, mergedOptions, nuxt)
+  await callModule(nuxtModule, meta, mergedOptions, resolvedModulePath, moduleToInstall, localLayerModuleDirs, buildTimeModuleMeta, nuxt)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/kit/src/module/install.ts around lines 148-149, the code calls
callLifecycleHooks(...) and callModule(...) passing inlineOptions, but the
mergedOptions computed earlier (lines ~129-146) are not used; update these two
calls to pass mergedOptions (the fully merged dependency options) instead of
inlineOptions so module lifecycle hooks and module execution receive the same
merged options as installModule does, ensuring consistent behavior regardless of
installation path.

Comment on lines +180 to +183
const optionsFns = [
...nuxt._moduleOptionsFunctions.get(moduleToInstall) || [],
...nuxt._moduleOptionsFunctions.get(configKey) || [],
]
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing meta.name check when collecting option functions.

The option function collection only checks moduleToInstall and configKey, but doesn't check for meta.name like installModules does on line 131. This means if a module dependency specifies options keyed by the module's name, those options won't be applied when using installModule.

Apply this diff to match the logic in installModules:

   const optionsFns = [
     ...nuxt._moduleOptionsFunctions.get(moduleToInstall) || [],
+    ...meta?.name ? nuxt._moduleOptionsFunctions.get(meta.name) || [] : [],
     ...nuxt._moduleOptionsFunctions.get(configKey) || [],
   ]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const optionsFns = [
...nuxt._moduleOptionsFunctions.get(moduleToInstall) || [],
...nuxt._moduleOptionsFunctions.get(configKey) || [],
]
const optionsFns = [
...nuxt._moduleOptionsFunctions.get(moduleToInstall) || [],
...meta?.name ? nuxt._moduleOptionsFunctions.get(meta.name) || [] : [],
...nuxt._moduleOptionsFunctions.get(configKey) || [],
]
🤖 Prompt for AI Agents
In packages/kit/src/module/install.ts around lines 180 to 183, the code
collecting option functions misses checking the module's meta.name (as
installModules does on line 131), so options keyed by the module's declared name
are not applied; update the optionsFns assembly to also include
nuxt._moduleOptionsFunctions.get(meta?.name) (guarding meta may be undefined) so
it merges functions from moduleToInstall, configKey, and the module's meta.name
before proceeding.

@danielroe danielroe merged commit b8b53ed into main Dec 9, 2025
57 checks passed
@danielroe danielroe deleted the fix/module-dep-install branch December 9, 2025 15:20
@github-actions github-actions bot mentioned this pull request Dec 9, 2025
danielroe added a commit that referenced this pull request Dec 9, 2025
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
@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.

2 participants