@@ -8,6 +8,7 @@ import * as vite from 'vite'
88import type { Plugin , PluginOption , ResolvedConfig } from 'vite'
99import {
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 : / \/ n o d e _ m o d u l e s \/ / } } ,
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- / _ _ R E A D M E _ U R L _ _ / 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+ / _ _ R E A D M E _ U R L _ _ / 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
350363viteReact . 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+
352377const loadedPlugin = new Map < string , any > ( )
353378function loadPlugin ( path : string ) : any {
354379 const cached = loadedPlugin . get ( path )
0 commit comments