Skip to content

Conversation

@onmax
Copy link
Contributor

@onmax onmax commented Oct 11, 2025

Fixes #33009

Corrected the documentation to reflect that Node.js only uses exports and main fields for module resolution, not the module field (which is a bundler-only convention).

Changes:

  • Fixed line stating Node.js looks for module field
  • Added note clarifying module is bundler-only with links to official Node.js docs

@onmax onmax requested a review from danielroe as a code owner October 11, 2025 07:48
@bolt-new-by-stackblitz
Copy link

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

Copy link
Member

@danielroe danielroe left a comment

Choose a reason for hiding this comment

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

thank you!

@@ -0,0 +1,364 @@
---
Copy link
Member

Choose a reason for hiding this comment

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

did you mean to include this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@coderabbitai
Copy link

coderabbitai bot commented Oct 11, 2025

Walkthrough

Updates documentation in docs/2.guide/2.concepts/7.esm.md. Adds a note that the package.json module field is a bundler convention and not used by Node.js. Revises the Node.js resolution rule in the “What Are Valid Imports in a Node.js Context?” section to state that Node.js checks the exports entry first and falls back to main if exports is absent, replacing prior wording that mentioned exports or module. Retains existing ESM guidance with phrasing adjusted to reflect the new rule.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly and concisely summarises the primary change by indicating that the documentation for ESM module field information has been corrected, directly reflecting the main purpose of the pull request. It is specific to the docs scope and mentions the exact nature of the fix without extraneous details. This makes it easy for a teammate to understand the intent at a glance.
Linked Issues Check ✅ Passed The updates in docs/2.guide/2.concepts/7.esm.md fully address issue #33009 by removing the incorrect reference to the module field, clarifying its bundler-only status, and correctly describing Node.js’s use of exports and main. Each bullet point of the linked issue’s objectives is satisfied through these targeted documentation changes.
Out of Scope Changes Check ✅ Passed All modifications are confined to the specified documentation file and directly relate to correcting the ESM module field information as per the linked issue, with no unrelated code or additional features introduced. There are no out-of-scope changes present.
Description Check ✅ Passed The description clearly outlines the issue being fixed, references the linked issue, and details the exact changes made to the documentation regarding Node.js module resolution fields. It remains directly relevant to the changeset without veering off-topic or being overly vague.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 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 1fa1f15 and 714c1b8.

📒 Files selected for processing (1)
  • docs/2.guide/2.concepts/7.esm.md (2 hunks)
🔇 Additional comments (1)
docs/2.guide/2.concepts/7.esm.md (1)

53-67: Accurate clarification on Node entry points

Good correction: Node’s resolver considers exports first and only consults main when exports is absent; the wording now mirrors the Node.js package docs and eliminates the misleading module reference.


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: 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 d989fb8 and 244b6de.

⛔ Files ignored due to path filters (3)
  • examples/standard-schema-validation/package.json is excluded by !**/package.json
  • packages/schema/package.json is excluded by !**/package.json
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (12)
  • docs/2.guide/1.concepts/7.esm.md (2 hunks)
  • docs/3.api/2.composables/use-nuxt-standard-schema.md (1 hunks)
  • examples/standard-schema-validation/README.md (1 hunks)
  • examples/standard-schema-validation/modules/example-module.ts (1 hunks)
  • examples/standard-schema-validation/nuxt.config.ts (1 hunks)
  • packages/kit/src/index.ts (1 hunks)
  • packages/kit/src/loader/schema.ts (2 hunks)
  • packages/nuxt/src/core/schema.ts (3 hunks)
  • packages/schema/build.config.ts (1 hunks)
  • packages/schema/src/index.ts (1 hunks)
  • packages/schema/src/types/config.ts (2 hunks)
  • packages/schema/src/utils/standard-schema.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

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

Follow standard TypeScript conventions and best practices

Files:

  • packages/schema/src/types/config.ts
  • packages/kit/src/index.ts
  • packages/schema/src/utils/standard-schema.ts
  • packages/schema/src/index.ts
  • packages/schema/build.config.ts
  • packages/kit/src/loader/schema.ts
  • examples/standard-schema-validation/nuxt.config.ts
  • packages/nuxt/src/core/schema.ts
  • examples/standard-schema-validation/modules/example-module.ts
🧬 Code graph analysis (5)
packages/schema/src/types/config.ts (1)
packages/schema/src/utils/standard-schema.ts (1)
  • StandardSchemaV1 (5-10)
packages/schema/src/utils/standard-schema.ts (2)
packages/schema/src/index.ts (8)
  • StandardSchemaV1 (14-14)
  • isStandardSchema (15-15)
  • ExtendedSchemaDefinition (14-14)
  • SchemaDefinition (4-4)
  • StandardSchemaExtension (14-14)
  • standardSchemaToJsonSchema (15-15)
  • validateWithStandardSchema (15-15)
  • formatStandardSchemaIssue (15-15)
packages/schema/src/types/config.ts (1)
  • SchemaDefinition (14-14)
packages/kit/src/loader/schema.ts (2)
packages/kit/src/index.ts (2)
  • extendNuxtStandardSchema (9-9)
  • useNuxt (21-21)
packages/schema/src/utils/standard-schema.ts (2)
  • StandardSchemaV1 (5-10)
  • StandardSchemaExtension (25-27)
packages/nuxt/src/core/schema.ts (2)
packages/schema/src/utils/standard-schema.ts (6)
  • StandardSchemaV1 (5-10)
  • isStandardSchema (13-19)
  • standardSchemaToJsonSchema (30-43)
  • StandardSchemaExtension (25-27)
  • validateWithStandardSchema (46-68)
  • formatStandardSchemaIssue (71-76)
packages/nuxt/src/utils.ts (1)
  • logger (13-13)
examples/standard-schema-validation/modules/example-module.ts (1)
packages/kit/src/loader/schema.ts (1)
  • extendNuxtStandardSchema (28-37)
🪛 markdownlint-cli2 (0.18.1)
examples/standard-schema-validation/README.md

279-279: Trailing spaces
Expected: 0 or 2; Actual: 1

(MD009, no-trailing-spaces)

docs/3.api/2.composables/use-nuxt-standard-schema.md

364-364: Trailing spaces
Expected: 0 or 2; Actual: 1

(MD009, no-trailing-spaces)

Comment on lines 51 to 59
nitroConfig.virtual['#build/example-module-composable'] = `
export const useExampleModule = () => {
return {
apiEndpoint: '${options.apiEndpoint}',
timeout: ${options.timeout},
retries: ${options.retries},
enableDebug: ${options.enableDebug}
}
}
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

Serialise option values before emitting the virtual module

Interpolating options.* directly into the template will break as soon as a value contains a quote or newline (e.g. an endpoint like https://api.example.com/foo'bar), producing invalid generated code and aborting the build. Please serialise each value (e.g. with JSON.stringify) before embedding it in the virtual module payload.

-    nuxt.hook('nitro:config', (nitroConfig) => {
+    nuxt.hook('nitro:config', (nitroConfig) => {
       nitroConfig.virtual ||= {}
-      nitroConfig.virtual['#build/example-module-composable'] = `
+      const { apiEndpoint, timeout, retries, enableDebug } = options
+      nitroConfig.virtual['#build/example-module-composable'] = `
 export const useExampleModule = () => {
   return {
-    apiEndpoint: '${options.apiEndpoint}',
-    timeout: ${options.timeout},
-    retries: ${options.retries},
-    enableDebug: ${options.enableDebug}
+    apiEndpoint: ${JSON.stringify(apiEndpoint)},
+    timeout: ${JSON.stringify(timeout)},
+    retries: ${JSON.stringify(retries)},
+    enableDebug: ${JSON.stringify(enableDebug)}
   }
 }
 `
     })
📝 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
nitroConfig.virtual['#build/example-module-composable'] = `
export const useExampleModule = () => {
return {
apiEndpoint: '${options.apiEndpoint}',
timeout: ${options.timeout},
retries: ${options.retries},
enableDebug: ${options.enableDebug}
}
}
nitroConfig.virtual ||= {}
const { apiEndpoint, timeout, retries, enableDebug } = options
nitroConfig.virtual['#build/example-module-composable'] = `
export const useExampleModule = () => {
return {
- apiEndpoint: '${options.apiEndpoint}',
- timeout: ${options.timeout},
- retries: ${options.retries},
apiEndpoint: ${JSON.stringify(apiEndpoint)},
timeout: ${JSON.stringify(timeout)},
retries: ${JSON.stringify(retries)},
enableDebug: ${JSON.stringify(enableDebug)}
}
}
`
🤖 Prompt for AI Agents
In examples/standard-schema-validation/modules/example-module.ts around lines 51
to 59, the virtual module string interpolates options.* directly which will
produce invalid JS if values contain quotes/newlines; update the template to
serialise each embedded option using JSON.stringify (or equivalent) so
apiEndpoint, timeout, retries and enableDebug are JSON-encoded before insertion
into the template literal, ensuring the generated module is valid even for
strings with quotes or newlines.

Comment on lines 149 to 197
nuxt.hooks.addHooks({
'schema:extend': (schemas: (SchemaDefinition | StandardSchemaExtension)[]) => {
for (const schemaOrExtension of schemas) {
// Check if this is a Standard Schema extension
if (typeof schemaOrExtension === 'object' && '$standardSchema' in schemaOrExtension && schemaOrExtension.$standardSchema) {
const standardSchema = schemaOrExtension.$standardSchema
// Store for validation later
const keys = Object.keys(schemaOrExtension).filter(key => key !== '$standardSchema')
for (const key of keys) {
moduleStandardSchemas[key] = standardSchema
}
}
}
},
})

// Validate configuration against Standard Schemas if any
if (standardSchemas.length > 0 || Object.keys(moduleStandardSchemas).length > 0) {
// Validate app-level schemas
for (const standardSchema of standardSchemas) {
const validationResult = await validateWithStandardSchema(standardSchema, nuxt.options)
if (!validationResult.success && validationResult.issues) {
const errorMessages = validationResult.issues.map(issue => formatStandardSchemaIssue(issue))
logger.error('❌ [Nuxt Schema] Standard Schema validation failed:')
for (const [index, message] of errorMessages.entries()) {
logger.error(` ${index + 1}. ${message}`)
}
throw new Error('Configuration validation failed')
}
}

// Validate module-level schemas
for (const [configPath, standardSchema] of Object.entries(moduleStandardSchemas)) {
const configValue = configPath.split('.').reduce((obj, key) => obj?.[key], nuxt.options)
if (configValue !== undefined) {
const validationResult = await validateWithStandardSchema(standardSchema, configValue)
if (!validationResult.success && validationResult.issues) {
const errorMessages = validationResult.issues.map(issue => formatStandardSchemaIssue(issue))
logger.error(`❌ [Nuxt Schema] Standard Schema validation failed for "${configPath}":`)
for (const [index, message] of errorMessages.entries()) {
logger.error(` ${index + 1}. ${message}`)
}
throw new Error(`Configuration validation failed for "${configPath}"`)
}
}
}

logger.success('✅ Standard Schema validation passed')
}
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

Fix Standard Schema hook registration order

moduleStandardSchemas stays empty on the first run because the collector hook is registered after schema:extend has already been fired. As a result, module authors never see their Standard Schema validated (and the new helper is effectively a no-op until a subsequent rebuild). Register the collector before calling schema:extend, invoke it immediately for the current schemaDefs, and reuse the same function for future extensions.

-      await nuxt.hooks.callHook('schema:extend', schemaDefs)
-
-      // Process any Standard Schemas from modules via the new hook mechanism
-      nuxt.hooks.addHooks({
-        'schema:extend': (schemas: (SchemaDefinition | StandardSchemaExtension)[]) => {
-          for (const schemaOrExtension of schemas) {
-            // Check if this is a Standard Schema extension
-            if (typeof schemaOrExtension === 'object' && '$standardSchema' in schemaOrExtension && schemaOrExtension.$standardSchema) {
-              const standardSchema = schemaOrExtension.$standardSchema
-              // Store for validation later
-              const keys = Object.keys(schemaOrExtension).filter(key => key !== '$standardSchema')
-              for (const key of keys) {
-                moduleStandardSchemas[key] = standardSchema
-              }
-            }
-          }
-        },
-      })
+      const collectStandardSchemaExtensions = (schemas: (SchemaDefinition | StandardSchemaExtension)[]) => {
+        for (const schemaOrExtension of schemas) {
+          if (schemaOrExtension && typeof schemaOrExtension === 'object' && '$standardSchema' in schemaOrExtension && schemaOrExtension.$standardSchema) {
+            const standardSchema = schemaOrExtension.$standardSchema
+            for (const key of Object.keys(schemaOrExtension)) {
+              if (key !== '$standardSchema') {
+                moduleStandardSchemas[key] = standardSchema
+              }
+            }
+          }
+        }
+      }
+
+      collectStandardSchemaExtensions(schemaDefs)
+      nuxt.hooks.hook('schema:extend', collectStandardSchemaExtensions)
+      await nuxt.hooks.callHook('schema:extend', schemaDefs)
📝 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
nuxt.hooks.addHooks({
'schema:extend': (schemas: (SchemaDefinition | StandardSchemaExtension)[]) => {
for (const schemaOrExtension of schemas) {
// Check if this is a Standard Schema extension
if (typeof schemaOrExtension === 'object' && '$standardSchema' in schemaOrExtension && schemaOrExtension.$standardSchema) {
const standardSchema = schemaOrExtension.$standardSchema
// Store for validation later
const keys = Object.keys(schemaOrExtension).filter(key => key !== '$standardSchema')
for (const key of keys) {
moduleStandardSchemas[key] = standardSchema
}
}
}
},
})
// Validate configuration against Standard Schemas if any
if (standardSchemas.length > 0 || Object.keys(moduleStandardSchemas).length > 0) {
// Validate app-level schemas
for (const standardSchema of standardSchemas) {
const validationResult = await validateWithStandardSchema(standardSchema, nuxt.options)
if (!validationResult.success && validationResult.issues) {
const errorMessages = validationResult.issues.map(issue => formatStandardSchemaIssue(issue))
logger.error('❌ [Nuxt Schema] Standard Schema validation failed:')
for (const [index, message] of errorMessages.entries()) {
logger.error(` ${index + 1}. ${message}`)
}
throw new Error('Configuration validation failed')
}
}
// Validate module-level schemas
for (const [configPath, standardSchema] of Object.entries(moduleStandardSchemas)) {
const configValue = configPath.split('.').reduce((obj, key) => obj?.[key], nuxt.options)
if (configValue !== undefined) {
const validationResult = await validateWithStandardSchema(standardSchema, configValue)
if (!validationResult.success && validationResult.issues) {
const errorMessages = validationResult.issues.map(issue => formatStandardSchemaIssue(issue))
logger.error(`❌ [Nuxt Schema] Standard Schema validation failed for "${configPath}":`)
for (const [index, message] of errorMessages.entries()) {
logger.error(` ${index + 1}. ${message}`)
}
throw new Error(`Configuration validation failed for "${configPath}"`)
}
}
}
logger.success('✅ Standard Schema validation passed')
}
const collectStandardSchemaExtensions = (schemas: (SchemaDefinition | StandardSchemaExtension)[]) => {
for (const schemaOrExtension of schemas) {
if (
schemaOrExtension &&
typeof schemaOrExtension === 'object' &&
'$standardSchema' in schemaOrExtension &&
schemaOrExtension.$standardSchema
) {
const standardSchema = schemaOrExtension.$standardSchema
for (const key of Object.keys(schemaOrExtension)) {
if (key !== '$standardSchema') {
moduleStandardSchemas[key] = standardSchema
}
}
}
}
}
collectStandardSchemaExtensions(schemaDefs)
nuxt.hooks.hook('schema:extend', collectStandardSchemaExtensions)
await nuxt.hooks.callHook('schema:extend', schemaDefs)
// Validate configuration against Standard Schemas if any
if (standardSchemas.length > 0 || Object.keys(moduleStandardSchemas).length > 0) {
// Validate app-level schemas
for (const standardSchema of standardSchemas) {
const validationResult = await validateWithStandardSchema(standardSchema, nuxt.options)
if (!validationResult.success && validationResult.issues) {
const errorMessages = validationResult.issues.map(issue => formatStandardSchemaIssue(issue))
logger.error('❌ [Nuxt Schema] Standard Schema validation failed:')
for (const [index, message] of errorMessages.entries()) {
logger.error(` ${index + 1}. ${message}`)
}
throw new Error('Configuration validation failed')
}
}
// Validate module-level schemas
for (const [configPath, standardSchema] of Object.entries(moduleStandardSchemas)) {
const configValue = configPath.split('.').reduce((obj, key) => obj?.[key], nuxt.options)
if (configValue !== undefined) {
const validationResult = await validateWithStandardSchema(standardSchema, configValue)
if (!validationResult.success && validationResult.issues) {
const errorMessages = validationResult.issues.map(issue => formatStandardSchemaIssue(issue))
logger.error(`❌ [Nuxt Schema] Standard Schema validation failed for "${configPath}":`)
for (const [index, message] of errorMessages.entries()) {
logger.error(` ${index + 1}. ${message}`)
}
throw new Error(`Configuration validation failed for "${configPath}"`)
}
}
}
logger.success('✅ Standard Schema validation passed')
}
🤖 Prompt for AI Agents
packages/nuxt/src/core/schema.ts lines 149-197: the moduleStandardSchemas
collector is registered after the 'schema:extend' hook fires, so it misses the
initial schemaDefs; move registration so the collector function is defined and
invoked before calling nuxt.hooks.addHooks('schema:extend'), call the collector
immediately with the current schemaDefs to populate moduleStandardSchemas for
the first run, and keep a reference to that same collector function to register
as the hook handler for future 'schema:extend' events.

@onmax onmax closed this Oct 11, 2025
@onmax
Copy link
Contributor Author

onmax commented Oct 11, 2025

Closing to recreate from correct base branch

@onmax onmax reopened this Oct 11, 2025
@onmax onmax force-pushed the docs-33009-fix-esm-imports branch from 244b6de to 1fa1f15 Compare October 11, 2025 08:01
@onmax onmax force-pushed the docs-33009-fix-esm-imports branch from 1fa1f15 to 714c1b8 Compare October 11, 2025 08:13
@onmax
Copy link
Contributor Author

onmax commented Oct 11, 2025

Linter is not happy [403] <https://www.npmjs.com/> | Rejected status code (this depends on your "accept" configuration): Forbidden.

Should I remove the links?

@danielroe
Copy link
Member

you can ignore that error. I need to investigate

@danielroe danielroe merged commit 8789514 into nuxt:main Oct 11, 2025
7 of 8 checks passed
@github-actions github-actions bot mentioned this pull request Oct 10, 2025
@onmax onmax deleted the docs-33009-fix-esm-imports branch October 11, 2025 17:17
@github-actions github-actions bot mentioned this pull request Oct 23, 2025
@github-actions github-actions bot mentioned this pull request Oct 25, 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.

incorrect ESM documentation

2 participants