@ecrindigital/facetpack 0.1.10 → 0.2.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/dist/index.js CHANGED
@@ -6,15 +6,277 @@ import {
6
6
  analyzeSync,
7
7
  shakeSync
8
8
  } from "@ecrindigital/facetpack-native";
9
+
10
+ // src/stats.ts
11
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from "fs";
12
+ import { tmpdir } from "os";
13
+ import { join } from "path";
14
+ var ANSI = {
15
+ green: "\x1B[32m",
16
+ yellow: "\x1B[33m",
17
+ cyan: "\x1B[36m",
18
+ white: "\x1B[37m",
19
+ gray: "\x1B[90m",
20
+ bold: "\x1B[1m",
21
+ dim: "\x1B[2m",
22
+ reset: "\x1B[0m"
23
+ };
24
+ var STATS_DIR = join(tmpdir(), "facetpack-stats");
25
+ var STATS_FILE_PREFIX = "stats-";
26
+ var LOCK_FILE = join(STATS_DIR, ".print-lock");
27
+ var PRINT_DELAY = 1000;
28
+ function getStatsFilePath() {
29
+ const workerId = process.env.METRO_WORKER_ID || process.pid.toString();
30
+ return join(STATS_DIR, `${STATS_FILE_PREFIX}${workerId}.json`);
31
+ }
32
+ function ensureStatsDir() {
33
+ if (!existsSync(STATS_DIR)) {
34
+ mkdirSync(STATS_DIR, { recursive: true });
35
+ }
36
+ }
37
+ function acquirePrintLock() {
38
+ try {
39
+ ensureStatsDir();
40
+ if (existsSync(LOCK_FILE)) {
41
+ const lockTime = parseInt(readFileSync(LOCK_FILE, "utf-8"), 10);
42
+ if (Date.now() - lockTime < 5000) {
43
+ return false;
44
+ }
45
+ }
46
+ writeFileSync(LOCK_FILE, Date.now().toString());
47
+ return true;
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
52
+ function releasePrintLock() {
53
+ try {
54
+ if (existsSync(LOCK_FILE)) {
55
+ unlinkSync(LOCK_FILE);
56
+ }
57
+ } catch {}
58
+ }
59
+
60
+ class GlobalStats {
61
+ stats = this.createEmptyStats();
62
+ exitHandlerRegistered = false;
63
+ hasPrinted = false;
64
+ printTimer = null;
65
+ createEmptyStats() {
66
+ return {
67
+ transformer: { oxc: 0, babel: 0 },
68
+ resolver: { facetpack: 0, metro: 0 },
69
+ minifier: { files: 0, originalSize: 0, minifiedSize: 0 },
70
+ treeShaking: { modulesAnalyzed: 0, modulesRemoved: 0, exportsRemoved: 0 },
71
+ startTime: Date.now()
72
+ };
73
+ }
74
+ recordTransform(engine) {
75
+ this.stats.transformer[engine]++;
76
+ this.persistStats();
77
+ this.schedulePrint();
78
+ }
79
+ adjustTransformFallback() {
80
+ this.stats.transformer.oxc--;
81
+ this.stats.transformer.babel++;
82
+ this.persistStats();
83
+ }
84
+ recordResolve(engine) {
85
+ this.stats.resolver[engine]++;
86
+ }
87
+ flush() {
88
+ this.persistStats();
89
+ }
90
+ schedulePrint() {
91
+ if (this.printTimer) {
92
+ clearTimeout(this.printTimer);
93
+ }
94
+ this.printTimer = setTimeout(() => {
95
+ if (acquirePrintLock()) {
96
+ this.print();
97
+ releasePrintLock();
98
+ }
99
+ }, PRINT_DELAY);
100
+ }
101
+ recordMinify(originalSize, minifiedSize) {
102
+ this.stats.minifier.files++;
103
+ this.stats.minifier.originalSize += originalSize;
104
+ this.stats.minifier.minifiedSize += minifiedSize;
105
+ }
106
+ recordTreeShaking(analyzed, removed, exports) {
107
+ this.stats.treeShaking.modulesAnalyzed += analyzed;
108
+ this.stats.treeShaking.modulesRemoved += removed;
109
+ this.stats.treeShaking.exportsRemoved += exports;
110
+ }
111
+ get() {
112
+ return JSON.parse(JSON.stringify(this.stats));
113
+ }
114
+ reset() {
115
+ this.stats = this.createEmptyStats();
116
+ this.hasPrinted = false;
117
+ this.cleanupStatsFiles();
118
+ }
119
+ persistStats() {
120
+ try {
121
+ ensureStatsDir();
122
+ writeFileSync(getStatsFilePath(), JSON.stringify(this.stats));
123
+ } catch {}
124
+ }
125
+ cleanupStatsFiles() {
126
+ try {
127
+ if (existsSync(STATS_DIR)) {
128
+ const { readdirSync } = __require("fs");
129
+ const files = readdirSync(STATS_DIR);
130
+ for (const file of files) {
131
+ if (file.startsWith(STATS_FILE_PREFIX)) {
132
+ try {
133
+ unlinkSync(join(STATS_DIR, file));
134
+ } catch {}
135
+ }
136
+ }
137
+ }
138
+ } catch {}
139
+ }
140
+ aggregateWorkerStats() {
141
+ const aggregated = this.createEmptyStats();
142
+ aggregated.startTime = this.stats.startTime;
143
+ try {
144
+ if (existsSync(STATS_DIR)) {
145
+ const { readdirSync } = __require("fs");
146
+ const files = readdirSync(STATS_DIR);
147
+ for (const file of files) {
148
+ if (file.startsWith(STATS_FILE_PREFIX)) {
149
+ try {
150
+ const content = readFileSync(join(STATS_DIR, file), "utf-8");
151
+ const workerStats = JSON.parse(content);
152
+ aggregated.transformer.oxc += workerStats.transformer.oxc;
153
+ aggregated.transformer.babel += workerStats.transformer.babel;
154
+ aggregated.resolver.facetpack += workerStats.resolver.facetpack;
155
+ aggregated.resolver.metro += workerStats.resolver.metro;
156
+ if (workerStats.startTime < aggregated.startTime) {
157
+ aggregated.startTime = workerStats.startTime;
158
+ }
159
+ } catch {}
160
+ }
161
+ }
162
+ }
163
+ } catch {}
164
+ aggregated.minifier = this.stats.minifier;
165
+ aggregated.treeShaking = this.stats.treeShaking;
166
+ return aggregated;
167
+ }
168
+ formatPercent(value, total) {
169
+ if (total === 0)
170
+ return "0.0";
171
+ return (value / total * 100).toFixed(1);
172
+ }
173
+ formatSize(bytes) {
174
+ if (bytes < 1024)
175
+ return `${bytes} B`;
176
+ if (bytes < 1024 * 1024)
177
+ return `${(bytes / 1024).toFixed(1)} KB`;
178
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
179
+ }
180
+ formatDuration(ms) {
181
+ if (ms < 1000)
182
+ return `${ms}ms`;
183
+ return `${(ms / 1000).toFixed(2)}s`;
184
+ }
185
+ print() {
186
+ if (!process.env.FACETPACK_DEBUG || this.hasPrinted)
187
+ return;
188
+ const aggregated = this.aggregateWorkerStats();
189
+ const { transformer, resolver, minifier, treeShaking, startTime } = aggregated;
190
+ const duration = Date.now() - startTime;
191
+ const transformTotal = transformer.oxc + transformer.babel;
192
+ const resolveTotal = resolver.facetpack + resolver.metro;
193
+ if (transformTotal === 0 && resolveTotal === 0 && minifier.files === 0)
194
+ return;
195
+ this.hasPrinted = true;
196
+ const { cyan, green, yellow, white, gray, bold, dim, reset } = ANSI;
197
+ console.log(`
198
+ `);
199
+ console.log(`${bold}${cyan}╔════════════════════════════════════════════════════════════════════╗${reset}`);
200
+ console.log(`${bold}${cyan}║${reset} ${bold}FACETPACK BUNDLE STATS${reset} ${cyan}║${reset}`);
201
+ console.log(`${bold}${cyan}╠════════════════════════════════════════════════════════════════════╣${reset}`);
202
+ if (transformTotal > 0) {
203
+ const oxcPct = this.formatPercent(transformer.oxc, transformTotal);
204
+ const babelPct = this.formatPercent(transformer.babel, transformTotal);
205
+ console.log(`${cyan}║${reset} ${cyan}║${reset}`);
206
+ console.log(`${cyan}║${reset} ${bold}TRANSFORMER${reset} ${cyan}║${reset}`);
207
+ console.log(`${cyan}║${reset} ${green}●${reset} OXC (native) ${bold}${green}${transformer.oxc.toString().padStart(6)}${reset} files ${green}${oxcPct.padStart(6)}%${reset} ${cyan}║${reset}`);
208
+ console.log(`${cyan}║${reset} ${yellow}●${reset} Babel ${bold}${yellow}${transformer.babel.toString().padStart(6)}${reset} files ${yellow}${babelPct.padStart(6)}%${reset} ${cyan}║${reset}`);
209
+ console.log(`${cyan}║${reset} ${dim}${white} Total ${transformTotal.toString().padStart(6)} files${reset} ${cyan}║${reset}`);
210
+ }
211
+ if (resolveTotal > 0) {
212
+ const fpPct = this.formatPercent(resolver.facetpack, resolveTotal);
213
+ const metroPct = this.formatPercent(resolver.metro, resolveTotal);
214
+ console.log(`${cyan}║${reset} ${cyan}║${reset}`);
215
+ console.log(`${cyan}║${reset} ${bold}RESOLVER${reset} ${cyan}║${reset}`);
216
+ console.log(`${cyan}║${reset} ${green}●${reset} Facetpack ${bold}${green}${resolver.facetpack.toString().padStart(6)}${reset} hits ${green}${fpPct.padStart(6)}%${reset} ${cyan}║${reset}`);
217
+ console.log(`${cyan}║${reset} ${yellow}●${reset} Metro ${bold}${yellow}${resolver.metro.toString().padStart(6)}${reset} hits ${yellow}${metroPct.padStart(6)}%${reset} ${cyan}║${reset}`);
218
+ console.log(`${cyan}║${reset} ${dim}${white} Total ${resolveTotal.toString().padStart(6)} resolutions${reset} ${cyan}║${reset}`);
219
+ }
220
+ if (minifier.files > 0) {
221
+ const savings = minifier.originalSize - minifier.minifiedSize;
222
+ const savingsPct = this.formatPercent(savings, minifier.originalSize);
223
+ console.log(`${cyan}║${reset} ${cyan}║${reset}`);
224
+ console.log(`${cyan}║${reset} ${bold}MINIFIER${reset} ${cyan}║${reset}`);
225
+ console.log(`${cyan}║${reset} ${green}●${reset} Files minified ${bold}${green}${minifier.files.toString().padStart(6)}${reset} ${cyan}║${reset}`);
226
+ console.log(`${cyan}║${reset} ${gray}●${reset} Original size ${this.formatSize(minifier.originalSize).padStart(12)} ${cyan}║${reset}`);
227
+ console.log(`${cyan}║${reset} ${green}●${reset} Minified size ${this.formatSize(minifier.minifiedSize).padStart(12)} ${green}-${savingsPct}%${reset} ${cyan}║${reset}`);
228
+ }
229
+ if (treeShaking.modulesAnalyzed > 0) {
230
+ console.log(`${cyan}║${reset} ${cyan}║${reset}`);
231
+ console.log(`${cyan}║${reset} ${bold}TREE SHAKING${reset} ${cyan}║${reset}`);
232
+ console.log(`${cyan}║${reset} ${gray}●${reset} Modules analyzed ${bold}${treeShaking.modulesAnalyzed.toString().padStart(5)}${reset} ${cyan}║${reset}`);
233
+ console.log(`${cyan}║${reset} ${green}●${reset} Modules removed ${bold}${green}${treeShaking.modulesRemoved.toString().padStart(5)}${reset} ${cyan}║${reset}`);
234
+ console.log(`${cyan}║${reset} ${green}●${reset} Exports removed ${bold}${green}${treeShaking.exportsRemoved.toString().padStart(5)}${reset} ${cyan}║${reset}`);
235
+ }
236
+ console.log(`${cyan}║${reset} ${cyan}║${reset}`);
237
+ console.log(`${cyan}║${reset} ${dim}Duration: ${this.formatDuration(duration)}${reset} ${cyan}║${reset}`);
238
+ console.log(`${bold}${cyan}╚════════════════════════════════════════════════════════════════════╝${reset}`);
239
+ console.log(`
240
+ `);
241
+ this.cleanupStatsFiles();
242
+ }
243
+ registerExitHandler() {
244
+ if (this.exitHandlerRegistered)
245
+ return;
246
+ this.exitHandlerRegistered = true;
247
+ process.on("SIGINT", () => {
248
+ this.print();
249
+ process.exit(0);
250
+ });
251
+ process.on("beforeExit", () => this.print());
252
+ }
253
+ }
254
+ var globalStats = new GlobalStats;
255
+ function printStats() {
256
+ globalStats.print();
257
+ }
258
+ function resetStats() {
259
+ globalStats.reset();
260
+ }
261
+ function getStats() {
262
+ return globalStats.get();
263
+ }
264
+
265
+ // src/serializer.ts
9
266
  function createFacetpackSerializer(existingSerializer, config = {}) {
10
267
  return async (entryPoint, preModules, graph, options) => {
11
268
  if (options.dev || config.treeShake === false) {
269
+ let result2;
12
270
  if (existingSerializer) {
13
- return existingSerializer(entryPoint, preModules, graph, options);
271
+ result2 = await existingSerializer(entryPoint, preModules, graph, options);
272
+ } else {
273
+ result2 = defaultSerialize(entryPoint, preModules, graph, options);
14
274
  }
15
- return defaultSerialize(entryPoint, preModules, graph, options);
275
+ globalStats.print();
276
+ return result2;
16
277
  }
17
278
  const analyses = new Map;
279
+ let modulesAnalyzed = 0;
18
280
  for (const [path, module] of graph.dependencies) {
19
281
  if (path.includes("node_modules")) {
20
282
  continue;
@@ -23,6 +285,7 @@ function createFacetpackSerializer(existingSerializer, config = {}) {
23
285
  const code = module.output[0]?.data?.code ?? "";
24
286
  const analysis = analyzeSync(path, code);
25
287
  analyses.set(path, analysis);
288
+ modulesAnalyzed++;
26
289
  } catch {
27
290
  analyses.set(path, {
28
291
  exports: [],
@@ -33,7 +296,8 @@ function createFacetpackSerializer(existingSerializer, config = {}) {
33
296
  }
34
297
  const usedExports = computeUsedExports(entryPoint, analyses, graph);
35
298
  const shakenModules = new Map;
36
- let totalRemoved = 0;
299
+ let modulesRemoved = 0;
300
+ let exportsRemoved = 0;
37
301
  for (const [path, module] of graph.dependencies) {
38
302
  if (path.includes("node_modules")) {
39
303
  const code = module.output[0]?.data?.code ?? "";
@@ -43,28 +307,30 @@ function createFacetpackSerializer(existingSerializer, config = {}) {
43
307
  const used = usedExports.get(path);
44
308
  const analysis = analyses.get(path);
45
309
  if ((!used || used.size === 0) && analysis && !analysis.hasSideEffects) {
46
- totalRemoved++;
310
+ modulesRemoved++;
47
311
  continue;
48
312
  }
49
313
  try {
50
314
  const code = module.output[0]?.data?.code ?? "";
51
315
  const usedArray = used ? Array.from(used) : ["*"];
52
- const result = shakeSync(path, code, usedArray);
53
- shakenModules.set(path, { code: result.code, map: result.map ?? undefined });
54
- totalRemoved += result.removedExports.length;
316
+ const result2 = shakeSync(path, code, usedArray);
317
+ shakenModules.set(path, { code: result2.code, map: result2.map ?? undefined });
318
+ exportsRemoved += result2.removedExports.length;
55
319
  } catch {
56
320
  const code = module.output[0]?.data?.code ?? "";
57
321
  shakenModules.set(path, { code });
58
322
  }
59
323
  }
60
- if (totalRemoved > 0) {
61
- console.log(`[facetpack] Tree-shaking removed ${totalRemoved} unused exports`);
62
- }
324
+ globalStats.recordTreeShaking(modulesAnalyzed, modulesRemoved, exportsRemoved);
325
+ let result;
63
326
  if (existingSerializer) {
64
327
  const shakenGraph = createShakenGraph(graph, shakenModules);
65
- return existingSerializer(entryPoint, preModules, shakenGraph, options);
328
+ result = await existingSerializer(entryPoint, preModules, shakenGraph, options);
329
+ } else {
330
+ result = defaultSerialize(entryPoint, preModules, graph, options, shakenModules);
66
331
  }
67
- return defaultSerialize(entryPoint, preModules, graph, options, shakenModules);
332
+ globalStats.print();
333
+ return result;
68
334
  };
69
335
  }
70
336
  function computeUsedExports(entryPoint, analyses, graph) {
@@ -207,8 +473,112 @@ function getCacheStats() {
207
473
  }
208
474
 
209
475
  // src/transformer.ts
476
+ var ANSI2 = {
477
+ green: "\x1B[32m",
478
+ yellow: "\x1B[33m",
479
+ cyan: "\x1B[36m",
480
+ bold: "\x1B[1m",
481
+ reset: "\x1B[0m"
482
+ };
210
483
  var IMPORT_REGEX = /(?:import|export)\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
211
484
  var REQUIRE_REGEX = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
485
+ var BABEL_REQUIRED_PATTERNS = [
486
+ /'worklet'/,
487
+ /"worklet"/,
488
+ /require\.context\s*\(/,
489
+ /useAnimatedStyle/,
490
+ /useAnimatedProps/,
491
+ /useAnimatedScrollHandler/,
492
+ /useAnimatedGestureHandler/,
493
+ /useAnimatedReaction/,
494
+ /useDerivedValue/,
495
+ /useAnimatedSensor/,
496
+ /useFrameCallback/,
497
+ /useScrollViewOffset/,
498
+ /runOnUI/,
499
+ /runOnJS/
500
+ ];
501
+ var DEFAULT_OPTIONS = {
502
+ jsx: true,
503
+ jsxRuntime: "automatic",
504
+ jsxImportSource: "react",
505
+ jsxPragma: "React.createElement",
506
+ jsxPragmaFrag: "React.Fragment",
507
+ typescript: true,
508
+ sourceExts: ["ts", "tsx", "js", "jsx", "mjs", "cjs"],
509
+ minifier: true,
510
+ treeShake: true,
511
+ noAst: false
512
+ };
513
+
514
+ class Logger {
515
+ startupLogged = false;
516
+ logStartup() {
517
+ if (this.startupLogged || !process.env.FACETPACK_DEBUG)
518
+ return;
519
+ this.startupLogged = true;
520
+ console.log(`${ANSI2.cyan}${ANSI2.bold}[Facetpack]${ANSI2.reset} Transformer loaded`);
521
+ }
522
+ logTransform(decision, filename) {
523
+ if (!process.env.FACETPACK_DEBUG)
524
+ return;
525
+ const color = decision === "oxc" ? ANSI2.green : ANSI2.yellow;
526
+ console.log(`${color}[Facetpack]${ANSI2.reset} ${decision.toUpperCase()}: ${filename}`);
527
+ }
528
+ logFallback(filename, error) {
529
+ if (!process.env.FACETPACK_DEBUG)
530
+ return;
531
+ const message = error instanceof Error ? error.message : String(error);
532
+ console.log(`${ANSI2.yellow}[Facetpack]${ANSI2.reset} OXC failed, falling back to Babel: ${filename}`);
533
+ console.log(`${ANSI2.yellow}[Facetpack]${ANSI2.reset} Error: ${message}`);
534
+ }
535
+ }
536
+
537
+ class OptionsManager {
538
+ globalOptions = {};
539
+ setGlobal(options) {
540
+ this.globalOptions = options;
541
+ }
542
+ get() {
543
+ return { ...DEFAULT_OPTIONS, ...this.globalOptions, ...this.getFromEnv() };
544
+ }
545
+ merge(options) {
546
+ return { ...DEFAULT_OPTIONS, ...options };
547
+ }
548
+ getFromEnv() {
549
+ try {
550
+ const json = process.env.FACETPACK_OPTIONS;
551
+ return json ? JSON.parse(json) : {};
552
+ } catch {
553
+ return {};
554
+ }
555
+ }
556
+ }
557
+
558
+ class FallbackTransformerManager {
559
+ instance = null;
560
+ get() {
561
+ if (this.instance)
562
+ return this.instance;
563
+ const envPath = process.env.FACETPACK_FALLBACK_TRANSFORMER;
564
+ if (envPath) {
565
+ try {
566
+ this.instance = __require(envPath);
567
+ return this.instance;
568
+ } catch (e) {
569
+ console.warn(`[Facetpack] Failed to load fallback transformer from ${envPath}:`, e);
570
+ }
571
+ }
572
+ this.instance = {
573
+ transform: ({ src }) => ({ code: src, map: null })
574
+ };
575
+ return this.instance;
576
+ }
577
+ }
578
+ var logger = new Logger;
579
+ var options = new OptionsManager;
580
+ var fallback = new FallbackTransformerManager;
581
+ logger.logStartup();
212
582
  function extractSpecifiers(code) {
213
583
  const specifiers = new Set;
214
584
  let match;
@@ -237,211 +607,126 @@ function preResolveImports(filename, code, sourceExts) {
237
607
  const specifier = specifiers[i];
238
608
  if (specifier) {
239
609
  resolutions.set(specifier, results[i]?.path ?? null);
610
+ if (results[i]?.path) {
611
+ globalStats.recordResolve("facetpack");
612
+ }
240
613
  }
241
614
  }
242
615
  setCachedResolutions(filename, resolutions);
243
616
  }
244
- var defaultOptions = {
245
- jsx: true,
246
- jsxRuntime: "automatic",
247
- jsxImportSource: "react",
248
- jsxPragma: "React.createElement",
249
- jsxPragmaFrag: "React.Fragment",
250
- typescript: true,
251
- sourceExts: ["ts", "tsx", "js", "jsx", "mjs", "cjs"],
252
- minifier: true,
253
- treeShake: true,
254
- noAst: false
255
- };
256
- var globalOptions = {};
257
- var fallbackTransformer = null;
258
- function getFallbackTransformer() {
259
- if (fallbackTransformer) {
260
- return fallbackTransformer;
261
- }
262
- const fallbackPath = process.env.FACETPACK_FALLBACK_TRANSFORMER;
263
- if (fallbackPath) {
264
- try {
265
- fallbackTransformer = __require(fallbackPath);
266
- return fallbackTransformer;
267
- } catch (e) {
268
- console.warn(`[Facetpack] Failed to load fallback transformer from ${fallbackPath}:`, e);
269
- }
270
- }
271
- fallbackTransformer = {
272
- transform: ({ src }) => ({ code: src, map: null })
273
- };
274
- return fallbackTransformer;
275
- }
276
- function setTransformerOptions(options) {
277
- globalOptions = options;
278
- }
279
- function getStoredOptions() {
280
- try {
281
- const optionsJson = process.env.FACETPACK_OPTIONS;
282
- if (optionsJson) {
283
- return JSON.parse(optionsJson);
284
- }
285
- } catch {}
286
- return {};
287
- }
288
- function getOptions() {
289
- const storedOptions = getStoredOptions();
290
- return { ...defaultOptions, ...globalOptions, ...storedOptions };
617
+ function requiresBabelTransform(src) {
618
+ return BABEL_REQUIRED_PATTERNS.some((pattern) => pattern.test(src));
291
619
  }
292
620
  function isNodeModules(filename) {
293
621
  return filename.includes("node_modules");
294
622
  }
295
- var BABEL_REQUIRED_PATTERNS = [
296
- /'worklet'/,
297
- /"worklet"/,
298
- /useAnimatedStyle/,
299
- /useAnimatedProps/,
300
- /useDerivedValue/,
301
- /useAnimatedReaction/,
302
- /useAnimatedScrollHandler/,
303
- /useAnimatedGestureHandler/,
304
- /runOnUI/,
305
- /runOnJS/
306
- ];
307
- var HERMES_COMPAT_PATTERNS = [
308
- /\basync\s+function\b/,
309
- /\basync\s*\(/,
310
- /\basync\s+\w+\s*\(/,
311
- /=\s*async\s*\(/,
312
- /=\s*async\s+function\b/
313
- ];
314
- function requiresBabelTransform(src) {
315
- return BABEL_REQUIRED_PATTERNS.some((pattern) => pattern.test(src));
623
+ function getFileExtension(filename) {
624
+ return filename.split(".").pop()?.toLowerCase();
316
625
  }
317
- function requiresHermesCompat(src) {
318
- return HERMES_COMPAT_PATTERNS.some((pattern) => pattern.test(src));
626
+ function getTransformDecision(filename, src, opts) {
627
+ if (requiresBabelTransform(src))
628
+ return "babel";
629
+ if (isNodeModules(filename))
630
+ return "babel";
631
+ const ext = getFileExtension(filename);
632
+ if (!ext || !opts.sourceExts.includes(ext))
633
+ return "babel";
634
+ return "oxc";
319
635
  }
320
- function shouldTransform(filename, src, options) {
321
- if (isNodeModules(filename)) {
322
- return false;
323
- }
324
- if (requiresBabelTransform(src)) {
325
- if (process.env.FACETPACK_DEBUG) {
326
- console.log(`[Facetpack] Babel required for worklets: ${filename}`);
636
+ function formatDiagnostics(diagnostics) {
637
+ return diagnostics.map((d) => {
638
+ if (d.formatted)
639
+ return d.formatted;
640
+ if (!d.message)
641
+ return "";
642
+ let output = `
643
+ × ${d.message}
644
+ `;
645
+ if (d.snippet) {
646
+ output += ` ╭─[${d.filename}:${d.line}:${d.column}]
647
+ `;
648
+ output += ` ${d.line} │ ${d.snippet}
649
+ `;
650
+ output += ` ╰────
651
+ `;
327
652
  }
328
- return false;
653
+ if (d.help)
654
+ output += ` help: ${d.help}
655
+ `;
656
+ return output;
657
+ }).join(`
658
+ `);
659
+ }
660
+ function transformWithOxc(filename, src, opts, isDev) {
661
+ const parseResult = parseSync(filename, src);
662
+ if (parseResult.errors.length > 0) {
663
+ const error = parseResult.diagnostics.length > 0 ? new Error(`
664
+ ${formatDiagnostics(parseResult.diagnostics)}`) : new Error(`Parse error in ${filename}:
665
+ ${parseResult.errors.join(`
666
+ `)}`);
667
+ error.isParseError = true;
668
+ throw error;
329
669
  }
330
- if (requiresHermesCompat(src)) {
331
- if (process.env.FACETPACK_DEBUG) {
332
- console.log(`[Facetpack] Babel required for async/await (Hermes compat): ${filename}`);
333
- }
334
- return false;
670
+ const isClassic = opts.jsxRuntime === "classic";
671
+ const result = transformSync(filename, src, {
672
+ jsx: opts.jsx,
673
+ jsxRuntime: isClassic ? JsxRuntime.Classic : JsxRuntime.Automatic,
674
+ ...isClassic ? { jsxPragma: opts.jsxPragma, jsxPragmaFrag: opts.jsxPragmaFrag } : { jsxImportSource: opts.jsxImportSource },
675
+ typescript: opts.typescript,
676
+ sourcemap: isDev
677
+ });
678
+ if (result.errors.length > 0) {
679
+ throw new Error(`Facetpack transform error in ${filename}:
680
+ ${result.errors.join(`
681
+ `)}`);
335
682
  }
336
- const ext = filename.split(".").pop()?.toLowerCase();
337
- if (!ext)
338
- return false;
339
- return options.sourceExts.includes(ext);
683
+ preResolveImports(filename, result.code, opts.sourceExts);
684
+ globalStats.flush();
685
+ const ast = parse(result.code, {
686
+ sourceType: "unambiguous",
687
+ plugins: ["jsx"]
688
+ });
689
+ return {
690
+ ast,
691
+ code: result.code,
692
+ map: result.map ? JSON.parse(result.map) : null
693
+ };
694
+ }
695
+ function setTransformerOptions(opts) {
696
+ options.setGlobal(opts);
340
697
  }
341
698
  function transform(params) {
342
699
  const { filename, src, options: metroOptions } = params;
343
- const opts = getOptions();
344
- if (process.env.FACETPACK_DEBUG) {
345
- console.log(`[Facetpack] Processing: ${filename}`);
346
- }
347
- if (!shouldTransform(filename, src, opts)) {
348
- if (process.env.FACETPACK_DEBUG) {
349
- console.log(`[Facetpack] Fallback: ${filename}`);
350
- }
351
- return getFallbackTransformer().transform(params);
352
- }
353
- if (process.env.FACETPACK_DEBUG) {
354
- console.log(`[Facetpack] OXC Transform: ${filename}`);
700
+ const opts = options.get();
701
+ globalStats.registerExitHandler();
702
+ const decision = getTransformDecision(filename, src, opts);
703
+ globalStats.recordTransform(decision);
704
+ logger.logTransform(decision, filename);
705
+ if (decision === "babel") {
706
+ return fallback.get().transform(params);
355
707
  }
356
708
  try {
357
- const parseResult = parseSync(filename, src);
358
- if (parseResult.errors.length > 0 && parseResult.diagnostics.length > 0) {
359
- const formattedErrors = parseResult.diagnostics.map((d) => {
360
- let output2 = d.formatted || "";
361
- if (!output2 && d.message) {
362
- output2 = `
363
- × ${d.message}
364
- `;
365
- if (d.snippet) {
366
- output2 += ` ╭─[${d.filename}:${d.line}:${d.column}]
367
- `;
368
- output2 += ` ${d.line} │ ${d.snippet}
369
- `;
370
- output2 += ` ╰────
371
- `;
372
- }
373
- if (d.help) {
374
- output2 += ` help: ${d.help}
375
- `;
376
- }
377
- }
378
- return output2;
379
- }).join(`
380
- `);
381
- throw new Error(`
382
- ${formattedErrors}`);
383
- }
384
- const isClassic = opts.jsxRuntime === "classic";
385
- const result = transformSync(filename, src, {
386
- jsx: opts.jsx,
387
- jsxRuntime: isClassic ? JsxRuntime.Classic : JsxRuntime.Automatic,
388
- ...isClassic ? { jsxPragma: opts.jsxPragma, jsxPragmaFrag: opts.jsxPragmaFrag } : { jsxImportSource: opts.jsxImportSource },
389
- typescript: opts.typescript,
390
- sourcemap: metroOptions.dev
391
- });
392
- if (result.errors.length > 0) {
393
- const errorMessage = result.errors.join(`
394
- `);
395
- throw new Error(`Facetpack transform error in ${filename}:
396
- ${errorMessage}`);
397
- }
398
- preResolveImports(filename, result.code, opts.sourceExts);
399
- const ast = opts.noAst ? undefined : parse(result.code, {
400
- sourceType: "unambiguous",
401
- plugins: ["jsx"]
402
- });
403
- const output = {
404
- ast,
405
- code: result.code,
406
- map: result.map ? JSON.parse(result.map) : null
407
- };
408
- if (process.env.FACETPACK_DEBUG) {
409
- console.log(`[Facetpack] Output for ${filename}:`);
410
- console.log(result.code.slice(0, 500));
411
- }
412
- return output;
709
+ return transformWithOxc(filename, src, opts, metroOptions.dev);
413
710
  } catch (error) {
414
- if (error instanceof Error) {
415
- error.message = `[Facetpack] ${error.message}`;
711
+ if (error.isParseError) {
712
+ throw error;
416
713
  }
417
- throw error;
714
+ logger.logFallback(filename, error);
715
+ globalStats.adjustTransformFallback();
716
+ return fallback.get().transform(params);
418
717
  }
419
718
  }
420
- function createTransformer(options = {}) {
421
- const opts = { ...defaultOptions, ...options };
719
+ function createTransformer(customOptions = {}) {
720
+ const opts = options.merge(customOptions);
422
721
  return {
423
722
  transform(params) {
424
723
  const { filename, src, options: metroOptions } = params;
425
- if (!shouldTransform(filename, src, opts)) {
426
- return getFallbackTransformer().transform(params);
724
+ const decision = getTransformDecision(filename, src, opts);
725
+ globalStats.recordTransform(decision);
726
+ if (decision === "babel") {
727
+ return fallback.get().transform(params);
427
728
  }
428
- const isClassic = opts.jsxRuntime === "classic";
429
- const result = transformSync(filename, src, {
430
- jsx: opts.jsx,
431
- jsxRuntime: isClassic ? JsxRuntime.Classic : JsxRuntime.Automatic,
432
- ...isClassic ? { jsxPragma: opts.jsxPragma, jsxPragmaFrag: opts.jsxPragmaFrag } : { jsxImportSource: opts.jsxImportSource },
433
- typescript: opts.typescript,
434
- sourcemap: metroOptions.dev
435
- });
436
- if (result.errors.length > 0) {
437
- throw new Error(`Facetpack transform error in ${filename}:
438
- ${result.errors.join(`
439
- `)}`);
440
- }
441
- return {
442
- code: result.code,
443
- map: result.map ? JSON.parse(result.map) : null
444
- };
729
+ return transformWithOxc(filename, src, opts, metroOptions.dev);
445
730
  }
446
731
  };
447
732
  }
@@ -449,7 +734,7 @@ ${result.errors.join(`
449
734
  // src/minifier.ts
450
735
  import { minifySync } from "@ecrindigital/facetpack-native";
451
736
  function minify(input) {
452
- const options = {
737
+ const options2 = {
453
738
  compress: input.config.compress ?? true,
454
739
  mangle: input.config.mangle ?? true,
455
740
  keepFnames: input.config.keep_fnames ?? false,
@@ -457,22 +742,28 @@ function minify(input) {
457
742
  dropDebugger: input.config.drop_debugger ?? true,
458
743
  sourcemap: input.map !== undefined
459
744
  };
460
- const result = minifySync(input.code, input.filename, options);
745
+ const originalSize = Buffer.byteLength(input.code, "utf8");
746
+ const result = minifySync(input.code, input.filename, options2);
747
+ const minifiedSize = Buffer.byteLength(result.code, "utf8");
748
+ globalStats.recordMinify(originalSize, minifiedSize);
461
749
  return {
462
750
  code: result.code,
463
751
  map: result.map ?? undefined
464
752
  };
465
753
  }
466
- function minifyCode(code, filename, options) {
754
+ function minifyCode(code, filename, options2) {
467
755
  const nativeOptions = {
468
- compress: options?.compress ?? true,
469
- mangle: options?.mangle ?? true,
470
- keepFnames: options?.keep_fnames ?? false,
471
- dropConsole: options?.drop_console ?? false,
472
- dropDebugger: options?.drop_debugger ?? true,
756
+ compress: options2?.compress ?? true,
757
+ mangle: options2?.mangle ?? true,
758
+ keepFnames: options2?.keep_fnames ?? false,
759
+ dropConsole: options2?.drop_console ?? false,
760
+ dropDebugger: options2?.drop_debugger ?? true,
473
761
  sourcemap: false
474
762
  };
763
+ const originalSize = Buffer.byteLength(code, "utf8");
475
764
  const result = minifySync(code, filename, nativeOptions);
765
+ const minifiedSize = Buffer.byteLength(result.code, "utf8");
766
+ globalStats.recordMinify(originalSize, minifiedSize);
476
767
  return {
477
768
  code: result.code,
478
769
  map: result.map ?? undefined
@@ -483,15 +774,15 @@ var minifier_default = minify;
483
774
  // src/withFacetpack.ts
484
775
  import { resolveSync } from "@ecrindigital/facetpack-native";
485
776
  import { createRequire as createRequire2 } from "module";
486
- import { join, dirname } from "path";
777
+ import { join as join2, dirname } from "path";
487
778
  var DEFAULT_SOURCE_EXTS = ["ts", "tsx", "js", "jsx", "mjs", "cjs"];
488
779
  function getPackageDir() {
489
- const projectRequire = createRequire2(join(process.cwd(), "package.json"));
780
+ const projectRequire = createRequire2(join2(process.cwd(), "package.json"));
490
781
  const packageJsonPath = projectRequire.resolve("@ecrindigital/facetpack/package.json");
491
782
  return dirname(packageJsonPath);
492
783
  }
493
784
  function findFallbackTransformer(projectRoot) {
494
- const projectRequire = createRequire2(join(projectRoot, "package.json"));
785
+ const projectRequire = createRequire2(join2(projectRoot, "package.json"));
495
786
  const transformerPaths = [
496
787
  "@expo/metro-config/babel-transformer",
497
788
  "@react-native/metro-babel-transformer",
@@ -504,20 +795,20 @@ function findFallbackTransformer(projectRoot) {
504
795
  }
505
796
  return;
506
797
  }
507
- function withFacetpack(config, options = {}) {
508
- const sourceExts = options.sourceExts ?? DEFAULT_SOURCE_EXTS;
798
+ function withFacetpack(config, options2 = {}) {
799
+ const sourceExts = options2.sourceExts ?? DEFAULT_SOURCE_EXTS;
509
800
  const packageDir = getPackageDir();
510
- const transformerPath = join(packageDir, "dist", "transformer.js");
511
- const useMinifier = options.minifier !== false;
512
- const minifierPath = useMinifier ? join(packageDir, "dist", "minifier.js") : config.transformer?.minifierPath;
513
- const minifierConfig = typeof options.minifier === "object" ? options.minifier : {};
514
- const useTreeShake = options.treeShake === true;
801
+ const transformerPath = join2(packageDir, "dist", "transformer.js");
802
+ const useMinifier = options2.minifier !== false;
803
+ const minifierPath = useMinifier ? join2(packageDir, "dist", "minifier.js") : config.transformer?.minifierPath;
804
+ const minifierConfig = typeof options2.minifier === "object" ? options2.minifier : {};
805
+ const useTreeShake = options2.treeShake === true;
515
806
  const existingSerializer = config.serializer?.customSerializer;
516
807
  const customSerializer = useTreeShake ? createFacetpackSerializer(existingSerializer, { treeShake: true }) : existingSerializer;
517
808
  const projectRoot = config.projectRoot || process.cwd();
518
809
  const originalTransformerPath = config.transformer?.babelTransformerPath;
519
810
  const fallbackTransformerPath = originalTransformerPath || findFallbackTransformer(projectRoot);
520
- storeTransformerOptions(options, fallbackTransformerPath);
811
+ storeTransformerOptions(options2, fallbackTransformerPath);
521
812
  const existingResolver = config.resolver?.resolveRequest;
522
813
  return {
523
814
  ...config,
@@ -576,13 +867,13 @@ function withFacetpack(config, options = {}) {
576
867
  }
577
868
  };
578
869
  }
579
- function storeTransformerOptions(options, fallbackTransformerPath) {
580
- process.env.FACETPACK_OPTIONS = JSON.stringify(options);
870
+ function storeTransformerOptions(options2, fallbackTransformerPath) {
871
+ process.env.FACETPACK_OPTIONS = JSON.stringify(options2);
581
872
  if (fallbackTransformerPath) {
582
873
  process.env.FACETPACK_FALLBACK_TRANSFORMER = fallbackTransformerPath;
583
874
  }
584
875
  }
585
- function getStoredOptions2() {
876
+ function getStoredOptions() {
586
877
  try {
587
878
  const optionsJson = process.env.FACETPACK_OPTIONS;
588
879
  if (optionsJson) {
@@ -593,11 +884,16 @@ function getStoredOptions2() {
593
884
  }
594
885
  // src/resolver.ts
595
886
  import { resolveSync as resolveSync2 } from "@ecrindigital/facetpack-native";
596
- function createResolver(options) {
887
+ function createResolver(options2) {
597
888
  return {
598
889
  resolve(originModulePath, moduleName) {
599
890
  const directory = originModulePath.substring(0, originModulePath.lastIndexOf("/"));
600
- const result = resolveSync2(directory, moduleName, options);
891
+ const result = resolveSync2(directory, moduleName, options2);
892
+ if (result.path) {
893
+ globalStats.recordResolve("facetpack");
894
+ } else {
895
+ globalStats.recordResolve("metro");
896
+ }
601
897
  return result.path ?? null;
602
898
  }
603
899
  };
@@ -607,9 +903,12 @@ export {
607
903
  transform,
608
904
  setTransformerOptions,
609
905
  resolveSync2 as resolveSync,
906
+ resetStats,
907
+ printStats,
610
908
  minifyCode,
611
909
  minify,
612
- getStoredOptions2 as getStoredOptions,
910
+ getStoredOptions,
911
+ getStats,
613
912
  getCacheStats,
614
913
  createTransformer,
615
914
  createResolver,