1
1
import path from 'node:path'
2
+ import { createRequire } from 'node:module'
2
3
import type {
3
4
TransformOptions as OxcTransformOptions ,
4
5
TransformResult as OxcTransformResult ,
5
6
} from 'rolldown/experimental'
6
7
import { transform } from 'rolldown/experimental'
7
8
import type { RawSourceMap } from '@ampproject/remapping'
8
- import type { SourceMap } from 'rolldown'
9
+ import { type InternalModuleFormat , type SourceMap , rolldown } from 'rolldown'
9
10
import type { FSWatcher } from 'dep-types/chokidar'
10
11
import { TSConfckParseError } from 'tsconfck'
11
12
import type { RollupError } from 'rollup'
@@ -23,6 +24,12 @@ import type { ViteDevServer } from '../server'
23
24
import type { ESBuildOptions } from './esbuild'
24
25
import { loadTsconfigJsonForFile } from './esbuild'
25
26
27
+ // IIFE content looks like `var MyLib = (function() {`.
28
+ const IIFE_BEGIN_RE =
29
+ / (?: c o n s t | v a r ) \s + \S + \s * = \s * \( ? f u n c t i o n \( [ ^ ( ) ] * \) \s * \{ \s * " u s e s t r i c t " ; /
30
+ // UMD content looks like `(this, function(exports) {`.
31
+ const UMD_BEGIN_RE = / \( t h i s , \s * f u n c t i o n \( [ ^ ( ) ] * \) \s * \{ \s * " u s e s t r i c t " ; /
32
+
26
33
const jsxExtensionsRE = / \. (?: j | t ) s x \b /
27
34
const validExtensionRE = / \. \w + $ /
28
35
@@ -267,6 +274,177 @@ export function oxcPlugin(config: ResolvedConfig): Plugin {
267
274
}
268
275
}
269
276
277
+ export const buildOxcPlugin = ( config : ResolvedConfig ) : Plugin => {
278
+ return {
279
+ name : 'vite:oxc-transpile' ,
280
+ async renderChunk ( code , chunk , opts ) {
281
+ // @ts -expect-error injected by @vitejs/plugin-legacy
282
+ if ( opts . __vite_skip_esbuild__ ) {
283
+ return null
284
+ }
285
+
286
+ const options = resolveOxcTranspileOptions ( config , opts . format )
287
+
288
+ if ( ! options ) {
289
+ return null
290
+ }
291
+
292
+ const res = await transformWithOxc (
293
+ this ,
294
+ code ,
295
+ chunk . fileName ,
296
+ options ,
297
+ undefined ,
298
+ config ,
299
+ )
300
+
301
+ const runtimeHelpers = Object . entries ( res . helpersUsed )
302
+ if ( runtimeHelpers . length > 0 ) {
303
+ const helpersCode = await generateRuntimeHelpers ( runtimeHelpers )
304
+ switch ( opts . format ) {
305
+ case 'es' : {
306
+ if ( res . code . startsWith ( '#!' ) ) {
307
+ let secondLinePos = res . code . indexOf ( '\n' )
308
+ if ( secondLinePos === - 1 ) {
309
+ secondLinePos = 0
310
+ }
311
+ // inject after hashbang
312
+ res . code =
313
+ res . code . slice ( 0 , secondLinePos ) +
314
+ helpersCode +
315
+ res . code . slice ( secondLinePos )
316
+ if ( res . map ) {
317
+ res . map . mappings = res . map . mappings . replace ( ';' , ';;' )
318
+ }
319
+ } else {
320
+ res . code = helpersCode + res . code
321
+ if ( res . map ) {
322
+ res . map . mappings = ';' + res . map . mappings
323
+ }
324
+ }
325
+ break
326
+ }
327
+ case 'cjs' : {
328
+ if ( / ^ \s * [ ' " ] u s e s t r i c t [ ' " ] ; / . test ( res . code ) ) {
329
+ // inject after use strict
330
+ res . code = res . code . replace (
331
+ / ^ \s * [ ' " ] u s e s t r i c t [ ' " ] ; / ,
332
+ ( m ) => m + helpersCode ,
333
+ )
334
+ // no need to update sourcemap because the runtime helpers are injected in the same line with "use strict"
335
+ } else {
336
+ res . code = helpersCode + res . code
337
+ if ( res . map ) {
338
+ res . map . mappings = ';' + res . map . mappings
339
+ }
340
+ }
341
+ break
342
+ }
343
+ // runtime helpers needs to be injected inside the UMD and IIFE wrappers
344
+ // to avoid collision with other globals.
345
+ // We inject the helpers inside the wrappers.
346
+ // e.g. turn:
347
+ // (function(){ /*actual content/* })()
348
+ // into:
349
+ // (function(){ <runtime helpers> /*actual content/* })()
350
+ // Not using regex because it's too hard to rule out performance issues like #8738 #8099 #10900 #14065
351
+ // Instead, using plain string index manipulation (indexOf, slice) which is simple and performant
352
+ // We don't need to create a MagicString here because both the helpers and
353
+ // the headers don't modify the sourcemap
354
+ case 'iife' :
355
+ case 'umd' : {
356
+ const m = (
357
+ opts . format === 'iife' ? IIFE_BEGIN_RE : UMD_BEGIN_RE
358
+ ) . exec ( res . code )
359
+ if ( ! m ) {
360
+ this . error ( 'Unexpected IIFE format' )
361
+ return
362
+ }
363
+ const pos = m . index + m . length
364
+ res . code =
365
+ res . code . slice ( 0 , pos ) + helpersCode + '\n' + res . code . slice ( pos )
366
+ break
367
+ }
368
+ case 'app' : {
369
+ throw new Error ( 'format: "app" is not supported yet' )
370
+ break
371
+ }
372
+ default : {
373
+ opts . format satisfies never
374
+ }
375
+ }
376
+ }
377
+
378
+ return res
379
+ } ,
380
+ }
381
+ }
382
+
383
+ export function resolveOxcTranspileOptions (
384
+ config : ResolvedConfig ,
385
+ format : InternalModuleFormat ,
386
+ ) : OxcTransformOptions | null {
387
+ const target = config . build . target
388
+ if ( ! target || target === 'esnext' ) {
389
+ return null
390
+ }
391
+
392
+ return {
393
+ ...( config . oxc || { } ) ,
394
+ helpers : { mode : 'External' } ,
395
+ lang : 'js' ,
396
+ sourceType : format === 'es' ? 'module' : 'script' ,
397
+ target : target || undefined ,
398
+ sourcemap : ! ! config . build . sourcemap ,
399
+ }
400
+ }
401
+
402
+ let rolldownDir : string
403
+
404
+ async function generateRuntimeHelpers (
405
+ runtimeHelpers : readonly [ string , string ] [ ] ,
406
+ ) : Promise < string > {
407
+ if ( ! rolldownDir ) {
408
+ let dir = createRequire ( import . meta. url ) . resolve ( 'rolldown' )
409
+ while ( dir && path . basename ( dir ) !== 'rolldown' ) {
410
+ dir = path . dirname ( dir )
411
+ }
412
+ rolldownDir = dir
413
+ }
414
+
415
+ const bundle = await rolldown ( {
416
+ cwd : rolldownDir ,
417
+ input : 'entrypoint' ,
418
+ platform : 'neutral' ,
419
+ plugins : [
420
+ {
421
+ name : 'entrypoint' ,
422
+ resolveId : {
423
+ filter : { id : / ^ e n t r y p o i n t $ / } ,
424
+ handler : ( id ) => id ,
425
+ } ,
426
+ load : {
427
+ filter : { id : / ^ e n t r y p o i n t $ / } ,
428
+ handler ( ) {
429
+ return runtimeHelpers
430
+ . map (
431
+ ( [ name , helper ] ) =>
432
+ `export { default as ${ name } } from ${ JSON . stringify ( helper ) } ;` ,
433
+ )
434
+ . join ( '\n' )
435
+ } ,
436
+ } ,
437
+ } ,
438
+ ] ,
439
+ } )
440
+ const output = await bundle . generate ( {
441
+ format : 'iife' ,
442
+ name : 'babelHelpers' ,
443
+ minify : true ,
444
+ } )
445
+ return output . output [ 0 ] . code
446
+ }
447
+
270
448
export function convertEsbuildConfigToOxcConfig (
271
449
esbuildConfig : ESBuildOptions ,
272
450
logger : Logger ,
0 commit comments