@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.
@@ -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) {
@@ -0,0 +1,55 @@
1
+ interface TransformerStats {
2
+ oxc: number;
3
+ babel: number;
4
+ }
5
+ interface ResolverStats {
6
+ facetpack: number;
7
+ metro: number;
8
+ }
9
+ interface MinifierStats {
10
+ files: number;
11
+ originalSize: number;
12
+ minifiedSize: number;
13
+ }
14
+ interface TreeShakingStats {
15
+ modulesAnalyzed: number;
16
+ modulesRemoved: number;
17
+ exportsRemoved: number;
18
+ }
19
+ interface FacetpackStats {
20
+ transformer: TransformerStats;
21
+ resolver: ResolverStats;
22
+ minifier: MinifierStats;
23
+ treeShaking: TreeShakingStats;
24
+ startTime: number;
25
+ }
26
+ declare class GlobalStats {
27
+ private stats;
28
+ private exitHandlerRegistered;
29
+ private hasPrinted;
30
+ private printTimer;
31
+ private createEmptyStats;
32
+ recordTransform(engine: 'oxc' | 'babel'): void;
33
+ adjustTransformFallback(): void;
34
+ recordResolve(engine: 'facetpack' | 'metro'): void;
35
+ flush(): void;
36
+ schedulePrint(): void;
37
+ recordMinify(originalSize: number, minifiedSize: number): void;
38
+ recordTreeShaking(analyzed: number, removed: number, exports: number): void;
39
+ get(): FacetpackStats;
40
+ reset(): void;
41
+ private persistStats;
42
+ private cleanupStatsFiles;
43
+ aggregateWorkerStats(): FacetpackStats;
44
+ private formatPercent;
45
+ private formatSize;
46
+ private formatDuration;
47
+ print(): void;
48
+ registerExitHandler(): void;
49
+ }
50
+ export declare const globalStats: GlobalStats;
51
+ export declare function printStats(): void;
52
+ export declare function resetStats(): void;
53
+ export declare function getStats(): FacetpackStats;
54
+ export {};
55
+ //# sourceMappingURL=stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"AAeA,UAAU,gBAAgB;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;CACd;AAED,UAAU,aAAa;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,UAAU,gBAAgB;IACxB,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,UAAU,cAAc;IACtB,WAAW,EAAE,gBAAgB,CAAA;IAC7B,QAAQ,EAAE,aAAa,CAAA;IACvB,QAAQ,EAAE,aAAa,CAAA;IACvB,WAAW,EAAE,gBAAgB,CAAA;IAC7B,SAAS,EAAE,MAAM,CAAA;CAClB;AA0CD,cAAM,WAAW;IACf,OAAO,CAAC,KAAK,CAA0C;IACvD,OAAO,CAAC,qBAAqB,CAAQ;IACrC,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,UAAU,CAA6C;IAE/D,OAAO,CAAC,gBAAgB;IAUxB,eAAe,CAAC,MAAM,EAAE,KAAK,GAAG,OAAO,GAAG,IAAI;IAM9C,uBAAuB,IAAI,IAAI;IAM/B,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,GAAG,IAAI;IAIlD,KAAK,IAAI,IAAI;IAIb,aAAa,IAAI,IAAI;IAYrB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAM9D,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAM3E,GAAG,IAAI,cAAc;IAIrB,KAAK,IAAI,IAAI;IAMb,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,iBAAiB;IAgBzB,oBAAoB,IAAI,cAAc;IAmCtC,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,cAAc;IAKtB,KAAK,IAAI,IAAI;IAmEb,mBAAmB,IAAI,IAAI;CAU5B;AAED,eAAO,MAAM,WAAW,aAAoB,CAAA;AAE5C,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,wBAAgB,QAAQ,IAAI,cAAc,CAEzC"}