@rollipop/core 0.0.0 → 0.1.0-alpha.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 ADDED
@@ -0,0 +1,1619 @@
1
+ import { n as __require, t as __export } from "./chunk-BYW8Mqxw.js";
2
+ import path from "node:path";
3
+ import { DEV_SERVER_ASSET_PATH, FileStorage, Logger, Logo, chalk, getCachePath } from "@rollipop/common";
4
+ import { invariant, isNotNil, merge, mergeWith, pick, range, throttle } from "es-toolkit";
5
+ import * as rolldown from "rolldown";
6
+ import * as rolldownExperimental from "rolldown/experimental";
7
+ import { dev, transformSync } from "rolldown/experimental";
8
+ import crypto from "node:crypto";
9
+ import { xxh32 } from "@node-rs/xxhash";
10
+ import fs from "node:fs";
11
+ import dedent from "dedent";
12
+ import wrapAnsi from "wrap-ansi";
13
+ import path$1 from "path";
14
+ import * as babel from "@babel/core";
15
+ import { exactRegex } from "@rolldown/pluginutils";
16
+ import { generate } from "@babel/generator";
17
+ import * as swc from "@swc/core";
18
+ import flowRemoveTypes from "flow-remove-types";
19
+ import * as hermesParser from "hermes-parser";
20
+ import { imageSize } from "image-size";
21
+ import { transform } from "@svgr/core";
22
+ import * as c12 from "c12";
23
+
24
+ //#region src/constants.ts
25
+ const ROLLIPOP_VERSION = "0.1.0-alpha.0";
26
+ const GLOBAL_IDENTIFIER = "__ROLLIPOP_GLOBAL__";
27
+ /**
28
+ * Unlike the Metro bundler configuration, this prioritizes resolving module(ESM) fields first.
29
+ *
30
+ * @see {@link https://github.com/facebook/metro/blob/0.81.x/docs/Configuration.md#resolvermainfields}
31
+ */
32
+ const DEFAULT_RESOLVER_MAIN_FIELDS = [
33
+ "react-native",
34
+ "module",
35
+ "main"
36
+ ];
37
+ const DEFAULT_RESOLVER_CONDITION_NAMES = [
38
+ "react-native",
39
+ "import",
40
+ "require"
41
+ ];
42
+ /**
43
+ * Unlike the Metro bundler configuration, this prioritizes resolving TypeScript and ESM first.
44
+ *
45
+ * @see {@link https://github.com/facebook/metro/blob/0.81.x/packages/metro-config/src/defaults/defaults.js}
46
+ * @see {@link https://github.com/facebook/metro/blob/0.81.x/packages/metro-file-map/src/workerExclusionList.js}
47
+ */
48
+ const DEFAULT_SOURCE_EXTENSIONS = [
49
+ "ts",
50
+ "tsx",
51
+ "js",
52
+ "jsx",
53
+ "mjs",
54
+ "cjs",
55
+ "json"
56
+ ];
57
+ const DEFAULT_ASSET_EXTENSIONS = [
58
+ "bmp",
59
+ "gif",
60
+ "jpg",
61
+ "jpeg",
62
+ "png",
63
+ "psd",
64
+ "svg",
65
+ "webp",
66
+ "xml",
67
+ "m4v",
68
+ "mov",
69
+ "mp4",
70
+ "mpeg",
71
+ "mpg",
72
+ "webm",
73
+ "aac",
74
+ "aiff",
75
+ "caf",
76
+ "m4a",
77
+ "mp3",
78
+ "wav",
79
+ "html",
80
+ "pdf",
81
+ "yaml",
82
+ "yml",
83
+ "otf",
84
+ "ttf",
85
+ "zip"
86
+ ];
87
+ const DEFAULT_ASSET_REGISTRY_PATH = "react-native/Libraries/Image/AssetRegistry.js";
88
+ const DEFAULT_HMR_CLIENT_PATH = "react-native/Libraries/Utilities/HMRClient.js";
89
+
90
+ //#endregion
91
+ //#region src/utils/hash.ts
92
+ function xxhash(data) {
93
+ return xxh32(data).toString(16);
94
+ }
95
+ function md5(data) {
96
+ return crypto.createHash("md5").update(data).digest("hex");
97
+ }
98
+
99
+ //#endregion
100
+ //#region src/utils/serialize.ts
101
+ function serialize(value) {
102
+ return JSON.stringify(value, (_, value$1) => {
103
+ if (typeof value$1 === "function") return value$1.toString();
104
+ if (value$1 instanceof RegExp) return value$1.toString();
105
+ return value$1;
106
+ });
107
+ }
108
+
109
+ //#endregion
110
+ //#region src/utils/id.ts
111
+ function createId(config, buildOptions) {
112
+ return md5(serialize([
113
+ ROLLIPOP_VERSION,
114
+ pick(buildOptions, ["platform", "dev"]),
115
+ config.resolver,
116
+ config.transformer,
117
+ config.plugins,
118
+ config.serializer.prelude
119
+ ]));
120
+ }
121
+
122
+ //#endregion
123
+ //#region src/logger.ts
124
+ const logger = new Logger("bundler");
125
+
126
+ //#endregion
127
+ //#region src/core/cache/file-system-cache.ts
128
+ var FileSystemCache = class {
129
+ constructor(cacheDirectory) {
130
+ this.cacheDirectory = cacheDirectory;
131
+ this.ensureCacheDirectory();
132
+ logger.debug("cache directory:", cacheDirectory);
133
+ }
134
+ ensureCacheDirectory() {
135
+ if (!fs.existsSync(this.cacheDirectory)) fs.mkdirSync(this.cacheDirectory, { recursive: true });
136
+ }
137
+ get(key) {
138
+ try {
139
+ return fs.readFileSync(path.join(this.cacheDirectory, key), "utf-8");
140
+ } catch {
141
+ return;
142
+ }
143
+ }
144
+ set(key, value) {
145
+ try {
146
+ fs.writeFileSync(path.join(this.cacheDirectory, key), value);
147
+ } catch {
148
+ logger.error("Failed to write cache file", key);
149
+ }
150
+ }
151
+ clear() {
152
+ fs.rmSync(this.cacheDirectory, {
153
+ recursive: true,
154
+ force: true
155
+ });
156
+ this.ensureCacheDirectory();
157
+ }
158
+ };
159
+
160
+ //#endregion
161
+ //#region ../common/src/constants.ts
162
+ const DEBUG_KEY = "rollipop";
163
+
164
+ //#endregion
165
+ //#region ../common/src/debug.ts
166
+ const TRUTHY_VALUES = [
167
+ "yes",
168
+ "on",
169
+ "true",
170
+ "enabled"
171
+ ];
172
+ const FALSY_VALUES = [
173
+ "no",
174
+ "off",
175
+ "false",
176
+ "disabled"
177
+ ];
178
+ function parseDebugKeys() {
179
+ return Object.keys(process.env).filter((key) => /^debug_/i.test(key)).reduce((acc, key) => {
180
+ const prop = key.slice(6).toLowerCase().replace(/_([a-z])/g, (_, key$1) => key$1.toUpperCase());
181
+ let value = process.env[key];
182
+ const lowerCase = typeof value === "string" ? value.toLowerCase() : value.toString();
183
+ if (TRUTHY_VALUES.includes(lowerCase)) value = true;
184
+ else if (FALSY_VALUES.includes(lowerCase)) value = false;
185
+ else value = Boolean(Number(value));
186
+ acc[prop] = value;
187
+ return acc;
188
+ }, {});
189
+ }
190
+ let debugKeys = null;
191
+ function isDebugEnabled() {
192
+ if (debugKeys == null) debugKeys = parseDebugKeys();
193
+ return debugKeys[DEBUG_KEY] ?? false;
194
+ }
195
+
196
+ //#endregion
197
+ //#region src/utils/string.ts
198
+ function indent(text, indent$1, space = " ") {
199
+ return text.replace(/^/gm, space.repeat(indent$1));
200
+ }
201
+
202
+ //#endregion
203
+ //#region src/common/code.ts
204
+ function asLiteral(value) {
205
+ return JSON.stringify(value);
206
+ }
207
+ function asIdentifier(name) {
208
+ return name;
209
+ }
210
+ function nodeEnvironment(dev$1) {
211
+ return dev$1 ? "development" : "production";
212
+ }
213
+ function iife(body, path$2 = "<unknown>") {
214
+ const bodyPlaceholder = "__BODY__";
215
+ return dedent`
216
+ // ${path$2}
217
+ (function (global) {
218
+ ${bodyPlaceholder}
219
+ })(${GLOBAL_IDENTIFIER});
220
+ `.replace(bodyPlaceholder, indent(body, 1));
221
+ }
222
+
223
+ //#endregion
224
+ //#region src/utils/storage.ts
225
+ function getBuildTotalModules(storage, id) {
226
+ return storage.get().build[id]?.totalModules ?? 0;
227
+ }
228
+ function setBuildTotalModules(storage, id, totalModules) {
229
+ storage.set({ build: { [id]: { totalModules } } });
230
+ }
231
+
232
+ //#endregion
233
+ //#region src/utils/terminal.ts
234
+ /**
235
+ * Based on https://github.com/sindresorhus/log-update/blob/master/index.js
236
+ * Based on https://github.com/unjs/webpackbar
237
+ */
238
+ function eraseLines(count) {
239
+ let clear = "";
240
+ for (let i = 0; i < count; i++) clear += `\u001B[2K` + (i < count - 1 ? `\u001B[1A` : "");
241
+ if (count) clear += `\u001B[G`;
242
+ return clear;
243
+ }
244
+ function ellipsisLeft(value, maxLength) {
245
+ if (value.length <= maxLength - 3) return value;
246
+ return `...${value.slice(value.length - maxLength - 1)}`;
247
+ }
248
+ const originalWrite = Symbol("original-write");
249
+ var StreamManager = class {
250
+ prevLineCount;
251
+ listening;
252
+ extraLines;
253
+ _streams;
254
+ constructor() {
255
+ this.prevLineCount = 0;
256
+ this.listening = false;
257
+ this.extraLines = "";
258
+ this._onData = this._onData.bind(this);
259
+ this._streams = [process.stdout, process.stderr];
260
+ }
261
+ render(lines) {
262
+ this.listen();
263
+ const wrappedLines = wrapAnsi(lines, this.columns, {
264
+ trim: false,
265
+ hard: true,
266
+ wordWrap: false
267
+ });
268
+ const data = eraseLines(this.prevLineCount) + wrappedLines + "\n" + this.extraLines;
269
+ this.write(data);
270
+ this.prevLineCount = data.split("\n").length;
271
+ }
272
+ get columns() {
273
+ return (process.stderr.columns || 80) - 2;
274
+ }
275
+ write(data) {
276
+ const stream = process.stderr;
277
+ if (stream.write[originalWrite]) stream.write[originalWrite].call(stream, data, "utf8");
278
+ else stream.write(data, "utf8");
279
+ }
280
+ clear() {
281
+ this.done();
282
+ this.write(eraseLines(this.prevLineCount));
283
+ }
284
+ done() {
285
+ this.stopListen();
286
+ this.prevLineCount = 0;
287
+ this.extraLines = "";
288
+ }
289
+ _onData(data) {
290
+ const lines = String(data).split("\n").length - 1;
291
+ if (lines > 0) {
292
+ this.prevLineCount += lines;
293
+ this.extraLines += data;
294
+ }
295
+ }
296
+ listen() {
297
+ if (this.listening) return;
298
+ for (const stream of this._streams) {
299
+ if (stream.write[originalWrite]) continue;
300
+ const write = (data, ...args) => {
301
+ if (!stream.write[originalWrite]) return stream.write(data, ...args);
302
+ this._onData(data);
303
+ return stream.write[originalWrite].call(stream, data, ...args);
304
+ };
305
+ write[originalWrite] = stream.write;
306
+ stream.write = write;
307
+ }
308
+ this.listening = true;
309
+ }
310
+ stopListen() {
311
+ for (const stream of this._streams) if (stream.write[originalWrite]) stream.write = stream.write[originalWrite];
312
+ this.listening = false;
313
+ }
314
+ };
315
+
316
+ //#endregion
317
+ //#region src/common/progress-bar.ts
318
+ const BAR_LENGTH = 25;
319
+ const BLOCK_CHAR = "█";
320
+ var ProgressBar = class {
321
+ columns = (process.stderr.columns || 80) - 2;
322
+ state = null;
323
+ current = 0;
324
+ total = 0;
325
+ label;
326
+ stale = false;
327
+ done = false;
328
+ constructor(options) {
329
+ this.total = options.total;
330
+ this.label = options.label;
331
+ }
332
+ setCurrent(current) {
333
+ this.current = current;
334
+ this.stale = true;
335
+ return this;
336
+ }
337
+ setTotal(total) {
338
+ this.total = total;
339
+ this.stale = true;
340
+ return this;
341
+ }
342
+ start() {
343
+ this.stale = true;
344
+ this.done = false;
345
+ }
346
+ end() {
347
+ this.done = true;
348
+ }
349
+ update(state) {
350
+ this.state = state;
351
+ this.stale = true;
352
+ return this;
353
+ }
354
+ render() {
355
+ const { state, label, current, total } = this;
356
+ const unknownTotal = total === 0;
357
+ const progress = unknownTotal ? 0 : current / total * 100;
358
+ let line1 = "";
359
+ let line2 = "";
360
+ if (state && "duration" in state) {
361
+ const icon = state.hasErrors ? chalk.red("✘") : chalk.green("✔");
362
+ const durationInSeconds = (state.duration / 1e3).toFixed(2);
363
+ line1 = `${icon} Build completed ${chalk.gray(label)}`;
364
+ line2 = chalk.grey(` Built in ${durationInSeconds}s (${current}/${total} modules)`);
365
+ } else {
366
+ const width = unknownTotal ? 0 : progress * (BAR_LENGTH / 100);
367
+ const bg = chalk.white(BLOCK_CHAR);
368
+ const fg = chalk.cyan(BLOCK_CHAR);
369
+ const bar = range(BAR_LENGTH).map((n) => n < width ? fg : bg).join("");
370
+ const progressLabel = unknownTotal ? chalk.gray("(calculating...)") : `(${progress.toFixed(2)}%)`;
371
+ const moduleCountLabel = unknownTotal ? `${current} modules` : `${current}/${total} modules`;
372
+ line1 = [
373
+ chalk.cyan("●"),
374
+ bar,
375
+ progressLabel,
376
+ chalk.gray(moduleCountLabel),
377
+ chalk.gray(label)
378
+ ].join(" ");
379
+ line2 = state?.id ? " " + chalk.grey(ellipsisLeft(state?.id, this.columns - 10)) : "";
380
+ }
381
+ this.stale = false;
382
+ return `${line1}\n${line2}`;
383
+ }
384
+ };
385
+ var ProgressBarRenderer = class ProgressBarRenderer {
386
+ static instance = null;
387
+ streamManager = new StreamManager();
388
+ progressBars = /* @__PURE__ */ new Map();
389
+ throttledRender;
390
+ static getInstance() {
391
+ if (!ProgressBarRenderer.instance) ProgressBarRenderer.instance = new ProgressBarRenderer();
392
+ return ProgressBarRenderer.instance;
393
+ }
394
+ constructor() {
395
+ this.throttledRender = throttle(this._render.bind(this), 50);
396
+ }
397
+ _render() {
398
+ const renderedLines = Array.from(this.progressBars.values().filter((progressBar$1) => progressBar$1.stale).map((progressBar$1) => progressBar$1.render()));
399
+ if (renderedLines.length > 0) this.streamManager.render(renderedLines.join("\n\n"));
400
+ }
401
+ register(key, options) {
402
+ const progressBar$1 = this.progressBars.get(key);
403
+ if (progressBar$1 == null) {
404
+ const newProgressBar = new ProgressBar(options);
405
+ this.progressBars.set(key, newProgressBar);
406
+ return newProgressBar;
407
+ }
408
+ return progressBar$1;
409
+ }
410
+ start() {
411
+ console.log();
412
+ this.streamManager.listen();
413
+ this._render();
414
+ }
415
+ render() {
416
+ this.throttledRender();
417
+ }
418
+ release() {
419
+ if (this.progressBars.values().every((progressBar$1) => progressBar$1.done)) {
420
+ this._render();
421
+ this.streamManager.done();
422
+ console.log();
423
+ }
424
+ }
425
+ clear() {
426
+ this.streamManager.clear();
427
+ }
428
+ };
429
+
430
+ //#endregion
431
+ //#region src/common/status-presets.ts
432
+ var ProgressFlags = /* @__PURE__ */ function(ProgressFlags$1) {
433
+ ProgressFlags$1[ProgressFlags$1["NONE"] = 0] = "NONE";
434
+ ProgressFlags$1[ProgressFlags$1["BUILD_IN_PROGRESS"] = 1] = "BUILD_IN_PROGRESS";
435
+ ProgressFlags$1[ProgressFlags$1["WATCH_CHANGE"] = 2] = "WATCH_CHANGE";
436
+ return ProgressFlags$1;
437
+ }(ProgressFlags || {});
438
+ function progressBar(reporter, context, label) {
439
+ let flags = ProgressFlags.NONE;
440
+ const initialTotalModules = getBuildTotalModules(context.storage, context.id);
441
+ const progressBarRenderer = ProgressBarRenderer.getInstance();
442
+ const progressBar$1 = progressBarRenderer.register(context.id, {
443
+ label,
444
+ total: initialTotalModules
445
+ });
446
+ const renderProgress = (id, totalModules, transformedModules) => {
447
+ if (totalModules != null) progressBar$1.setTotal(totalModules);
448
+ progressBar$1.setCurrent(transformedModules).update({ id });
449
+ progressBarRenderer.render();
450
+ };
451
+ return withReporter(reporter, {
452
+ initialTotalModules,
453
+ onStart() {
454
+ flags = ProgressFlags.BUILD_IN_PROGRESS;
455
+ progressBar$1.start();
456
+ progressBarRenderer.start();
457
+ },
458
+ onEnd({ error, duration, totalModules }) {
459
+ flags = ProgressFlags.NONE;
460
+ progressBar$1.setTotal(totalModules).update({
461
+ duration,
462
+ hasErrors: Boolean(error)
463
+ }).end();
464
+ progressBarRenderer.release();
465
+ setBuildTotalModules(context.storage, context.id, totalModules);
466
+ },
467
+ onTransform({ id, totalModules, transformedModules }) {
468
+ switch (true) {
469
+ case Boolean(flags & ProgressFlags.BUILD_IN_PROGRESS):
470
+ renderProgress(id, totalModules, transformedModules);
471
+ break;
472
+ case Boolean(flags & ProgressFlags.WATCH_CHANGE):
473
+ logger.debug("Transformed changed file", { id });
474
+ break;
475
+ }
476
+ },
477
+ onWatchChange() {
478
+ flags = flags | ProgressFlags.WATCH_CHANGE;
479
+ }
480
+ });
481
+ }
482
+ function compat(reporter) {
483
+ return withReporter(reporter, {
484
+ onStart() {
485
+ logger.info("Build started...");
486
+ },
487
+ onEnd({ totalModules, duration }) {
488
+ const time = chalk.blue(`${duration.toFixed(2)}ms`);
489
+ const modules = chalk.blue(`(${totalModules} modules)`);
490
+ logger.info(`Build finished in ${time} ${modules}`);
491
+ }
492
+ });
493
+ }
494
+ function withReporter(reporter, statusPluginOptions) {
495
+ return {
496
+ ...statusPluginOptions,
497
+ onStart() {
498
+ reporter.update({ type: "bundle_build_started" });
499
+ statusPluginOptions.onStart?.();
500
+ },
501
+ onEnd(result) {
502
+ if (result.error) reporter.update({
503
+ type: "bundle_build_failed",
504
+ error: result.error
505
+ });
506
+ else reporter.update({ type: "bundle_build_done" });
507
+ statusPluginOptions.onEnd?.(result);
508
+ },
509
+ onTransform(result) {
510
+ reporter.update({
511
+ type: "transform",
512
+ ...result
513
+ });
514
+ statusPluginOptions.onTransform?.(result);
515
+ },
516
+ onWatchChange(id) {
517
+ reporter.update({
518
+ type: "watch_change",
519
+ id
520
+ });
521
+ statusPluginOptions.onWatchChange?.(id);
522
+ }
523
+ };
524
+ }
525
+ const statusPresets = {
526
+ progressBar,
527
+ compat
528
+ };
529
+
530
+ //#endregion
531
+ //#region src/internal/react-native.ts
532
+ function getInitializeCorePath(basePath) {
533
+ return __require.resolve("react-native/Libraries/Core/InitializeCore", { paths: [basePath] });
534
+ }
535
+ function getPolyfillScriptPaths(reactNativePath) {
536
+ const scriptPath = path.join(reactNativePath, "rn-get-polyfills");
537
+ return __require(scriptPath)();
538
+ }
539
+ function getGlobalVariables(dev$1, mode) {
540
+ return [
541
+ `var __BUNDLE_START_TIME__=globalThis.nativePerformanceNow?nativePerformanceNow():Date.now();`,
542
+ `var __DEV__=${dev$1};`,
543
+ `var ${GLOBAL_IDENTIFIER}=typeof globalThis!=='undefined'?globalThis:typeof global !== 'undefined'?global:typeof window!=='undefined'?window:this;`,
544
+ `var process=globalThis.process||{};process.env=process.env||{};process.env.NODE_ENV=process.env.NODE_ENV||"${dev$1 ? "development" : "production"}";`,
545
+ dev$1 && mode === "serve" ? `var $RefreshReg$ = () => {};` : null,
546
+ dev$1 && mode === "serve" ? `var $RefreshSig$ = () => (v) => v;` : null
547
+ ].filter(isNotNil);
548
+ }
549
+
550
+ //#endregion
551
+ //#region src/common/transformer.ts
552
+ function stripFlowSyntax(code, id) {
553
+ const typeRemoved = flowRemoveTypes(code, {
554
+ all: true,
555
+ removeEmptyImports: true
556
+ });
557
+ const generated = generate(hermesParser.parse(typeRemoved.toString(), {
558
+ flow: "all",
559
+ babel: true
560
+ }), {
561
+ sourceMaps: true,
562
+ sourceFileName: path.basename(id)
563
+ });
564
+ return {
565
+ code: generated.code,
566
+ map: generated.map
567
+ };
568
+ }
569
+ function blockScoping(code, id, dev$1, UNSTABLE_enableSourceMap = false) {
570
+ return swc.transformSync(code, {
571
+ filename: path.basename(id),
572
+ configFile: false,
573
+ swcrc: false,
574
+ sourceMaps: UNSTABLE_enableSourceMap,
575
+ jsc: {
576
+ target: "es5",
577
+ parser: {
578
+ syntax: "typescript",
579
+ tsx: true
580
+ },
581
+ keepClassNames: true,
582
+ loose: false,
583
+ transform: { react: {
584
+ runtime: "automatic",
585
+ development: dev$1
586
+ } },
587
+ assumptions: {
588
+ setPublicClassFields: true,
589
+ privateFieldsAsProperties: true
590
+ }
591
+ },
592
+ isModule: true
593
+ });
594
+ }
595
+
596
+ //#endregion
597
+ //#region src/core/assets.ts
598
+ /**
599
+ * **NOTE**: Type definitions are ported from `metro` implementation.
600
+ *
601
+ * @see https://github.com/facebook/metro/blob/0.81.x/packages/metro/src/Assets.js
602
+ */
603
+ var assets_exports = /* @__PURE__ */ __export({
604
+ copyAssetsToDestination: () => copyAssetsToDestination,
605
+ getAssetPriority: () => getAssetPriority,
606
+ getSuffixedPath: () => getSuffixedPath,
607
+ platformSuffixPattern: () => platformSuffixPattern,
608
+ resolveAssetPath: () => resolveAssetPath,
609
+ resolveScaledAssets: () => resolveScaledAssets,
610
+ stripSuffix: () => stripSuffix
611
+ });
612
+ const SCALE_PATTERN = "@(\\d+\\.?\\d*)x";
613
+ /**
614
+ * key: platform,
615
+ * value: allowed scales
616
+ *
617
+ * @see https://github.com/facebook/react-native/blob/0.83-stable/packages/community-cli-plugin/src/commands/bundle/filterPlatformAssetScales.js#L11
618
+ */
619
+ const ALLOW_SCALES = { ios: [
620
+ 1,
621
+ 2,
622
+ 3
623
+ ] };
624
+ /**
625
+ * @see https://developer.android.com/training/multiscreen/screendensities#TaskProvideAltBmp
626
+ */
627
+ const ANDROID_ASSET_QUALIFIER = {
628
+ .75: "ldpi",
629
+ 1: "mdpi",
630
+ 1.5: "hdpi",
631
+ 2: "xhdpi",
632
+ 3: "xxhdpi",
633
+ 4: "xxxhdpi"
634
+ };
635
+ async function resolveScaledAssets(options) {
636
+ const { projectRoot, assetPath, platform, preferNativePlatform } = options;
637
+ const context = {
638
+ platform,
639
+ preferNativePlatform
640
+ };
641
+ const extension = path.extname(assetPath);
642
+ const relativePath = path.relative(projectRoot, assetPath);
643
+ const dirname = path.dirname(assetPath);
644
+ const files = fs.readdirSync(dirname);
645
+ const stripedBasename = stripSuffix(assetPath, context);
646
+ const suffixPattern = platformSuffixPattern(context);
647
+ const assetRegExp = /* @__PURE__ */ new RegExp(`${stripedBasename}(${SCALE_PATTERN})?(?:${suffixPattern})?${extension}$`);
648
+ const scaledAssets = {};
649
+ for (const file of files.sort((a, b) => getAssetPriority(b, context) - getAssetPriority(a, context))) {
650
+ const match = assetRegExp.exec(file);
651
+ if (match) {
652
+ const [, , scale = "1"] = match;
653
+ if (scaledAssets[scale]) continue;
654
+ scaledAssets[scale] = file;
655
+ }
656
+ }
657
+ if (!(Object.keys(scaledAssets).length && scaledAssets[1])) throw new Error(`cannot resolve base asset of ${assetPath}`);
658
+ const imageData = fs.readFileSync(assetPath);
659
+ const dimensions = imageSize(imageData);
660
+ const filteredScaledAssets = Object.entries(scaledAssets).map(([scale, file]) => ({
661
+ scale: parseFloat(scale),
662
+ file
663
+ })).filter(({ scale }) => ALLOW_SCALES[platform]?.includes(scale) ?? true).reduce((acc, { scale, file }) => {
664
+ acc.files.push(file);
665
+ acc.scales.push(scale);
666
+ return acc;
667
+ }, {
668
+ scales: [],
669
+ files: []
670
+ });
671
+ return {
672
+ __packager_asset: true,
673
+ id: assetPath,
674
+ name: stripedBasename.replace(extension, ""),
675
+ type: extension.substring(1),
676
+ width: dimensions.width,
677
+ height: dimensions.height,
678
+ files: filteredScaledAssets.files,
679
+ scales: filteredScaledAssets.scales,
680
+ fileSystemLocation: path.dirname(assetPath),
681
+ httpServerLocation: path.join(DEV_SERVER_ASSET_PATH, path.dirname(relativePath)),
682
+ hash: md5(imageData)
683
+ };
684
+ }
685
+ function platformSuffixPattern(context) {
686
+ return [context.platform, context.preferNativePlatform ? "native" : null].filter(isNotNil).map((platform) => `.${platform}`).join("|");
687
+ }
688
+ function stripSuffix(assetPath, context) {
689
+ const basename = path.basename(assetPath);
690
+ const extension = path.extname(assetPath);
691
+ const suffixPattern = platformSuffixPattern(context);
692
+ return basename.replace(/* @__PURE__ */ new RegExp(`(${SCALE_PATTERN})?(?:${suffixPattern})?${extension}$`), "");
693
+ }
694
+ function getAssetPriority(assetPath, context) {
695
+ const suffixPattern = platformSuffixPattern(context);
696
+ if ((/* @__PURE__ */ new RegExp(`${SCALE_PATTERN}(?:${suffixPattern})`)).test(assetPath)) return 3;
697
+ else if ((/* @__PURE__ */ new RegExp(`(?:${suffixPattern})`)).test(assetPath)) return 2;
698
+ else if ((/* @__PURE__ */ new RegExp(`${SCALE_PATTERN}`)).test(assetPath)) return 1;
699
+ return 0;
700
+ }
701
+ function addSuffix(assetPath, context, options) {
702
+ const extension = path.extname(assetPath);
703
+ return stripSuffix(assetPath, context).concat(options?.scale ? `@${options.scale}x` : "").concat(options?.platform ? `.${options.platform}${extension}` : extension);
704
+ }
705
+ /**
706
+ * add suffix to asset path
707
+ *
708
+ * ```js
709
+ * // assetPath input
710
+ * '/path/to/assets/image.png'
711
+ *
712
+ * // `platform` suffixed
713
+ * '/path/to/assets/image.android.png'
714
+ *
715
+ * // `scale` suffixed
716
+ * '/path/to/assets/image@1x.png'
717
+ *
718
+ * // both `platform` and `scale` suffixed
719
+ * '/path/to/assets/image@1x.android.png'
720
+ * ```
721
+ */
722
+ function getSuffixedPath(assetPath, context, options) {
723
+ const suffixedBasename = addSuffix(assetPath, context, {
724
+ scale: options?.scale,
725
+ platform: options?.platform
726
+ });
727
+ const dirname = path.dirname(assetPath);
728
+ return path.join(dirname, suffixedBasename);
729
+ }
730
+ function resolveAssetPath(assetPath, context, scale) {
731
+ const suffixedPaths = [
732
+ getSuffixedPath(assetPath, context, {
733
+ scale,
734
+ platform: context.platform
735
+ }),
736
+ context.preferNativePlatform ? getSuffixedPath(assetPath, context, {
737
+ scale,
738
+ platform: "native"
739
+ }) : null,
740
+ getSuffixedPath(assetPath, context, { scale })
741
+ ].filter(isNotNil);
742
+ /**
743
+ * When scale is 1, filename can be suffixed or non-suffixed(`image.png`).
744
+ *
745
+ * - Suffixed
746
+ * - `filename.<platform>@<scale>x.ext`
747
+ * - `filename.<platform>.ext`
748
+ * - `filename@<scale>x.ext`
749
+ * - Non suffixed
750
+ * - `filename.ext`
751
+ *
752
+ * 1. Resolve non-suffixed asset first.
753
+ * 2. If file is not exist, resolve suffixed path.
754
+ */
755
+ if (scale === 1) try {
756
+ fs.statSync(assetPath);
757
+ return assetPath;
758
+ } catch {}
759
+ for (const suffixedPath of suffixedPaths) try {
760
+ fs.statSync(suffixedPath);
761
+ return suffixedPath;
762
+ } catch {}
763
+ throw new Error(`cannot resolve asset path for ${assetPath}`);
764
+ }
765
+ /**
766
+ * @see https://github.com/facebook/react-native/blob/0.83-stable/packages/community-cli-plugin/src/commands/bundle/assetPathUtils.js
767
+ */
768
+ async function copyAssetsToDestination(options) {
769
+ const { assets, platform, assetsDir, preferNativePlatform } = options;
770
+ const context = {
771
+ platform,
772
+ preferNativePlatform
773
+ };
774
+ const mkdirWithAssertPath = (targetPath) => {
775
+ const dirname = path.dirname(targetPath);
776
+ fs.mkdirSync(dirname, { recursive: true });
777
+ };
778
+ return Promise.all(assets.map((asset) => {
779
+ return Promise.all(asset.scales.map(async (scale) => {
780
+ if (platform !== "android") {
781
+ const from$1 = resolveAssetPath(asset.id, context, scale);
782
+ const to$1 = path.join(assetsDir, getIosAssetDestinationPath(asset, scale));
783
+ mkdirWithAssertPath(to$1);
784
+ return fs.copyFileSync(from$1, to$1);
785
+ }
786
+ const from = resolveAssetPath(asset.id, context, scale);
787
+ const to = path.join(assetsDir, getAndroidAssetDestinationPath(asset, scale));
788
+ mkdirWithAssertPath(to);
789
+ fs.copyFileSync(from, to);
790
+ })).then(() => void 0);
791
+ })).then(() => void 0);
792
+ }
793
+ /**
794
+ * @see https://github.com/facebook/react-native/blob/0.83-stable/packages/community-cli-plugin/src/commands/bundle/getAssetDestPathIOS.js
795
+ */
796
+ function getIosAssetDestinationPath(asset, scale) {
797
+ const suffix = scale === 1 ? "" : `@${scale}x`;
798
+ const fileName = `${asset.name + suffix}.${asset.type}`;
799
+ const devServerBasePath = asset.httpServerLocation.at(0) === "/" ? asset.httpServerLocation.slice(1) : asset.httpServerLocation;
800
+ return path.join(devServerBasePath.replace(/\.\.\//g, "_"), fileName);
801
+ }
802
+ function getAndroidAssetDestinationPath(asset, scale) {
803
+ const assetQualifierSuffix = ANDROID_ASSET_QUALIFIER[scale];
804
+ const assetName = `${asset.httpServerLocation.at(0) === "/" ? asset.httpServerLocation.slice(1) : asset.httpServerLocation}/${asset.name}`.toLowerCase().replace(/\//g, "_").replace(/(?:[^a-z0-9_])/g, "").replace(/^assets_/, "");
805
+ if (!assetQualifierSuffix) throw new Error(`invalid asset qualifier: ${asset.id}`);
806
+ return path.join(isDrawable(asset.type) ? `drawable-${assetQualifierSuffix}` : "raw", `${assetName}.${asset.type}`);
807
+ }
808
+ /**
809
+ * @see https://developer.android.com/guide/topics/resources/drawable-resource
810
+ */
811
+ function isDrawable(type) {
812
+ return [
813
+ "gif",
814
+ "heic",
815
+ "heif",
816
+ "jpeg",
817
+ "jpg",
818
+ "ktx",
819
+ "png",
820
+ "webp",
821
+ "xml"
822
+ ].includes(type);
823
+ }
824
+
825
+ //#endregion
826
+ //#region src/core/plugins/utils/index.ts
827
+ const CACHE_HIT = Symbol("CACHE_HIT");
828
+ /**
829
+ * @internal
830
+ */
831
+ function withPersistCache(plugins, options) {
832
+ const { enabled, sourceExtensions, context } = options;
833
+ if (!enabled) return plugins;
834
+ const includePattern = /* @__PURE__ */ new RegExp(`\\.(?:${sourceExtensions.join("|")})$`);
835
+ const excludePattern = /@oxc-project\+runtime/;
836
+ let cacheHits = 0;
837
+ const startMarker = {
838
+ name: "rollipop:persist-cache-start",
839
+ buildStart() {
840
+ cacheHits = 0;
841
+ },
842
+ buildEnd() {
843
+ this.debug(`Cache hits: ${cacheHits}`);
844
+ },
845
+ load: {
846
+ order: "pre",
847
+ filter: { id: {
848
+ include: includePattern,
849
+ exclude: excludePattern
850
+ } },
851
+ handler(id) {
852
+ const key = getCacheKey(id, context.id);
853
+ const cache = context.cache.get(key);
854
+ if (cache != null) {
855
+ cacheHits++;
856
+ return {
857
+ code: cache,
858
+ moduleType: "tsx",
859
+ meta: { [CACHE_HIT]: true }
860
+ };
861
+ }
862
+ }
863
+ }
864
+ };
865
+ const endMarker = {
866
+ name: "rollipop:persist-cache-end",
867
+ transform: {
868
+ order: "post",
869
+ filter: { id: {
870
+ include: includePattern,
871
+ exclude: excludePattern
872
+ } },
873
+ handler(code, id) {
874
+ const moduleInfo = this.getModuleInfo(id);
875
+ if (!(moduleInfo && isCacheHit(moduleInfo.meta))) {
876
+ const key = getCacheKey(id, context.id);
877
+ context.cache.set(key, code);
878
+ }
879
+ }
880
+ }
881
+ };
882
+ return [
883
+ startMarker,
884
+ ...plugins,
885
+ endMarker
886
+ ];
887
+ }
888
+ function isCacheHit(meta) {
889
+ return CACHE_HIT in meta;
890
+ }
891
+ function getCacheKey(id, buildHash) {
892
+ const { mtimeMs } = fs.statSync(id);
893
+ return xxhash(`${id}${buildHash}${mtimeMs}`);
894
+ }
895
+ /**
896
+ * Enhance a plugin to cache the result. (transform hook only)
897
+ */
898
+ function cacheable(plugin) {
899
+ const originalTransform = plugin.transform;
900
+ if (typeof originalTransform === "function") plugin.transform = function(code, id, meta) {
901
+ const moduleInfo = this.getModuleInfo(id);
902
+ if (moduleInfo && isCacheHit(moduleInfo.meta)) return;
903
+ return originalTransform.call(this, code, id, meta);
904
+ };
905
+ if (typeof originalTransform === "object") plugin.transform = {
906
+ ...originalTransform,
907
+ handler(code, id, meta) {
908
+ const moduleInfo = this.getModuleInfo(id);
909
+ if (moduleInfo && isCacheHit(moduleInfo.meta)) return;
910
+ return originalTransform.handler.call(this, code, id, meta);
911
+ }
912
+ };
913
+ return plugin;
914
+ }
915
+ const PluginUtils = Object.freeze({ cacheable });
916
+
917
+ //#endregion
918
+ //#region src/core/plugins/react-native-plugin.ts
919
+ var TransformFlags = /* @__PURE__ */ function(TransformFlags$1) {
920
+ TransformFlags$1[TransformFlags$1["SKIP_FLOW"] = 1] = "SKIP_FLOW";
921
+ TransformFlags$1[TransformFlags$1["NONE"] = 0] = "NONE";
922
+ return TransformFlags$1;
923
+ }(TransformFlags || {});
924
+ function reactNativePlugin(config, options) {
925
+ const { mode, flowFilter, codegenFilter, assetsDir, assetExtensions, assetRegistryPath } = options;
926
+ const assetExtensionRegex = /* @__PURE__ */ new RegExp(`\\.(?:${assetExtensions.join("|")})$`);
927
+ const codegenPlugin = {
928
+ name: "rollipop:react-native-codegen",
929
+ transform: {
930
+ order: "pre",
931
+ filter: codegenFilter,
932
+ handler(code, id) {
933
+ this.debug(`Transforming codegen native component ${id}`);
934
+ const result = babel.transformSync(code, {
935
+ filename: path$1.basename(id),
936
+ babelrc: false,
937
+ configFile: false,
938
+ sourceMaps: true,
939
+ parserOpts: { flow: "all" },
940
+ plugins: [
941
+ __require.resolve("babel-plugin-syntax-hermes-parser"),
942
+ __require.resolve("@babel/plugin-transform-flow-strip-types"),
943
+ [__require.resolve("@babel/plugin-syntax-typescript"), {
944
+ isTSX: id.endsWith("x"),
945
+ dts: false
946
+ }],
947
+ __require.resolve("@react-native/babel-plugin-codegen")
948
+ ]
949
+ });
950
+ if (result?.code == null) throw new Error(`Failed to transform codegen native component: ${id}`);
951
+ return {
952
+ code: result.code,
953
+ map: result.map,
954
+ meta: setFlag(this, id, TransformFlags.SKIP_FLOW)
955
+ };
956
+ }
957
+ }
958
+ };
959
+ const stripFlowSyntaxPlugin = {
960
+ name: "rollipop:react-native-strip-flow-syntax",
961
+ transform: {
962
+ order: "pre",
963
+ filter: flowFilter,
964
+ handler(code, id) {
965
+ if (getFlags(this, id) & TransformFlags.SKIP_FLOW) return;
966
+ const result = stripFlowSyntax(code, id);
967
+ return {
968
+ code: result.code,
969
+ map: result.map,
970
+ moduleType: "tsx"
971
+ };
972
+ }
973
+ }
974
+ };
975
+ const blockScopingPlugin = {
976
+ name: "rollipop:react-native-block-scoping",
977
+ transform: {
978
+ order: "post",
979
+ handler(code, id) {
980
+ const result = blockScoping(code, id, options.dev);
981
+ return {
982
+ code: result.code,
983
+ map: result.map
984
+ };
985
+ }
986
+ }
987
+ };
988
+ const assets = [];
989
+ const assetPlugin = {
990
+ name: "rollipop:react-native-asset",
991
+ load: {
992
+ filter: { id: assetExtensionRegex },
993
+ async handler(id) {
994
+ this.debug(`Asset ${id} found`);
995
+ const assetData = await resolveScaledAssets({
996
+ projectRoot: config.root,
997
+ assetPath: id,
998
+ platform: options.platform,
999
+ preferNativePlatform: config.resolver.preferNativePlatform
1000
+ });
1001
+ assets.push(assetData);
1002
+ return { code: `
1003
+ module.exports = require('${assetRegistryPath}').registerAsset(${JSON.stringify(assetData)});
1004
+ ` };
1005
+ }
1006
+ },
1007
+ buildStart() {
1008
+ assets.length = 0;
1009
+ },
1010
+ async buildEnd(error) {
1011
+ if (error || mode === "serve") return;
1012
+ if (assetsDir != null) {
1013
+ this.debug(`Copying assets to ${assetsDir}`);
1014
+ await copyAssetsToDestination({
1015
+ assets,
1016
+ assetsDir,
1017
+ platform: options.platform,
1018
+ preferNativePlatform: config.resolver.preferNativePlatform
1019
+ });
1020
+ }
1021
+ }
1022
+ };
1023
+ const hmrClientImplement = fs.readFileSync(__require.resolve("@rollipop/core/hmr-client"), "utf-8");
1024
+ const hmrClientPath = __require.resolve(process.env.ROLLIPOP_HMR_CLIENT_PATH ?? DEFAULT_HMR_CLIENT_PATH, { paths: [config.root] });
1025
+ const replaceHMRClientPlugin = {
1026
+ name: "rollipop:react-native-replace-hmr-client",
1027
+ resolveId: {
1028
+ filter: { id: /\/HMRClient\.js$/ },
1029
+ async handler(id, importer) {
1030
+ const resolvedId = await this.resolve(id, importer, { skipSelf: true });
1031
+ if (resolvedId?.id === hmrClientPath) await this.load({ id: resolvedId.id });
1032
+ }
1033
+ },
1034
+ load: {
1035
+ filter: { id: exactRegex(hmrClientPath) },
1036
+ handler(id) {
1037
+ this.debug(`Replacing HMR client: ${id}`);
1038
+ return hmrClientImplement;
1039
+ }
1040
+ }
1041
+ };
1042
+ const devServerPlugins = mode === "serve" ? [replaceHMRClientPlugin] : null;
1043
+ return [
1044
+ PluginUtils.cacheable(codegenPlugin),
1045
+ PluginUtils.cacheable(stripFlowSyntaxPlugin),
1046
+ PluginUtils.cacheable(blockScopingPlugin),
1047
+ assetPlugin,
1048
+ ...devServerPlugins ?? []
1049
+ ];
1050
+ }
1051
+ function setFlag(context, id, flag) {
1052
+ const moduleInfo = context.getModuleInfo(id);
1053
+ if (moduleInfo && hasFlags(moduleInfo.meta)) {
1054
+ moduleInfo.meta.flags |= flag;
1055
+ return moduleInfo.meta;
1056
+ } else return { meta: flag };
1057
+ }
1058
+ function hasFlags(meta) {
1059
+ return "flags" in meta;
1060
+ }
1061
+ function getFlags(context, id) {
1062
+ const moduleInfo = context.getModuleInfo(id);
1063
+ if (moduleInfo && hasFlags(moduleInfo.meta)) return moduleInfo.meta.flags;
1064
+ return TransformFlags.NONE;
1065
+ }
1066
+
1067
+ //#endregion
1068
+ //#region src/core/plugins/react-refresh-plugin.ts
1069
+ const DEFAULT_INCLUDE_REGEX = /\.[tj]sx?(?:$|\?)/;
1070
+ const DEFAULT_EXCLUDE_REGEX = /\/node_modules\//;
1071
+ const HAS_REFRESH_REGEX = /\$RefreshReg\$\(/;
1072
+ const ONLY_REACT_COMPONENT_REGEX = /extends\s+(?:React\.)?(?:Pure)?Component/;
1073
+ function reactRefreshPlugin(options) {
1074
+ const { include = DEFAULT_INCLUDE_REGEX, exclude = DEFAULT_EXCLUDE_REGEX } = options ?? {};
1075
+ return {
1076
+ name: "rollipop:react-refresh",
1077
+ transform: {
1078
+ filter: { id: {
1079
+ include,
1080
+ exclude
1081
+ } },
1082
+ handler(code, id, meta) {
1083
+ const { magicString } = meta;
1084
+ invariant(magicString != null, "magicString is not available");
1085
+ const { code: transformedCode } = transformSync(id, code, {
1086
+ sourcemap: true,
1087
+ jsx: {
1088
+ runtime: "automatic",
1089
+ development: true,
1090
+ refresh: {
1091
+ refreshReg: `${GLOBAL_IDENTIFIER}.$RefreshReg$`,
1092
+ refreshSig: `${GLOBAL_IDENTIFIER}.$RefreshSig$`
1093
+ }
1094
+ }
1095
+ });
1096
+ magicString.overwrite(0, magicString.length(), transformedCode);
1097
+ applyRefreshWrapper(magicString, {
1098
+ id,
1099
+ hasRefresh: HAS_REFRESH_REGEX.test(transformedCode),
1100
+ onlyReactComponent: ONLY_REACT_COMPONENT_REGEX.test(transformedCode)
1101
+ });
1102
+ return { code: magicString };
1103
+ }
1104
+ }
1105
+ };
1106
+ }
1107
+ function applyRefreshWrapper(s, options) {
1108
+ const { id, hasRefresh, onlyReactComponent } = options;
1109
+ if (!(hasRefresh || onlyReactComponent)) return;
1110
+ if (hasRefresh) s.prepend(`
1111
+ var __prev$RefreshReg$ = global.$RefreshReg$;
1112
+ var __prev$RefreshSig$ = global.$RefreshSig$;
1113
+ global.$RefreshReg$ = function(type, id) { return __ReactRefresh.register(type, ${JSON.stringify(id)} + ' ' + id) }
1114
+ global.$RefreshSig$ = function() { return __ReactRefresh.createSignatureFunctionForTransform(); }
1115
+ `);
1116
+ s.append(`
1117
+ if (import.meta.hot) {
1118
+ if (import.meta.hot.refresh == null) throw new Error('react-refresh runtime is not initialized');
1119
+ import.meta.hot.accept((nextExports) => {
1120
+ if (!nextExports) return;
1121
+ if (import.meta.hot.refreshUtils.isReactRefreshBoundary(nextExports)) {
1122
+ import.meta.hot.refresh.performReactRefresh();
1123
+ }
1124
+ });
1125
+ }`);
1126
+ if (hasRefresh) s.append(`
1127
+ global.$RefreshReg$ = __prev$RefreshReg$;
1128
+ global.$RefreshSig$ = __prev$RefreshSig$;
1129
+ `);
1130
+ }
1131
+
1132
+ //#endregion
1133
+ //#region src/core/plugins/shim.ts
1134
+ function shim() {
1135
+ return { name: `rollipop:shim-${shim.index++}` };
1136
+ }
1137
+ shim.index = 0;
1138
+
1139
+ //#endregion
1140
+ //#region src/core/plugins/prelude-plugin.ts
1141
+ const IS_ENTRY = Symbol("IS_ENTRY");
1142
+ function preludePlugin(options) {
1143
+ if (options.modulePaths.length === 0) return shim();
1144
+ const preludeImportStatements = options.modulePaths.map((modulePath) => `import '${modulePath}';`).join("\n");
1145
+ let processed = false;
1146
+ return {
1147
+ name: "rollipop:prelude",
1148
+ buildStart() {
1149
+ processed = false;
1150
+ },
1151
+ resolveId: { handler: (source, _importer, extraOptions) => {
1152
+ if (extraOptions.isEntry) return {
1153
+ id: source,
1154
+ meta: { [IS_ENTRY]: true }
1155
+ };
1156
+ } },
1157
+ load: { handler(id) {
1158
+ if (processed) return;
1159
+ const moduleInfo = this.getModuleInfo(id);
1160
+ if (moduleInfo && isEntry(moduleInfo.meta)) {
1161
+ this.debug(`Prelude plugin found entry ${id}`);
1162
+ const modifiedSource = [preludeImportStatements, fs.readFileSync(id, "utf-8")].join("\n");
1163
+ processed = true;
1164
+ return modifiedSource;
1165
+ }
1166
+ } }
1167
+ };
1168
+ }
1169
+ function isEntry(meta) {
1170
+ return IS_ENTRY in meta;
1171
+ }
1172
+
1173
+ //#endregion
1174
+ //#region src/core/plugins/status-plugin.ts
1175
+ function statusPlugin(options) {
1176
+ let totalModules = options?.initialTotalModules ?? 0;
1177
+ let startedAt = 0;
1178
+ let transformedModules = 0;
1179
+ let unknownTotalModules = totalModules === 0;
1180
+ return {
1181
+ name: "rollipop:status",
1182
+ buildStart() {
1183
+ startedAt = performance.now();
1184
+ transformedModules = 0;
1185
+ options?.onStart?.();
1186
+ },
1187
+ buildEnd(error) {
1188
+ totalModules = transformedModules;
1189
+ unknownTotalModules = false;
1190
+ options?.onEnd?.({
1191
+ error,
1192
+ totalModules,
1193
+ duration: performance.now() - startedAt
1194
+ });
1195
+ },
1196
+ resolveId: {
1197
+ order: "post",
1198
+ handler(id) {
1199
+ options?.onResolve?.(id);
1200
+ }
1201
+ },
1202
+ transform: {
1203
+ order: "post",
1204
+ handler(_code, id) {
1205
+ ++transformedModules;
1206
+ if (!unknownTotalModules && totalModules < transformedModules) totalModules = transformedModules;
1207
+ options?.onTransform?.({
1208
+ id,
1209
+ totalModules: unknownTotalModules ? void 0 : totalModules,
1210
+ transformedModules
1211
+ });
1212
+ }
1213
+ },
1214
+ watchChange(id) {
1215
+ options?.onWatchChange?.(id);
1216
+ }
1217
+ };
1218
+ }
1219
+
1220
+ //#endregion
1221
+ //#region src/core/plugins/json-plugin.ts
1222
+ function jsonPlugin() {
1223
+ return {
1224
+ name: "rollipop:json",
1225
+ load: {
1226
+ filter: { id: /\.json$/ },
1227
+ handler(id) {
1228
+ const rawJson = fs.readFileSync(id, "utf-8");
1229
+ return {
1230
+ code: jsonToEsm(JSON.parse(rawJson)),
1231
+ moduleType: "js"
1232
+ };
1233
+ }
1234
+ }
1235
+ };
1236
+ }
1237
+ function jsonToEsm(data) {
1238
+ const declarations = [];
1239
+ const exports = [];
1240
+ const exportDefaultMappings = [];
1241
+ Object.entries(data).forEach(([key, value], index) => {
1242
+ const identifier = `_${index}`;
1243
+ declarations.push(`const ${identifier} = ${JSON.stringify(value)};`);
1244
+ exports.push(`export { ${identifier} as "${key}" };`);
1245
+ exportDefaultMappings.push(`"${key}":${identifier}`);
1246
+ });
1247
+ return [
1248
+ ...declarations,
1249
+ ...exports,
1250
+ `export default {${exportDefaultMappings.join(",")}};`
1251
+ ].join("\n");
1252
+ }
1253
+
1254
+ //#endregion
1255
+ //#region src/core/plugins/svg-plugin.ts
1256
+ function svgPlugin(options) {
1257
+ if (!options.enabled) return shim();
1258
+ return {
1259
+ name: "rollipop:svg",
1260
+ load: {
1261
+ filter: { id: /\.svg$/ },
1262
+ async handler(id) {
1263
+ return {
1264
+ code: await transform(fs.readFileSync(id, "utf-8"), {
1265
+ template: defaultTemplate,
1266
+ plugins: [__require.resolve("@svgr/plugin-jsx")],
1267
+ native: true
1268
+ }, { filePath: id }),
1269
+ moduleType: "jsx"
1270
+ };
1271
+ }
1272
+ }
1273
+ };
1274
+ }
1275
+ const SVG_COMPONENT_NAME = "SvgLogo";
1276
+ const defaultTemplate = (variables, { tpl }) => {
1277
+ return tpl`${variables.imports};
1278
+
1279
+ ${variables.interfaces};
1280
+
1281
+ const ${SVG_COMPONENT_NAME} = (${variables.props}) => (
1282
+ ${variables.jsx}
1283
+ );
1284
+
1285
+ export default ${SVG_COMPONENT_NAME};`;
1286
+ };
1287
+
1288
+ //#endregion
1289
+ //#region src/core/plugins/index.ts
1290
+ var plugins_exports = /* @__PURE__ */ __export({
1291
+ applyRefreshWrapper: () => applyRefreshWrapper,
1292
+ json: () => jsonPlugin,
1293
+ prelude: () => preludePlugin,
1294
+ reactNative: () => reactNativePlugin,
1295
+ reactRefresh: () => reactRefreshPlugin,
1296
+ status: () => statusPlugin,
1297
+ svg: () => svgPlugin
1298
+ });
1299
+
1300
+ //#endregion
1301
+ //#region src/core/rolldown.ts
1302
+ const rolldownLogger = new Logger("rolldown");
1303
+ function resolveBuildOptions(buildOptions) {
1304
+ return merge({
1305
+ dev: true,
1306
+ cache: true,
1307
+ minify: false
1308
+ }, buildOptions);
1309
+ }
1310
+ resolveRolldownOptions.cache = /* @__PURE__ */ new Map();
1311
+ async function resolveRolldownOptions(context, config, buildOptions) {
1312
+ const cachedOptions = resolveRolldownOptions.cache.get(context.id);
1313
+ if (cachedOptions != null) return cachedOptions;
1314
+ const { platform, dev: dev$1, cache, minify } = resolveBuildOptions(buildOptions);
1315
+ const { sourceExtensions, assetExtensions, preferNativePlatform, ...rolldownResolve } = config.resolver;
1316
+ const { prelude: preludePaths, polyfills } = config.serializer;
1317
+ const { flow, ...rolldownTransform } = config.transformer;
1318
+ const { codegen, assetRegistryPath } = config.reactNative;
1319
+ const resolvedSourceExtensions = config.transformer.svg ? [...sourceExtensions, "svg"] : sourceExtensions;
1320
+ const resolvedAssetExtensions = config.transformer.svg ? assetExtensions.filter((extension) => extension !== "svg") : assetExtensions;
1321
+ const mergedResolveOptions = merge({ extensions: getResolveExtensions({
1322
+ sourceExtensions: resolvedSourceExtensions,
1323
+ assetExtensions: resolvedAssetExtensions,
1324
+ platform,
1325
+ preferNativePlatform
1326
+ }) }, rolldownResolve);
1327
+ const mergedTransformOptions = merge({
1328
+ target: "es2015",
1329
+ jsx: {
1330
+ runtime: "automatic",
1331
+ development: dev$1
1332
+ },
1333
+ define: {
1334
+ __DEV__: asLiteral(dev$1),
1335
+ "process.env.NODE_ENV": asLiteral(nodeEnvironment(dev$1)),
1336
+ global: asIdentifier(GLOBAL_IDENTIFIER)
1337
+ },
1338
+ typescript: { removeClassFieldsWithoutInitializer: true },
1339
+ assumptions: { setPublicClassFields: true },
1340
+ helpers: { mode: "Runtime" }
1341
+ }, rolldownTransform);
1342
+ const devServerPlugins = context.mode === "serve" ? [reactRefreshPlugin()] : [];
1343
+ const statusPreset = config.terminal.status === "progress" ? statusPresets.progressBar(config.reporter, context, `[${platform}, ${buildOptions.dev ? "dev" : "prod"}]`) : statusPresets.compat(config.reporter);
1344
+ const finalOptions = await applyDangerouslyOverrideOptionsFinalizer(config, {
1345
+ cwd: config.root,
1346
+ input: config.entry,
1347
+ platform: "neutral",
1348
+ treeshake: true,
1349
+ resolve: mergedResolveOptions,
1350
+ transform: mergedTransformOptions,
1351
+ plugins: withPersistCache([
1352
+ preludePlugin({ modulePaths: preludePaths }),
1353
+ reactNativePlugin(config, {
1354
+ dev: dev$1,
1355
+ platform,
1356
+ mode: context.mode,
1357
+ codegenFilter: codegen.filter,
1358
+ flowFilter: flow.filter,
1359
+ assetsDir: buildOptions.assetsDir,
1360
+ assetExtensions: resolvedAssetExtensions,
1361
+ assetRegistryPath
1362
+ }),
1363
+ svgPlugin({ enabled: config.transformer.svg }),
1364
+ jsonPlugin(),
1365
+ statusPlugin(statusPreset),
1366
+ ...devServerPlugins ?? [],
1367
+ ...config.plugins ?? []
1368
+ ], {
1369
+ enabled: cache,
1370
+ context,
1371
+ sourceExtensions
1372
+ }),
1373
+ checks: {
1374
+ eval: false,
1375
+ pluginTimings: false
1376
+ },
1377
+ logLevel: isDebugEnabled() ? "debug" : "info",
1378
+ onLog(level, log) {
1379
+ const { message, code } = log;
1380
+ const logArgs = [code, message].filter(isNotNil);
1381
+ switch (level) {
1382
+ case "debug":
1383
+ rolldownLogger.debug(...logArgs);
1384
+ break;
1385
+ case "info":
1386
+ rolldownLogger.info(...logArgs);
1387
+ break;
1388
+ case "warn":
1389
+ rolldownLogger.warn(...logArgs);
1390
+ break;
1391
+ }
1392
+ }
1393
+ }, {
1394
+ postBanner: [...getGlobalVariables(dev$1, context.mode)].join("\n"),
1395
+ intro: [...loadPolyfills(polyfills)].join("\n"),
1396
+ file: buildOptions.outfile,
1397
+ minify,
1398
+ format: "iife",
1399
+ keepNames: true,
1400
+ sourcemap: true
1401
+ });
1402
+ resolveRolldownOptions.cache.set(context.id, finalOptions);
1403
+ return finalOptions;
1404
+ }
1405
+ function getResolveExtensions({ platform, sourceExtensions, assetExtensions, preferNativePlatform }) {
1406
+ const supportedExtensions = [...sourceExtensions, ...assetExtensions];
1407
+ return [...[platform, preferNativePlatform ? "native" : null].filter(isNotNil).map((platform$1) => {
1408
+ return supportedExtensions.map((extension) => `.${platform$1}.${extension}`);
1409
+ }), ...supportedExtensions.map((extension) => `.${extension}`)].flat();
1410
+ }
1411
+ function loadPolyfills(polyfills) {
1412
+ return polyfills.map((polyfill) => {
1413
+ if (typeof polyfill === "string") return fs.readFileSync(polyfill, "utf-8");
1414
+ const path$2 = "path" in polyfill ? polyfill.path : void 0;
1415
+ const content = "code" in polyfill ? polyfill.code : fs.readFileSync(polyfill.path, "utf-8");
1416
+ return polyfill.type === "iife" ? iife(content, path$2) : content;
1417
+ });
1418
+ }
1419
+ async function applyDangerouslyOverrideOptionsFinalizer(config, inputOptions, outputOptions) {
1420
+ if (typeof config.dangerously_overrideRolldownOptions === "function") return await config.dangerously_overrideRolldownOptions({
1421
+ input: inputOptions,
1422
+ output: outputOptions
1423
+ });
1424
+ return {
1425
+ input: merge(inputOptions, config.dangerously_overrideRolldownOptions?.input ?? {}),
1426
+ output: merge(outputOptions, config.dangerously_overrideRolldownOptions?.output ?? {})
1427
+ };
1428
+ }
1429
+ function getOverrideOptionsForDevServer() {
1430
+ return {
1431
+ input: {
1432
+ transform: { jsx: { development: true } },
1433
+ experimental: {
1434
+ devMode: { implement: fs.readFileSync(__require.resolve("@rollipop/core/hmr-runtime"), "utf-8") },
1435
+ incrementalBuild: true,
1436
+ strictExecutionOrder: true,
1437
+ nativeMagicString: true
1438
+ },
1439
+ treeshake: false
1440
+ },
1441
+ output: { sourcemap: true }
1442
+ };
1443
+ }
1444
+
1445
+ //#endregion
1446
+ //#region src/core/bundler.ts
1447
+ var Bundler = class Bundler {
1448
+ static async devEngine(config, buildOptions, devEngineOptions) {
1449
+ const mode = "serve";
1450
+ const resolvedBuildOptions = {
1451
+ ...buildOptions,
1452
+ dev: true
1453
+ };
1454
+ const { input = {}, output = {} } = await resolveRolldownOptions({
1455
+ ...Bundler.createContext(mode, config, resolvedBuildOptions),
1456
+ mode
1457
+ }, config, resolvedBuildOptions);
1458
+ const devServerOptions = getOverrideOptionsForDevServer();
1459
+ return await dev(merge(input, devServerOptions.input), merge(output, devServerOptions.output), devEngineOptions);
1460
+ }
1461
+ static createId(config, buildOptions) {
1462
+ return createId(config, buildOptions);
1463
+ }
1464
+ static createContext(mode, config, buildOptions) {
1465
+ const id = `${mode}:${Bundler.createId(config, buildOptions)}`;
1466
+ return {
1467
+ id,
1468
+ cache: new FileSystemCache(path.join(getCachePath(config.root), id)),
1469
+ storage: FileStorage.getInstance(config.root)
1470
+ };
1471
+ }
1472
+ constructor(config) {
1473
+ this.config = config;
1474
+ Logo.printOnce();
1475
+ }
1476
+ async build(buildOptions) {
1477
+ const mode = "build";
1478
+ const contextBase = Bundler.createContext(mode, this.config, buildOptions);
1479
+ const { config } = this;
1480
+ const { input, output } = await resolveRolldownOptions({
1481
+ ...contextBase,
1482
+ mode
1483
+ }, config, buildOptions);
1484
+ const rolldownBuildOptions = {
1485
+ ...input,
1486
+ output,
1487
+ write: true
1488
+ };
1489
+ const chunk = (await rolldown.build(rolldownBuildOptions)).output[0];
1490
+ invariant(chunk, "Bundled chunk is not found");
1491
+ return chunk;
1492
+ }
1493
+ };
1494
+
1495
+ //#endregion
1496
+ //#region src/reporter.ts
1497
+ var TerminalReporter = class {
1498
+ logger = new Logger("app");
1499
+ update(event) {
1500
+ if (event.type === "client_log") {
1501
+ if (event.level === "group" || event.level === "groupCollapsed") {
1502
+ this.logger.info(...event.data);
1503
+ return;
1504
+ } else if (event.level === "groupEnd") return;
1505
+ this.logger[event.level](...event.data);
1506
+ }
1507
+ }
1508
+ };
1509
+
1510
+ //#endregion
1511
+ //#region src/utils/node-resolve.ts
1512
+ function resolvePackagePath(basePath, packageName) {
1513
+ let packagePath = null;
1514
+ try {
1515
+ packagePath = resolvePackagePathWithNodeRequire(basePath, packageName);
1516
+ if (packagePath) return packagePath;
1517
+ } catch {}
1518
+ try {
1519
+ packagePath = resolvePackagePathWithNodeRequire(basePath, packageName, "");
1520
+ if (packagePath) return packagePath;
1521
+ } catch {}
1522
+ throw new Error(`Failed to resolve package path for '${packageName}'`);
1523
+ }
1524
+ function resolvePackagePathWithNodeRequire(basePath, packageName, lookupSubpath = "package.json") {
1525
+ const lookupPath = lookupSubpath ? `/${lookupSubpath}` : "";
1526
+ const resolvedPath = __require.resolve(`${packageName}${lookupPath}`, { paths: [basePath] });
1527
+ const root = path.parse(resolvedPath).root;
1528
+ let currentPath = path.dirname(resolvedPath);
1529
+ while (currentPath !== root) {
1530
+ if (fs.existsSync(path.join(currentPath, "package.json"))) return currentPath;
1531
+ currentPath = path.dirname(currentPath);
1532
+ }
1533
+ return null;
1534
+ }
1535
+
1536
+ //#endregion
1537
+ //#region src/config/defaults.ts
1538
+ function getDefaultConfig(basePath, context) {
1539
+ const reactNativePath = resolvePackagePath(basePath, "react-native");
1540
+ const isDevServer = context.command === "start";
1541
+ return {
1542
+ root: basePath,
1543
+ entry: "index.js",
1544
+ resolver: {
1545
+ sourceExtensions: DEFAULT_SOURCE_EXTENSIONS,
1546
+ assetExtensions: DEFAULT_ASSET_EXTENSIONS,
1547
+ mainFields: DEFAULT_RESOLVER_MAIN_FIELDS,
1548
+ conditionNames: DEFAULT_RESOLVER_CONDITION_NAMES,
1549
+ preferNativePlatform: true
1550
+ },
1551
+ transformer: {
1552
+ svg: true,
1553
+ flow: { filter: {
1554
+ id: /\.jsx?$/,
1555
+ code: /@flow/
1556
+ } }
1557
+ },
1558
+ serializer: {
1559
+ prelude: [getInitializeCorePath(basePath)],
1560
+ polyfills: [...getPolyfillScriptPaths(reactNativePath).map((path$2) => ({
1561
+ type: "iife",
1562
+ code: stripFlowSyntax(fs.readFileSync(path$2, "utf-8"), path$2).code
1563
+ })), isDevServer ? __require.resolve("@rollipop/core/hmr-shims") : void 0].filter(isNotNil)
1564
+ },
1565
+ watcher: {
1566
+ skipWrite: true,
1567
+ useDebounce: true,
1568
+ debounceDuration: 50
1569
+ },
1570
+ reactNative: {
1571
+ codegen: { filter: { code: /codegenNativeComponent/ } },
1572
+ assetRegistryPath: DEFAULT_ASSET_REGISTRY_PATH
1573
+ },
1574
+ terminal: { status: process.stderr.isTTY ? "progress" : "compat" },
1575
+ reporter: new TerminalReporter()
1576
+ };
1577
+ }
1578
+
1579
+ //#endregion
1580
+ //#region src/config/define-config.ts
1581
+ function defineConfig(userConfig) {
1582
+ return userConfig;
1583
+ }
1584
+
1585
+ //#endregion
1586
+ //#region src/config/merge-config.ts
1587
+ function mergeConfig(baseConfig, overrideConfig) {
1588
+ return mergeWith(baseConfig, overrideConfig, (target, source, key) => {
1589
+ if (key === "reporter") return source.reporter ?? target.reporter;
1590
+ });
1591
+ }
1592
+
1593
+ //#endregion
1594
+ //#region src/config/load-config.ts
1595
+ const CONFIG_FILE_NAME = "rollipop";
1596
+ async function loadConfig(options = {}) {
1597
+ const { cwd = process.cwd(), configFile, context = {} } = options;
1598
+ const defaultConfig = getDefaultConfig(cwd, context);
1599
+ const commonOptions = {
1600
+ context: {
1601
+ ...context,
1602
+ defaultConfig
1603
+ },
1604
+ rcFile: false
1605
+ };
1606
+ const { config: userConfig } = await c12.loadConfig(configFile ? {
1607
+ configFile: path.resolve(cwd, configFile),
1608
+ configFileRequired: true
1609
+ } : {
1610
+ cwd,
1611
+ defaultConfig,
1612
+ name: CONFIG_FILE_NAME,
1613
+ ...commonOptions
1614
+ });
1615
+ return mergeConfig(defaultConfig, userConfig);
1616
+ }
1617
+
1618
+ //#endregion
1619
+ export { assets_exports as AssetUtils, Bundler, TerminalReporter as DefaultReporter, PluginUtils, defineConfig, getDefaultConfig, loadConfig, mergeConfig, plugins_exports as plugins, rolldown, rolldownExperimental };