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