@vxrn/compiler 1.2.17 → 1.2.18

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/cache.ts ADDED
@@ -0,0 +1,135 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
3
+ import { join } from 'node:path'
4
+
5
+ /**
6
+ * Fast file-based cache for babel transforms
7
+ * Uses file mtime + content hash for invalidation
8
+ */
9
+
10
+ interface CacheEntry {
11
+ /** File modification time when cached */
12
+ mtime: number
13
+ /** Hash of file content for additional validation */
14
+ hash: string
15
+ /** Cached transform result */
16
+ code: string
17
+ /** Optional source map */
18
+ map?: any
19
+ }
20
+
21
+ interface CacheStats {
22
+ hits: 0
23
+ misses: 0
24
+ writes: 0
25
+ }
26
+
27
+ const stats: CacheStats = { hits: 0, misses: 0, writes: 0 }
28
+
29
+ function getCacheDir(): string {
30
+ // Use .vxrn cache directory
31
+ const cacheDir = join(process.cwd(), 'node_modules', '.vxrn', 'compiler-cache')
32
+ if (!existsSync(cacheDir)) {
33
+ mkdirSync(cacheDir, { recursive: true })
34
+ }
35
+ return cacheDir
36
+ }
37
+
38
+ function getCacheKey(filePath: string, environment: string): string {
39
+ // Create a hash of the file path + environment for the cache key
40
+ const hash = createHash('sha1').update(`${environment}:${filePath}`).digest('hex')
41
+ return hash
42
+ }
43
+
44
+ function getContentHash(code: string): string {
45
+ // Fast hash of file content
46
+ return createHash('sha1').update(code).digest('hex').slice(0, 16)
47
+ }
48
+
49
+ export function getCachedTransform(
50
+ filePath: string,
51
+ code: string,
52
+ environment: string
53
+ ): { code: string; map?: any } | null {
54
+ try {
55
+ const cacheDir = getCacheDir()
56
+ const cacheKey = getCacheKey(filePath, environment)
57
+ const cachePath = join(cacheDir, `${cacheKey}.json`)
58
+
59
+ if (!existsSync(cachePath)) {
60
+ stats.misses++
61
+ return null
62
+ }
63
+
64
+ const cached: CacheEntry = JSON.parse(readFileSync(cachePath, 'utf-8'))
65
+
66
+ // Check file mtime
67
+ const currentMtime = statSync(filePath).mtimeMs
68
+ if (cached.mtime !== currentMtime) {
69
+ stats.misses++
70
+ return null
71
+ }
72
+
73
+ // Double-check with content hash for safety
74
+ const currentHash = getContentHash(code)
75
+ if (cached.hash !== currentHash) {
76
+ stats.misses++
77
+ return null
78
+ }
79
+
80
+ stats.hits++
81
+ return { code: cached.code, map: cached.map }
82
+ } catch (err) {
83
+ // If cache read fails, just treat as miss
84
+ stats.misses++
85
+ return null
86
+ }
87
+ }
88
+
89
+ export function setCachedTransform(
90
+ filePath: string,
91
+ code: string,
92
+ result: { code: string; map?: any },
93
+ environment: string
94
+ ): void {
95
+ try {
96
+ const cacheDir = getCacheDir()
97
+ const cacheKey = getCacheKey(filePath, environment)
98
+ const cachePath = join(cacheDir, `${cacheKey}.json`)
99
+
100
+ const mtime = statSync(filePath).mtimeMs
101
+ const hash = getContentHash(code)
102
+
103
+ const entry: CacheEntry = {
104
+ mtime,
105
+ hash,
106
+ code: result.code,
107
+ map: result.map,
108
+ }
109
+
110
+ writeFileSync(cachePath, JSON.stringify(entry), 'utf-8')
111
+ stats.writes++
112
+ } catch (err) {
113
+ // Silently fail cache writes
114
+ console.warn(`[cache] Failed to write cache for ${filePath}:`, err)
115
+ }
116
+ }
117
+
118
+ export function getCacheStats(): CacheStats {
119
+ return { ...stats }
120
+ }
121
+
122
+ export function logCacheStats(): void {
123
+ // Only log cache stats when debugging
124
+ if (!process.env.DEBUG_COMPILER_PERF) {
125
+ return
126
+ }
127
+
128
+ const total = stats.hits + stats.misses
129
+ if (total === 0) return
130
+
131
+ const hitRate = ((stats.hits / total) * 100).toFixed(1)
132
+ console.info(
133
+ `\n💾 [Cache Stats] ${stats.hits} hits / ${stats.misses} misses (${hitRate}% hit rate), ${stats.writes} writes`
134
+ )
135
+ }
package/src/index.ts CHANGED
@@ -15,12 +15,57 @@ import { debug, runtimePublicPath, validParsers } from './constants'
15
15
  import { getBabelOptions, transformBabel } from './transformBabel'
16
16
  import { transformSWC } from './transformSWC'
17
17
  import type { Environment, GetTransformProps, Options } from './types'
18
+ import { getCachedTransform, logCacheStats, setCachedTransform } from './cache'
18
19
 
19
20
  export * from './configure'
20
21
  export * from './transformBabel'
21
22
  export * from './transformSWC'
22
23
  export type { GetTransform } from './types'
23
24
 
25
+ // Performance tracking
26
+ const perfStats = {
27
+ babel: {
28
+ totalCalls: 0,
29
+ totalTransforms: 0,
30
+ totalTime: 0,
31
+ byEnvironment: {} as Record<string, { calls: number; transforms: number; time: number }>,
32
+ },
33
+ optimizeDeps: {
34
+ byEnvironment: {} as Record<
35
+ string,
36
+ { filesChecked: number; filesTransformed: number; startTime: number }
37
+ >,
38
+ },
39
+ }
40
+
41
+ function logPerfSummary() {
42
+ // Only log detailed perf stats when debugging
43
+ if (!process.env.DEBUG_COMPILER_PERF) {
44
+ return
45
+ }
46
+
47
+ console.info('\n📊 [Compiler Performance Summary]')
48
+ console.info(
49
+ `Babel: ${perfStats.babel.totalTransforms} transforms / ${perfStats.babel.totalCalls} calls (${((perfStats.babel.totalTransforms / Math.max(perfStats.babel.totalCalls, 1)) * 100).toFixed(1)}% transform rate)`
50
+ )
51
+ console.info(`Babel total time: ${perfStats.babel.totalTime}ms`)
52
+
53
+ for (const [env, stats] of Object.entries(perfStats.babel.byEnvironment)) {
54
+ if (stats.transforms > 0) {
55
+ console.info(
56
+ ` ${env}: ${stats.transforms} transforms, ${stats.time}ms (${(stats.time / stats.transforms).toFixed(1)}ms avg)`
57
+ )
58
+ }
59
+ }
60
+
61
+ for (const [env, stats] of Object.entries(perfStats.optimizeDeps.byEnvironment)) {
62
+ const elapsed = Date.now() - stats.startTime
63
+ console.info(
64
+ `optimizeDeps ${env}: checked ${stats.filesChecked} files, transformed ${stats.filesTransformed} (${elapsed}ms)`
65
+ )
66
+ }
67
+ }
68
+
24
69
  // Shared Babel transform logic
25
70
  async function performBabelTransform({
26
71
  id,
@@ -37,6 +82,13 @@ async function performBabelTransform({
37
82
  reactForRNVersion: '18' | '19'
38
83
  optionsIn?: Partial<Options>
39
84
  }) {
85
+ // Track stats
86
+ perfStats.babel.totalCalls++
87
+ if (!perfStats.babel.byEnvironment[environment]) {
88
+ perfStats.babel.byEnvironment[environment] = { calls: 0, transforms: 0, time: 0 }
89
+ }
90
+ perfStats.babel.byEnvironment[environment].calls++
91
+
40
92
  const transformProps: GetTransformProps = {
41
93
  id,
42
94
  code,
@@ -60,11 +112,33 @@ async function performBabelTransform({
60
112
  })
61
113
 
62
114
  if (babelOptions) {
115
+ // Check cache first
116
+ const cached = getCachedTransform(id, code, environment)
117
+ if (cached) {
118
+ perfStats.babel.byEnvironment[environment].transforms++
119
+ debug?.(`[babel/cached] ${id}`)
120
+ return cached
121
+ }
122
+
123
+ // Cache miss - do the transform
124
+ const startTime = Date.now()
63
125
  const babelOut = await transformBabel(id, code, babelOptions)
126
+ const babelTime = Date.now() - startTime
127
+
64
128
  if (babelOut?.code) {
129
+ perfStats.babel.totalTransforms++
130
+ perfStats.babel.totalTime += babelTime
131
+ perfStats.babel.byEnvironment[environment].transforms++
132
+ perfStats.babel.byEnvironment[environment].time += babelTime
133
+
65
134
  debug?.(`[babel] ${id}`)
66
135
  const outCode = `${babelOut.code}\n// vxrn-did-babel`
67
- return { code: outCode, map: babelOut.map }
136
+ const result = { code: outCode, map: babelOut.map }
137
+
138
+ // Cache the result
139
+ setCachedTransform(id, code, result, environment)
140
+
141
+ return result
68
142
  }
69
143
  }
70
144
  }
@@ -121,7 +195,7 @@ async function performFullTransform({
121
195
  let code = codeIn
122
196
  let out: {
123
197
  code: string
124
- map: any
198
+ map?: any
125
199
  } | null = null
126
200
 
127
201
  // avoid double-processing files already handled by optimizeDeps
@@ -289,9 +363,20 @@ ${rootJS.code}
289
363
  {
290
364
  name: `transform-before-optimize-deps-${environment}`,
291
365
  setup(build) {
366
+ // Init stats for this environment
367
+ if (!perfStats.optimizeDeps.byEnvironment[environment]) {
368
+ perfStats.optimizeDeps.byEnvironment[environment] = {
369
+ filesChecked: 0,
370
+ filesTransformed: 0,
371
+ startTime: Date.now(),
372
+ }
373
+ }
374
+
292
375
  build.onLoad(
293
376
  { filter: /node_modules\/.*\.(tsx?|jsx?|mjs|cjs)$/ },
294
377
  async (args) => {
378
+ perfStats.optimizeDeps.byEnvironment[environment].filesChecked++
379
+
295
380
  const production = process.env.NODE_ENV === 'production'
296
381
  const code = await readFile(args.path, 'utf-8')
297
382
 
@@ -310,6 +395,8 @@ ${rootJS.code}
310
395
  return null
311
396
  }
312
397
 
398
+ perfStats.optimizeDeps.byEnvironment[environment].filesTransformed++
399
+
313
400
  // Determine loader based on file extension
314
401
  const ext = extname(args.path)
315
402
  const loader =
@@ -327,6 +414,25 @@ ${rootJS.code}
327
414
  }
328
415
  }
329
416
  )
417
+
418
+ build.onEnd(() => {
419
+ // Only log detailed stats when debugging
420
+ if (process.env.DEBUG_COMPILER_PERF) {
421
+ const stats = perfStats.optimizeDeps.byEnvironment[environment]
422
+ const elapsed = Date.now() - stats.startTime
423
+ console.info(
424
+ `[optimizeDeps ${environment}] Done: ${stats.filesChecked} files checked, ${stats.filesTransformed} transformed (${elapsed}ms)`
425
+ )
426
+ }
427
+
428
+ // Log cache stats when all environments are done
429
+ const allDone =
430
+ Object.keys(perfStats.optimizeDeps.byEnvironment).length >= 2
431
+ if (allDone) {
432
+ logCacheStats()
433
+ logPerfSummary()
434
+ }
435
+ })
330
436
  },
331
437
  },
332
438
  ],
@@ -243,11 +243,18 @@ const REANIMATED_AUTOWORKLETIZATION_KEYWORDS = [
243
243
  */
244
244
  const REANIMATED_REGEX = new RegExp(REANIMATED_AUTOWORKLETIZATION_KEYWORDS.join('|'))
245
245
 
246
+ // Packages to skip for reanimated babel transform
247
+ // These either have false positives (mention keywords but don't use worklets)
248
+ // or cause issues when transformed
246
249
  const REANIMATED_IGNORED_PATHS = [
247
- // React and React Native libraries are not likely to use reanimated.
248
- // This can also avoid the "[BABEL] Note: The code generator has deoptimised the styling of ... as it exceeds the max of 500KB" warning since the react-native source code also contains `useAnimatedProps`.
250
+ // Prebuilt/vendored react-native that shouldn't be transformed
249
251
  'react-native-prebuilt',
250
252
  'node_modules/.vxrn/react-native',
253
+ // Known false positives - they mention worklet keywords in comments/strings but don't use them
254
+ 'node_modules/react/',
255
+ 'node_modules/react-dom/',
256
+ 'node_modules/react-native/',
257
+ 'node_modules/react-native-web/',
251
258
  ]
252
259
 
253
260
  const REANIMATED_IGNORED_PATHS_REGEX = new RegExp(
@@ -258,10 +265,18 @@ function shouldBabelReanimated({ code, id }: Props) {
258
265
  if (!configuration.enableReanimated) {
259
266
  return false
260
267
  }
261
- if (!REANIMATED_IGNORED_PATHS_REGEX.test(id) && REANIMATED_REGEX.test(code)) {
262
- debug?.(` 🪄 [reanimated] ${relative(process.cwd(), id)}`)
268
+
269
+ // Check if path should be ignored
270
+ if (REANIMATED_IGNORED_PATHS_REGEX.test(id)) {
271
+ return false
272
+ }
273
+
274
+ // Check regex for all files (both node_modules and user code)
275
+ if (REANIMATED_REGEX.test(code)) {
276
+ const location = id.includes('node_modules') ? 'node_modules' : 'user-code'
277
+ debug?.(` 🪄 [reanimated/${location}] ${relative(process.cwd(), id)}`)
263
278
  return true
264
279
  }
265
- debug?.(` not using reanimated ${relative(process.cwd(), id)}`)
280
+
266
281
  return false
267
282
  }
@@ -0,0 +1,17 @@
1
+ interface CacheStats {
2
+ hits: 0;
3
+ misses: 0;
4
+ writes: 0;
5
+ }
6
+ export declare function getCachedTransform(filePath: string, code: string, environment: string): {
7
+ code: string;
8
+ map?: any;
9
+ } | null;
10
+ export declare function setCachedTransform(filePath: string, code: string, result: {
11
+ code: string;
12
+ map?: any;
13
+ }, environment: string): void;
14
+ export declare function getCacheStats(): CacheStats;
15
+ export declare function logCacheStats(): void;
16
+ export {};
17
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAoBA,UAAU,UAAU;IAClB,IAAI,EAAE,CAAC,CAAA;IACP,MAAM,EAAE,CAAC,CAAA;IACT,MAAM,EAAE,CAAC,CAAA;CACV;AAwBD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,GAAG,CAAA;CAAE,GAAG,IAAI,CAkCpC;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,GAAG,CAAA;CAAE,EACnC,WAAW,EAAE,MAAM,GAClB,IAAI,CAsBN;AAED,wBAAgB,aAAa,IAAI,UAAU,CAE1C;AAED,wBAAgB,aAAa,IAAI,IAAI,CAapC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,KAAK,EAAE,YAAY,EAA8B,MAAM,MAAM,CAAA;AAKpE,OAAO,KAAK,EAAkC,OAAO,EAAE,MAAM,SAAS,CAAA;AAEtE,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AA0J3C,wBAAsB,wBAAwB,CAC5C,SAAS,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAC3B,OAAO,CAAC,YAAY,EAAE,CAAC,CA0NzB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,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;AAmO3C,wBAAsB,wBAAwB,CAC5C,SAAS,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAC3B,OAAO,CAAC,YAAY,EAAE,CAAC,CA0PzB"}