Skip to content

Commit c1218dd

Browse files
authored
fix(nextjs): avoid vendored React in eval (#251)
1 parent d3477a6 commit c1218dd

File tree

3 files changed

+65
-0
lines changed

3 files changed

+65
-0
lines changed

.changeset/quiet-hounds-drive.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@wyw-in-js/nextjs': patch
3+
---
4+
5+
Fix Next.js eval crashes by defaulting `importOverrides` for `react` (and JSX runtimes) so build-time evaluation resolves React via Node instead of Next's vendored RSC runtime.

packages/nextjs/src/__tests__/withWyw.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,14 @@ describe('withWyw', () => {
1717

1818
if (Array.isArray(tsRule)) {
1919
expect(tsRule[0].loader).toContain('turbopack-loader');
20+
expect(tsRule[0].options.importOverrides).toMatchObject({
21+
react: { mock: 'react' },
22+
});
2023
} else {
2124
expect(tsRule.loaders[0].loader).toContain('turbopack-loader');
25+
expect(tsRule.loaders[0].options.importOverrides).toMatchObject({
26+
react: { mock: 'react' },
27+
});
2228
}
2329
});
2430

@@ -76,6 +82,41 @@ describe('withWyw', () => {
7682

7783
expect(use).toHaveLength(2);
7884
expect(use[1].loader).toContain('webpack-loader');
85+
expect(use[1].options.importOverrides).toMatchObject({
86+
react: { mock: 'react' },
87+
});
88+
});
89+
90+
it('merges default React importOverrides with user overrides', () => {
91+
const config: Configuration = {
92+
module: {
93+
rules: [
94+
{
95+
use: [{ loader: 'next-swc-loader' }],
96+
},
97+
],
98+
},
99+
};
100+
101+
const nextConfig = withWyw(
102+
{},
103+
{
104+
loaderOptions: {
105+
importOverrides: {
106+
react: { mock: 'preact/compat' },
107+
},
108+
},
109+
}
110+
);
111+
112+
const result = nextConfig.webpack!(config, { dev: true } as any);
113+
const use = (result.module!.rules![0] as RuleSetRule).use as any[];
114+
115+
expect(use[1].options.importOverrides).toMatchObject({
116+
react: { mock: 'preact/compat' },
117+
'react/jsx-runtime': { mock: 'react/jsx-runtime' },
118+
'react/jsx-dev-runtime': { mock: 'react/jsx-dev-runtime' },
119+
});
79120
});
80121

81122
it('converts loader+options rules to use[] when injecting', () => {

packages/nextjs/src/index.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ const DEFAULT_EXTENSION = '.wyw-in-js.module.css';
99

1010
const DEFAULT_TURBO_RULE_KEYS = ['*.js', '*.jsx', '*.ts', '*.tsx'];
1111

12+
const DEFAULT_REACT_IMPORT_OVERRIDES = {
13+
react: { mock: 'react' },
14+
'react/jsx-runtime': { mock: 'react/jsx-runtime' },
15+
'react/jsx-dev-runtime': { mock: 'react/jsx-dev-runtime' },
16+
} satisfies WywWebpackLoaderOptions['importOverrides'];
17+
1218
const PLACEHOLDER_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx']);
1319
const PLACEHOLDER_IGNORED_DIRS = new Set([
1420
'.git',
@@ -275,11 +281,17 @@ function injectWywLoader(
275281
presets: [nextBabelPreset],
276282
};
277283

284+
const userImportOverrides = wywNext.loaderOptions?.importOverrides;
285+
const importOverrides = userImportOverrides
286+
? { ...DEFAULT_REACT_IMPORT_OVERRIDES, ...userImportOverrides }
287+
: DEFAULT_REACT_IMPORT_OVERRIDES;
288+
278289
const loaderOptions = {
279290
cssImport: 'import',
280291
...wywNext.loaderOptions,
281292
babelOptions,
282293
extension,
294+
importOverrides,
283295
sourceMap: wywNext.loaderOptions?.sourceMap ?? nextOptions.dev,
284296
} satisfies WywWebpackLoaderOptions;
285297

@@ -397,10 +409,17 @@ function injectWywTurbopackRules(
397409

398410
assertNoFunctions(userOptions, 'turbopackLoaderOptions');
399411

412+
const userImportOverrides = isPlainObject(userOptions.importOverrides)
413+
? (userOptions.importOverrides as Record<string, unknown>)
414+
: undefined;
415+
400416
const loaderOptions = {
401417
babelOptions: { presets: [nextBabelPreset] },
402418
sourceMap: process.env.NODE_ENV !== 'production',
403419
...userOptions,
420+
importOverrides: userImportOverrides
421+
? { ...DEFAULT_REACT_IMPORT_OVERRIDES, ...userImportOverrides }
422+
: DEFAULT_REACT_IMPORT_OVERRIDES,
404423
};
405424

406425
const useTurbopackConfig = shouldUseTurbopackConfig(nextConfig);

0 commit comments

Comments
 (0)