Skip to content

Conversation

@linspw
Copy link
Contributor

@linspw linspw commented Oct 17, 2025

🔗 Linked issue

📚 Description

  1. Introduce support for defining moduleDependencies as an async function so modules can resolve dependencies dynamically
  2. Add a Vitest case in packages/kit/test/module.test.ts that exercises the async dependency path
  3. Refresh the guide at docs/2.guide/3.going-further/3.modules.md with an async moduleDependencies example to document the new capability

Example:

import { defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  meta: {
    name: 'my-conditional-module',
  },
  async moduleDependencies (nuxt) {
    const needsImageSupport = nuxt.options.runtimeConfig.public?.enableImages

    if (needsImageSupport) {
      await makeSomeStuff()
    }

    return {
      'other-module': needsImageSupport ? {} : { optional: true },
    }
  },
  setup () {
    // ...
  },
})

@linspw linspw requested a review from danielroe as a code owner October 17, 2025 13:51
@bolt-new-by-stackblitz
Copy link

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

@github-actions github-actions bot added the 4.x label Oct 17, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 17, 2025

Walkthrough

This pull request adds support for asynchronous module dependencies throughout the Nuxt kit ecosystem. The changes enable module developers to define moduleDependencies as an awaitable function that resolves asynchronously. The implementation updates type signatures in packages/schema to accept Awaitable<ModuleDependencies>, modifies the dependency resolution logic in packages/kit to await the result, introduces a test case verifying async dependency handling, and adds documentation demonstrating the async moduleDependencies pattern with runtime inspection and conditional IO operations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The changes introduce asynchronous capability to module dependencies across multiple integrated areas—type definitions, runtime implementation, test coverage, and documentation. Whilst the individual changes follow a consistent pattern of adding Awaitable support, the review requires verifying: type signature compatibility across interfaces, proper await handling in the installation flow, alignment between test scenarios and documented examples, and ensuring no breaking changes to existing synchronous dependency patterns. The spread across heterogeneous file types (schema, implementation, tests, docs) necessitates cross-disciplinary reasoning despite the homogeneous intent.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed The PR description is well-structured and clearly related to the changeset. It outlines three specific objectives: introducing async support for moduleDependencies, adding a Vitest case, and updating documentation with an example. The description provides a concrete code example demonstrating the new capability, showing how async moduleDependencies can conditionally resolve dependencies based on runtime configuration. This level of detail and clarity directly supports the changes present in the raw summary.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The pull request title clearly and specifically describes the main change: adding async function support for moduleDependencies, which aligns perfectly with the changeset across documentation, type definitions, and implementation.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@linspw linspw changed the title Introduce support for defining moduleDependencies as an async function Add support for defining moduleDependencies as an async function Oct 17, 2025
@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 17, 2025

Open in StackBlitz

@nuxt/kit

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

@nuxt/nitro-server

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

nuxt

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

@nuxt/rspack-builder

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

@nuxt/schema

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

@nuxt/vite-builder

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

@nuxt/webpack-builder

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

commit: 06c0ad0

@linspw linspw changed the title Add support for defining moduleDependencies as an async function feat: Add support for defining moduleDependencies as an async function Oct 17, 2025
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/2.guide/3.going-further/3.modules.md (1)

86-96: Replace npmjs.com link to resolve CI 403 error

The link https://www.npmjs.com at line 89 returns a 403 status and blocks the documentation pipeline. Change to https://docs.npmjs.com/, which returns a 200 status and is accessible.

🧹 Nitpick comments (5)
packages/kit/src/module/install.ts (1)

59-59: Attach module context to async dependency failures and prefer nullish coalescing

The await is correct. For clearer semantics and better diagnostics, catch errors here and use ?? {} instead of || {}.

Apply:

-    const dependencyMeta = await res.nuxtModule.getModuleDependencies?.(nuxt) || {}
+    let dependencyMeta = {}
+    try {
+      dependencyMeta = await res.nuxtModule.getModuleDependencies?.(nuxt) ?? {}
+    } catch (e) {
+      const which = typeof key === 'string' ? `\`${key}\`` : 'an inline module'
+      throw new TypeError(`Error resolving moduleDependencies for ${which}: ${e instanceof Error ? e.message : String(e)}`)
+    }
docs/2.guide/3.going-further/3.modules.md (1)

549-573: Good example; add a short caveat about heavy I/O

The sample is clear. Consider a one‑line note that heavy I/O in moduleDependencies delays start‑up and should be deferred to hooks when possible, to align with the “Async Modules” guidance below.

packages/kit/test/module.test.ts (1)

138-157: Add coverage for async optional and async overrides/defaults

To harden the contract, include:

  • Async dependency returning { optional: true } → not installed.
  • Async dependency that returns overrides/defaults → merged as in sync path.

Example additions:

+  it('should honour optional when provided asynchronously', async () => {
+    nuxt = await loadNuxt({
+      cwd: tempDir,
+      overrides: {
+        modules: [
+          defineNuxtModule({
+            meta: { name: 'foo' },
+            async moduleDependencies () {
+              await Promise.resolve()
+              return { 'some-module': { optional: true } }
+            },
+          }),
+        ],
+      },
+    })
+    expect(globalThis.someModuleLoaded).toBeUndefined()
+  })
+
+  it('should merge overrides/defaults from async dependencies', async () => {
+    nuxt = await loadNuxt({
+      cwd: tempDir,
+      overrides: {
+        // @ts-expect-error test-only option
+        a: { user: 'provided by user' },
+        modules: [
+          defineNuxtModule({
+            meta: { name: 'a' },
+            defaults: { value: 'provided by a' },
+            setup (options) {
+              expect(options.value).toBe('provided by c')
+              expect(options.user).toBe('provided by user')
+            },
+          }),
+          defineNuxtModule({
+            meta: { name: 'c' },
+            async moduleDependencies () {
+              await Promise.resolve()
+              return {
+                a: {
+                  overrides: { value: 'provided by c' },
+                  defaults: { user: 'provided by c' },
+                },
+              }
+            },
+          }),
+        ],
+      },
+    })
+  })
packages/schema/src/types/module.ts (2)

87-87: Tighten union: avoid duplication

You can simplify to a single async-capable function type.

Apply:

-  moduleDependencies?: ModuleDependencies | ((nuxt: Nuxt) => ModuleDependencies) | ((nuxt: Nuxt) => Awaitable<ModuleDependencies>)
+  moduleDependencies?: ModuleDependencies | ((nuxt: Nuxt) => Awaitable<ModuleDependencies>)

119-119: Return type simplification

Prefer Awaitable<ModuleDependencies | undefined> for readability.

Apply:

-  getModuleDependencies?: (nuxt: Nuxt) => Awaitable<ModuleDependencies> | ModuleDependencies | undefined
+  getModuleDependencies?: (nuxt: Nuxt) => Awaitable<ModuleDependencies | undefined>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 39dd9b6 and a996bee.

📒 Files selected for processing (4)
  • docs/2.guide/3.going-further/3.modules.md (1 hunks)
  • packages/kit/src/module/install.ts (1 hunks)
  • packages/kit/test/module.test.ts (1 hunks)
  • packages/schema/src/types/module.ts (2 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/module.ts
  • packages/kit/src/module/install.ts
  • packages/kit/test/module.test.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
🧬 Code graph analysis (1)
packages/kit/test/module.test.ts (2)
packages/nuxt/src/core/nuxt.ts (1)
  • loadNuxt (752-870)
packages/kit/src/module/define.ts (1)
  • defineNuxtModule (23-35)
🪛 GitHub Actions: docs
docs/2.guide/3.going-further/3.modules.md

[error] 1-1: [403] https://www.npmjs.com/ Rejected status code (Forbidden)

⏰ 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 (1)
packages/kit/test/module.test.ts (1)

138-157: LGTM: async moduleDependencies path is exercised

Test verifies awaiting and installation; simple and effective.

@linspw linspw changed the title feat: Add support for defining moduleDependencies as an async function feat(modules): Add support for defining moduleDependencies as an async function Oct 17, 2025
@codspeed-hq
Copy link

codspeed-hq bot commented Oct 17, 2025

CodSpeed Performance Report

Merging #33504 will not alter performance

Comparing linspw:jesse-allow-nuxt-async-modules (06c0ad0) with main (4f0cb86)

Summary

✅ 10 untouched

@linspw linspw changed the title feat(modules): Add support for defining moduleDependencies as an async function feat(kit): Add support for defining moduleDependencies as an async function Oct 17, 2025
@linspw linspw changed the title feat(kit): Add support for defining moduleDependencies as an async function feat(kit): add support for defining moduleDependencies as an async function Oct 17, 2025
@danielroe danielroe mentioned this pull request Oct 23, 2025
@danielroe
Copy link
Member

the main reason I didn't make it support async functions initially was that this would be a way of slowing down the start of nuxt (and it wouldn't be easily trackable/attributable to the module which slowed down nuxt in this way).

what use case are you imagining?

@linspw
Copy link
Contributor Author

linspw commented Oct 24, 2025

Thanks for the question, @danielroe !

Use case (why async): I want modules to be able to determine their own dependencies based on a user configuration file (for example, my-module.config.ts) and the project context.

In practice, this usually means:

  • loading configuration via import() (TS/JS/YAML-> asynchronous);
  • performing quick file system checks (presence of files, locales, plugins) to decide which companion modules to install or configure.

Today it’s possible to perform this logic inside setup, but it’s not possible to define moduleDependencies based on it without duplicating the configuration loading or creating awkward workarounds.
Allowing async moduleDependencies keeps the module cohesive, it loads the config once and then decides the dependencies based on it.

But I understand your point about blocking the loading process, thinking about it, maybe a way to solve this could be optimizing with parallelism and/or a timeout.

@danielroe danielroe added this to the 4.3 milestone Dec 16, 2025
@vis97c
Copy link
Contributor

vis97c commented Dec 16, 2025

I wanted to update a module where the dependencies options depended on my module options plus some extra logic, the current moduleDependencies implementation breaks that posibility.

@danielroe danielroe changed the title feat(kit): add support for defining moduleDependencies as an async function feat(kit): allow specifying moduleDependencies as an async function Dec 19, 2025
@danielroe danielroe merged commit c02e32a into nuxt:main Dec 19, 2025
100 of 101 checks passed
@github-actions github-actions bot mentioned this pull request Dec 19, 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.

3 participants