@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/dist/cjs/cache.cjs +97 -0
- package/dist/cjs/cache.js +76 -0
- package/dist/cjs/cache.js.map +6 -0
- package/dist/cjs/index.cjs +57 -8
- package/dist/cjs/index.js +56 -7
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/transformBabel.cjs +10 -4
- package/dist/cjs/transformBabel.js +14 -4
- package/dist/cjs/transformBabel.js.map +1 -1
- package/dist/esm/cache.js +62 -0
- package/dist/esm/cache.js.map +6 -0
- package/dist/esm/cache.mjs +71 -0
- package/dist/esm/cache.mjs.map +1 -0
- package/dist/esm/index.js +56 -5
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.mjs +55 -6
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/transformBabel.js +14 -4
- package/dist/esm/transformBabel.js.map +1 -1
- package/dist/esm/transformBabel.mjs +10 -4
- package/dist/esm/transformBabel.mjs.map +1 -1
- package/package.json +4 -4
- package/src/cache.ts +135 -0
- package/src/index.ts +108 -2
- package/src/transformBabel.ts +20 -5
- package/types/cache.d.ts +17 -0
- package/types/cache.d.ts.map +1 -0
- package/types/index.d.ts.map +1 -1
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
|
-
|
|
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
|
|
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
|
],
|
package/src/transformBabel.ts
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
|
|
262
|
-
|
|
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
|
-
|
|
280
|
+
|
|
266
281
|
return false
|
|
267
282
|
}
|
package/types/cache.d.ts
ADDED
|
@@ -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"}
|
package/types/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|