-
-
Notifications
You must be signed in to change notification settings - Fork 158
feat: add filter #470
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
feat: add filter #470
Changes from 2 commits
6357f56
cc23cdb
b1a31ae
f2112d5
1c4f8b9
22f121a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, '\\$&') | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from './filter-utils' | ||
export * from './refresh-utils' | ||
export * from './warning' |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ import * as vite from 'vite' | |
import type { Plugin, PluginOption, ResolvedConfig } from 'vite' | ||
import { | ||
addRefreshWrapper, | ||
exactRegex, | ||
getPreambleCode, | ||
preambleCode, | ||
runtimePublicPath, | ||
|
@@ -102,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` | ||
|
@@ -181,114 +185,128 @@ 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I think this is needed. For example, when |
||
) { | ||
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: { | ||
include: ensureArray(include).map(matchWithQuery), | ||
exclude: [ | ||
...(exclude ? ensureArray(exclude).map(matchWithQuery) : []), | ||
/\/node_modules\//, | ||
], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This filter conversion and the two utils feels a bit tricky IMO. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we add a util function to Vite, we need to bump the required peer dep in this plugin (and the other plugins that wants to use that) which is a breaking change. I think if we do that we probably should add it to a separate package. I'll consider if we can have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be added and then we could do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've updated the code to use |
||
}, | ||
}, | ||
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 (tsRE.test(filepath)) { | ||
parserPlugins.push('typescript') | ||
} | ||
|
||
if (result) { | ||
if (!useFastRefresh) { | ||
return { code: result.code!, map: result.map } | ||
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, | ||
) | ||
} | ||
return addRefreshWrapper( | ||
result.code!, | ||
result.map!, | ||
'@vitejs/plugin-react', | ||
id, | ||
opts.reactRefreshHost, | ||
) | ||
} | ||
}, | ||
}, | ||
} | ||
|
||
|
@@ -319,18 +337,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 +373,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<string, any>() | ||
function loadPlugin(path: string): any { | ||
const cached = loadedPlugin.get(path) | ||
|
@@ -408,3 +444,22 @@ function getReactCompilerRuntimeModule( | |
} | ||
return moduleName | ||
} | ||
|
||
function ensureArray<T>(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(/(?<!\\)\$/g, '(?:\\?.*)?$'), | ||
input.flags, | ||
) | ||
} |
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.
We still need to keep the
if
statements to keep the compat for Vite older than v6.3.