-
-
Notifications
You must be signed in to change notification settings - Fork 5.3k
fix(webpack): use plugin for rollup-compatible dynamic imports #32281
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
Conversation
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR updates the dynamic require plugin integration within the Nitro hook for webpack by replacing the legacy dynamic plugin injection with a hook-based approach.
- Moved plugin injection into the 'rollup:before' hook
- Replaced old dynamicRequire plugin instantiation with a new rollupCompatPlugin
- Simplified plugin management by using a single plugins array from config
Comments suppressed due to low confidence (1)
packages/webpack/src/webpack.ts:39
- The new rollupCompatPlugin configuration omits the 'inline' and 'ignore' options that were present in the original dynamicRequire plugin. Please confirm that these options are no longer necessary, as their removal might affect dynamic import handling.
const rollupCompatPlugin = dynamicRequire({
WalkthroughThe change removes the injection of the ✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
@nuxt/kit
nuxt
@nuxt/rspack-builder
@nuxt/schema
@nuxt/vite-builder
@nuxt/webpack-builder
commit: |
There was a problem hiding this 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
📒 Files selected for processing (1)
packages/webpack/src/webpack.ts
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/webpack/src/webpack.ts (1)
packages/webpack/src/nitro/plugins/dynamic-require.ts (1)
dynamicRequire
(35-90)
🪛 GitHub Check: code
packages/webpack/src/webpack.ts
[failure] 42-42:
Mixed spaces and tabs
[failure] 42-42:
Unexpected tab character
🪛 GitHub Actions: autofix.ci
packages/webpack/src/webpack.ts
[error] 42-42: ESLint: Unexpected tab character (@stylistic/no-tabs)
⏰ Context from checks skipped due to timeout of 90000ms (20)
- GitHub Check: test-fixtures (windows-latest, built, vite, async, manifest-off, json, 20)
- GitHub Check: test-fixtures (windows-latest, built, webpack, async, manifest-on, json, 20)
- GitHub Check: test-fixtures (ubuntu-latest, built, rspack, async, manifest-on, json, 20)
- GitHub Check: test-fixtures (windows-latest, built, rspack, async, manifest-on, json, 20)
- GitHub Check: test-fixtures (windows-latest, built, vite, async, manifest-on, json, 20)
- GitHub Check: test-fixtures (windows-latest, dev, vite, async, manifest-on, json, 20)
- GitHub Check: test-fixtures (ubuntu-latest, built, webpack, default, manifest-on, json, 20)
- GitHub Check: test-fixtures (windows-latest, dev, vite, default, manifest-off, json, 20)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite, default, manifest-on, json, 20)
- GitHub Check: test-fixtures (ubuntu-latest, built, webpack, async, manifest-on, json, 20)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite, default, manifest-off, json, 20)
- GitHub Check: test-fixtures (ubuntu-latest, built, rspack, default, manifest-on, json, 20)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-on, js, 20)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-on, json, 20)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-off, json, 20)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite, async, manifest-off, json, 20)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite, async, manifest-on, js, 20)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite, async, manifest-on, json, 20)
- GitHub Check: test-benchmark
- GitHub Check: typecheck (windows-latest, bundler)
🔇 Additional comments (1)
packages/webpack/src/webpack.ts (1)
38-59
: Excellent architectural improvement using Nitro's hook system.This refactoring properly leverages Nitro's
rollup:before
hook instead of directly manipulating internal plugin arrays. The approach is more maintainable and follows Nitro's intended extension patterns whilst preserving the same plugin injection logic.🧰 Tools
🪛 GitHub Check: code
[failure] 42-42:
Mixed spaces and tabs
[failure] 42-42:
Unexpected tab character🪛 GitHub Actions: autofix.ci
[error] 42-42: ESLint: Unexpected tab character (@stylistic/no-tabs)
CodSpeed Performance ReportMerging #32281 will not alter performanceComparing Summary
|
There was a problem hiding this 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/webpack/src/plugins/rollup-compat-dynamic-import.ts (1)
79-79
: Consider using a more robust import detection method.While unlikely to cause issues, using
includes()
to check for existing imports could match the string in comments or other contexts. Consider using a regex or AST-based approach for more precise detection.- if (needsHelperImport && !transformed.includes('from "./_dynamic-import-helper.mjs"')) { + if (needsHelperImport && !/^import\s+.*from\s+["'].?\/_dynamic-import-helper\.mjs["']/m.test(transformed)) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
packages/webpack/src/nitro/plugins/dynamic-require.ts
(0 hunks)packages/webpack/src/plugins/rollup-compat-dynamic-import.ts
(1 hunks)packages/webpack/src/presets/base.ts
(2 hunks)packages/webpack/src/webpack.ts
(1 hunks)
💤 Files with no reviewable changes (1)
- packages/webpack/src/nitro/plugins/dynamic-require.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/webpack/src/webpack.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/webpack/src/presets/base.ts (1)
packages/webpack/src/plugins/rollup-compat-dynamic-import.ts (1)
RollupCompatDynamicImportPlugin
(11-136)
🪛 GitHub Check: CodeQL
packages/webpack/src/plugins/rollup-compat-dynamic-import.ts
[warning] 120-120: Improper code sanitization
Code construction depends on an improperly sanitized value.
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: build
- GitHub Check: code
🔇 Additional comments (3)
packages/webpack/src/presets/base.ts (1)
17-17
: LGTM! Clean integration of the new plugin.The import and conditional plugin addition are well-placed and the logic correctly targets server-side production builds where Rollup compatibility is needed.
Also applies to: 123-126
packages/webpack/src/plugins/rollup-compat-dynamic-import.ts (2)
1-136
: Well-structured Webpack plugin implementation!The plugin correctly transforms Webpack-style dynamic imports into Rollup-compatible ones. The implementation follows Webpack plugin best practices with proper hooks, error handling, and performance optimisations.
🧰 Tools
🪛 GitHub Check: CodeQL
[warning] 120-120: Improper code sanitization
Code construction depends on an improperly sanitized value.
113-122
: The CodeQL warning is a false positive.The static analysis warning about improper code sanitization can be safely ignored. The
filename
values come from Webpack's internally generated chunk files, not user input, and are already properly escaped usingJSON.stringify()
on both the object key and the import path.🧰 Tools
🪛 GitHub Check: CodeQL
[warning] 120-120: Improper code sanitization
Code construction depends on an improperly sanitized value.
There was a problem hiding this 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
♻️ Duplicate comments (1)
packages/webpack/src/plugins/rollup-compat-dynamic-import.ts (1)
4-5
:⚠️ Potential issueAddress the security vulnerability in regex replacement.
The regex replacement pattern captures content from
__webpack_require__
expressions and directly interpolates it into generated code without validation, creating a code injection vulnerability.The captured group could contain malicious code that gets executed. Consider validating the captured content:
- transformed = transformed.replace(DYNAMIC_IMPORT_REPLACE_RE, (match, filename) => { + transformed = transformed.replace(DYNAMIC_IMPORT_REPLACE_RE, (match, filename) => { + // Validate that filename contains only safe webpack require patterns + if (!/^__webpack_require__\.[a-zA-Z_$][a-zA-Z0-9_$]*\([^)]*\)$/.test(filename.trim())) { + throw new Error(`Invalid webpack require pattern: ${filename}`) + } needsHelperImport = true // Generate a rollup-compatible dynamic import using the module ID return `_rollupDynamicImport(${filename}).then` })
🧹 Nitpick comments (1)
packages/webpack/src/plugins/rollup-compat-dynamic-import.ts (1)
13-66
: Improve file filtering and consider performance optimisation.The method structure is correct, but consider these improvements:
- Extract the file extension check to a helper function
- Add more explicit comments about chunk filtering logic
- Consider early termination for large files that don't need processing
+ private static isJavaScriptFile(filename: string): boolean { + return filename.endsWith('.js') || filename.endsWith('.mjs') || filename.endsWith('.cjs') + } // Transform JavaScript files that contain dynamic imports for (const [filename, asset] of Object.entries(assets)) { - if (!filename.endsWith('.js') && !filename.endsWith('.mjs') && !filename.endsWith('.cjs')) { + if (!RollupCompatDynamicImportPlugin.isJavaScriptFile(filename)) { continue }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/webpack/src/plugins/rollup-compat-dynamic-import.ts
(1 hunks)
🔇 Additional comments (3)
packages/webpack/src/plugins/rollup-compat-dynamic-import.ts (3)
1-2
: LGTM!The imports are appropriate for a Webpack plugin and use proper TypeScript typing.
7-12
: LGTM!Well-documented class that properly implements the WebpackPluginInstance interface.
114-133
: LGTM!Well-implemented helper content generation using safe code generation utilities and proper error handling.
private transformDynamicImports (source: string): string { | ||
let transformed = source | ||
let needsHelperImport = false | ||
|
||
// Transform webpack-style dynamic imports to rollup-compatible ones | ||
transformed = transformed.replace(DYNAMIC_IMPORT_REPLACE_RE, (match, filename) => { | ||
needsHelperImport = true | ||
// Generate a rollup-compatible dynamic import using the module ID | ||
return `_rollupDynamicImport(${filename}).then` | ||
}) | ||
|
||
// Add import statement at the top if we made any transformations | ||
if (needsHelperImport && !transformed.includes('from "./_dynamic-import-helper.mjs"')) { | ||
const importStatement = 'import { _rollupDynamicImport } from "./_dynamic-import-helper.mjs";\n' | ||
transformed = importStatement + transformed | ||
} | ||
|
||
return transformed | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve helper import injection robustness.
The helper import injection logic could be more robust:
- The string search for existing imports is fragile
- Consider using AST-based injection for reliability
- The hardcoded relative path might not work in all webpack configurations
// Add import statement at the top if we made any transformations
- if (needsHelperImport && !transformed.includes('from "./_dynamic-import-helper.mjs"')) {
+ if (needsHelperImport && !/import\s+\{[^}]*_rollupDynamicImport[^}]*\}\s+from\s+["']\.?\/?_dynamic-import-helper\.mjs["']/.test(transformed)) {
const importStatement = 'import { _rollupDynamicImport } from "./_dynamic-import-helper.mjs";\n'
transformed = importStatement + transformed
}
📝 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.
private transformDynamicImports (source: string): string { | |
let transformed = source | |
let needsHelperImport = false | |
// Transform webpack-style dynamic imports to rollup-compatible ones | |
transformed = transformed.replace(DYNAMIC_IMPORT_REPLACE_RE, (match, filename) => { | |
needsHelperImport = true | |
// Generate a rollup-compatible dynamic import using the module ID | |
return `_rollupDynamicImport(${filename}).then` | |
}) | |
// Add import statement at the top if we made any transformations | |
if (needsHelperImport && !transformed.includes('from "./_dynamic-import-helper.mjs"')) { | |
const importStatement = 'import { _rollupDynamicImport } from "./_dynamic-import-helper.mjs";\n' | |
transformed = importStatement + transformed | |
} | |
return transformed | |
} | |
private transformDynamicImports (source: string): string { | |
let transformed = source | |
let needsHelperImport = false | |
// Transform webpack-style dynamic imports to rollup-compatible ones | |
transformed = transformed.replace(DYNAMIC_IMPORT_REPLACE_RE, (match, filename) => { | |
needsHelperImport = true | |
// Generate a rollup-compatible dynamic import using the module ID | |
return `_rollupDynamicImport(${filename}).then` | |
}) | |
// Add import statement at the top if we made any transformations | |
if (needsHelperImport && !/import\s+\{[^}]*_rollupDynamicImport[^}]*\}\s+from\s+["']\.?\/?_dynamic-import-helper\.mjs["']/.test(transformed)) { | |
const importStatement = 'import { _rollupDynamicImport } from "./_dynamic-import-helper.mjs";\n' | |
transformed = importStatement + transformed | |
} | |
return transformed | |
} |
🤖 Prompt for AI Agents
In packages/webpack/src/plugins/rollup-compat-dynamic-import.ts around lines 68
to 86, the current method of injecting the helper import uses a fragile string
search and a hardcoded relative path. To fix this, replace the string-based
check and insertion with an AST-based approach to reliably detect and inject the
import statement. Also, avoid hardcoding the import path by resolving it
dynamically based on the webpack configuration or plugin context to ensure
compatibility across different setups.
There was a problem hiding this 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
♻️ Duplicate comments (1)
packages/webpack/src/plugins/rollup-compat-dynamic-import.ts (1)
98-99
:⚠️ Potential issueAdd null check for chunk.files to prevent runtime errors.
The code iterates over
chunk.files
without checking if it exists, unlike the safer approach used on line 28.Apply this diff to add the null check:
- for (const filename of chunk.files) { + for (const filename of chunk.files || []) {
🧹 Nitpick comments (1)
packages/webpack/src/plugins/rollup-compat-dynamic-import.ts (1)
5-6
: Strengthen regex patterns for better dynamic import detection.The current regex patterns may miss edge cases or incorrectly match unintended code patterns:
DYNAMIC_IMPORT_RE
assumes the presence of a+
operator and.then
suffix, which might not cover all webpack dynamic import variations- The capture group
(__webpack_require__[^+]*)
could potentially capture more than intended in complex expressionsConsider making the patterns more robust to handle various webpack dynamic import formats.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/webpack/src/plugins/rollup-compat-dynamic-import.ts
(1 hunks)
🔇 Additional comments (2)
packages/webpack/src/plugins/rollup-compat-dynamic-import.ts (2)
119-119
:✅ Verification successful
Address potential code injection vulnerability in dynamic code generation.
The dynamic generation of the chunk map uses filenames that could potentially contain malicious content, creating a code injection risk. While
genString
provides some protection, this represents a security concern as flagged by the static analysis.Consider validating and sanitising chunk filenames before code generation:
+ private sanitiseFilename(filename: string): string { + // Allow only safe characters for filenames + return filename.replace(/[^a-zA-Z0-9.\-_/]/g, '') + } private generateHelperContent (chunkFiles: string[]): string { + const sanitisedFiles = chunkFiles.map(f => this.sanitiseFilename(f)) return ` // Rollup-compatible dynamic import helper generated by webpack // This helper enables rollup to consume webpack chunks directly -const chunkMap = ${genObjectFromRawEntries(chunkFiles.map(filename => [filename, `() => import(${genString('./' + filename)})`]))} +const chunkMap = ${genObjectFromRawEntries(sanitisedFiles.map(filename => [filename, `() => import(${genString('./' + filename)})`]))}Let me verify the security implications of this approach:
🏁 Script executed:
#!/bin/bash # Search for other instances of dynamic code generation that might have similar security concerns rg -A 3 -B 3 "genObjectFromRawEntries|genString" --type tsLength of output: 10981
Safe dynamic import generation: no sanitisation needed
Webpack’s
chunkFiles
array is derived from internal asset filenames (typically deterministic hashes and extensions) rather than user-supplied input, andgenString
escapes all special characters when emitting a JS string literal. This guards against any code-injection risk in the generated helper. No additional filename sanitisation is required here.
125-125
: 🛠️ Refactor suggestionSanitise chunkId in error messages to prevent information disclosure.
The error message includes the raw
chunkId
which could potentially expose sensitive information or be used for reconnaissance attacks.Apply this diff to sanitise the error message:
- return Promise.reject(new Error(\`Chunk \${chunkId} not found in chunkMap. Available chunks: \${Object.keys(chunkMap).join(', ')}\`)) + return Promise.reject(new Error(\`Chunk not found in chunkMap. Available chunks: \${Object.keys(chunkMap).length}\`))Likely an incorrect or invalid review comment.
// Transform dynamic imports in this file | ||
const transformedCode = this.transformDynamicImports(originalCode) | ||
|
||
if (transformedCode !== originalCode) { | ||
assets[filename] = new compiler.webpack.sources.RawSource(transformedCode) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add validation for transformed code integrity.
The plugin transforms code using regex replacement but doesn't validate that the transformation produces syntactically correct JavaScript. Invalid transformations could lead to runtime errors.
Consider adding basic validation:
// Transform dynamic imports in this file
const transformedCode = this.transformDynamicImports(originalCode)
if (transformedCode !== originalCode) {
+ // Basic validation to ensure transformation didn't break syntax
+ try {
+ new Function(transformedCode)
+ } catch (error) {
+ throw new Error(`Code transformation resulted in invalid JavaScript for ${filename}: ${error.message}`)
+ }
assets[filename] = new compiler.webpack.sources.RawSource(transformedCode)
}
📝 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.
// Transform dynamic imports in this file | |
const transformedCode = this.transformDynamicImports(originalCode) | |
if (transformedCode !== originalCode) { | |
assets[filename] = new compiler.webpack.sources.RawSource(transformedCode) | |
} | |
} | |
// Transform dynamic imports in this file | |
const transformedCode = this.transformDynamicImports(originalCode) | |
if (transformedCode !== originalCode) { | |
// Basic validation to ensure transformation didn't break syntax | |
try { | |
new Function(transformedCode) | |
} catch (error) { | |
throw new Error( | |
`Code transformation resulted in invalid JavaScript for ${filename}: ${error.message}` | |
) | |
} | |
assets[filename] = new compiler.webpack.sources.RawSource(transformedCode) | |
} |
🤖 Prompt for AI Agents
In packages/webpack/src/plugins/rollup-compat-dynamic-import.ts around lines 52
to 58, after transforming the dynamic imports, add a validation step to check
that the transformedCode is syntactically valid JavaScript. You can do this by
attempting to parse the transformedCode using a JavaScript parser or a try-catch
block with eval or a similar safe method. If the validation fails, log an error
or throw an exception to prevent invalid code from being emitted. This ensures
that only syntactically correct transformations are applied to the assets.
if (chunkFiles.length === 0) { | ||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Consider the implications of skipping helper generation when no chunks are found.
If dynamic imports exist but no valid chunks are found, the helper won't be generated, potentially causing runtime errors when the transformed code tries to use _rollupDynamicImport
.
Verify this scenario and consider whether to generate an empty helper or provide better error handling:
🏁 Script executed:
#!/bin/bash
# Check if there are scenarios where dynamic imports might exist without corresponding chunks
rg -A 5 -B 5 "hasRuntime\(\)|canBeInitial\(\)" --type ts
Length of output: 2163
🏁 Script executed:
#!/bin/bash
rg -n "generateDynamicImportHelper" --type ts
Length of output: 296
Ensure proper handling when no chunks are found but dynamic imports were transformed
The current check in generateDynamicImportHelper
if (chunkFiles.length === 0) {
return
}
will silently skip helper injection even if you’ve rewritten calls to _rollupDynamicImport
, leading to uncaught runtime errors.
You should:
- Track whether any dynamic-import calls were rewritten (e.g. set a
hasDynamicImport
flag during your AST-transform). - Change the empty-array guard to only bail out when no dynamic imports exist.
- When
hasDynamicImport
is true butchunkFiles.length === 0
, either:- Emit a build-time error or warning pointing out that no chunks were generated for dynamic imports, or
- Generate a stub helper that throws a clear, descriptive error at runtime.
For example, in packages/webpack/src/plugins/rollup-compat-dynamic-import.ts around line 105:
if (chunkFiles.length === 0) {
- return
+ // If no dynamic imports were seen, it’s safe to skip helper.
+ if (!this.hasDynamicImport) return
+
+ // Otherwise we need to fail fast or stub out the helper.
+ compilation.warnings.push(
+ new Error('[rollup-compat] Dynamic import transformed but no chunks found.')
+ )
+ this.emitHelper(`
+ function _rollupDynamicImport() {
+ throw new Error('No dynamic import chunks available – check your configuration.')
+ }
+ `)
+ return
}
This ensures you neither generate a useless helper when it isn’t needed nor leave transformed import calls dangling when it is.
🤖 Prompt for AI Agents
In packages/webpack/src/plugins/rollup-compat-dynamic-import.ts around lines 105
to 107, the current code returns early without generating the helper if
chunkFiles is empty, even if dynamic imports were rewritten, causing runtime
errors. Modify the logic to track whether any dynamic imports were rewritten
with a flag like hasDynamicImport. Only skip helper generation if no dynamic
imports exist. If hasDynamicImport is true but chunkFiles is empty, either emit
a build-time error or generate a stub helper that throws a clear runtime error
to prevent dangling transformed calls.
🔗 Linked issue
📚 Description
webpack generates a chunk loading mechanism which doesn't place nicely with nitro.
in nitro v2 there's a built in dynamic-require plugin in the rollup build which handles this, but it has periodic problems when webpack changes its output format, and it also relies on a webpack manifest.
this changes switches to a webpack-native plugin to implement this directly in the webpack build, which allows us to handle changes/bugfixes in nuxt directly