@vxrn/compiler 1.10.6 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/constants.ts CHANGED
@@ -1,5 +1,3 @@
1
- import type { ParserConfig } from '@swc/core'
2
-
3
1
  import { createDebugger } from '@vxrn/utils'
4
2
 
5
3
  export const { debug } = createDebugger('vxrn:compiler-plugin')
@@ -8,7 +6,8 @@ export const runtimePublicPath = '/@react-refresh'
8
6
 
9
7
  export const asyncGeneratorRegex = /(async \*|async function\*|for await)/
10
8
 
11
- export const parsers: Record<string, ParserConfig> = {
9
+ // parser configs for swc (used by transformSWC consumers)
10
+ export const parsers: Record<string, any> = {
12
11
  '.tsx': { syntax: 'typescript', tsx: true, decorators: true },
13
12
  '.ts': { syntax: 'typescript', tsx: false, decorators: true },
14
13
  '.jsx': {
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Adapted from https://github.com/vitejs/vite-plugin-react-swc/blob/main/src/index.ts
3
- * to work on both native and web, and with reanimated and other babel fallbacks
2
+ * Compiler plugin for One/VXRN
3
+ * Automates babel transforms (react compiler, codegen, user transforms) and
4
+ * react native CSS-to-JS conversion.
4
5
  */
5
6
 
6
7
  import { readFileSync } from 'node:fs'
@@ -9,12 +10,11 @@ import { dirname, extname, join, relative, sep } from 'node:path'
9
10
  import { fileURLToPath } from 'node:url'
10
11
  import { resolvePath } from '@vxrn/utils'
11
12
  import { cssToReactNativeRuntime } from 'react-native-css-interop/css-to-rn/index.js'
12
- import type { OutputChunk } from 'rollup'
13
+ import type { OutputChunk } from 'rolldown'
13
14
  import type { PluginOption, ResolvedConfig, UserConfig } from 'vite'
14
15
  import { configuration } from './configure'
15
16
  import { debug, runtimePublicPath, validParsers } from './constants'
16
17
  import { getBabelOptions, transformBabel } from './transformBabel'
17
- import { transformSWC } from './transformSWC'
18
18
  import type { Environment, GetTransformProps, Options } from './types'
19
19
  import { getCachedTransform, logCacheStats, setCachedTransform } from './cache'
20
20
 
@@ -107,9 +107,7 @@ async function performBabelTransform({
107
107
  return null
108
108
  }
109
109
 
110
- const isPreProcess = id.startsWith(`vxrn-swc-preprocess:`)
111
-
112
- if (!isPreProcess && userTransform !== 'swc') {
110
+ if (userTransform !== 'swc') {
113
111
  const babelOptions = getBabelOptions({
114
112
  ...transformProps,
115
113
  userSetting: userTransform,
@@ -170,108 +168,6 @@ async function performBabelTransform({
170
168
  return null
171
169
  }
172
170
 
173
- // Full transform (Babel + SWC) for Vite transform hook
174
- async function performFullTransform({
175
- codeIn,
176
- _id,
177
- environment,
178
- production,
179
- reactForRNVersion,
180
- optionsIn,
181
- mode,
182
- }: {
183
- codeIn: string
184
- _id: string
185
- environment: Environment
186
- production: boolean
187
- reactForRNVersion: '18' | '19'
188
- optionsIn?: Partial<Options>
189
- mode: 'serve' | 'build'
190
- }) {
191
- const shouldDebug =
192
- process.env.NODE_ENV === 'development' && codeIn.startsWith('// debug')
193
-
194
- if (shouldDebug) {
195
- console.info(`[one] ${_id} input:`)
196
- console.info(codeIn)
197
- }
198
-
199
- let id = _id.split('?')[0]
200
-
201
- const extension = extname(id)
202
-
203
- if (extension === '.css') {
204
- return
205
- }
206
-
207
- if (!validParsers.has(extension)) {
208
- return
209
- }
210
-
211
- const isPreProcess = id.startsWith(`vxrn-swc-preprocess:`)
212
- if (isPreProcess) {
213
- id = id.replace(`vxrn-swc-preprocess:`, '')
214
- }
215
-
216
- if (id.includes(`virtual:`)) {
217
- return
218
- }
219
-
220
- let code = codeIn
221
- let out: {
222
- code: string
223
- map?: any
224
- } | null = null
225
-
226
- // avoid double-processing files already handled by optimizeDeps
227
- if (codeIn.endsWith(`// vxrn-did-babel`)) {
228
- debug?.(`[skip babel] ${id}`)
229
- } else {
230
- const babelResult = await performBabelTransform({
231
- id,
232
- code,
233
- environment,
234
- production,
235
- reactForRNVersion,
236
- optionsIn,
237
- })
238
-
239
- if (babelResult) {
240
- out = babelResult
241
- code = babelResult.code
242
- }
243
- }
244
-
245
- // Always run SWC for class transforms + react refresh
246
- const swcOptions = {
247
- environment,
248
- mode: optionsIn?.mode || mode,
249
- production,
250
- ...optionsIn,
251
- } satisfies Options
252
-
253
- const swcOut = await transformSWC(id, code, {
254
- es5: true,
255
- noHMR: isPreProcess || environment === 'ssr',
256
- ...swcOptions,
257
- })
258
-
259
- if (swcOut) {
260
- debug?.(`[swc] ${id}`)
261
- out = {
262
- code: swcOut.code,
263
- map: swcOut.map,
264
- }
265
- }
266
-
267
- if (shouldDebug) {
268
- console.info(`swcOptions`, swcOptions)
269
- console.info(`final output:`, out?.code)
270
- }
271
-
272
- return out
273
- }
274
-
275
171
  export async function createVXRNCompilerPlugin(
276
172
  optionsIn?: Partial<Options>
277
173
  ): Promise<PluginOption[]> {
@@ -297,9 +193,12 @@ export async function createVXRNCompilerPlugin(
297
193
 
298
194
  const cssTransformCache = new Map<string, string>()
299
195
 
300
- // fix so we can align the diff between vite and rollup id (rollup resolves from root monorepo)
301
- const rollupPath = resolvePath('rollup')
302
- const rollupNodeMods = rollupPath.slice(0, rollupPath.indexOf(sep + 'node_modules'))
196
+ // fix so we can align the diff between vite and rolldown id (rolldown resolves from root monorepo)
197
+ const rolldownPath = resolvePath('rolldown')
198
+ const rolldownNodeMods = rolldownPath.slice(
199
+ 0,
200
+ rolldownPath.indexOf(sep + 'node_modules')
201
+ )
303
202
 
304
203
  /**
305
204
  * Vite config, filled by a `configResolved` hook.
@@ -349,7 +248,7 @@ export async function createVXRNCompilerPlugin(
349
248
  const newId = `${id}.js`
350
249
 
351
250
  // rollup uses relative to its node_modules parent dir, vite here uses absolute
352
- const cssId = newId.replace(rollupNodeMods + sep, '')
251
+ const cssId = newId.replace(rolldownNodeMods + sep, '')
353
252
  cssTransformCache.set(cssId, code)
354
253
 
355
254
  return {
@@ -402,87 +301,76 @@ ${rootJS.code}
402
301
  enforce: 'pre',
403
302
 
404
303
  config: () => {
304
+ const nodeModulesFilter = /node_modules\/.*\.(tsx?|jsx?|mjs|cjs)$/
305
+
405
306
  const createEnvironmentConfig = (environment: Environment) => {
307
+ // init stats for this environment
308
+ if (!perfStats.optimizeDeps.byEnvironment[environment]) {
309
+ perfStats.optimizeDeps.byEnvironment[environment] = {
310
+ filesChecked: 0,
311
+ filesTransformed: 0,
312
+ startTime: Date.now(),
313
+ }
314
+ }
315
+
406
316
  return {
407
317
  optimizeDeps: {
408
- esbuildOptions: {
318
+ rolldownOptions: {
409
319
  plugins: [
410
320
  {
411
321
  name: `transform-before-optimize-deps-${environment}`,
412
- setup(build) {
413
- // Init stats for this environment
414
- if (!perfStats.optimizeDeps.byEnvironment[environment]) {
415
- perfStats.optimizeDeps.byEnvironment[environment] = {
416
- filesChecked: 0,
417
- filesTransformed: 0,
418
- startTime: Date.now(),
419
- }
322
+
323
+ async transform(code: string, id: string) {
324
+ if (!nodeModulesFilter.test(id)) {
325
+ return null
420
326
  }
421
327
 
422
- build.onLoad(
423
- { filter: /node_modules\/.*\.(tsx?|jsx?|mjs|cjs)$/ },
424
- async (args) => {
425
- perfStats.optimizeDeps.byEnvironment[environment].filesChecked++
426
-
427
- const production =
428
- process.env.NODE_ENV === 'production' ||
429
- process.env.NODE_ENV === 'test'
430
- const code = await readFile(args.path, 'utf-8')
431
-
432
- debug?.(`[esbuild optimizeDeps] ${args.path}`)
433
-
434
- const result = await performBabelTransform({
435
- id: args.path,
436
- code,
437
- environment,
438
- production,
439
- reactForRNVersion,
440
- optionsIn,
441
- })
442
-
443
- if (!result) {
444
- return null
445
- }
446
-
447
- perfStats.optimizeDeps.byEnvironment[environment]
448
- .filesTransformed++
449
-
450
- // Determine loader based on file extension
451
- const ext = extname(args.path)
452
- const loader =
453
- ext === '.tsx'
454
- ? 'tsx'
455
- : ext === '.ts'
456
- ? 'ts'
457
- : ext === '.jsx'
458
- ? 'jsx'
459
- : 'js'
460
-
461
- return {
462
- contents: result.code,
463
- loader,
464
- }
465
- }
466
- )
467
-
468
- build.onEnd(() => {
469
- // Only log detailed stats when debugging
470
- if (process.env.DEBUG_COMPILER_PERF) {
471
- const stats = perfStats.optimizeDeps.byEnvironment[environment]
472
- const elapsed = Date.now() - stats.startTime
473
- console.info(
474
- `[optimizeDeps ${environment}] Done: ${stats.filesChecked} files checked, ${stats.filesTransformed} transformed (${elapsed}ms)`
475
- )
476
- }
477
-
478
- // Log cache stats when all environments are done
479
- const allDone =
480
- Object.keys(perfStats.optimizeDeps.byEnvironment).length >= 2
481
- if (allDone) {
482
- logCacheStats()
483
- logPerfSummary()
484
- }
328
+ perfStats.optimizeDeps.byEnvironment[environment].filesChecked++
329
+
330
+ const production =
331
+ process.env.NODE_ENV === 'production' ||
332
+ process.env.NODE_ENV === 'test'
333
+
334
+ debug?.(`[rolldown optimizeDeps] ${id}`)
335
+
336
+ const result = await performBabelTransform({
337
+ id,
338
+ code,
339
+ environment,
340
+ production,
341
+ reactForRNVersion,
342
+ optionsIn,
485
343
  })
344
+
345
+ if (!result) {
346
+ return null
347
+ }
348
+
349
+ perfStats.optimizeDeps.byEnvironment[environment].filesTransformed++
350
+
351
+ return {
352
+ code: result.code,
353
+ map: result.map,
354
+ }
355
+ },
356
+
357
+ buildEnd() {
358
+ // only log detailed stats when debugging
359
+ if (process.env.DEBUG_COMPILER_PERF) {
360
+ const stats = perfStats.optimizeDeps.byEnvironment[environment]
361
+ const elapsed = Date.now() - stats.startTime
362
+ console.info(
363
+ `[optimizeDeps ${environment}] Done: ${stats.filesChecked} files checked, ${stats.filesTransformed} transformed (${elapsed}ms)`
364
+ )
365
+ }
366
+
367
+ // log cache stats when all environments are done
368
+ const allDone =
369
+ Object.keys(perfStats.optimizeDeps.byEnvironment).length >= 2
370
+ if (allDone) {
371
+ logCacheStats()
372
+ logPerfSummary()
373
+ }
486
374
  },
487
375
  },
488
376
  ],
@@ -539,16 +427,86 @@ ${rootJS.code}
539
427
  return code
540
428
  }
541
429
 
542
- return performFullTransform({
543
- codeIn,
544
- _id,
430
+ // filter out non-transformable files
431
+ const id = _id.split('?')[0]
432
+ const extension = extname(id)
433
+
434
+ if (extension === '.css' || !validParsers.has(extension)) {
435
+ return
436
+ }
437
+
438
+ if (id.includes(`virtual:`)) {
439
+ return
440
+ }
441
+
442
+ // avoid double-processing files already handled by optimizeDeps
443
+ if (codeIn.endsWith(`// vxrn-did-babel`)) {
444
+ debug?.(`[skip babel] ${id}`)
445
+ return
446
+ }
447
+
448
+ return performBabelTransform({
449
+ id,
450
+ code: codeIn,
545
451
  environment,
546
452
  production,
547
453
  reactForRNVersion,
548
454
  optionsIn,
549
- mode: config.command,
550
455
  })
551
456
  },
552
457
  },
458
+
459
+ // wraps client-side TSX/JSX with React Refresh preamble + import.meta.hot.accept
460
+ // runs after vite:oxc (no enforce:'pre') so it sees the already-transformed code
461
+ {
462
+ name: 'one:react-refresh-web',
463
+ apply: 'serve',
464
+
465
+ transform(code, _id) {
466
+ if (this.environment.name !== 'client') return
467
+ if (code.includes(runtimePublicPath)) return // already wrapped
468
+
469
+ const id = _id.split('?')[0]
470
+ if (id.includes('node_modules')) return
471
+ if (id.includes('virtual:')) return
472
+ if (id === runtimePublicPath) return
473
+
474
+ const ext = extname(id)
475
+ if (ext !== '.tsx' && ext !== '.jsx') return
476
+
477
+ const hasRefreshCalls = /\$RefreshReg\$\(/.test(code)
478
+
479
+ let out = `import * as RefreshRuntime from "${runtimePublicPath}";\n\n`
480
+
481
+ if (hasRefreshCalls) {
482
+ out += `if (!window.$RefreshReg$) throw new Error("React refresh preamble was not loaded. Something is wrong.");
483
+ const prevRefreshReg = window.$RefreshReg$;
484
+ const prevRefreshSig = window.$RefreshSig$;
485
+ window.$RefreshReg$ = RefreshRuntime.getRefreshReg("${id}");
486
+ window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
487
+
488
+ `
489
+ }
490
+
491
+ out += code
492
+
493
+ if (hasRefreshCalls) {
494
+ out += `\n\nwindow.$RefreshReg$ = prevRefreshReg;\nwindow.$RefreshSig$ = prevRefreshSig;\n`
495
+ }
496
+
497
+ out += `
498
+ RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
499
+ RefreshRuntime.registerExportsForReactRefresh("${id}", currentExports);
500
+ import.meta.hot.accept((nextExports) => {
501
+ if (!nextExports) return;
502
+ const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate("${id}", currentExports, nextExports);
503
+ if (invalidateMessage) import.meta.hot.invalidate(invalidateMessage);
504
+ });
505
+ });
506
+ `
507
+
508
+ return { code: out, map: null }
509
+ },
510
+ },
553
511
  ]
554
512
  }
@@ -1,9 +1,8 @@
1
- import type { ParserConfig } from '@swc/core';
2
1
  export declare const debug: (((...args: any[]) => any) & {
3
2
  namespace: string;
4
3
  }) | undefined;
5
4
  export declare const runtimePublicPath = "/@react-refresh";
6
5
  export declare const asyncGeneratorRegex: RegExp;
7
- export declare const parsers: Record<string, ParserConfig>;
6
+ export declare const parsers: Record<string, any>;
8
7
  export declare const validParsers: Set<string>;
9
8
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAI7C,eAAO,MAAQ,KAAK;;cAA2C,CAAA;AAE/D,eAAO,MAAM,iBAAiB,oBAAoB,CAAA;AAElD,eAAO,MAAM,mBAAmB,QAA0C,CAAA;AAE1E,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CA8BhD,CAAA;AAED,eAAO,MAAM,YAAY,aAA6C,CAAA"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAEA,eAAO,MAAQ,KAAK;;cAA2C,CAAA;AAE/D,eAAO,MAAM,iBAAiB,oBAAoB,CAAA;AAElD,eAAO,MAAM,mBAAmB,QAA0C,CAAA;AAG1E,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CA8BvC,CAAA;AAED,eAAO,MAAM,YAAY,aAA6C,CAAA"}
package/types/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Adapted from https://github.com/vitejs/vite-plugin-react-swc/blob/main/src/index.ts
3
- * to work on both native and web, and with reanimated and other babel fallbacks
2
+ * Compiler plugin for One/VXRN
3
+ * Automates babel transforms (react compiler, codegen, user transforms) and
4
+ * react native CSS-to-JS conversion.
4
5
  */
5
6
  import type { PluginOption } from 'vite';
6
7
  import type { Options } from './types';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH,OAAO,KAAK,EAAE,YAAY,EAA8B,MAAM,MAAM,CAAA;AAKpE,OAAO,KAAK,EAAkC,OAAO,EAAE,MAAM,SAAS,CAAA;AAGtE,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AA2P3C,wBAAsB,wBAAwB,CAC5C,SAAS,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAC3B,OAAO,CAAC,YAAY,EAAE,CAAC,CAqRzB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,KAAK,EAAE,YAAY,EAA8B,MAAM,MAAM,CAAA;AAIpE,OAAO,KAAK,EAAkC,OAAO,EAAE,MAAM,SAAS,CAAA;AAGtE,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAmJ3C,wBAAsB,wBAAwB,CAC5C,SAAS,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAC3B,OAAO,CAAC,YAAY,EAAE,CAAC,CAmVzB"}