Skip to content

Commit 6357f56

Browse files
committed
feat: add filter
1 parent 7517103 commit 6357f56

File tree

5 files changed

+162
-126
lines changed

5 files changed

+162
-126
lines changed

packages/common/filter-utils.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function exactRegex(input: string): RegExp {
2+
return new RegExp(`^${escapeRegex(input)}$`)
3+
}
4+
5+
const escapeRegexRE = /[-/\\^$*+?.()|[\]{}]/g
6+
function escapeRegex(str: string): string {
7+
return str.replace(escapeRegexRE, '\\$&')
8+
}

packages/common/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
export * from './filter-utils'
12
export * from './refresh-utils'
23
export * from './warning'

packages/plugin-react-oxc/src/index.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { BuildOptions, Plugin, PluginOption } from 'vite'
55
import {
66
addRefreshWrapper,
77
avoidSourceMapOption,
8+
exactRegex,
89
getPreambleCode,
910
runtimePublicPath,
1011
silenceUseClientWarning,
@@ -149,12 +150,3 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
149150

150151
return [viteConfig, viteRefreshRuntime, viteRefreshWrapper]
151152
}
152-
153-
function exactRegex(input: string): RegExp {
154-
return new RegExp(`^${escapeRegex(input)}$`)
155-
}
156-
157-
const escapeRegexRE = /[-/\\^$*+?.()|[\]{}]/g
158-
function escapeRegex(str: string): string {
159-
return str.replace(escapeRegexRE, '\\$&')
160-
}

packages/plugin-react-swc/src/index.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import type { PluginOption } from 'vite'
1515
import {
1616
addRefreshWrapper,
17+
exactRegex,
1718
getPreambleCode,
1819
runtimePublicPath,
1920
silenceUseClientWarning,
@@ -96,14 +97,23 @@ const react = (_options?: Options): PluginOption[] => {
9697
name: 'vite:react-swc:resolve-runtime',
9798
apply: 'serve',
9899
enforce: 'pre', // Run before Vite default resolve to avoid syscalls
99-
resolveId: (id) => (id === runtimePublicPath ? id : undefined),
100-
load: (id) =>
101-
id === runtimePublicPath
102-
? readFileSync(join(_dirname, 'refresh-runtime.js'), 'utf-8').replace(
103-
/__README_URL__/g,
104-
'https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react-swc',
105-
)
106-
: undefined,
100+
resolveId: {
101+
filter: { id: exactRegex(runtimePublicPath) },
102+
handler: (id) => (id === runtimePublicPath ? id : undefined),
103+
},
104+
load: {
105+
filter: { id: exactRegex(runtimePublicPath) },
106+
handler: (id) =>
107+
id === runtimePublicPath
108+
? readFileSync(
109+
join(_dirname, 'refresh-runtime.js'),
110+
'utf-8',
111+
).replace(
112+
/__README_URL__/g,
113+
'https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react-swc',
114+
)
115+
: undefined,
116+
},
107117
},
108118
{
109119
name: 'vite:react-swc',

packages/plugin-react/src/index.ts

Lines changed: 134 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as vite from 'vite'
88
import type { Plugin, PluginOption, ResolvedConfig } from 'vite'
99
import {
1010
addRefreshWrapper,
11+
exactRegex,
1112
getPreambleCode,
1213
preambleCode,
1314
runtimePublicPath,
@@ -181,114 +182,120 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
181182
// we only create static option in this case and re-create them
182183
// each time otherwise
183184
staticBabelOptions = createBabelOptions(opts.babel)
185+
186+
if (
187+
canSkipBabel(staticBabelOptions.plugins, staticBabelOptions) &&
188+
skipFastRefresh &&
189+
isProduction
190+
) {
191+
delete viteBabel.transform
192+
}
184193
}
185194
},
186-
async transform(code, id, options) {
187-
if (id.includes('/node_modules/')) return
188-
189-
const [filepath] = id.split('?')
190-
if (!filter(filepath)) return
191-
192-
const ssr = options?.ssr === true
193-
const babelOptions = (() => {
194-
if (staticBabelOptions) return staticBabelOptions
195-
const newBabelOptions = createBabelOptions(
196-
typeof opts.babel === 'function'
197-
? opts.babel(id, { ssr })
198-
: opts.babel,
199-
)
200-
runPluginOverrides?.(newBabelOptions, { id, ssr })
201-
return newBabelOptions
202-
})()
203-
const plugins = [...babelOptions.plugins]
204-
205-
const isJSX = filepath.endsWith('x')
206-
const useFastRefresh =
207-
!skipFastRefresh &&
208-
!ssr &&
209-
(isJSX ||
210-
(opts.jsxRuntime === 'classic'
211-
? importReactRE.test(code)
212-
: code.includes(jsxImportDevRuntime) ||
213-
code.includes(jsxImportRuntime)))
214-
if (useFastRefresh) {
215-
plugins.push([
216-
await loadPlugin('react-refresh/babel'),
217-
{ skipEnvCheck: true },
218-
])
219-
}
220-
221-
if (opts.jsxRuntime === 'classic' && isJSX) {
222-
if (!isProduction) {
223-
// These development plugins are only needed for the classic runtime.
224-
plugins.push(
225-
await loadPlugin('@babel/plugin-transform-react-jsx-self'),
226-
await loadPlugin('@babel/plugin-transform-react-jsx-source'),
195+
transform: {
196+
filter: { id: { exclude: /\/node_modules\// } },
197+
async handler(code, id, options) {
198+
if (id.includes('/node_modules/')) return
199+
200+
const [filepath] = id.split('?')
201+
if (!filter(filepath)) return
202+
203+
const ssr = options?.ssr === true
204+
const babelOptions = (() => {
205+
if (staticBabelOptions) return staticBabelOptions
206+
const newBabelOptions = createBabelOptions(
207+
typeof opts.babel === 'function'
208+
? opts.babel(id, { ssr })
209+
: opts.babel,
227210
)
211+
runPluginOverrides?.(newBabelOptions, { id, ssr })
212+
return newBabelOptions
213+
})()
214+
const plugins = [...babelOptions.plugins]
215+
216+
const isJSX = filepath.endsWith('x')
217+
const useFastRefresh =
218+
!skipFastRefresh &&
219+
!ssr &&
220+
(isJSX ||
221+
(opts.jsxRuntime === 'classic'
222+
? importReactRE.test(code)
223+
: code.includes(jsxImportDevRuntime) ||
224+
code.includes(jsxImportRuntime)))
225+
if (useFastRefresh) {
226+
plugins.push([
227+
await loadPlugin('react-refresh/babel'),
228+
{ skipEnvCheck: true },
229+
])
228230
}
229-
}
230231

231-
// Avoid parsing if no special transformation is needed
232-
if (
233-
!plugins.length &&
234-
!babelOptions.presets.length &&
235-
!babelOptions.configFile &&
236-
!babelOptions.babelrc
237-
) {
238-
return
239-
}
232+
if (opts.jsxRuntime === 'classic' && isJSX) {
233+
if (!isProduction) {
234+
// These development plugins are only needed for the classic runtime.
235+
plugins.push(
236+
await loadPlugin('@babel/plugin-transform-react-jsx-self'),
237+
await loadPlugin('@babel/plugin-transform-react-jsx-source'),
238+
)
239+
}
240+
}
240241

241-
const parserPlugins = [...babelOptions.parserOpts.plugins]
242+
// Avoid parsing if no special transformation is needed
243+
if (canSkipBabel(plugins, babelOptions)) {
244+
return
245+
}
242246

243-
if (!filepath.endsWith('.ts')) {
244-
parserPlugins.push('jsx')
245-
}
247+
const parserPlugins = [...babelOptions.parserOpts.plugins]
246248

247-
if (tsRE.test(filepath)) {
248-
parserPlugins.push('typescript')
249-
}
249+
if (!filepath.endsWith('.ts')) {
250+
parserPlugins.push('jsx')
251+
}
250252

251-
const babel = await loadBabel()
252-
const result = await babel.transformAsync(code, {
253-
...babelOptions,
254-
root: projectRoot,
255-
filename: id,
256-
sourceFileName: filepath,
257-
// Required for esbuild.jsxDev to provide correct line numbers
258-
// This creates issues the react compiler because the re-order is too important
259-
// People should use @babel/plugin-transform-react-jsx-development to get back good line numbers
260-
retainLines:
261-
getReactCompilerPlugin(plugins) != null
262-
? false
263-
: !isProduction && isJSX && opts.jsxRuntime !== 'classic',
264-
parserOpts: {
265-
...babelOptions.parserOpts,
266-
sourceType: 'module',
267-
allowAwaitOutsideFunction: true,
268-
plugins: parserPlugins,
269-
},
270-
generatorOpts: {
271-
...babelOptions.generatorOpts,
272-
// import attributes parsing available without plugin since 7.26
273-
importAttributesKeyword: 'with',
274-
decoratorsBeforeExport: true,
275-
},
276-
plugins,
277-
sourceMaps: true,
278-
})
279-
280-
if (result) {
281-
if (!useFastRefresh) {
282-
return { code: result.code!, map: result.map }
253+
if (tsRE.test(filepath)) {
254+
parserPlugins.push('typescript')
283255
}
284-
return addRefreshWrapper(
285-
result.code!,
286-
result.map!,
287-
'@vitejs/plugin-react',
288-
id,
289-
opts.reactRefreshHost,
290-
)
291-
}
256+
257+
const babel = await loadBabel()
258+
const result = await babel.transformAsync(code, {
259+
...babelOptions,
260+
root: projectRoot,
261+
filename: id,
262+
sourceFileName: filepath,
263+
// Required for esbuild.jsxDev to provide correct line numbers
264+
// This creates issues the react compiler because the re-order is too important
265+
// People should use @babel/plugin-transform-react-jsx-development to get back good line numbers
266+
retainLines:
267+
getReactCompilerPlugin(plugins) != null
268+
? false
269+
: !isProduction && isJSX && opts.jsxRuntime !== 'classic',
270+
parserOpts: {
271+
...babelOptions.parserOpts,
272+
sourceType: 'module',
273+
allowAwaitOutsideFunction: true,
274+
plugins: parserPlugins,
275+
},
276+
generatorOpts: {
277+
...babelOptions.generatorOpts,
278+
// import attributes parsing available without plugin since 7.26
279+
importAttributesKeyword: 'with',
280+
decoratorsBeforeExport: true,
281+
},
282+
plugins,
283+
sourceMaps: true,
284+
})
285+
286+
if (result) {
287+
if (!useFastRefresh) {
288+
return { code: result.code!, map: result.map }
289+
}
290+
return addRefreshWrapper(
291+
result.code!,
292+
result.map!,
293+
'@vitejs/plugin-react',
294+
id,
295+
opts.reactRefreshHost,
296+
)
297+
}
298+
},
292299
},
293300
}
294301

@@ -319,18 +326,24 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
319326
dedupe: ['react', 'react-dom'],
320327
},
321328
}),
322-
resolveId(id) {
323-
if (id === runtimePublicPath) {
324-
return id
325-
}
329+
resolveId: {
330+
filter: { id: exactRegex(runtimePublicPath) },
331+
handler(id) {
332+
if (id === runtimePublicPath) {
333+
return id
334+
}
335+
},
326336
},
327-
load(id) {
328-
if (id === runtimePublicPath) {
329-
return readFileSync(refreshRuntimePath, 'utf-8').replace(
330-
/__README_URL__/g,
331-
'https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react',
332-
)
333-
}
337+
load: {
338+
filter: { id: exactRegex(runtimePublicPath) },
339+
handler(id) {
340+
if (id === runtimePublicPath) {
341+
return readFileSync(refreshRuntimePath, 'utf-8').replace(
342+
/__README_URL__/g,
343+
'https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react',
344+
)
345+
}
346+
},
334347
},
335348
transformIndexHtml(_, config) {
336349
if (!skipFastRefresh)
@@ -349,6 +362,18 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
349362

350363
viteReact.preambleCode = preambleCode
351364

365+
function canSkipBabel(
366+
plugins: ReactBabelOptions['plugins'],
367+
babelOptions: ReactBabelOptions,
368+
) {
369+
return !(
370+
plugins.length ||
371+
babelOptions.presets.length ||
372+
babelOptions.configFile ||
373+
babelOptions.babelrc
374+
)
375+
}
376+
352377
const loadedPlugin = new Map<string, any>()
353378
function loadPlugin(path: string): any {
354379
const cached = loadedPlugin.get(path)

0 commit comments

Comments
 (0)