From 6357f569e1781cae6129c88a94e936fa3314c4a2 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Fri, 25 Apr 2025 22:50:16 +0900 Subject: [PATCH 1/5] feat: add filter --- packages/common/filter-utils.ts | 8 + packages/common/index.ts | 1 + packages/plugin-react-oxc/src/index.ts | 10 +- packages/plugin-react-swc/src/index.ts | 26 ++- packages/plugin-react/src/index.ts | 243 ++++++++++++++----------- 5 files changed, 162 insertions(+), 126 deletions(-) create mode 100644 packages/common/filter-utils.ts diff --git a/packages/common/filter-utils.ts b/packages/common/filter-utils.ts new file mode 100644 index 000000000..f2663b9c1 --- /dev/null +++ b/packages/common/filter-utils.ts @@ -0,0 +1,8 @@ +export function exactRegex(input: string): RegExp { + return new RegExp(`^${escapeRegex(input)}$`) +} + +const escapeRegexRE = /[-/\\^$*+?.()|[\]{}]/g +function escapeRegex(str: string): string { + return str.replace(escapeRegexRE, '\\$&') +} diff --git a/packages/common/index.ts b/packages/common/index.ts index e194bed42..7fd06f9e6 100644 --- a/packages/common/index.ts +++ b/packages/common/index.ts @@ -1,2 +1,3 @@ +export * from './filter-utils' export * from './refresh-utils' export * from './warning' diff --git a/packages/plugin-react-oxc/src/index.ts b/packages/plugin-react-oxc/src/index.ts index 377d4c042..4a353bcd0 100644 --- a/packages/plugin-react-oxc/src/index.ts +++ b/packages/plugin-react-oxc/src/index.ts @@ -5,6 +5,7 @@ import type { BuildOptions, Plugin, PluginOption } from 'vite' import { addRefreshWrapper, avoidSourceMapOption, + exactRegex, getPreambleCode, runtimePublicPath, silenceUseClientWarning, @@ -149,12 +150,3 @@ export default function viteReact(opts: Options = {}): PluginOption[] { return [viteConfig, viteRefreshRuntime, viteRefreshWrapper] } - -function exactRegex(input: string): RegExp { - return new RegExp(`^${escapeRegex(input)}$`) -} - -const escapeRegexRE = /[-/\\^$*+?.()|[\]{}]/g -function escapeRegex(str: string): string { - return str.replace(escapeRegexRE, '\\$&') -} diff --git a/packages/plugin-react-swc/src/index.ts b/packages/plugin-react-swc/src/index.ts index 8e197588e..0d834c4d7 100644 --- a/packages/plugin-react-swc/src/index.ts +++ b/packages/plugin-react-swc/src/index.ts @@ -14,6 +14,7 @@ import { import type { PluginOption } from 'vite' import { addRefreshWrapper, + exactRegex, getPreambleCode, runtimePublicPath, silenceUseClientWarning, @@ -96,14 +97,23 @@ const react = (_options?: Options): PluginOption[] => { name: 'vite:react-swc:resolve-runtime', apply: 'serve', enforce: 'pre', // Run before Vite default resolve to avoid syscalls - resolveId: (id) => (id === runtimePublicPath ? id : undefined), - load: (id) => - id === runtimePublicPath - ? readFileSync(join(_dirname, 'refresh-runtime.js'), 'utf-8').replace( - /__README_URL__/g, - 'https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react-swc', - ) - : undefined, + resolveId: { + filter: { id: exactRegex(runtimePublicPath) }, + handler: (id) => (id === runtimePublicPath ? id : undefined), + }, + load: { + filter: { id: exactRegex(runtimePublicPath) }, + handler: (id) => + id === runtimePublicPath + ? readFileSync( + join(_dirname, 'refresh-runtime.js'), + 'utf-8', + ).replace( + /__README_URL__/g, + 'https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react-swc', + ) + : undefined, + }, }, { name: 'vite:react-swc', diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index 66d280f93..bae38e51d 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -8,6 +8,7 @@ import * as vite from 'vite' import type { Plugin, PluginOption, ResolvedConfig } from 'vite' import { addRefreshWrapper, + exactRegex, getPreambleCode, preambleCode, runtimePublicPath, @@ -181,114 +182,120 @@ export default function viteReact(opts: Options = {}): PluginOption[] { // we only create static option in this case and re-create them // each time otherwise staticBabelOptions = createBabelOptions(opts.babel) + + if ( + canSkipBabel(staticBabelOptions.plugins, staticBabelOptions) && + skipFastRefresh && + isProduction + ) { + delete viteBabel.transform + } } }, - async transform(code, id, options) { - if (id.includes('/node_modules/')) return - - const [filepath] = id.split('?') - if (!filter(filepath)) return - - const ssr = options?.ssr === true - const babelOptions = (() => { - if (staticBabelOptions) return staticBabelOptions - const newBabelOptions = createBabelOptions( - typeof opts.babel === 'function' - ? opts.babel(id, { ssr }) - : opts.babel, - ) - runPluginOverrides?.(newBabelOptions, { id, ssr }) - return newBabelOptions - })() - const plugins = [...babelOptions.plugins] - - const isJSX = filepath.endsWith('x') - const useFastRefresh = - !skipFastRefresh && - !ssr && - (isJSX || - (opts.jsxRuntime === 'classic' - ? importReactRE.test(code) - : code.includes(jsxImportDevRuntime) || - code.includes(jsxImportRuntime))) - if (useFastRefresh) { - plugins.push([ - await loadPlugin('react-refresh/babel'), - { skipEnvCheck: true }, - ]) - } - - if (opts.jsxRuntime === 'classic' && isJSX) { - if (!isProduction) { - // These development plugins are only needed for the classic runtime. - plugins.push( - await loadPlugin('@babel/plugin-transform-react-jsx-self'), - await loadPlugin('@babel/plugin-transform-react-jsx-source'), + transform: { + filter: { id: { exclude: /\/node_modules\// } }, + async handler(code, id, options) { + if (id.includes('/node_modules/')) return + + const [filepath] = id.split('?') + if (!filter(filepath)) return + + const ssr = options?.ssr === true + const babelOptions = (() => { + if (staticBabelOptions) return staticBabelOptions + const newBabelOptions = createBabelOptions( + typeof opts.babel === 'function' + ? opts.babel(id, { ssr }) + : opts.babel, ) + runPluginOverrides?.(newBabelOptions, { id, ssr }) + return newBabelOptions + })() + const plugins = [...babelOptions.plugins] + + const isJSX = filepath.endsWith('x') + const useFastRefresh = + !skipFastRefresh && + !ssr && + (isJSX || + (opts.jsxRuntime === 'classic' + ? importReactRE.test(code) + : code.includes(jsxImportDevRuntime) || + code.includes(jsxImportRuntime))) + if (useFastRefresh) { + plugins.push([ + await loadPlugin('react-refresh/babel'), + { skipEnvCheck: true }, + ]) } - } - // Avoid parsing if no special transformation is needed - if ( - !plugins.length && - !babelOptions.presets.length && - !babelOptions.configFile && - !babelOptions.babelrc - ) { - return - } + if (opts.jsxRuntime === 'classic' && isJSX) { + if (!isProduction) { + // These development plugins are only needed for the classic runtime. + plugins.push( + await loadPlugin('@babel/plugin-transform-react-jsx-self'), + await loadPlugin('@babel/plugin-transform-react-jsx-source'), + ) + } + } - const parserPlugins = [...babelOptions.parserOpts.plugins] + // Avoid parsing if no special transformation is needed + if (canSkipBabel(plugins, babelOptions)) { + return + } - if (!filepath.endsWith('.ts')) { - parserPlugins.push('jsx') - } + const parserPlugins = [...babelOptions.parserOpts.plugins] - if (tsRE.test(filepath)) { - parserPlugins.push('typescript') - } + if (!filepath.endsWith('.ts')) { + parserPlugins.push('jsx') + } - const babel = await loadBabel() - const result = await babel.transformAsync(code, { - ...babelOptions, - root: projectRoot, - filename: id, - sourceFileName: filepath, - // Required for esbuild.jsxDev to provide correct line numbers - // This creates issues the react compiler because the re-order is too important - // People should use @babel/plugin-transform-react-jsx-development to get back good line numbers - retainLines: - getReactCompilerPlugin(plugins) != null - ? false - : !isProduction && isJSX && opts.jsxRuntime !== 'classic', - parserOpts: { - ...babelOptions.parserOpts, - sourceType: 'module', - allowAwaitOutsideFunction: true, - plugins: parserPlugins, - }, - generatorOpts: { - ...babelOptions.generatorOpts, - // import attributes parsing available without plugin since 7.26 - importAttributesKeyword: 'with', - decoratorsBeforeExport: true, - }, - plugins, - sourceMaps: true, - }) - - if (result) { - if (!useFastRefresh) { - return { code: result.code!, map: result.map } + if (tsRE.test(filepath)) { + parserPlugins.push('typescript') } - return addRefreshWrapper( - result.code!, - result.map!, - '@vitejs/plugin-react', - id, - opts.reactRefreshHost, - ) - } + + const babel = await loadBabel() + const result = await babel.transformAsync(code, { + ...babelOptions, + root: projectRoot, + filename: id, + sourceFileName: filepath, + // Required for esbuild.jsxDev to provide correct line numbers + // This creates issues the react compiler because the re-order is too important + // People should use @babel/plugin-transform-react-jsx-development to get back good line numbers + retainLines: + getReactCompilerPlugin(plugins) != null + ? false + : !isProduction && isJSX && opts.jsxRuntime !== 'classic', + parserOpts: { + ...babelOptions.parserOpts, + sourceType: 'module', + allowAwaitOutsideFunction: true, + plugins: parserPlugins, + }, + generatorOpts: { + ...babelOptions.generatorOpts, + // import attributes parsing available without plugin since 7.26 + importAttributesKeyword: 'with', + decoratorsBeforeExport: true, + }, + plugins, + sourceMaps: true, + }) + + if (result) { + if (!useFastRefresh) { + return { code: result.code!, map: result.map } + } + return addRefreshWrapper( + result.code!, + result.map!, + '@vitejs/plugin-react', + id, + opts.reactRefreshHost, + ) + } + }, }, } @@ -319,18 +326,24 @@ export default function viteReact(opts: Options = {}): PluginOption[] { dedupe: ['react', 'react-dom'], }, }), - resolveId(id) { - if (id === runtimePublicPath) { - return id - } + resolveId: { + filter: { id: exactRegex(runtimePublicPath) }, + handler(id) { + if (id === runtimePublicPath) { + return id + } + }, }, - load(id) { - if (id === runtimePublicPath) { - return readFileSync(refreshRuntimePath, 'utf-8').replace( - /__README_URL__/g, - 'https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react', - ) - } + load: { + filter: { id: exactRegex(runtimePublicPath) }, + handler(id) { + if (id === runtimePublicPath) { + return readFileSync(refreshRuntimePath, 'utf-8').replace( + /__README_URL__/g, + 'https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react', + ) + } + }, }, transformIndexHtml(_, config) { if (!skipFastRefresh) @@ -349,6 +362,18 @@ export default function viteReact(opts: Options = {}): PluginOption[] { viteReact.preambleCode = preambleCode +function canSkipBabel( + plugins: ReactBabelOptions['plugins'], + babelOptions: ReactBabelOptions, +) { + return !( + plugins.length || + babelOptions.presets.length || + babelOptions.configFile || + babelOptions.babelrc + ) +} + const loadedPlugin = new Map() function loadPlugin(path: string): any { const cached = loadedPlugin.get(path) From cc23cdbe0ce9d0dc05bbb40c8c4018fb87cc29e9 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Mon, 28 Apr 2025 17:54:33 +0900 Subject: [PATCH 2/5] feat(react): add filter for include/exclude --- packages/plugin-react/src/index.ts | 34 ++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index bae38e51d..fa38d0dcf 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -103,7 +103,10 @@ const defaultIncludeRE = /\.[tj]sx?$/ const tsRE = /\.tsx?$/ export default function viteReact(opts: Options = {}): PluginOption[] { - const filter = createFilter(opts.include ?? defaultIncludeRE, opts.exclude) + const include = opts.include ?? defaultIncludeRE + const exclude = opts.exclude + const filter = createFilter(include, exclude) + const jsxImportSource = opts.jsxImportSource ?? 'react' const jsxImportRuntime = `${jsxImportSource}/jsx-runtime` const jsxImportDevRuntime = `${jsxImportSource}/jsx-dev-runtime` @@ -193,7 +196,15 @@ export default function viteReact(opts: Options = {}): PluginOption[] { } }, transform: { - filter: { id: { exclude: /\/node_modules\// } }, + filter: { + id: { + include: ensureArray(include).map(matchWithQuery), + exclude: [ + ...(exclude ? ensureArray(exclude).map(matchWithQuery) : []), + /\/node_modules\//, + ], + }, + }, async handler(code, id, options) { if (id.includes('/node_modules/')) return @@ -433,3 +444,22 @@ function getReactCompilerRuntimeModule( } return moduleName } + +function ensureArray(value: T | T[]): T[] { + return Array.isArray(value) ? value : [value] +} + +function matchWithQuery(input: string | RegExp) { + if (typeof input === 'string') { + return `${input}{?*,}` + } + return addQueryToRegex(input) +} + +function addQueryToRegex(input: RegExp) { + return new RegExp( + // replace `$` with `(?:\?.*)?$` (ignore `\$`) + input.source.replace(/(? Date: Mon, 19 May 2025 18:11:38 +0900 Subject: [PATCH 3/5] refactor: use utils from `@rolldown/pluginutils` --- packages/common/filter-utils.ts | 8 -------- packages/common/index.ts | 1 - packages/plugin-react-oxc/package.json | 3 +++ packages/plugin-react-oxc/src/index.ts | 2 +- packages/plugin-react-swc/package.json | 1 + packages/plugin-react-swc/src/index.ts | 2 +- packages/plugin-react/package.json | 1 + packages/plugin-react/src/index.ts | 26 ++++++++------------------ pnpm-lock.yaml | 16 ++++++++++++++++ 9 files changed, 31 insertions(+), 29 deletions(-) delete mode 100644 packages/common/filter-utils.ts diff --git a/packages/common/filter-utils.ts b/packages/common/filter-utils.ts deleted file mode 100644 index f2663b9c1..000000000 --- a/packages/common/filter-utils.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function exactRegex(input: string): RegExp { - return new RegExp(`^${escapeRegex(input)}$`) -} - -const escapeRegexRE = /[-/\\^$*+?.()|[\]{}]/g -function escapeRegex(str: string): string { - return str.replace(escapeRegexRE, '\\$&') -} diff --git a/packages/common/index.ts b/packages/common/index.ts index 7fd06f9e6..e194bed42 100644 --- a/packages/common/index.ts +++ b/packages/common/index.ts @@ -1,3 +1,2 @@ -export * from './filter-utils' export * from './refresh-utils' export * from './warning' diff --git a/packages/plugin-react-oxc/package.json b/packages/plugin-react-oxc/package.json index f31de7df1..8b9321249 100644 --- a/packages/plugin-react-oxc/package.json +++ b/packages/plugin-react-oxc/package.json @@ -46,5 +46,8 @@ "@vitejs/react-common": "workspace:*", "unbuild": "^3.5.0", "vite": "catalog:rolldown-vite" + }, + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.9" } } diff --git a/packages/plugin-react-oxc/src/index.ts b/packages/plugin-react-oxc/src/index.ts index 4a353bcd0..f234ad80d 100644 --- a/packages/plugin-react-oxc/src/index.ts +++ b/packages/plugin-react-oxc/src/index.ts @@ -5,11 +5,11 @@ import type { BuildOptions, Plugin, PluginOption } from 'vite' import { addRefreshWrapper, avoidSourceMapOption, - exactRegex, getPreambleCode, runtimePublicPath, silenceUseClientWarning, } from '@vitejs/react-common' +import { exactRegex } from '@rolldown/pluginutils' const _dirname = dirname(fileURLToPath(import.meta.url)) diff --git a/packages/plugin-react-swc/package.json b/packages/plugin-react-swc/package.json index dd1140604..e41f5cb0a 100644 --- a/packages/plugin-react-swc/package.json +++ b/packages/plugin-react-swc/package.json @@ -29,6 +29,7 @@ }, "homepage": "https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react-swc#readme", "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.9", "@swc/core": "^1.11.22" }, "peerDependencies": { diff --git a/packages/plugin-react-swc/src/index.ts b/packages/plugin-react-swc/src/index.ts index 0d834c4d7..6e18334ac 100644 --- a/packages/plugin-react-swc/src/index.ts +++ b/packages/plugin-react-swc/src/index.ts @@ -14,11 +14,11 @@ import { import type { PluginOption } from 'vite' import { addRefreshWrapper, - exactRegex, getPreambleCode, runtimePublicPath, silenceUseClientWarning, } from '@vitejs/react-common' +import { exactRegex } from '@rolldown/pluginutils' /* eslint-disable no-restricted-globals */ const _dirname = diff --git a/packages/plugin-react/package.json b/packages/plugin-react/package.json index e893b948b..cc6bb6ddc 100644 --- a/packages/plugin-react/package.json +++ b/packages/plugin-react/package.json @@ -51,6 +51,7 @@ "@babel/core": "^7.26.10", "@babel/plugin-transform-react-jsx-self": "^7.25.9", "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@rolldown/pluginutils": "1.0.0-beta.9", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index fa38d0dcf..da1fc06ee 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -8,12 +8,15 @@ import * as vite from 'vite' import type { Plugin, PluginOption, ResolvedConfig } from 'vite' import { addRefreshWrapper, - exactRegex, getPreambleCode, preambleCode, runtimePublicPath, silenceUseClientWarning, } from '@vitejs/react-common' +import { + exactRegex, + makeIdFiltersToMatchWithQuery, +} from '@rolldown/pluginutils' const _dirname = dirname(fileURLToPath(import.meta.url)) @@ -198,9 +201,11 @@ export default function viteReact(opts: Options = {}): PluginOption[] { transform: { filter: { id: { - include: ensureArray(include).map(matchWithQuery), + include: makeIdFiltersToMatchWithQuery(include), exclude: [ - ...(exclude ? ensureArray(exclude).map(matchWithQuery) : []), + ...(exclude + ? makeIdFiltersToMatchWithQuery(ensureArray(exclude)) + : []), /\/node_modules\//, ], }, @@ -448,18 +453,3 @@ function getReactCompilerRuntimeModule( function ensureArray(value: T | T[]): T[] { return Array.isArray(value) ? value : [value] } - -function matchWithQuery(input: string | RegExp) { - if (typeof input === 'string') { - return `${input}{?*,}` - } - return addQueryToRegex(input) -} - -function addQueryToRegex(input: RegExp) { - return new RegExp( - // replace `$` with `(?:\?.*)?$` (ignore `\$`) - input.source.replace(/(?=14.0.0'} @@ -1716,6 +1729,7 @@ packages: '@swc/core@1.11.22': resolution: {integrity: sha512-mjPYbqq8XjwqSE0hEPT9CzaJDyxql97LgK4iyvYlwVSQhdN1uK0DBG4eP9PxYzCS2MUGAXB34WFLegdUj5HGpg==} engines: {node: '>=10'} + deprecated: It has a bug. See https://github.com/swc-project/swc/issues/10413 peerDependencies: '@swc/helpers': '>=0.5.17' peerDependenciesMeta: @@ -4767,6 +4781,8 @@ snapshots: '@rolldown/binding-win32-x64-msvc@1.0.0-beta.8-commit.2686eb1': optional: true + '@rolldown/pluginutils@1.0.0-beta.9': {} + '@rollup/plugin-alias@5.1.1(rollup@4.37.0)': optionalDependencies: rollup: 4.37.0 From f2112d52cb886175e4ca89d8fe138cc9e1627f22 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Mon, 19 May 2025 18:20:28 +0900 Subject: [PATCH 4/5] refactor: tweak the condition of `delete plugin.transform` --- packages/plugin-react/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin-react/src/index.ts b/packages/plugin-react/src/index.ts index da1fc06ee..250c4477e 100644 --- a/packages/plugin-react/src/index.ts +++ b/packages/plugin-react/src/index.ts @@ -192,7 +192,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] { if ( canSkipBabel(staticBabelOptions.plugins, staticBabelOptions) && skipFastRefresh && - isProduction + (opts.jsxRuntime === 'classic' ? isProduction : true) ) { delete viteBabel.transform } From 1c4f8b9d4d9813565af2b5e971ada2481bb8c9ee Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Tue, 20 May 2025 15:43:11 +0900 Subject: [PATCH 5/5] chore: add changelog --- packages/plugin-react-oxc/CHANGELOG.md | 4 ++++ packages/plugin-react-swc/CHANGELOG.md | 4 ++++ packages/plugin-react/CHANGELOG.md | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/packages/plugin-react-oxc/CHANGELOG.md b/packages/plugin-react-oxc/CHANGELOG.md index 1fc7ebfe0..7648dec4d 100644 --- a/packages/plugin-react-oxc/CHANGELOG.md +++ b/packages/plugin-react-oxc/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Add `filter` for rolldown-vite + +Added `filter` so that it is more performant when running this plugin with rolldown-powered version of Vite. + ## 0.1.1 (2025-04-10) ## 0.1.0 (2025-04-09) diff --git a/packages/plugin-react-swc/CHANGELOG.md b/packages/plugin-react-swc/CHANGELOG.md index 8fb66bc3f..e2a13984e 100644 --- a/packages/plugin-react-swc/CHANGELOG.md +++ b/packages/plugin-react-swc/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Add `filter` for rolldown-vite + +Added `filter` so that it is more performant when running this plugin with rolldown-powered version of Vite. + ## 3.9.0 (2025-04-15) ### Make compatible with rolldown-vite diff --git a/packages/plugin-react/CHANGELOG.md b/packages/plugin-react/CHANGELOG.md index 86c6efd09..ccb6b02c9 100644 --- a/packages/plugin-react/CHANGELOG.md +++ b/packages/plugin-react/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Add `filter` for rolldown-vite + +Added `filter` so that it is more performant when running this plugin with rolldown-powered version of Vite. + ## 4.4.1 (2025-04-19) Fix type issue when using `moduleResolution: "node"` in tsconfig [#462](https://github.com/vitejs/vite-plugin-react/pull/462)