Skip to content

Commit 243a5b5

Browse files
committed
feat(preset): add react dedupe and optimizeDeps hints
1 parent d3edcdf commit 243a5b5

File tree

3 files changed

+93
-7
lines changed

3 files changed

+93
-7
lines changed

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,11 +258,14 @@ Creates an action ref from a raw QRL string.
258258

259259
Returns Vite plugins that scope the React JSX transform to a directory.
260260

261-
| Option | Type | Default | Description |
262-
| --------- | -------------------- | ------------------------------ | ----------------------------------------- |
263-
| `include` | `FilterPattern` | `[/src\/react\/.*\.[jt]sx?$/]` | Files to transform with React JSX |
264-
| `exclude` | `FilterPattern` || Files to exclude |
265-
| `react` | `ReactPluginOptions` || Additional `@vitejs/plugin-react` options |
261+
| Option | Type | Default | Description |
262+
| -------------------------- | -------------------- | ------------------------------ | ----------------------------------------- |
263+
| `include` | `FilterPattern` | `[/src\/react\/.*\.[jt]sx?$/]` | Files to transform with React JSX |
264+
| `exclude` | `FilterPattern` || Files to exclude |
265+
| `react` | `ReactPluginOptions` || Additional `@vitejs/plugin-react` options |
266+
| `optimizeReactDeps` | `boolean` | `true` | Add React `dedupe` + `optimizeDeps` hints |
267+
| `reactDedupe` | `string[]` | `['react', 'react-dom']` | Override dedupe package list |
268+
| `reactOptimizeDepsInclude` | `string[]` | React runtime modules | Override optimizeDeps include list |
266269

267270
## Host Attributes
268271

src/preset.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import react, { type Options as ReactPluginOptions } from '@vitejs/plugin-react'
22
import type { PluginOption } from 'vite'
33

4+
const DEFAULT_REACT_DEDUPE = ['react', 'react-dom']
5+
const DEFAULT_REACT_OPTIMIZE_DEPS_INCLUDE = [
6+
'react',
7+
'react-dom',
8+
'react-dom/client',
9+
'react-dom/server',
10+
'react/jsx-runtime',
11+
'react/jsx-dev-runtime',
12+
]
13+
414
export interface FictReactPresetOptions {
515
/**
616
* React transform include filter. Keep it narrow so Fict TSX can use Fict compiler/runtime.
@@ -16,6 +26,20 @@ export interface FictReactPresetOptions {
1626
* `include` and `exclude` are controlled by this preset.
1727
*/
1828
react?: Omit<ReactPluginOptions, 'include' | 'exclude'>
29+
/**
30+
* Inject Vite React dependency hints (`resolve.dedupe` + `optimizeDeps.include`).
31+
* Helps avoid duplicate React instances and missing pre-bundles in mixed projects.
32+
* @default true
33+
*/
34+
optimizeReactDeps?: boolean
35+
/**
36+
* React packages to dedupe when `optimizeReactDeps` is enabled.
37+
*/
38+
reactDedupe?: string[]
39+
/**
40+
* Dependencies to include in Vite pre-bundling when `optimizeReactDeps` is enabled.
41+
*/
42+
reactOptimizeDepsInclude?: string[]
1943
}
2044

2145
/**
@@ -34,5 +58,23 @@ export function fictReactPreset(options: FictReactPresetOptions = {}): PluginOpt
3458
reactOptions.exclude = options.exclude
3559
}
3660

37-
return [react(reactOptions)]
61+
const plugins: PluginOption[] = [react(reactOptions)]
62+
63+
if (options.optimizeReactDeps !== false) {
64+
plugins.push({
65+
name: 'fict-react-deps',
66+
config() {
67+
return {
68+
resolve: {
69+
dedupe: options.reactDedupe ?? DEFAULT_REACT_DEDUPE,
70+
},
71+
optimizeDeps: {
72+
include: options.reactOptimizeDepsInclude ?? DEFAULT_REACT_OPTIMIZE_DEPS_INCLUDE,
73+
},
74+
}
75+
},
76+
})
77+
}
78+
79+
return plugins
3880
}

test/preset.test.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,30 @@ describe('fictReactPreset', () => {
1717

1818
const plugins = fictReactPreset()
1919

20-
expect(plugins).toHaveLength(1)
20+
expect(plugins).toHaveLength(2)
2121
expect(reactPluginMock).toHaveBeenCalledTimes(1)
2222
expect(reactPluginMock).toHaveBeenCalledWith(
2323
expect.objectContaining({
2424
include: [/src\/react\/.*\.[jt]sx?$/],
2525
}),
2626
)
27+
28+
const depsPlugin = plugins[1] as { config: () => Record<string, unknown> }
29+
expect(depsPlugin.config()).toEqual({
30+
resolve: {
31+
dedupe: ['react', 'react-dom'],
32+
},
33+
optimizeDeps: {
34+
include: [
35+
'react',
36+
'react-dom',
37+
'react-dom/client',
38+
'react-dom/server',
39+
'react/jsx-runtime',
40+
'react/jsx-dev-runtime',
41+
],
42+
},
43+
})
2744
})
2845

2946
it('passes through include/exclude/extra options', async () => {
@@ -45,4 +62,28 @@ describe('fictReactPreset', () => {
4562
}),
4663
)
4764
})
65+
66+
it('allows disabling and overriding dependency optimization hints', async () => {
67+
const { fictReactPreset } = await import('../src/preset')
68+
69+
const disabled = fictReactPreset({
70+
optimizeReactDeps: false,
71+
})
72+
expect(disabled).toHaveLength(1)
73+
74+
const customized = fictReactPreset({
75+
reactDedupe: ['react'],
76+
reactOptimizeDepsInclude: ['react', 'react/jsx-runtime'],
77+
})
78+
expect(customized).toHaveLength(2)
79+
const depsPlugin = customized[1] as { config: () => Record<string, unknown> }
80+
expect(depsPlugin.config()).toEqual({
81+
resolve: {
82+
dedupe: ['react'],
83+
},
84+
optimizeDeps: {
85+
include: ['react', 'react/jsx-runtime'],
86+
},
87+
})
88+
})
4889
})

0 commit comments

Comments
 (0)