@ionify/ionify 0.1.2 → 0.1.4

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/cli/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  tryBundleNodeModule,
9
9
  tryNativeTransform,
10
10
  writeCache
11
- } from "../chunk-GOZUBOYH.js";
11
+ } from "../chunk-QQSYYX7B.js";
12
12
  import {
13
13
  getCasArtifactPath
14
14
  } from "../chunk-X5UIMJDA.js";
@@ -21,7 +21,7 @@ import chalk from "chalk";
21
21
  function logInfo(message) {
22
22
  console.log(chalk.cyan(`[Ionify] ${message}`));
23
23
  }
24
- function logWarn(message) {
24
+ function logWarn2(message) {
25
25
  console.warn(chalk.yellow(`[Ionify] ${message}`));
26
26
  }
27
27
  function logError(message, err) {
@@ -32,8 +32,8 @@ function logError(message, err) {
32
32
  // src/cli/commands/dev.ts
33
33
  import http from "http";
34
34
  import url from "url";
35
- import fs7 from "fs";
36
- import path9 from "path";
35
+ import fs9 from "fs";
36
+ import path12 from "path";
37
37
  import { fileURLToPath as fileURLToPath2 } from "url";
38
38
  import { createRequire as createRequire2 } from "module";
39
39
 
@@ -447,9 +447,12 @@ function buildAliasEntries(aliases, baseDir) {
447
447
  const entries = [];
448
448
  for (const [pattern, value] of Object.entries(aliases)) {
449
449
  const replacements = Array.isArray(value) ? value : [value];
450
- const targets = replacements.filter((rep) => typeof rep === "string" && rep.trim().length > 0).map(
451
- (rep) => path2.isAbsolute(rep) ? rep : path2.resolve(baseDir, rep)
452
- );
450
+ const targets = replacements.filter((rep) => typeof rep === "string" && rep.trim().length > 0).map((rep) => {
451
+ if (rep.startsWith("/")) {
452
+ return path2.resolve(baseDir, rep.slice(1));
453
+ }
454
+ return path2.isAbsolute(rep) ? rep : path2.resolve(baseDir, rep);
455
+ });
453
456
  if (!targets.length) continue;
454
457
  entries.push(createAliasEntry(pattern, targets));
455
458
  }
@@ -480,20 +483,41 @@ function loadTsconfigAliases() {
480
483
  return cachedTsconfigAliases;
481
484
  }
482
485
  function resolveFromEntries(entries, specifier) {
486
+ const debug = process.env.IONIFY_RESOLVE_DEBUG === "1";
483
487
  for (const entry of entries) {
484
488
  const candidates = entry.resolveCandidates(specifier);
489
+ if (debug && candidates.length > 0) {
490
+ console.log(`[RESOLVE] Candidates for ${specifier}:`, candidates);
491
+ }
485
492
  for (const candidate of candidates) {
486
493
  const resolved = tryWithExt(candidate);
487
- if (resolved) return resolved;
494
+ if (resolved) {
495
+ if (debug) console.log(`[RESOLVE] Found: ${resolved}`);
496
+ return resolved;
497
+ }
488
498
  }
489
499
  }
490
500
  return null;
491
501
  }
492
502
  function resolveWithAliases(specifier) {
503
+ const debug = process.env.IONIFY_RESOLVE_DEBUG === "1";
504
+ if (debug) {
505
+ console.log(`[RESOLVE] Trying to resolve: ${specifier}`);
506
+ console.log(`[RESOLVE] Custom aliases count: ${customAliasEntries.length}`);
507
+ }
493
508
  const custom = resolveFromEntries(customAliasEntries, specifier);
494
- if (custom) return custom;
509
+ if (custom) {
510
+ if (debug) console.log(`[RESOLVE] \u2705 Resolved via custom alias: ${custom}`);
511
+ return custom;
512
+ }
495
513
  const tsconfigEntries = loadTsconfigAliases();
496
- return resolveFromEntries(tsconfigEntries, specifier);
514
+ if (debug) console.log(`[RESOLVE] Tsconfig aliases count: ${tsconfigEntries.length}`);
515
+ const result = resolveFromEntries(tsconfigEntries, specifier);
516
+ if (debug) {
517
+ if (result) console.log(`[RESOLVE] \u2705 Resolved via tsconfig: ${result}`);
518
+ else console.log(`[RESOLVE] \u274C Not resolved`);
519
+ }
520
+ return result;
497
521
  }
498
522
  function configureResolverAliases(aliases, baseDir) {
499
523
  customAliasEntries = aliases ? buildAliasEntries(aliases, baseDir) : [];
@@ -569,6 +593,7 @@ var DEFAULT_MAIN_FIELDS = ["module", "main"];
569
593
  var ModuleResolver = class {
570
594
  options;
571
595
  rootDir;
596
+ metadataByPath = /* @__PURE__ */ new Map();
572
597
  constructor(rootDir, options = {}) {
573
598
  this.rootDir = rootDir;
574
599
  this.options = {
@@ -594,6 +619,9 @@ var ModuleResolver = class {
594
619
  }
595
620
  return this.resolveBareModule(importSpecifier, importer);
596
621
  }
622
+ getMetadata(resolvedPath) {
623
+ return this.metadataByPath.get(resolvedPath);
624
+ }
597
625
  resolveAlias(specifier) {
598
626
  for (const [alias, target] of Object.entries(this.options.alias)) {
599
627
  if (specifier === alias || specifier.startsWith(`${alias}/`)) {
@@ -634,6 +662,33 @@ var ModuleResolver = class {
634
662
  return null;
635
663
  }
636
664
  resolveBareModule(specifier, importer) {
665
+ const nativeResolved = native?.resolveModule?.(specifier, importer);
666
+ if (nativeResolved?.kind) {
667
+ const fsPath = nativeResolved.fsPath ?? nativeResolved.fs_path ?? null;
668
+ const kind = normalizeResolveKind(nativeResolved.kind);
669
+ if (kind === "pkg_cjs") {
670
+ if (fsPath) {
671
+ this.metadataByPath.set(fsPath, {
672
+ format: "cjs",
673
+ needsInterop: true
674
+ });
675
+ }
676
+ if (process.env.IONIFY_DEBUG) {
677
+ const name = nativeResolved.pkg?.name ?? specifier;
678
+ console.log(`[resolver] CJS package detected: ${name} (conversion deferred)`);
679
+ }
680
+ }
681
+ if (kind === "pkg_esm" && fsPath) {
682
+ return fsPath;
683
+ }
684
+ if (kind === "pkg_cjs" && fsPath) {
685
+ return fsPath;
686
+ }
687
+ if (kind === "local" && fsPath) {
688
+ return fsPath;
689
+ }
690
+ return null;
691
+ }
637
692
  const parts = specifier.split("/");
638
693
  const packageName = parts[0].startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0];
639
694
  const subpath = parts.slice(packageName.startsWith("@") ? 2 : 1).join("/");
@@ -715,6 +770,19 @@ var ModuleResolver = class {
715
770
  return null;
716
771
  }
717
772
  };
773
+ function normalizeResolveKind(kind) {
774
+ const mapping = {
775
+ PkgEsm: "pkg_esm",
776
+ PkgCjs: "pkg_cjs",
777
+ Builtin: "builtin",
778
+ Virtual: "virtual",
779
+ Local: "local"
780
+ };
781
+ if (kind in mapping) {
782
+ return mapping[kind];
783
+ }
784
+ return kind.toLowerCase();
785
+ }
718
786
 
719
787
  // src/core/watcher.ts
720
788
  import fs4 from "fs";
@@ -849,17 +917,17 @@ var TransformEngine = class {
849
917
  this.loaders.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
850
918
  }
851
919
  async run(ctx) {
852
- const { getCacheKey: getCacheKey2 } = await import("../cache-Y4NMRSZO.js");
853
- const path13 = await import("path");
854
- const fs11 = await import("fs");
920
+ const { getCacheKey: getCacheKey2 } = await import("../cache-LH24ZR2B.js");
921
+ const path16 = await import("path");
922
+ const fs13 = await import("fs");
855
923
  const { getCasArtifactPath: getCasArtifactPath2 } = await import("../cas-FEOXFD7R.js");
856
924
  const moduleHash = ctx.moduleHash || getCacheKey2(ctx.code);
857
925
  const loaderSig = this.loaders.map((l) => l.name || "loader").join("|");
858
926
  const loaderHash = getCacheKey2(loaderSig);
859
927
  const memKey = `${moduleHash}-${loaderHash}`;
860
928
  const casDir = this.casRoot && this.versionHash ? getCasArtifactPath2(this.casRoot, this.versionHash, moduleHash) : null;
861
- const casFile = casDir ? path13.join(casDir, "transformed.js") : null;
862
- const casMapFile = casDir ? path13.join(casDir, "transformed.js.map") : null;
929
+ const casFile = casDir ? path16.join(casDir, "transformed.js") : null;
930
+ const casMapFile = casDir ? path16.join(casDir, "transformed.js.map") : null;
863
931
  const debug = process.env.IONIFY_DEV_TRANSFORM_CACHE_DEBUG === "1";
864
932
  if (this.cacheEnabled) {
865
933
  const memHit = transformCache.get(memKey);
@@ -869,10 +937,10 @@ var TransformEngine = class {
869
937
  }
870
938
  return { code: memHit.transformed, map: memHit.map };
871
939
  }
872
- if (casFile && fs11.existsSync(casFile)) {
940
+ if (casFile && fs13.existsSync(casFile)) {
873
941
  try {
874
- const code = fs11.readFileSync(casFile, "utf8");
875
- const map = casMapFile && fs11.existsSync(casMapFile) ? fs11.readFileSync(casMapFile, "utf8") : void 0;
942
+ const code = fs13.readFileSync(casFile, "utf8");
943
+ const map = casMapFile && fs13.existsSync(casMapFile) ? fs13.readFileSync(casMapFile, "utf8") : void 0;
876
944
  const parsed = { code, map };
877
945
  transformCache.set(memKey, {
878
946
  hash: moduleHash,
@@ -909,10 +977,10 @@ var TransformEngine = class {
909
977
  });
910
978
  if (casFile) {
911
979
  try {
912
- fs11.mkdirSync(path13.dirname(casFile), { recursive: true });
913
- fs11.writeFileSync(casFile, result.code, "utf8");
980
+ fs13.mkdirSync(path16.dirname(casFile), { recursive: true });
981
+ fs13.writeFileSync(casFile, result.code, "utf8");
914
982
  if (result.map && casMapFile) {
915
- fs11.writeFileSync(casMapFile, typeof result.map === "string" ? result.map : JSON.stringify(result.map), "utf8");
983
+ fs13.writeFileSync(casMapFile, typeof result.map === "string" ? result.map : JSON.stringify(result.map), "utf8");
916
984
  }
917
985
  } catch {
918
986
  }
@@ -1048,17 +1116,6 @@ async function compileCss({
1048
1116
  rootDir,
1049
1117
  modules = false
1050
1118
  }) {
1051
- const loaderHash = getCacheKey(JSON.stringify({ modules, filePath: filePath.replace(/\\+/g, "/") }));
1052
- const contentHash = getCacheKey(code);
1053
- const cacheKey = `${contentHash}-${loaderHash}`;
1054
- const cached = transformCache.get(cacheKey);
1055
- if (cached) {
1056
- try {
1057
- const parsed = JSON.parse(cached.transformed);
1058
- return parsed;
1059
- } catch {
1060
- }
1061
- }
1062
1119
  const { plugins, options } = await getPostcssConfig(rootDir);
1063
1120
  const pipeline = [...plugins];
1064
1121
  let tokens;
@@ -1083,16 +1140,34 @@ async function compileCss({
1083
1140
  from: filePath,
1084
1141
  map: false
1085
1142
  });
1143
+ const deps = [];
1144
+ const seen = /* @__PURE__ */ new Set();
1145
+ const addDep = (depPath) => {
1146
+ const normalized = depPath.replace(/\\+/g, "/");
1147
+ if (seen.has(normalized)) return;
1148
+ seen.add(normalized);
1149
+ deps.push({ filePath: depPath, kind: "dependency" });
1150
+ };
1151
+ for (const message of result.messages || []) {
1152
+ const anyMsg = message;
1153
+ if (anyMsg?.type === "dependency" && typeof anyMsg.file === "string") {
1154
+ addDep(anyMsg.file);
1155
+ }
1156
+ }
1157
+ const importRe = /@import\s+(?:url\(\s*)?(?:'([^']+)'|"([^"]+)"|([^'"\s)]+))\s*\)?[^;]*;/gi;
1158
+ let match;
1159
+ while (match = importRe.exec(code)) {
1160
+ const spec = (match[1] || match[2] || match[3] || "").trim();
1161
+ if (!spec) continue;
1162
+ if (/^(data:|https?:|\/\/)/i.test(spec)) continue;
1163
+ const resolved = spec.startsWith("/") ? path5.resolve(rootDir, "." + spec) : path5.resolve(path5.dirname(filePath), spec);
1164
+ addDep(resolved);
1165
+ }
1086
1166
  const compiled = {
1087
1167
  css: result.css,
1088
- tokens
1168
+ tokens,
1169
+ deps
1089
1170
  };
1090
- transformCache.set(cacheKey, {
1091
- hash: contentHash,
1092
- loaderHash,
1093
- transformed: JSON.stringify(compiled),
1094
- timestamp: Date.now()
1095
- });
1096
1171
  return compiled;
1097
1172
  }
1098
1173
  function renderCssModule({
@@ -1104,6 +1179,7 @@ function renderCssModule({
1104
1179
  const styleId = `ionify-css-${getCacheKey(filePath).slice(0, 8)}`;
1105
1180
  const tokensJson = tokens ? JSON.stringify(tokens) : "null";
1106
1181
  return `
1182
+ // ionify:css
1107
1183
  const cssText = ${cssJson};
1108
1184
  const styleId = ${JSON.stringify(styleId)};
1109
1185
  let style = document.querySelector(\`style[data-ionify-id="\${styleId}"]\`);
@@ -1126,6 +1202,22 @@ if (import.meta.hot) {
1126
1202
  }
1127
1203
  `.trim();
1128
1204
  }
1205
+ function renderCssRawStringModule(cssText) {
1206
+ return `
1207
+ // ionify:css
1208
+ const css = ${JSON.stringify(cssText)};
1209
+ export { css };
1210
+ export default css;
1211
+ `.trim();
1212
+ }
1213
+ function renderCssUrlModule(url2) {
1214
+ return `
1215
+ // ionify:css
1216
+ const url = ${JSON.stringify(url2)};
1217
+ export { url };
1218
+ export default url;
1219
+ `.trim();
1220
+ }
1129
1221
 
1130
1222
  // src/core/utils/public-path.ts
1131
1223
  import path6 from "path";
@@ -1218,17 +1310,350 @@ function normalizeUrlFromFs(rootDir, fsPath) {
1218
1310
  // src/core/loaders/js.ts
1219
1311
  import { transform as swcTransform } from "@swc/core";
1220
1312
  import { init, parse } from "es-module-lexer";
1221
- var JS_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx"]);
1222
- function needsReactRefresh(ext) {
1223
- if (ext === ".jsx" || ext === ".tsx") return true;
1224
- if (!ext.endsWith("x")) return false;
1313
+
1314
+ // src/core/deps/registry.ts
1315
+ import crypto2 from "crypto";
1316
+ import fs5 from "fs";
1317
+ import path7 from "path";
1318
+ var registry = /* @__PURE__ */ new Map();
1319
+ function computeStableDepFileName(options) {
1320
+ const pkgName = sanitizePackageName(options.packageName);
1321
+ const pkgVersion = options.packageVersion || "0.0.0";
1322
+ const subpath = normalizeSubpath(options.subpath);
1323
+ let canonicalPath = options.entryPath;
1324
+ try {
1325
+ canonicalPath = fs5.realpathSync(options.entryPath);
1326
+ } catch {
1327
+ }
1328
+ const hash = crypto2.createHash("sha256").update(canonicalPath).digest("hex").slice(0, 6);
1329
+ const subpathSuffix = subpath ? `__${subpath}` : "";
1330
+ return `${pkgName}@${pkgVersion}${subpathSuffix}_${hash}.js`;
1331
+ }
1332
+ function registerDepEntry(entry) {
1333
+ const fileName = computeStableDepFileName({
1334
+ entryPath: entry.entryPath,
1335
+ packageName: entry.packageName,
1336
+ packageVersion: entry.packageVersion,
1337
+ subpath: entry.subpath
1338
+ });
1339
+ const existing = registry.get(fileName);
1340
+ if (existing) {
1341
+ return existing;
1342
+ }
1343
+ const record = { ...entry, fileName };
1344
+ registry.set(fileName, record);
1345
+ return record;
1346
+ }
1347
+ function getDepEntry(fileName) {
1348
+ return registry.get(fileName);
1349
+ }
1350
+ function computeSubpathFromEntryPath(entryPath) {
1351
+ const packageRoot = findPackageRoot(entryPath);
1352
+ if (!packageRoot) {
1353
+ if (process.env.DEBUG_DEPS) {
1354
+ console.log(`[computeSubpathFromEntryPath] No package root for: ${entryPath}`);
1355
+ }
1356
+ return "";
1357
+ }
1358
+ let rel = path7.relative(packageRoot, entryPath).replace(/\\/g, "/");
1359
+ const extIndex = rel.lastIndexOf(".");
1360
+ if (extIndex !== -1) {
1361
+ rel = rel.substring(0, extIndex);
1362
+ }
1363
+ if (rel.endsWith("/index")) {
1364
+ rel = rel.substring(0, rel.length - "/index".length);
1365
+ }
1366
+ const pkgName = path7.basename(packageRoot);
1367
+ if (process.env.DEBUG_DEPS) {
1368
+ console.log(`[subpath] entry: ${path7.basename(entryPath)}, root: ${pkgName}, rel: "${rel}", isMain: ${rel === pkgName}`);
1369
+ }
1370
+ if (rel === pkgName || rel === "index" || rel === "" || rel === ".") {
1371
+ return "";
1372
+ }
1373
+ return rel || "";
1374
+ }
1375
+ function findPackageRoot(entryPath) {
1376
+ let currentDir = path7.dirname(entryPath);
1377
+ let previousDir = entryPath;
1378
+ while (currentDir && currentDir !== previousDir) {
1379
+ const parent = path7.dirname(currentDir);
1380
+ const grandparent = path7.dirname(parent);
1381
+ if (path7.basename(parent) === "node_modules") {
1382
+ const pkgJsonPath = path7.join(currentDir, "package.json");
1383
+ if (fs5.existsSync(pkgJsonPath)) {
1384
+ return currentDir;
1385
+ }
1386
+ }
1387
+ if (path7.basename(grandparent) === "node_modules" && path7.basename(parent).startsWith("@")) {
1388
+ const pkgJsonPath = path7.join(currentDir, "package.json");
1389
+ if (fs5.existsSync(pkgJsonPath)) {
1390
+ return currentDir;
1391
+ }
1392
+ }
1393
+ previousDir = currentDir;
1394
+ currentDir = parent;
1395
+ }
1396
+ return null;
1397
+ }
1398
+ function sanitizePackageName(name) {
1399
+ return name.replace(/^@/, "").replace(/\//g, "__");
1400
+ }
1401
+ function normalizeSubpath(subpath) {
1402
+ if (!subpath) return "";
1403
+ const cleaned = subpath.replace(/^\.\//, "").replace(/^\//, "");
1404
+ if (!cleaned || cleaned === "." || cleaned === "index") return "";
1405
+ return cleaned.replace(/\//g, "__");
1406
+ }
1407
+
1408
+ // src/core/refresh/reactRefreshInstrumentation.ts
1409
+ function isPascalCaseIdentifier(name) {
1410
+ return /^[A-Z][A-Za-z0-9_$]*$/.test(name);
1411
+ }
1412
+ function hasRefreshRegistrationsAlready(code) {
1413
+ return /\$RefreshReg\$/.test(code);
1414
+ }
1415
+ function dedupeByExportName(items) {
1416
+ const seen = /* @__PURE__ */ new Set();
1417
+ const out = [];
1418
+ for (const it of items) {
1419
+ const key = `${it.exportName}::${it.localName}`;
1420
+ if (seen.has(key)) continue;
1421
+ seen.add(key);
1422
+ out.push(it);
1423
+ }
1424
+ return out;
1425
+ }
1426
+ function detectRefreshBoundaryExports(code) {
1427
+ const out = [];
1428
+ function isValidIdentifier(name) {
1429
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
1430
+ }
1431
+ {
1432
+ const re = /export\s+(?:async\s+)?function\s+([A-Z][A-Za-z0-9_$]*)\s*\(/g;
1433
+ let m;
1434
+ while (m = re.exec(code)) {
1435
+ const name = m[1];
1436
+ out.push({ exportName: name, localName: name });
1437
+ }
1438
+ }
1439
+ {
1440
+ const re = /export\s+(?:const|let)\s+([A-Z][A-Za-z0-9_$]*)\s*=\s*(?:async\s*)?(?:function\b|\([^)]*\)\s*=>|[A-Za-z_$][A-Za-z0-9_$]*\s*=>)/g;
1441
+ let m;
1442
+ while (m = re.exec(code)) {
1443
+ const name = m[1];
1444
+ out.push({ exportName: name, localName: name });
1445
+ }
1446
+ }
1447
+ {
1448
+ const re = /export\s+default\s+(?:async\s+)?function\s+([A-Za-z_$][A-Za-z0-9_$]*)\b/g;
1449
+ const m = re.exec(code);
1450
+ if (m?.[1]) {
1451
+ const local = m[1];
1452
+ if (isPascalCaseIdentifier(local)) {
1453
+ out.push({ exportName: "default", localName: local });
1454
+ }
1455
+ }
1456
+ }
1457
+ {
1458
+ const re = /export\s+default\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*;?/g;
1459
+ let m;
1460
+ while (m = re.exec(code)) {
1461
+ const local = m[1];
1462
+ if (local === "function" || local === "class") continue;
1463
+ if (isPascalCaseIdentifier(local)) {
1464
+ out.push({ exportName: "default", localName: local });
1465
+ }
1466
+ }
1467
+ }
1468
+ {
1469
+ const re = /export\s+(default\s+)?class\s+([A-Z][A-Za-z0-9_$]*)\b/g;
1470
+ let m;
1471
+ while (m = re.exec(code)) {
1472
+ const isDefault = Boolean(m[1]);
1473
+ const local = m[2];
1474
+ if (!isPascalCaseIdentifier(local)) continue;
1475
+ out.push({ exportName: isDefault ? "default" : local, localName: local });
1476
+ }
1477
+ }
1478
+ {
1479
+ const re = /export\s+(?:const|let)\s+([A-Z][A-Za-z0-9_$]*)\s*=\s*class\b/g;
1480
+ let m;
1481
+ while (m = re.exec(code)) {
1482
+ const name = m[1];
1483
+ if (!isPascalCaseIdentifier(name)) continue;
1484
+ out.push({ exportName: name, localName: name });
1485
+ }
1486
+ }
1487
+ {
1488
+ const re = /export\s*{\s*([^}]+)\s*}\s*(?:from\s*(['"][^'"]+['"]))?\s*;?/g;
1489
+ let m;
1490
+ while (m = re.exec(code)) {
1491
+ const from = m[2];
1492
+ if (from) continue;
1493
+ const specList = m[1] ?? "";
1494
+ const parts = specList.split(",").map((p) => p.trim()).filter(Boolean);
1495
+ for (const part of parts) {
1496
+ if (part.startsWith("type ")) continue;
1497
+ const asMatch = part.split(/\s+as\s+/);
1498
+ const local = (asMatch[0] ?? "").trim();
1499
+ const exported = (asMatch[1] ?? local).trim();
1500
+ if (!local || !exported) continue;
1501
+ if (local === "default") continue;
1502
+ if (!isValidIdentifier(local) || !isValidIdentifier(exported)) continue;
1503
+ if (!isPascalCaseIdentifier(local)) continue;
1504
+ if (exported !== "default" && !isPascalCaseIdentifier(exported)) continue;
1505
+ out.push({ exportName: exported, localName: local });
1506
+ }
1507
+ }
1508
+ }
1509
+ return dedupeByExportName(out);
1510
+ }
1511
+ async function buildReactRefreshRegistrations(code, _filePath) {
1512
+ if (hasRefreshRegistrationsAlready(code)) return "";
1513
+ const candidates = detectRefreshBoundaryExports(code);
1514
+ if (!candidates.length) return "";
1515
+ const lines = candidates.map(({ exportName, localName }) => {
1516
+ return `window.$RefreshReg$?.(${localName}, normalizeRefreshModuleId(import.meta.url) + ":" + ${JSON.stringify(exportName)});`;
1517
+ });
1518
+ return "\n" + lines.join("\n") + "\n";
1519
+ }
1520
+ function needsReactRefresh(ext, isDev) {
1521
+ if (!isDev) return false;
1522
+ return ext === ".jsx" || ext === ".tsx";
1523
+ }
1524
+ async function instrumentReactRefresh(options) {
1525
+ const { code, filePath, ext, isDev, isEntry = false } = options;
1526
+ if (!needsReactRefresh(ext, isDev)) {
1527
+ return { shouldInstrument: false, prologue: "", registrations: "", epilogue: "" };
1528
+ }
1529
+ const registrations = isEntry ? "" : await buildReactRefreshRegistrations(code, filePath);
1530
+ const prologue = `import { setupReactRefresh, normalizeRefreshModuleId } from "/__ionify_react_refresh.js";
1531
+ const __ionifyRefresh__ = setupReactRefresh(import.meta.hot ?? { accept() {}, dispose() {} }, normalizeRefreshModuleId(import.meta.url));
1532
+ `;
1533
+ const epilogue = `
1534
+ __ionifyRefresh__?.finalize?.();
1535
+
1536
+ if (import.meta.hot) {
1537
+ import.meta.hot.accept((newModule) => {
1538
+ __ionifyRefresh__?.refresh?.(newModule);
1539
+ });
1540
+ import.meta.hot.dispose(() => {
1541
+ __ionifyRefresh__?.dispose?.();
1542
+ });
1543
+ }
1544
+ `;
1545
+ return { shouldInstrument: true, prologue, registrations, epilogue };
1546
+ }
1547
+
1548
+ // src/core/refresh/entryDetection.ts
1549
+ import path8 from "path";
1550
+ var ENTRY_PATTERNS = [
1551
+ /\/src\/main\.(tsx?|jsx?)$/,
1552
+ /\/src\/index\.(tsx?|jsx?)$/
1553
+ ];
1554
+ function normalizePath(input) {
1555
+ const normalized = path8.normalize(input).replace(/\\/g, "/");
1556
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
1557
+ }
1558
+ function isEntryModule(filePath, config) {
1559
+ const normalized = normalizePath(path8.resolve(filePath));
1560
+ if (config?.entry) {
1561
+ const root = config.root ?? process.cwd();
1562
+ const entries = Array.isArray(config.entry) ? config.entry : [config.entry];
1563
+ for (const entry of entries) {
1564
+ const resolvedEntry = path8.resolve(root, entry);
1565
+ const normalizedEntry = normalizePath(resolvedEntry);
1566
+ if (normalized === normalizedEntry) return true;
1567
+ }
1568
+ }
1569
+ return ENTRY_PATTERNS.some((pattern) => pattern.test(normalized));
1570
+ }
1571
+
1572
+ // src/core/refresh/refreshEligibility.ts
1573
+ function containsJSX(code) {
1574
+ const sample = code.slice(0, 8 * 1024);
1575
+ if (sample.includes("React.createElement")) return true;
1576
+ if (/\bjsx(?:s)?\s*\(/.test(sample)) return true;
1577
+ if (sample.includes("<>") || sample.includes("</>")) return true;
1578
+ if (/<[A-Za-z][A-Za-z0-9.$_-]*\b[^>]*\/>/.test(sample)) return true;
1579
+ if (/<[A-Za-z][A-Za-z0-9.$_-]*\b[^>]*>/.test(sample) && /<\/[A-Za-z]/.test(sample)) {
1580
+ return true;
1581
+ }
1225
1582
  return false;
1226
1583
  }
1584
+ function shouldUseReactRefresh(options) {
1585
+ const { ext, code, isDev, config } = options;
1586
+ if (!isDev) return false;
1587
+ if (config?.fastRefresh === false) return false;
1588
+ if (ext === ".jsx" || ext === ".tsx") return containsJSX(code);
1589
+ if (ext === ".js" || ext === ".ts" || ext === ".mjs" || ext === ".mts") return containsJSX(code);
1590
+ return false;
1591
+ }
1592
+
1593
+ // src/core/loaders/js.ts
1594
+ import fs6 from "fs";
1595
+ import path9 from "path";
1596
+ var JS_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"]);
1227
1597
  function shouldTransform(ext, filePath) {
1228
1598
  if (!JS_EXTENSIONS.has(ext)) return false;
1229
1599
  if (filePath.endsWith(".d.ts")) return false;
1230
1600
  return true;
1231
1601
  }
1602
+ function computeSubpathForDep(fsPath, pkg) {
1603
+ const computed = computeSubpathFromEntryPath(fsPath);
1604
+ if (!computed && !fs6.existsSync(fsPath) && pkg && typeof pkg.subpath === "string") {
1605
+ const raw = pkg.subpath;
1606
+ const cleaned = raw.replace(/^\.\//, "").replace(/^\/+/, "");
1607
+ if (cleaned && cleaned !== "." && cleaned !== "index") {
1608
+ return cleaned;
1609
+ }
1610
+ }
1611
+ if (process.env.DEBUG_DEPS) {
1612
+ console.log(`[computeSubpathForDep] fsPath: ${fsPath}`);
1613
+ console.log(`[computeSubpathForDep] pkg.name: ${pkg?.name}, pkg.subpath: ${pkg?.subpath}`);
1614
+ console.log(`[computeSubpathForDep] computed: "${computed}"`);
1615
+ }
1616
+ return computed || null;
1617
+ }
1618
+ function looksLikeCjsWrapperSource(source) {
1619
+ const sample = source.slice(0, 16 * 1024);
1620
+ return sample.includes("module.exports") || sample.includes("exports.") || sample.includes("Object.defineProperty(exports") || sample.includes("Object.defineProperty(module.exports") || sample.includes("require(") || sample.includes("require (");
1621
+ }
1622
+ function looksLikeEsmSource(source) {
1623
+ const sample = source.slice(0, 16 * 1024);
1624
+ return sample.includes("import ") || sample.includes("export ") || sample.includes("import{") || sample.includes("export{") || sample.includes("import(");
1625
+ }
1626
+ function findNearestPackageJson(filePath) {
1627
+ let current = path9.dirname(filePath);
1628
+ for (let i = 0; i < 25; i++) {
1629
+ const candidate = path9.join(current, "package.json");
1630
+ if (fs6.existsSync(candidate)) return candidate;
1631
+ const parent = path9.dirname(current);
1632
+ if (parent === current) break;
1633
+ current = parent;
1634
+ }
1635
+ return null;
1636
+ }
1637
+ function makeDepsProxyForFile(filePath, code) {
1638
+ if (!looksLikeCjsWrapperSource(code)) return null;
1639
+ const pkgJsonPath = findNearestPackageJson(filePath);
1640
+ if (!pkgJsonPath) return null;
1641
+ try {
1642
+ const pkg = JSON.parse(fs6.readFileSync(pkgJsonPath, "utf8"));
1643
+ const fileName = registerDepEntry({
1644
+ entryPath: filePath,
1645
+ packageName: pkg?.name ?? "dep",
1646
+ packageVersion: pkg?.version ?? "0.0.0",
1647
+ subpath: null
1648
+ }).fileName;
1649
+ return `import * as __ionify_dep__ from "/@deps/${fileName}";
1650
+ export default __ionify_dep__;
1651
+ export * from "/@deps/${fileName}";
1652
+ `;
1653
+ } catch {
1654
+ return null;
1655
+ }
1656
+ }
1232
1657
  async function swcTranspile(code, filePath, ext, reactRefresh) {
1233
1658
  const isTypeScript = ext === ".ts" || ext === ".tsx";
1234
1659
  const isTsx = ext === ".tsx";
@@ -1274,18 +1699,30 @@ var jsLoader = {
1274
1699
  name: "js",
1275
1700
  order: 0,
1276
1701
  test: ({ ext, path: filePath }) => shouldTransform(ext, filePath),
1277
- transform: async ({ path: filePath, code, ext }) => {
1702
+ transform: async ({ path: filePath, code, ext, config }) => {
1278
1703
  const isNodeModules = filePath.includes("node_modules");
1704
+ const rewriteDebug = process.env.IONIFY_IMPORT_REWRITE_DEBUG === "1";
1279
1705
  let output = code;
1280
1706
  if (isNodeModules) {
1281
- const bundled = tryBundleNodeModule(filePath, code);
1282
- if (bundled) {
1283
- output = bundled;
1707
+ const depsProxy = makeDepsProxyForFile(filePath, code);
1708
+ if (depsProxy) {
1709
+ output = depsProxy;
1284
1710
  } else {
1285
- output = code;
1711
+ const shouldAttemptBundle = ext === ".cjs" || looksLikeCjsWrapperSource(code) || !looksLikeEsmSource(code) && ext !== ".mjs";
1712
+ if (shouldAttemptBundle) {
1713
+ const bundled = tryBundleNodeModule(filePath, code);
1714
+ if (bundled) {
1715
+ output = bundled;
1716
+ } else {
1717
+ output = code;
1718
+ }
1719
+ } else {
1720
+ output = code;
1721
+ }
1286
1722
  }
1287
1723
  } else {
1288
- const reactRefresh = needsReactRefresh(ext);
1724
+ const isDev = process.env.NODE_ENV !== "production";
1725
+ const reactRefresh = shouldUseReactRefresh({ ext, code, isDev, config });
1289
1726
  const mode = currentMode();
1290
1727
  const nativeResult = tryNativeTransform(mode, code, {
1291
1728
  filename: filePath,
@@ -1299,22 +1736,29 @@ var jsLoader = {
1299
1736
  output = await swcTranspile(code, filePath, ext, reactRefresh);
1300
1737
  }
1301
1738
  if (reactRefresh) {
1302
- const prologue = `import { setupReactRefresh } from "/__ionify_react_refresh.js";
1303
- const __ionifyRefresh__ = setupReactRefresh(import.meta.hot, import.meta.url);
1304
- `;
1305
- const epilogue = `
1306
- __ionifyRefresh__?.finalize?.();
1307
-
1308
- if (import.meta.hot) {
1309
- import.meta.hot.accept((newModule) => {
1310
- __ionifyRefresh__?.refresh?.(newModule);
1311
- });
1312
- import.meta.hot.dispose(() => {
1313
- __ionifyRefresh__?.dispose?.();
1314
- });
1315
- }
1739
+ const isEntry = isEntryModule(filePath, config ?? void 0);
1740
+ if (process.env.IONIFY_REFRESH_DEBUG === "1") {
1741
+ console.log(`[Refresh] ${filePath} \u2192 isEntry=${isEntry}, ext=${ext}`);
1742
+ }
1743
+ const result = await instrumentReactRefresh({
1744
+ code: output,
1745
+ filePath,
1746
+ ext,
1747
+ isDev,
1748
+ isEntry
1749
+ });
1750
+ if (process.env.IONIFY_REFRESH_DEBUG === "1") {
1751
+ console.log(
1752
+ `[Refresh] instrument=${result.shouldInstrument} ${filePath} \u2192 isEntry=${isEntry}`
1753
+ );
1754
+ }
1755
+ if (result.shouldInstrument) {
1756
+ output = result.prologue + output + result.registrations + result.epilogue;
1757
+ } else {
1758
+ output += `
1759
+ if (import.meta.hot) import.meta.hot.accept();
1316
1760
  `;
1317
- output = prologue + output + epilogue;
1761
+ }
1318
1762
  } else {
1319
1763
  output += `
1320
1764
  if (import.meta.hot) {
@@ -1325,8 +1769,11 @@ if (import.meta.hot) {
1325
1769
  }
1326
1770
  await init;
1327
1771
  const [imports] = parse(output);
1772
+ if (rewriteDebug && ext === ".mjs" && isNodeModules) {
1773
+ console.warn(`[Ionify][rewrite] scanning ${imports.length} import(s) in ${filePath}`);
1774
+ }
1328
1775
  if (imports.length) {
1329
- const rootDir = process.cwd();
1776
+ const rootDir = config?.root ? path9.resolve(config.root) : process.cwd();
1330
1777
  let rewritten = "";
1331
1778
  let lastIndex = 0;
1332
1779
  let mutated = false;
@@ -1345,8 +1792,96 @@ if (import.meta.hot) {
1345
1792
  pathPart = spec.slice(0, splitIndex);
1346
1793
  suffix = spec.slice(splitIndex);
1347
1794
  }
1795
+ const isBare = !pathPart.startsWith(".") && !pathPart.startsWith("/") && !pathPart.startsWith("http://") && !pathPart.startsWith("https://");
1796
+ if (isBare && native?.resolveModule) {
1797
+ const resolvedNative = native.resolveModule(pathPart, filePath);
1798
+ const kind = resolvedNative?.kind;
1799
+ const fsPath = resolvedNative?.fsPath ?? resolvedNative?.fs_path ?? null;
1800
+ if (kind === "PkgCjs" && fsPath) {
1801
+ const pkg = resolvedNative?.pkg;
1802
+ const fileName = registerDepEntry({
1803
+ entryPath: fsPath,
1804
+ packageName: pkg?.name ?? pathPart,
1805
+ packageVersion: pkg?.version ?? "0.0.0",
1806
+ subpath: computeSubpathForDep(fsPath, pkg)
1807
+ }).fileName;
1808
+ const replacement2 = `/@deps/${fileName}`;
1809
+ if (!mutated) {
1810
+ mutated = true;
1811
+ }
1812
+ if (record.t === 2) {
1813
+ rewritten += output.slice(lastIndex, record.s + 1);
1814
+ rewritten += replacement2;
1815
+ rewritten += output[record.e - 1];
1816
+ lastIndex = record.e;
1817
+ } else {
1818
+ rewritten += output.slice(lastIndex, record.s);
1819
+ rewritten += replacement2;
1820
+ lastIndex = record.e;
1821
+ }
1822
+ continue;
1823
+ }
1824
+ if (kind === "PkgEsm" && fsPath) {
1825
+ try {
1826
+ const resolvedCode = fs6.readFileSync(fsPath, "utf8");
1827
+ if (looksLikeCjsWrapperSource(resolvedCode)) {
1828
+ const pkg2 = resolvedNative?.pkg;
1829
+ const fileName2 = registerDepEntry({
1830
+ entryPath: fsPath,
1831
+ packageName: pkg2?.name ?? pathPart,
1832
+ packageVersion: pkg2?.version ?? "0.0.0",
1833
+ subpath: computeSubpathForDep(fsPath, pkg2)
1834
+ }).fileName;
1835
+ const replacement3 = `/@deps/${fileName2}`;
1836
+ if (!mutated) mutated = true;
1837
+ if (record.t === 2) {
1838
+ rewritten += output.slice(lastIndex, record.s + 1);
1839
+ rewritten += replacement3;
1840
+ rewritten += output[record.e - 1];
1841
+ lastIndex = record.e;
1842
+ } else {
1843
+ rewritten += output.slice(lastIndex, record.s);
1844
+ rewritten += replacement3;
1845
+ lastIndex = record.e;
1846
+ }
1847
+ continue;
1848
+ }
1849
+ } catch {
1850
+ }
1851
+ const pkg = resolvedNative?.pkg;
1852
+ const fileName = registerDepEntry({
1853
+ entryPath: fsPath,
1854
+ packageName: pkg?.name ?? pathPart,
1855
+ packageVersion: pkg?.version ?? "0.0.0",
1856
+ subpath: computeSubpathForDep(fsPath, pkg)
1857
+ }).fileName;
1858
+ const replacement2 = `/@deps/${fileName}`;
1859
+ if (!mutated) mutated = true;
1860
+ if (record.t === 2) {
1861
+ rewritten += output.slice(lastIndex, record.s + 1);
1862
+ rewritten += replacement2;
1863
+ rewritten += output[record.e - 1];
1864
+ lastIndex = record.e;
1865
+ } else {
1866
+ rewritten += output.slice(lastIndex, record.s);
1867
+ rewritten += replacement2;
1868
+ lastIndex = record.e;
1869
+ }
1870
+ continue;
1871
+ }
1872
+ if (kind === "Builtin" || kind === "Virtual") {
1873
+ continue;
1874
+ }
1875
+ }
1348
1876
  const resolved = resolveImport(pathPart, filePath);
1349
- if (!resolved) continue;
1877
+ if (!resolved) {
1878
+ if (rewriteDebug) {
1879
+ console.warn(
1880
+ `[Ionify][rewrite] FAILED to resolve '${pathPart}' from '${filePath}'`
1881
+ );
1882
+ }
1883
+ continue;
1884
+ }
1350
1885
  const resolvedExt = resolved.slice(resolved.lastIndexOf("."));
1351
1886
  let augmentedSuffix = suffix;
1352
1887
  if (resolvedExt === ".css" && !suffix) {
@@ -1390,6 +1925,11 @@ if (import.meta.hot) {
1390
1925
  if (mutated) {
1391
1926
  rewritten += output.slice(lastIndex);
1392
1927
  output = rewritten;
1928
+ } else if (rewriteDebug && isNodeModules) {
1929
+ const sample = imports.slice(0, 8).map((r) => r.n).filter(Boolean).join(", ");
1930
+ console.warn(
1931
+ `[Ionify][rewrite] no rewrites applied for ${filePath}; first imports: ${sample}`
1932
+ );
1393
1933
  }
1394
1934
  }
1395
1935
  return { code: output };
@@ -1397,12 +1937,12 @@ if (import.meta.hot) {
1397
1937
  };
1398
1938
 
1399
1939
  // src/core/loaders/registry.ts
1400
- var registry = [];
1940
+ var registry2 = /* @__PURE__ */ new Set();
1401
1941
  function registerLoader(registration) {
1402
- registry.push(registration);
1942
+ registry2.add(registration);
1403
1943
  }
1404
1944
  async function applyRegisteredLoaders(engine, config) {
1405
- for (const registration of registry) {
1945
+ for (const registration of registry2) {
1406
1946
  await registration(engine, config ?? null);
1407
1947
  }
1408
1948
  if (config?.plugins) {
@@ -1414,9 +1954,8 @@ async function applyRegisteredLoaders(engine, config) {
1414
1954
  }
1415
1955
  if (plugin.setup) {
1416
1956
  const context = {
1417
- registerLoader: (loader) => {
1418
- engine.useLoader(loader);
1419
- }
1957
+ config: config ?? null,
1958
+ registerLoader: (loader) => engine.useLoader(loader)
1420
1959
  };
1421
1960
  await plugin.setup(context);
1422
1961
  }
@@ -1433,8 +1972,8 @@ registerLoader((engine) => {
1433
1972
  });
1434
1973
 
1435
1974
  // src/cli/utils/config.ts
1436
- import fs5 from "fs";
1437
- import path7 from "path";
1975
+ import fs7 from "fs";
1976
+ import path10 from "path";
1438
1977
  import { pathToFileURL as pathToFileURL2 } from "url";
1439
1978
  import { build } from "esbuild";
1440
1979
  var CONFIG_BASENAMES = [
@@ -1447,7 +1986,7 @@ var CONFIG_BASENAMES = [
1447
1986
  var cachedConfig2 = null;
1448
1987
  var configLoaded = false;
1449
1988
  async function bundleConfig(entry) {
1450
- const absDir = path7.dirname(entry);
1989
+ const absDir = path10.dirname(entry);
1451
1990
  const inlineIonifyPlugin = {
1452
1991
  name: "inline-ionify",
1453
1992
  setup(build2) {
@@ -1495,8 +2034,8 @@ const __filename = ${filenameLiteral};
1495
2034
  }
1496
2035
  function findConfigFile(cwd) {
1497
2036
  for (const name of CONFIG_BASENAMES) {
1498
- const candidate = path7.resolve(cwd, name);
1499
- if (fs5.existsSync(candidate) && fs5.statSync(candidate).isFile()) {
2037
+ const candidate = path10.resolve(cwd, name);
2038
+ if (fs7.existsSync(candidate) && fs7.statSync(candidate).isFile()) {
1500
2039
  return candidate;
1501
2040
  }
1502
2041
  }
@@ -1507,7 +2046,7 @@ async function loadIonifyConfig(cwd = process.cwd()) {
1507
2046
  configLoaded = true;
1508
2047
  const configPath = findConfigFile(cwd);
1509
2048
  if (!configPath) {
1510
- cachedConfig2 = null;
2049
+ cachedConfig2 = { root: cwd };
1511
2050
  configureResolverAliases(void 0, cwd);
1512
2051
  return cachedConfig2;
1513
2052
  }
@@ -1523,15 +2062,33 @@ async function loadIonifyConfig(cwd = process.cwd()) {
1523
2062
  resolved = await resolved;
1524
2063
  }
1525
2064
  if (resolved && typeof resolved === "object") {
2065
+ if (resolved.root) {
2066
+ const rootPath = path10.isAbsolute(resolved.root) ? resolved.root : path10.resolve(path10.dirname(configPath), resolved.root);
2067
+ if (!fs7.existsSync(rootPath)) {
2068
+ logError(`Config error: root directory does not exist: ${rootPath}`);
2069
+ throw new Error(`Invalid root: ${rootPath}`);
2070
+ }
2071
+ if (!fs7.statSync(rootPath).isDirectory()) {
2072
+ logError(`Config error: root must be a directory: ${rootPath}`);
2073
+ throw new Error(`Invalid root: ${rootPath}`);
2074
+ }
2075
+ resolved.root = rootPath;
2076
+ logInfo(`Using project root: ${path10.relative(cwd, rootPath)}`);
2077
+ } else {
2078
+ resolved.root = path10.dirname(configPath);
2079
+ }
2080
+ if (resolved.optimizeDeps?.esbuildOptions) {
2081
+ logWarn("optimizeDeps.esbuildOptions is not supported in Ionify (uses native Rust optimizer). This option will be ignored.");
2082
+ }
1526
2083
  cachedConfig2 = resolved;
1527
- const baseDir = path7.dirname(configPath);
2084
+ const baseDir = path10.dirname(configPath);
1528
2085
  const aliases = resolved?.resolve?.alias;
1529
2086
  if (aliases && typeof aliases === "object") {
1530
2087
  configureResolverAliases(aliases, baseDir);
1531
2088
  } else {
1532
2089
  configureResolverAliases(void 0, baseDir);
1533
2090
  }
1534
- logInfo(`Loaded ionify config from ${path7.relative(cwd, configPath)}`);
2091
+ logInfo(`Loaded ionify config from ${path10.relative(cwd, configPath)}`);
1535
2092
  } else {
1536
2093
  throw new Error("Config did not export an object");
1537
2094
  }
@@ -1561,13 +2118,10 @@ function resolveMinifier(config, opts = {}) {
1561
2118
  if (fromConfig) return fromConfig;
1562
2119
  return "auto";
1563
2120
  }
1564
- function applyMinifierEnv(choice) {
1565
- process.env.IONIFY_MINIFIER = choice;
1566
- }
1567
2121
 
1568
2122
  // src/cli/utils/env.ts
1569
- import fs6 from "fs";
1570
- import path8 from "path";
2123
+ import fs8 from "fs";
2124
+ import path11 from "path";
1571
2125
  function parseValue(raw) {
1572
2126
  let value = raw.trim();
1573
2127
  if (!value) return "";
@@ -1599,11 +2153,11 @@ function loadEnv(mode = "development", rootDir = process.cwd()) {
1599
2153
  ];
1600
2154
  const merged = {};
1601
2155
  for (const name of candidates) {
1602
- const filePath = path8.resolve(rootDir, name);
1603
- if (!fs6.existsSync(filePath) || !fs6.statSync(filePath).isFile()) {
2156
+ const filePath = path11.resolve(rootDir, name);
2157
+ if (!fs8.existsSync(filePath) || !fs8.statSync(filePath).isFile()) {
1604
2158
  continue;
1605
2159
  }
1606
- const contents = fs6.readFileSync(filePath, "utf8");
2160
+ const contents = fs8.readFileSync(filePath, "utf8");
1607
2161
  const parsed = parseEnvFile(contents);
1608
2162
  Object.assign(merged, parsed);
1609
2163
  }
@@ -1689,11 +2243,6 @@ function resolveTreeshake(input, options = {}) {
1689
2243
  }
1690
2244
  return resolved;
1691
2245
  }
1692
- function applyTreeshakeEnv(resolved) {
1693
- process.env.IONIFY_TREESHAKE = resolved.mode;
1694
- process.env.IONIFY_TREESHAKE_INCLUDE = JSON.stringify(resolved.include);
1695
- process.env.IONIFY_TREESHAKE_EXCLUDE = JSON.stringify(resolved.exclude);
1696
- }
1697
2246
 
1698
2247
  // src/cli/utils/scope-hoist.ts
1699
2248
  var DEFAULT_SCOPE_HOIST = {
@@ -1756,12 +2305,6 @@ function resolveScopeHoist(configValue, options = {}) {
1756
2305
  }
1757
2306
  return resolved;
1758
2307
  }
1759
- function applyScopeHoistEnv(result) {
1760
- process.env.IONIFY_SCOPE_HOIST = result.enable ? "true" : "false";
1761
- process.env.IONIFY_SCOPE_HOIST_INLINE = result.inlineFunctions ? "true" : "false";
1762
- process.env.IONIFY_SCOPE_HOIST_CONST = result.constantFolding ? "true" : "false";
1763
- process.env.IONIFY_SCOPE_HOIST_COMBINE = result.combineVariables ? "true" : "false";
1764
- }
1765
2308
 
1766
2309
  // src/cli/utils/parser.ts
1767
2310
  function normalize2(mode) {
@@ -1783,20 +2326,95 @@ function applyParserEnv(mode) {
1783
2326
  process.env.IONIFY_PARSER = mode;
1784
2327
  }
1785
2328
 
2329
+ // src/core/utils/define.ts
2330
+ function applyDefineReplacements(code, definitions) {
2331
+ if (!definitions || Object.keys(definitions).length === 0) {
2332
+ return code;
2333
+ }
2334
+ let result = code;
2335
+ const sortedKeys = Object.keys(definitions).sort((a, b) => b.length - a.length);
2336
+ for (const key of sortedKeys) {
2337
+ const value = definitions[key];
2338
+ let replacement;
2339
+ if (typeof value === "string") {
2340
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
2341
+ replacement = value;
2342
+ } else {
2343
+ replacement = JSON.stringify(value);
2344
+ }
2345
+ } else if (typeof value === "number" || typeof value === "boolean") {
2346
+ replacement = String(value);
2347
+ } else if (value === null || value === void 0) {
2348
+ replacement = "null";
2349
+ } else {
2350
+ replacement = JSON.stringify(value);
2351
+ }
2352
+ if (key.includes(".")) {
2353
+ result = replaceMemberExpression(result, key, replacement);
2354
+ } else {
2355
+ result = replaceIdentifier(result, key, replacement);
2356
+ }
2357
+ }
2358
+ return result;
2359
+ }
2360
+ function replaceIdentifier(code, identifier, replacement) {
2361
+ const regex = new RegExp(
2362
+ `(?<![\\w.$])${escapeRegExp(identifier)}(?![\\w])`,
2363
+ "g"
2364
+ );
2365
+ return code.replace(regex, replacement);
2366
+ }
2367
+ function replaceMemberExpression(code, expression, replacement) {
2368
+ const parts = expression.split(".");
2369
+ const pattern = parts.map(escapeRegExp).join("\\s*\\.\\s*");
2370
+ const regex = new RegExp(
2371
+ `(?<![\\w.$])${pattern}(?![\\w.])`,
2372
+ "g"
2373
+ );
2374
+ return code.replace(regex, replacement);
2375
+ }
2376
+ function escapeRegExp(str) {
2377
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2378
+ }
2379
+ function buildDefineConfig(userDefine, envValues, envPrefix = ["VITE_", "IONIFY_"]) {
2380
+ const define = { ...userDefine || {} };
2381
+ const prefixes = Array.isArray(envPrefix) ? envPrefix : [envPrefix];
2382
+ for (const [key, value] of Object.entries(envValues)) {
2383
+ const hasPrefix = prefixes.some((prefix) => key.startsWith(prefix));
2384
+ if (hasPrefix || key === "NODE_ENV" || key === "MODE") {
2385
+ const importMetaKey = `import.meta.env.${key}`;
2386
+ if (!(importMetaKey in define)) {
2387
+ define[importMetaKey] = value;
2388
+ }
2389
+ }
2390
+ }
2391
+ if (!("import.meta.env.DEV" in define)) {
2392
+ define["import.meta.env.DEV"] = envValues.MODE !== "production";
2393
+ }
2394
+ if (!("import.meta.env.PROD" in define)) {
2395
+ define["import.meta.env.PROD"] = envValues.MODE === "production";
2396
+ }
2397
+ return define;
2398
+ }
2399
+
1786
2400
  // src/cli/commands/dev.ts
1787
- import crypto2 from "crypto";
2401
+ import crypto3 from "crypto";
2402
+ import zlib from "zlib";
2403
+ var IONIFY_CSS_JS_MARKER = "// ionify:css";
2404
+ var IONIFY_VENDOR_PACK_MARKER = "// ionify:vendor-pack";
1788
2405
  var __filename2 = fileURLToPath2(import.meta.url);
1789
- var __dirname2 = path9.dirname(__filename2);
1790
- var CLIENT_DIR = path9.resolve(__dirname2, "../client");
1791
- var CLIENT_FALLBACK_DIR = path9.resolve(process.cwd(), "src/client");
2406
+ var __dirname2 = path12.dirname(__filename2);
2407
+ var CLIENT_DIR = path12.resolve(__dirname2, "../client");
2408
+ var CLIENT_FALLBACK_DIR = path12.resolve(process.cwd(), "src/client");
2409
+ var DEPS_PREFIX = "/@deps/";
1792
2410
  function readClientAssetFile(fileName) {
1793
- const primary = path9.join(CLIENT_DIR, fileName);
1794
- if (fs7.existsSync(primary)) {
1795
- return { filePath: primary, code: fs7.readFileSync(primary, "utf8") };
2411
+ const primary = path12.join(CLIENT_DIR, fileName);
2412
+ if (fs9.existsSync(primary)) {
2413
+ return { filePath: primary, code: fs9.readFileSync(primary, "utf8") };
1796
2414
  }
1797
- const fallback = path9.join(CLIENT_FALLBACK_DIR, fileName);
1798
- if (fs7.existsSync(fallback)) {
1799
- return { filePath: fallback, code: fs7.readFileSync(fallback, "utf8") };
2415
+ const fallback = path12.join(CLIENT_FALLBACK_DIR, fileName);
2416
+ if (fs9.existsSync(fallback)) {
2417
+ return { filePath: fallback, code: fs9.readFileSync(fallback, "utf8") };
1800
2418
  }
1801
2419
  throw new Error(`Missing Ionify client asset: ${fileName}`);
1802
2420
  }
@@ -1804,7 +2422,7 @@ function readClientAsset(fileName) {
1804
2422
  return readClientAssetFile(fileName).code;
1805
2423
  }
1806
2424
  function guessContentType(filePath) {
1807
- const ext = path9.extname(filePath);
2425
+ const ext = path12.extname(filePath);
1808
2426
  if (ext === ".html") return "text/html; charset=utf-8";
1809
2427
  if (ext === ".css") return "text/css; charset=utf-8";
1810
2428
  if (ext === ".json") return "application/json; charset=utf-8";
@@ -1816,16 +2434,289 @@ function guessContentType(filePath) {
1816
2434
  return "application/json; charset=utf-8";
1817
2435
  return "text/plain; charset=utf-8";
1818
2436
  }
2437
+ function mergeVaryHeader(existing, next) {
2438
+ const parts = /* @__PURE__ */ new Set();
2439
+ const add = (value) => {
2440
+ value.split(",").map((v) => v.trim()).filter(Boolean).forEach((v) => parts.add(v));
2441
+ };
2442
+ if (typeof existing === "string") add(existing);
2443
+ else if (Array.isArray(existing)) existing.forEach(add);
2444
+ add(next);
2445
+ return Array.from(parts).join(", ");
2446
+ }
2447
+ function normalizeEtag(tag) {
2448
+ return tag.trim().replace(/^W\//, "");
2449
+ }
2450
+ function isNotModified(req, etag) {
2451
+ const header = req.headers["if-none-match"];
2452
+ const value = Array.isArray(header) ? header.join(",") : header;
2453
+ if (!value) return false;
2454
+ if (value.trim() === "*") return true;
2455
+ const expected = normalizeEtag(etag);
2456
+ return value.split(",").map((t) => t.trim()).filter(Boolean).some((t) => normalizeEtag(t) === expected);
2457
+ }
2458
+ function weakEtagFromStat(prefix, stat) {
2459
+ const mtime = Math.floor(stat.mtimeMs);
2460
+ return `W/"${prefix}-${stat.size}-${mtime}"`;
2461
+ }
2462
+ function shouldCompressContentType(contentType) {
2463
+ const ct = contentType.toLowerCase();
2464
+ return ct.startsWith("text/") || ct.includes("javascript") || ct.includes("json") || ct.includes("xml") || ct.includes("svg");
2465
+ }
2466
+ function selectCompressionEncoding(req) {
2467
+ const header = req.headers["accept-encoding"];
2468
+ const value = Array.isArray(header) ? header.join(",") : header;
2469
+ if (!value) return null;
2470
+ const enc = value.toLowerCase();
2471
+ if (enc.includes("gzip")) return "gzip";
2472
+ return null;
2473
+ }
2474
+ function looksLikeIonifyCssJsModule(body) {
2475
+ const head = body.subarray(0, 96).toString("utf8");
2476
+ return head.trimStart().startsWith(IONIFY_CSS_JS_MARKER);
2477
+ }
2478
+ function computeDepsStampHash(depsAbs) {
2479
+ if (!depsAbs.length) return "0";
2480
+ const entries = [];
2481
+ for (const dep of depsAbs) {
2482
+ const abs = path12.resolve(dep);
2483
+ try {
2484
+ const stat = fs9.statSync(abs);
2485
+ entries.push(`${abs}:${stat.size}:${Math.floor(stat.mtimeMs)}`);
2486
+ } catch {
2487
+ entries.push(`${abs}:missing`);
2488
+ }
2489
+ }
2490
+ entries.sort();
2491
+ return getCacheKey(entries.join("|"));
2492
+ }
2493
+ function sendBuffer(req, res, status, contentType, body, opts) {
2494
+ res.setHeader("Content-Type", contentType);
2495
+ res.setHeader("Cache-Control", opts?.cacheControl ?? "no-cache");
2496
+ const etag = opts?.etag;
2497
+ if (etag) {
2498
+ res.setHeader("ETag", etag);
2499
+ if (isNotModified(req, etag)) {
2500
+ res.statusCode = 304;
2501
+ res.end();
2502
+ return;
2503
+ }
2504
+ }
2505
+ const encoding = body.length >= 1024 && shouldCompressContentType(contentType) ? selectCompressionEncoding(req) : null;
2506
+ if (encoding === "gzip") {
2507
+ res.setHeader("Vary", mergeVaryHeader(res.getHeader("Vary"), "Accept-Encoding"));
2508
+ res.setHeader("Content-Encoding", "gzip");
2509
+ res.statusCode = status;
2510
+ res.end(zlib.gzipSync(body, { level: 1 }));
2511
+ return;
2512
+ }
2513
+ res.statusCode = status;
2514
+ res.end(body);
2515
+ }
2516
+ var LOCKFILE_ORDER = [
2517
+ "pnpm-lock.yaml",
2518
+ "package-lock.json",
2519
+ "yarn.lock",
2520
+ "bun.lockb"
2521
+ ];
2522
+ function readLockfile(rootDir) {
2523
+ for (const name of LOCKFILE_ORDER) {
2524
+ const filePath = path12.join(rootDir, name);
2525
+ if (!fs9.existsSync(filePath)) continue;
2526
+ const contents = fs9.readFileSync(filePath);
2527
+ const packageCount = estimateLockfilePackageCount(name, contents);
2528
+ return { name, path: filePath, contents, packageCount };
2529
+ }
2530
+ return null;
2531
+ }
2532
+ function estimateLockfilePackageCount(name, contents) {
2533
+ if (name === "package-lock.json") {
2534
+ try {
2535
+ const parsed = JSON.parse(contents.toString("utf8"));
2536
+ if (parsed?.packages && typeof parsed.packages === "object") {
2537
+ return Object.keys(parsed.packages).length;
2538
+ }
2539
+ } catch {
2540
+ return null;
2541
+ }
2542
+ }
2543
+ if (name === "pnpm-lock.yaml") {
2544
+ const text = contents.toString("utf8");
2545
+ return text.split("\n").filter((line) => line.trimStart().startsWith("/")).length;
2546
+ }
2547
+ if (name === "yarn.lock") {
2548
+ const text = contents.toString("utf8");
2549
+ return text.split("\n").filter((line) => line && !line.startsWith(" ") && line.endsWith(":")).length;
2550
+ }
2551
+ return null;
2552
+ }
2553
+ function computeDepsHash(configHash, lockfile, opts) {
2554
+ const hash = crypto3.createHash("sha256");
2555
+ hash.update(configHash);
2556
+ if (lockfile) {
2557
+ hash.update(lockfile.contents);
2558
+ }
2559
+ hash.update(`NODE_ENV=${opts.nodeEnv}`);
2560
+ hash.update(`optimizeDeps.sourcemap=${opts.sourcemap ? "1" : "0"}`);
2561
+ hash.update(`optimizeDeps.bundleEsm=${opts.bundleEsm ? "1" : "0"}`);
2562
+ return hash.digest("hex").slice(0, 16);
2563
+ }
2564
+ function readProjectPackageJson(rootDir) {
2565
+ const pkgPath = path12.join(rootDir, "package.json");
2566
+ if (!fs9.existsSync(pkgPath)) return null;
2567
+ try {
2568
+ return JSON.parse(fs9.readFileSync(pkgPath, "utf8"));
2569
+ } catch {
2570
+ return null;
2571
+ }
2572
+ }
2573
+ function detectVendorSpecifiers(pkgJson) {
2574
+ if (!pkgJson || typeof pkgJson !== "object") return [];
2575
+ const deps = {
2576
+ ...pkgJson.dependencies || {},
2577
+ ...pkgJson.devDependencies || {},
2578
+ ...pkgJson.peerDependencies || {}
2579
+ };
2580
+ const has = (name) => Object.prototype.hasOwnProperty.call(deps, name);
2581
+ if (has("react") || has("react-dom")) {
2582
+ return [
2583
+ "react",
2584
+ "react-dom",
2585
+ "react-dom/client",
2586
+ "scheduler",
2587
+ "react/jsx-runtime",
2588
+ "react/jsx-dev-runtime",
2589
+ "react-refresh"
2590
+ ];
2591
+ }
2592
+ if (has("vue")) {
2593
+ return ["vue", "@vue/runtime-dom", "@vue/runtime-core"];
2594
+ }
2595
+ if (has("svelte")) {
2596
+ return ["svelte", "svelte/internal"];
2597
+ }
2598
+ return [];
2599
+ }
2600
+ function computeSubpathForDep2(fsPath, pkg) {
2601
+ const computed = computeSubpathFromEntryPath(fsPath);
2602
+ if (computed) return computed;
2603
+ if (fs9.existsSync(fsPath)) return null;
2604
+ const raw = pkg?.subpath;
2605
+ if (typeof raw === "string") {
2606
+ const cleaned = raw.replace(/^\.\//, "").replace(/^\/+/, "");
2607
+ if (cleaned && cleaned !== "." && cleaned !== "index") {
2608
+ return cleaned;
2609
+ }
2610
+ }
2611
+ return null;
2612
+ }
2613
+ function resolveVendorDeps(rootDir, specifiers) {
2614
+ if (!native?.resolveModule) return [];
2615
+ const seen = /* @__PURE__ */ new Set();
2616
+ const resolved = [];
2617
+ for (const spec of specifiers) {
2618
+ try {
2619
+ const r = native.resolveModule(spec, rootDir);
2620
+ const fsPath = r?.fsPath ?? r?.fs_path ?? null;
2621
+ if (!fsPath || typeof fsPath !== "string") continue;
2622
+ const pkg = r?.pkg ?? null;
2623
+ const packageName = typeof pkg?.name === "string" ? pkg.name : spec;
2624
+ const packageVersion = typeof pkg?.version === "string" ? pkg.version : "0.0.0";
2625
+ const subpath = computeSubpathForDep2(fsPath, pkg);
2626
+ const entry = registerDepEntry({
2627
+ entryPath: fsPath,
2628
+ packageName,
2629
+ packageVersion,
2630
+ subpath
2631
+ });
2632
+ if (seen.has(entry.fileName)) continue;
2633
+ seen.add(entry.fileName);
2634
+ resolved.push({
2635
+ specifier: spec,
2636
+ entryPath: fsPath,
2637
+ fileName: entry.fileName,
2638
+ packageLabel: formatDepLabel(packageName, subpath)
2639
+ });
2640
+ } catch {
2641
+ }
2642
+ }
2643
+ return resolved;
2644
+ }
2645
+ function injectModulePreload(html, href) {
2646
+ const tag = `<link rel="modulepreload" href="${href}">`;
2647
+ if (html.includes(tag)) return html;
2648
+ const headCloseMatch = html.match(/<\/head>/i);
2649
+ if (headCloseMatch?.index !== void 0) {
2650
+ const idx = headCloseMatch.index;
2651
+ return `${html.slice(0, idx)}${tag}
2652
+ ${html.slice(idx)}`;
2653
+ }
2654
+ const headOpenMatch = html.match(/<head[^>]*>/i);
2655
+ if (headOpenMatch?.index !== void 0) {
2656
+ const idx = headOpenMatch.index + headOpenMatch[0].length;
2657
+ return `${html.slice(0, idx)}
2658
+ ${tag}${html.slice(idx)}`;
2659
+ }
2660
+ return `${tag}
2661
+ ${html}`;
2662
+ }
2663
+ function pruneDepsCache(rootDir, depsHash) {
2664
+ const depsRoot = path12.join(rootDir, ".ionify", "deps");
2665
+ if (!fs9.existsSync(depsRoot)) return;
2666
+ const entries = fs9.readdirSync(depsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
2667
+ const fullPath = path12.join(depsRoot, entry.name);
2668
+ const stat = fs9.statSync(fullPath);
2669
+ return { name: entry.name, path: fullPath, mtimeMs: stat.mtimeMs };
2670
+ }).sort((a, b) => b.mtimeMs - a.mtimeMs);
2671
+ const keep = /* @__PURE__ */ new Set();
2672
+ keep.add(depsHash);
2673
+ for (const entry of entries.slice(0, 2)) {
2674
+ keep.add(entry.name);
2675
+ }
2676
+ for (const entry of entries) {
2677
+ if (!keep.has(entry.name)) {
2678
+ fs9.rmSync(entry.path, { recursive: true, force: true });
2679
+ }
2680
+ }
2681
+ }
2682
+ function loadDepsManifestIndex(depsRoot) {
2683
+ const manifestPath = path12.join(depsRoot, "manifest.json");
2684
+ if (!fs9.existsSync(manifestPath)) return /* @__PURE__ */ new Map();
2685
+ try {
2686
+ const raw = fs9.readFileSync(manifestPath, "utf8");
2687
+ const parsed = JSON.parse(raw);
2688
+ const entries = parsed?.entries ?? {};
2689
+ const map = /* @__PURE__ */ new Map();
2690
+ for (const [entryPath, entry] of Object.entries(entries)) {
2691
+ if (!entry?.outFile) continue;
2692
+ map.set(entry.outFile, {
2693
+ entryPath,
2694
+ packageLabel: entry.package || "unknown",
2695
+ hasSourcemap: entry.hasSourcemap === true
2696
+ });
2697
+ }
2698
+ return map;
2699
+ } catch {
2700
+ return /* @__PURE__ */ new Map();
2701
+ }
2702
+ }
2703
+ function formatDepLabel(name, subpath) {
2704
+ if (!subpath) return name;
2705
+ const cleaned = subpath.replace(/^\.\//, "").replace(/^\/+/, "");
2706
+ if (!cleaned || cleaned === ".") return name;
2707
+ return `${name}/${cleaned}`;
2708
+ }
1819
2709
  async function startDevServer({
1820
2710
  port = 5173,
2711
+ host = process.env.IONIFY_HOST || "127.0.0.1",
1821
2712
  enableSignalHandlers = true
1822
2713
  } = {}) {
1823
- const rootDir = process.cwd();
2714
+ const bootStartMs = Date.now();
2715
+ const userConfig = await loadIonifyConfig();
2716
+ const rootDir = userConfig?.root || process.cwd();
1824
2717
  const watcher = new IonifyWatcher(rootDir);
1825
2718
  const cacheDebug = process.env.IONIFY_DEV_TRANSFORM_CACHE_DEBUG === "1";
1826
- const userConfig = await loadIonifyConfig();
1827
2719
  const minifier = resolveMinifier(userConfig, { envVar: process.env.IONIFY_MINIFIER });
1828
- applyMinifierEnv(minifier);
1829
2720
  const parserMode = resolveParser(userConfig, { envMode: process.env.IONIFY_PARSER });
1830
2721
  applyParserEnv(parserMode);
1831
2722
  const treeshake = resolveTreeshake(userConfig?.treeshake, {
@@ -1833,15 +2724,15 @@ async function startDevServer({
1833
2724
  includeEnv: process.env.IONIFY_TREESHAKE_INCLUDE,
1834
2725
  excludeEnv: process.env.IONIFY_TREESHAKE_EXCLUDE
1835
2726
  });
1836
- applyTreeshakeEnv(treeshake);
1837
2727
  const scopeHoist = resolveScopeHoist(userConfig?.scopeHoist, {
1838
2728
  envMode: process.env.IONIFY_SCOPE_HOIST,
1839
2729
  inlineEnv: process.env.IONIFY_SCOPE_HOIST_INLINE,
1840
2730
  constantEnv: process.env.IONIFY_SCOPE_HOIST_CONST,
1841
2731
  combineEnv: process.env.IONIFY_SCOPE_HOIST_COMBINE
1842
2732
  });
1843
- applyScopeHoistEnv(scopeHoist);
1844
- const resolvedEntry = userConfig?.entry ? userConfig.entry.startsWith("/") ? path9.join(rootDir, userConfig.entry) : path9.resolve(rootDir, userConfig.entry) : void 0;
2733
+ const resolvedEntries = userConfig?.entry ? (Array.isArray(userConfig.entry) ? userConfig.entry : [userConfig.entry]).map(
2734
+ (entry) => entry.startsWith("/") ? path12.join(rootDir, entry) : path12.resolve(rootDir, entry)
2735
+ ) : void 0;
1845
2736
  const pluginNames = Array.isArray(userConfig?.plugins) ? userConfig.plugins.map((p) => typeof p === "string" ? p : p?.name).filter((name) => typeof name === "string" && name.length > 0) : void 0;
1846
2737
  const rawVersionInputs = {
1847
2738
  parserMode,
@@ -1849,14 +2740,64 @@ async function startDevServer({
1849
2740
  treeshake,
1850
2741
  scopeHoist,
1851
2742
  plugins: pluginNames,
1852
- entry: resolvedEntry ? [resolvedEntry] : null,
2743
+ entry: resolvedEntries ?? null,
1853
2744
  cssOptions: userConfig?.css,
1854
2745
  assetOptions: userConfig?.assets ?? userConfig?.asset
1855
2746
  };
1856
2747
  const configHash = computeGraphVersion(rawVersionInputs);
1857
2748
  logInfo(`[Dev] Version hash: ${configHash}`);
1858
2749
  process.env.IONIFY_CONFIG_HASH = configHash;
1859
- const casRoot = path9.join(rootDir, ".ionify", "cas");
2750
+ const casRoot = path12.join(rootDir, ".ionify", "cas");
2751
+ const lockfile = readLockfile(rootDir);
2752
+ if (lockfile) {
2753
+ const countLabel = lockfile.packageCount === null ? "unknown" : lockfile.packageCount;
2754
+ logInfo(`[deps] SCAN lockfile: ${lockfile.name} (${countLabel} packages)`);
2755
+ }
2756
+ const depsSourcemapEnabled = userConfig?.optimizeDeps?.sourcemap === true;
2757
+ const depsBundleEsmEnabled = userConfig?.optimizeDeps?.bundleEsm !== false;
2758
+ const depsNodeEnv = process.env.NODE_ENV ?? "development";
2759
+ const depsHash = computeDepsHash(configHash, lockfile, {
2760
+ nodeEnv: depsNodeEnv,
2761
+ sourcemap: depsSourcemapEnabled,
2762
+ bundleEsm: depsBundleEsmEnabled
2763
+ });
2764
+ logInfo(`[deps] depsHash: ${depsHash} from ${lockfile?.name ?? "config"}`);
2765
+ const depsRoot = path12.join(rootDir, ".ionify", "deps", depsHash);
2766
+ fs9.mkdirSync(depsRoot, { recursive: true });
2767
+ pruneDepsCache(rootDir, depsHash);
2768
+ const depsManifestIndex = loadDepsManifestIndex(depsRoot);
2769
+ const optimizeVendorMode = userConfig?.optimizeDeps?.vendor ?? "auto";
2770
+ const optimizeExclude = Array.isArray(userConfig?.optimizeDeps?.exclude) ? new Set(userConfig.optimizeDeps.exclude) : null;
2771
+ const autoVendor = optimizeVendorMode === "auto";
2772
+ const vendorSpecifiersRaw = optimizeVendorMode === false ? [] : Array.isArray(optimizeVendorMode) ? optimizeVendorMode : autoVendor ? detectVendorSpecifiers(readProjectPackageJson(rootDir)) : [];
2773
+ const vendorSpecifiers = vendorSpecifiersRaw.map((s) => String(s).trim()).filter(Boolean).filter((s) => !optimizeExclude?.has(s));
2774
+ const vendorDeps = resolveVendorDeps(rootDir, vendorSpecifiers);
2775
+ const vendorPackFileName = vendorDeps.length > 0 ? `vendor.${depsHash}.js` : null;
2776
+ const vendorPackUrl = vendorPackFileName ? `${DEPS_PREFIX}${vendorPackFileName}` : null;
2777
+ const ensureVendorPackFile = () => {
2778
+ if (!vendorPackFileName || !vendorPackUrl || vendorDeps.length === 0) return;
2779
+ const vendorKey = getCacheKey(
2780
+ `vendor:v1:${vendorDeps.map((d) => `${d.specifier}:${d.fileName}`).sort().join("|")}`
2781
+ );
2782
+ const filePath = path12.join(depsRoot, vendorPackFileName);
2783
+ if (fs9.existsSync(filePath)) {
2784
+ try {
2785
+ const head = fs9.readFileSync(filePath, "utf8").slice(0, 256);
2786
+ if (head.includes(`${IONIFY_VENDOR_PACK_MARKER} ${vendorKey}`)) return;
2787
+ } catch {
2788
+ }
2789
+ }
2790
+ const imports = vendorDeps.slice().sort((a, b) => a.specifier.localeCompare(b.specifier)).map((d) => `import "${DEPS_PREFIX}${d.fileName}";`).join("\n");
2791
+ const body = `${IONIFY_VENDOR_PACK_MARKER} ${vendorKey}
2792
+ // depsHash: ${depsHash}
2793
+ // vendor: ${vendorDeps.map((d) => d.specifier).join(", ")}
2794
+ ${imports}
2795
+ `;
2796
+ try {
2797
+ fs9.writeFileSync(filePath, body, "utf8");
2798
+ } catch {
2799
+ }
2800
+ };
1860
2801
  const transformer = new TransformEngine({ casRoot, versionHash: configHash });
1861
2802
  const graph = new Graph(rawVersionInputs);
1862
2803
  if (native?.initAstCache) {
@@ -1867,7 +2808,7 @@ async function startDevServer({
1867
2808
  try {
1868
2809
  native.astCacheWarmup();
1869
2810
  } catch (err) {
1870
- logWarn(`AST cache warmup skipped: ${err}`);
2811
+ logWarn2(`AST cache warmup skipped: ${err}`);
1871
2812
  }
1872
2813
  }
1873
2814
  if (native?.astCacheStats) {
@@ -1898,6 +2839,9 @@ async function startDevServer({
1898
2839
  NODE_ENV: process.env.NODE_ENV,
1899
2840
  MODE: process.env.MODE
1900
2841
  };
2842
+ const envPrefix = userConfig?.envPrefix || ["VITE_", "IONIFY_"];
2843
+ const defineConfig = buildDefineConfig(userConfig?.define, envValues, envPrefix);
2844
+ logInfo(`[define] ${Object.keys(defineConfig).length} replacements configured`);
1901
2845
  const envPlaceholderPattern = /%([A-Z0-9_]+)%/g;
1902
2846
  const envEnabledExts = /* @__PURE__ */ new Set([
1903
2847
  ".html",
@@ -1941,7 +2885,7 @@ async function startDevServer({
1941
2885
  const buildUpdatePayload = async (modules) => {
1942
2886
  const updates = [];
1943
2887
  for (const mod of modules) {
1944
- const exists = fs7.existsSync(mod.absPath);
2888
+ const exists = fs9.existsSync(mod.absPath);
1945
2889
  if (mod.reason === "deleted" || !exists) {
1946
2890
  graph.removeFile(mod.absPath);
1947
2891
  watcher.unwatchFile(mod.absPath);
@@ -1955,9 +2899,53 @@ async function startDevServer({
1955
2899
  continue;
1956
2900
  }
1957
2901
  watcher.watchFile(mod.absPath);
2902
+ const ext = path12.extname(mod.absPath).toLowerCase();
2903
+ if (ext === ".css") {
2904
+ let hash2 = mod.hash;
2905
+ if (!hash2) {
2906
+ try {
2907
+ hash2 = getCacheKey(fs9.readFileSync(mod.absPath, "utf8"));
2908
+ } catch {
2909
+ hash2 = graph.getNode(mod.absPath)?.hash ?? getCacheKey(mod.absPath);
2910
+ }
2911
+ }
2912
+ const depsAbs2 = graph.getNode(mod.absPath)?.deps ?? [];
2913
+ const kind = /\.module\.css$/i.test(mod.absPath) ? "css-module" : "css";
2914
+ graph.recordFile(mod.absPath, hash2, depsAbs2, [], kind);
2915
+ updates.push({
2916
+ url: mod.url,
2917
+ hash: hash2,
2918
+ deps: depsAbs2.map((dep) => normalizeUrlFromFs(rootDir, dep)),
2919
+ reason: mod.reason,
2920
+ status: "updated",
2921
+ code: ""
2922
+ });
2923
+ continue;
2924
+ }
2925
+ if (isAssetExt(ext)) {
2926
+ let hash2 = mod.hash;
2927
+ if (!hash2) {
2928
+ try {
2929
+ const buf = fs9.readFileSync(mod.absPath);
2930
+ hash2 = crypto3.createHash("sha256").update(buf).digest("hex");
2931
+ } catch {
2932
+ hash2 = graph.getNode(mod.absPath)?.hash ?? getCacheKey(mod.absPath);
2933
+ }
2934
+ }
2935
+ graph.recordFile(mod.absPath, hash2, [], [], "asset");
2936
+ updates.push({
2937
+ url: mod.url,
2938
+ hash: hash2,
2939
+ deps: [],
2940
+ reason: mod.reason,
2941
+ status: "updated",
2942
+ code: ""
2943
+ });
2944
+ continue;
2945
+ }
1958
2946
  let code;
1959
2947
  try {
1960
- code = fs7.readFileSync(mod.absPath, "utf8");
2948
+ code = fs9.readFileSync(mod.absPath, "utf8");
1961
2949
  } catch (err) {
1962
2950
  logError("Failed to read module during HMR apply", err);
1963
2951
  throw err;
@@ -1985,13 +2973,14 @@ async function startDevServer({
1985
2973
  const result = await transformer.run({
1986
2974
  path: mod.absPath,
1987
2975
  code,
1988
- ext: path9.extname(mod.absPath),
1989
- moduleHash: hash
2976
+ ext: path12.extname(mod.absPath),
2977
+ moduleHash: hash,
2978
+ config: userConfig ?? null
1990
2979
  });
1991
2980
  const transformed = result.code;
1992
2981
  const envApplied = applyEnvPlaceholders(
1993
2982
  transformed,
1994
- path9.extname(mod.absPath)
2983
+ path12.extname(mod.absPath)
1995
2984
  );
1996
2985
  updates.push({
1997
2986
  url: mod.url,
@@ -2032,7 +3021,7 @@ async function startDevServer({
2032
3021
  const asset = readClientAssetFile("react-refresh-runtime.js");
2033
3022
  let reactRefreshPath;
2034
3023
  try {
2035
- const projectRequire = createRequire2(path9.join(rootDir, "package.json"));
3024
+ const projectRequire = createRequire2(path12.join(rootDir, "package.json"));
2036
3025
  reactRefreshPath = projectRequire.resolve("react-refresh/runtime");
2037
3026
  } catch (err) {
2038
3027
  logError("Failed to resolve react-refresh/runtime", err);
@@ -2115,6 +3104,108 @@ async function startDevServer({
2115
3104
  sendJson(res, 200, { ok: true });
2116
3105
  return;
2117
3106
  }
3107
+ if (reqPath.startsWith(DEPS_PREFIX)) {
3108
+ const fileName = reqPath.slice(DEPS_PREFIX.length);
3109
+ if (vendorPackFileName && fileName === vendorPackFileName) {
3110
+ ensureVendorPackFile();
3111
+ }
3112
+ if (fileName.endsWith(".js.map")) {
3113
+ const mapPath = path12.join(depsRoot, fileName);
3114
+ if (fs9.existsSync(mapPath)) {
3115
+ const stat = fs9.statSync(mapPath);
3116
+ const etag = weakEtagFromStat(`deps-map-${depsHash}`, stat);
3117
+ if (isNotModified(req, etag)) {
3118
+ res.setHeader("ETag", etag);
3119
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
3120
+ res.statusCode = 304;
3121
+ res.end();
3122
+ return;
3123
+ }
3124
+ sendBuffer(
3125
+ req,
3126
+ res,
3127
+ 200,
3128
+ "application/json; charset=utf-8",
3129
+ fs9.readFileSync(mapPath),
3130
+ { etag, cacheControl: "public, max-age=31536000, immutable" }
3131
+ );
3132
+ return;
3133
+ }
3134
+ }
3135
+ const depsFilePath = path12.join(depsRoot, fileName);
3136
+ const entryFromManifest = depsManifestIndex.get(fileName);
3137
+ const entryFromRegistry = getDepEntry(fileName);
3138
+ const entryPath = entryFromManifest?.entryPath ?? entryFromRegistry?.entryPath;
3139
+ const packageLabel = entryFromRegistry?.packageName ? formatDepLabel(entryFromRegistry.packageName, entryFromRegistry.subpath) : entryFromManifest?.packageLabel ?? fileName;
3140
+ if (fs9.existsSync(depsFilePath)) {
3141
+ const stat = fs9.statSync(depsFilePath);
3142
+ const etag = weakEtagFromStat(`deps-${depsHash}`, stat);
3143
+ if (isNotModified(req, etag)) {
3144
+ res.setHeader("ETag", etag);
3145
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
3146
+ res.statusCode = 304;
3147
+ res.end();
3148
+ logInfo(`[deps] OPTIMIZE ${packageLabel}: HIT from cache (304)`);
3149
+ return;
3150
+ }
3151
+ sendBuffer(
3152
+ req,
3153
+ res,
3154
+ 200,
3155
+ "application/javascript; charset=utf-8",
3156
+ fs9.readFileSync(depsFilePath),
3157
+ { etag, cacheControl: "public, max-age=31536000, immutable" }
3158
+ );
3159
+ logInfo(`[deps] OPTIMIZE ${packageLabel}: HIT from cache`);
3160
+ return;
3161
+ }
3162
+ if (!entryPath || !native?.optimizeDependency) {
3163
+ res.statusCode = 404;
3164
+ res.end("Dependency not found");
3165
+ return;
3166
+ }
3167
+ try {
3168
+ const start = Date.now();
3169
+ const rawSize = fs9.existsSync(entryPath) ? fs9.statSync(entryPath).size : 0;
3170
+ const result2 = native.optimizeDependency(entryPath, depsHash, depsSourcemapEnabled, depsBundleEsmEnabled);
3171
+ const outPath = result2?.out_path ?? result2?.outPath ?? depsFilePath;
3172
+ const mapPath = result2?.map_path ?? result2?.mapPath ?? null;
3173
+ const resolvedOutPath = path12.isAbsolute(outPath) ? outPath : path12.join(depsRoot, outPath);
3174
+ if (!fs9.existsSync(resolvedOutPath)) {
3175
+ throw new Error("Optimizer did not produce output");
3176
+ }
3177
+ const outBuffer = fs9.readFileSync(resolvedOutPath);
3178
+ const optimizedSize = outBuffer.length;
3179
+ const stat = fs9.statSync(resolvedOutPath);
3180
+ const etag = weakEtagFromStat(`deps-${depsHash}`, stat);
3181
+ sendBuffer(req, res, 200, "application/javascript; charset=utf-8", outBuffer, {
3182
+ etag,
3183
+ cacheControl: "public, max-age=31536000, immutable"
3184
+ });
3185
+ const elapsed = Date.now() - start;
3186
+ const rawKb = (rawSize / 1024).toFixed(1);
3187
+ const optKb = (optimizedSize / 1024).toFixed(1);
3188
+ const mapSuffix = mapPath ? ` map=${path12.basename(mapPath)}` : "";
3189
+ logInfo(`[deps] OPTIMIZE ${packageLabel}: MISS \u2192 BUILD (${elapsed}ms, ${rawKb}KB \u2192 ${optKb}KB)${mapSuffix}`);
3190
+ const refreshed = loadDepsManifestIndex(depsRoot);
3191
+ refreshed.forEach((value, key) => depsManifestIndex.set(key, value));
3192
+ return;
3193
+ } catch (err) {
3194
+ logWarn2(
3195
+ `[deps] WARN: Optimization failed for ${packageLabel}, serving raw (fallback): ${String(err)}`
3196
+ );
3197
+ try {
3198
+ const raw = fs9.readFileSync(entryPath);
3199
+ res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
3200
+ res.end(raw);
3201
+ } catch (readErr) {
3202
+ logError("Failed to read raw dependency", readErr);
3203
+ res.statusCode = 500;
3204
+ res.end("Dependency optimization failed");
3205
+ }
3206
+ return;
3207
+ }
3208
+ }
2118
3209
  const fsPath = decodePublicPath(rootDir, reqPath);
2119
3210
  if (!fsPath) {
2120
3211
  res.statusCode = 404;
@@ -2123,12 +3214,12 @@ async function startDevServer({
2123
3214
  }
2124
3215
  let effectiveFsPath = fsPath;
2125
3216
  let effectiveUrlPath = reqPath;
2126
- if (fs7.existsSync(effectiveFsPath) && fs7.statSync(effectiveFsPath).isDirectory()) {
3217
+ if (fs9.existsSync(effectiveFsPath) && fs9.statSync(effectiveFsPath).isDirectory()) {
2127
3218
  const indexExtensions = [".html", ".js", ".ts", ".tsx", ".jsx"];
2128
3219
  let found = false;
2129
3220
  for (const ext2 of indexExtensions) {
2130
- const indexFile = path9.join(effectiveFsPath, `index${ext2}`);
2131
- if (fs7.existsSync(indexFile)) {
3221
+ const indexFile = path12.join(effectiveFsPath, `index${ext2}`);
3222
+ if (fs9.existsSync(indexFile)) {
2132
3223
  effectiveFsPath = indexFile;
2133
3224
  effectiveUrlPath = effectiveUrlPath.endsWith("/") ? `${effectiveUrlPath}index${ext2}` : `${effectiveUrlPath}/index${ext2}`;
2134
3225
  found = true;
@@ -2136,13 +3227,13 @@ async function startDevServer({
2136
3227
  }
2137
3228
  }
2138
3229
  if (!found) {
2139
- const packageJson = path9.join(effectiveFsPath, "package.json");
2140
- if (fs7.existsSync(packageJson)) {
3230
+ const packageJson = path12.join(effectiveFsPath, "package.json");
3231
+ if (fs9.existsSync(packageJson)) {
2141
3232
  try {
2142
- const pkg = JSON.parse(fs7.readFileSync(packageJson, "utf8"));
3233
+ const pkg = JSON.parse(fs9.readFileSync(packageJson, "utf8"));
2143
3234
  if (pkg.main) {
2144
- const mainFile = path9.join(effectiveFsPath, pkg.main);
2145
- if (fs7.existsSync(mainFile)) {
3235
+ const mainFile = path12.join(effectiveFsPath, pkg.main);
3236
+ if (fs9.existsSync(mainFile)) {
2146
3237
  effectiveFsPath = mainFile;
2147
3238
  found = true;
2148
3239
  }
@@ -2153,8 +3244,8 @@ async function startDevServer({
2153
3244
  }
2154
3245
  if (!found) {
2155
3246
  for (const ext2 of indexExtensions) {
2156
- const moduleFile = path9.join(effectiveFsPath, `module${ext2}`);
2157
- if (fs7.existsSync(moduleFile)) {
3247
+ const moduleFile = path12.join(effectiveFsPath, `module${ext2}`);
3248
+ if (fs9.existsSync(moduleFile)) {
2158
3249
  effectiveFsPath = moduleFile;
2159
3250
  found = true;
2160
3251
  break;
@@ -2167,16 +3258,16 @@ async function startDevServer({
2167
3258
  return;
2168
3259
  }
2169
3260
  }
2170
- if (!fs7.existsSync(effectiveFsPath)) {
3261
+ if (!fs9.existsSync(effectiveFsPath)) {
2171
3262
  res.statusCode = 404;
2172
3263
  res.end("Not found");
2173
3264
  return;
2174
3265
  }
2175
- const ext = path9.extname(effectiveFsPath);
3266
+ const ext = path12.extname(effectiveFsPath);
2176
3267
  if (isAssetExt(ext)) {
2177
3268
  try {
2178
- const data = fs7.readFileSync(effectiveFsPath);
2179
- const assetHash = crypto2.createHash("sha256").update(data).digest("hex");
3269
+ const data = fs9.readFileSync(effectiveFsPath);
3270
+ const assetHash = crypto3.createHash("sha256").update(data).digest("hex");
2180
3271
  const kind = "asset";
2181
3272
  const changed2 = graph.recordFile(effectiveFsPath, assetHash, [], [], kind);
2182
3273
  watcher.watchFile(effectiveFsPath);
@@ -2192,67 +3283,115 @@ async function startDevServer({
2192
3283
  return;
2193
3284
  } else {
2194
3285
  res.writeHead(200, { "Content-Type": contentTypeForAsset(ext) });
2195
- fs7.createReadStream(effectiveFsPath).pipe(res);
3286
+ fs9.createReadStream(effectiveFsPath).pipe(res);
2196
3287
  return;
2197
3288
  }
2198
3289
  }
2199
3290
  if (ext === ".css") {
2200
3291
  try {
2201
- const cssSource = fs7.readFileSync(effectiveFsPath, "utf8");
3292
+ const cssSource = fs9.readFileSync(effectiveFsPath, "utf8");
2202
3293
  const isModule = "module" in q || /\.module\.css$/i.test(effectiveFsPath);
2203
- const isInline = "inline" in q;
2204
- const mode = isModule ? "css:module" : isInline ? "css:inline" : "css:raw";
3294
+ const mode = "raw" in q ? "css:raw-string" : "url" in q ? "css:url" : isModule ? "css:module" : "inline" in q ? "css:inline" : "css:raw";
2205
3295
  const contentHash = getCacheKey(cssSource);
2206
3296
  watcher.watchFile(effectiveFsPath);
2207
3297
  const kind = isModule ? "css-module" : "css";
2208
- const changed2 = graph.recordFile(effectiveFsPath, contentHash, [], [], kind);
2209
- const casDir = getCasArtifactPath(casRoot, configHash, contentHash);
2210
- const casFile = path9.join(casDir, "transformed.js");
3298
+ const prevDeps = graph.getNode(effectiveFsPath)?.deps ?? [];
3299
+ for (const dep of prevDeps) {
3300
+ watcher.watchFile(dep);
3301
+ }
3302
+ const depsStampHash = computeDepsStampHash(prevDeps);
3303
+ let artifactHash = getCacheKey(
3304
+ `css:v2:${effectiveFsPath}:${contentHash}:${mode}:${depsStampHash}`
3305
+ );
3306
+ let casDir = getCasArtifactPath(casRoot, configHash, artifactHash);
3307
+ const jsMode = mode !== "css:raw";
3308
+ let casFile = path12.join(casDir, jsMode ? "transformed.js" : "transformed.css");
2211
3309
  let finalBuffer = null;
2212
- if (fs7.existsSync(casFile)) {
3310
+ if (fs9.existsSync(casFile)) {
2213
3311
  try {
2214
- finalBuffer = fs7.readFileSync(casFile);
2215
- res.setHeader("X-Ionify-Cache", "HIT");
3312
+ finalBuffer = fs9.readFileSync(casFile);
3313
+ const ok = jsMode ? looksLikeIonifyCssJsModule(finalBuffer) : !looksLikeIonifyCssJsModule(finalBuffer);
3314
+ if (ok) {
3315
+ res.setHeader("X-Ionify-Cache", "HIT");
3316
+ } else {
3317
+ finalBuffer = null;
3318
+ res.setHeader("X-Ionify-Cache", "MISMATCH");
3319
+ }
2216
3320
  } catch {
2217
3321
  finalBuffer = null;
2218
3322
  }
2219
3323
  }
2220
3324
  if (!finalBuffer) {
2221
- const { css: compiledCss, tokens } = await compileCss({
2222
- code: cssSource,
2223
- filePath: effectiveFsPath,
2224
- rootDir,
2225
- modules: isModule
2226
- });
2227
- const body = isModule || isInline ? renderCssModule({
2228
- css: compiledCss,
2229
- filePath: effectiveFsPath,
2230
- tokens: isModule ? tokens ?? {} : void 0
2231
- }) : compiledCss;
3325
+ let body;
3326
+ if (mode === "css:url") {
3327
+ const rawUrl = `${effectiveUrlPath}?v=${contentHash}-${depsStampHash.slice(0, 8)}`;
3328
+ body = renderCssUrlModule(rawUrl);
3329
+ } else {
3330
+ const { css: compiledCss, tokens, deps } = await compileCss({
3331
+ code: cssSource,
3332
+ filePath: effectiveFsPath,
3333
+ rootDir,
3334
+ modules: isModule
3335
+ });
3336
+ const depsAbs2 = deps.map((d) => d.filePath).filter(Boolean);
3337
+ const nextDepsStampHash = computeDepsStampHash(depsAbs2);
3338
+ artifactHash = getCacheKey(
3339
+ `css:v2:${effectiveFsPath}:${contentHash}:${mode}:${nextDepsStampHash}`
3340
+ );
3341
+ casDir = getCasArtifactPath(casRoot, configHash, artifactHash);
3342
+ casFile = path12.join(casDir, jsMode ? "transformed.js" : "transformed.css");
3343
+ const changed2 = graph.recordFile(effectiveFsPath, contentHash, depsAbs2, [], kind);
3344
+ if (changed2) {
3345
+ logInfo(`[Graph] CSS updated: ${effectiveFsPath}`);
3346
+ }
3347
+ for (const dep of depsAbs2) {
3348
+ watcher.watchFile(dep);
3349
+ }
3350
+ if (mode === "css:raw") {
3351
+ body = compiledCss;
3352
+ } else if (mode === "css:raw-string") {
3353
+ body = renderCssRawStringModule(compiledCss);
3354
+ } else {
3355
+ body = renderCssModule({
3356
+ css: compiledCss,
3357
+ filePath: effectiveFsPath,
3358
+ tokens: isModule ? tokens ?? {} : void 0
3359
+ });
3360
+ }
3361
+ }
2232
3362
  finalBuffer = Buffer.from(body, "utf8");
2233
3363
  res.setHeader("X-Ionify-Cache", "MISS");
2234
3364
  try {
2235
- fs7.mkdirSync(casDir, { recursive: true });
2236
- fs7.writeFileSync(casFile, finalBuffer);
3365
+ fs9.mkdirSync(casDir, { recursive: true });
3366
+ fs9.writeFileSync(casFile, finalBuffer);
2237
3367
  } catch {
2238
3368
  }
2239
3369
  }
2240
- if (isModule || isInline) {
2241
- res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
3370
+ const etag = `W/"css-${configHash}-${artifactHash}-${mode}"`;
3371
+ if (jsMode) {
3372
+ sendBuffer(req, res, 200, "application/javascript; charset=utf-8", finalBuffer, {
3373
+ etag,
3374
+ cacheControl: "no-cache"
3375
+ });
2242
3376
  } else {
2243
- res.writeHead(200, { "Content-Type": "text/css; charset=utf-8" });
3377
+ sendBuffer(req, res, 200, "text/css; charset=utf-8", finalBuffer, {
3378
+ etag,
3379
+ cacheControl: "no-cache"
3380
+ });
2244
3381
  }
2245
- res.end(finalBuffer);
2246
- logInfo(`Served: ${effectiveUrlPath} deps:0 ${changed2 ? "(updated)" : "(cached)"}`);
3382
+ logInfo(`Served: ${effectiveUrlPath} ${mode}`);
2247
3383
  return;
2248
3384
  } catch (err) {
2249
3385
  logError("Failed to process CSS", err);
3386
+ hmr.broadcastError({
3387
+ message: err instanceof Error ? `Failed to process CSS: ${err.stack || err.message}` : `Failed to process CSS: ${String(err)}`
3388
+ });
2250
3389
  res.statusCode = 500;
2251
3390
  res.end("Failed to process CSS");
2252
3391
  return;
2253
3392
  }
2254
3393
  }
2255
- const code = fs7.readFileSync(effectiveFsPath, "utf8");
3394
+ const code = fs9.readFileSync(effectiveFsPath, "utf8");
2256
3395
  let hash;
2257
3396
  let specs;
2258
3397
  if (native?.parseModuleIr) {
@@ -2274,23 +3413,40 @@ async function startDevServer({
2274
3413
  for (const dep of depsAbs) {
2275
3414
  watcher.watchFile(dep);
2276
3415
  }
2277
- const result = await transformer.run({
2278
- path: effectiveFsPath,
2279
- code,
2280
- ext,
2281
- moduleHash: hash
2282
- });
3416
+ let result;
3417
+ try {
3418
+ result = await transformer.run({
3419
+ path: effectiveFsPath,
3420
+ code,
3421
+ ext,
3422
+ moduleHash: hash,
3423
+ config: userConfig ?? null
3424
+ });
3425
+ } catch (err) {
3426
+ const message = err instanceof Error ? err.stack || err.message : String(err);
3427
+ hmr.broadcastError({ message: `Failed to transform ${effectiveUrlPath}: ${message}` });
3428
+ throw err;
3429
+ }
2283
3430
  const transformedCode = result.code;
2284
3431
  res.setHeader("X-Ionify-Cache", changed ? "MISS" : "HIT");
2285
- const envApplied = applyEnvPlaceholders(transformedCode, ext);
2286
- if (path9.extname(effectiveFsPath) === ".html") {
2287
- const injected = injectHMRClient(envApplied);
2288
- res.setHeader("Content-Type", "text/html; charset=utf-8");
2289
- res.end(injected);
3432
+ const withDefine = applyDefineReplacements(transformedCode, defineConfig);
3433
+ const envApplied = applyEnvPlaceholders(withDefine, ext);
3434
+ if (path12.extname(effectiveFsPath) === ".html") {
3435
+ ensureVendorPackFile();
3436
+ const withVendor = vendorPackUrl ? injectModulePreload(envApplied, vendorPackUrl) : envApplied;
3437
+ const injected = injectHMRClient(withVendor);
3438
+ const etag = `W/"html-${configHash}-${hash}"`;
3439
+ sendBuffer(req, res, 200, "text/html; charset=utf-8", Buffer.from(injected, "utf8"), {
3440
+ etag,
3441
+ cacheControl: "no-cache"
3442
+ });
2290
3443
  } else {
2291
3444
  const finalBuffer = Buffer.from(envApplied);
2292
- res.setHeader("Content-Type", guessContentType(effectiveFsPath));
2293
- res.end(finalBuffer);
3445
+ const etag = `W/"mod-${configHash}-${hash}"`;
3446
+ sendBuffer(req, res, 200, guessContentType(effectiveFsPath), finalBuffer, {
3447
+ etag,
3448
+ cacheControl: "no-cache"
3449
+ });
2294
3450
  }
2295
3451
  logInfo(`Served: ${effectiveUrlPath} deps:${depsAbs.length} ${changed ? "(updated)" : "(cached)"}`);
2296
3452
  if (cacheDebug) {
@@ -2305,7 +3461,16 @@ async function startDevServer({
2305
3461
  });
2306
3462
  watcher.on("change", (file, status) => {
2307
3463
  logInfo(`[Watcher] ${status}: ${file}`);
2308
- const affected = graph.collectAffected([file]);
3464
+ const ext = path12.extname(file).toLowerCase();
3465
+ const isReactFastRefreshBoundary = status !== "deleted" && (ext === ".tsx" || ext === ".jsx");
3466
+ const isCssBoundary = status !== "deleted" && ext === ".css";
3467
+ const collected = graph.collectAffected([file]);
3468
+ const affected = isReactFastRefreshBoundary || isCssBoundary ? [
3469
+ file,
3470
+ ...collected.filter(
3471
+ (absPath) => absPath !== file && path12.extname(absPath).toLowerCase() === ".css"
3472
+ )
3473
+ ] : collected;
2309
3474
  if (!affected.includes(file)) {
2310
3475
  affected.unshift(file);
2311
3476
  }
@@ -2316,7 +3481,7 @@ async function startDevServer({
2316
3481
  if (reason !== "deleted") {
2317
3482
  if (absPath === file) {
2318
3483
  try {
2319
- const code = fs7.readFileSync(absPath, "utf8");
3484
+ const code = fs9.readFileSync(absPath, "utf8");
2320
3485
  hash = getCacheKey(code);
2321
3486
  } catch {
2322
3487
  hash = graph.getNode(absPath)?.hash ?? null;
@@ -2327,7 +3492,7 @@ async function startDevServer({
2327
3492
  }
2328
3493
  modules.push({
2329
3494
  absPath,
2330
- url: normalizeUrlFromFs(rootDir, absPath),
3495
+ url: path12.extname(absPath).toLowerCase() === ".css" ? `${normalizeUrlFromFs(rootDir, absPath)}?inline` : isAssetExt(path12.extname(absPath).toLowerCase()) ? `${normalizeUrlFromFs(rootDir, absPath)}?import` : normalizeUrlFromFs(rootDir, absPath),
2331
3496
  hash,
2332
3497
  reason
2333
3498
  });
@@ -2415,13 +3580,98 @@ async function startDevServer({
2415
3580
  signalHandlers.push({ event: "SIGINT", handler: onSignal });
2416
3581
  signalHandlers.push({ event: "SIGTERM", handler: onSignal });
2417
3582
  }
2418
- await new Promise((resolve) => {
2419
- server.listen(port, () => resolve());
3583
+ await new Promise((resolve, reject) => {
3584
+ const onError = (err) => reject(err);
3585
+ server.once("error", onError);
3586
+ server.listen(port, host, () => {
3587
+ server.off("error", onError);
3588
+ resolve();
3589
+ });
2420
3590
  });
2421
3591
  const address = server.address();
2422
3592
  const actualPort = address && typeof address === "object" && address?.port ? address.port : port;
2423
3593
  logInfo(`Ionify Dev Server (Phase 2) at http://localhost:${actualPort}`);
3594
+ logInfo(`Ready in ${Date.now() - bootStartMs}ms`);
2424
3595
  logInfo(`HMR listening at /__ionify_hmr (SSE)`);
3596
+ if (vendorDeps.length > 0) {
3597
+ const vendorLabels = vendorDeps.map((d) => d.packageLabel).join(", ");
3598
+ logInfo(`[deps] Vendor pack detected (${vendorDeps.length}): ${vendorLabels}`);
3599
+ ensureVendorPackFile();
3600
+ const missing = vendorDeps.filter((d) => !fs9.existsSync(path12.join(depsRoot, d.fileName)));
3601
+ if (missing.length > 0) {
3602
+ const entryCount = missing.length;
3603
+ logInfo(`[deps] Pre-warming vendor deps (${entryCount}) in parallel...`);
3604
+ Promise.resolve().then(() => {
3605
+ if (native?.optimizeDependenciesBatch && !depsSourcemapEnabled && depsBundleEsmEnabled) {
3606
+ const results = native.optimizeDependenciesBatch(
3607
+ missing.map((d) => ({ entryPath: d.entryPath, depsHash }))
3608
+ );
3609
+ results.forEach((r, idx) => {
3610
+ const dep = missing[idx];
3611
+ if (r?.error) {
3612
+ logWarn2(`[deps] Vendor prewarm failed ${dep.packageLabel}: ${r.error}`);
3613
+ } else if (r?.out_path || r?.outPath) {
3614
+ const outPath = r.out_path ?? r.outPath;
3615
+ logInfo(
3616
+ `[deps] \u2713 Vendor prewarmed ${dep.packageLabel} \u2192 ${path12.basename(outPath)}`
3617
+ );
3618
+ }
3619
+ });
3620
+ return;
3621
+ }
3622
+ if (!native?.optimizeDependency) return;
3623
+ for (const dep of missing) {
3624
+ try {
3625
+ const result = native.optimizeDependency(
3626
+ dep.entryPath,
3627
+ depsHash,
3628
+ depsSourcemapEnabled,
3629
+ depsBundleEsmEnabled
3630
+ );
3631
+ const outPath = result?.out_path ?? result?.outPath ?? null;
3632
+ if (outPath) {
3633
+ logInfo(`[deps] \u2713 Vendor prewarmed ${dep.packageLabel} \u2192 ${path12.basename(outPath)}`);
3634
+ }
3635
+ } catch (err) {
3636
+ logWarn2(`[deps] Vendor prewarm failed ${dep.packageLabel}: ${String(err)}`);
3637
+ }
3638
+ }
3639
+ }).catch((err) => {
3640
+ logWarn2(`[deps] Vendor prewarm error: ${err}`);
3641
+ });
3642
+ }
3643
+ }
3644
+ if (userConfig?.optimizeDeps?.include && Array.isArray(userConfig.optimizeDeps.include)) {
3645
+ const includes = userConfig.optimizeDeps.include;
3646
+ if (includes.length > 0) {
3647
+ logInfo(`[deps] Pre-warming ${includes.length} dependencies: ${includes.join(", ")}`);
3648
+ Promise.all(
3649
+ includes.map(async (pkgName) => {
3650
+ try {
3651
+ if (!native?.resolveModule || !native?.optimizeDependency) {
3652
+ logWarn2(`[deps] Cannot pre-warm ${pkgName}: native functions not available`);
3653
+ return;
3654
+ }
3655
+ const resolved = native.resolveModule(pkgName, rootDir);
3656
+ if (!resolved || !resolved.fsPath && !resolved.fs_path) {
3657
+ logWarn2(`[deps] Cannot pre-warm ${pkgName}: resolution failed`);
3658
+ return;
3659
+ }
3660
+ const entryPath = resolved.fsPath || resolved.fs_path;
3661
+ const result = native.optimizeDependency(entryPath, depsHash, depsSourcemapEnabled, depsBundleEsmEnabled);
3662
+ if (result?.out_path) {
3663
+ const fileName = path12.basename(result.out_path);
3664
+ logInfo(`[deps] \u2713 Pre-warmed ${pkgName} \u2192 ${fileName}`);
3665
+ }
3666
+ } catch (err) {
3667
+ logWarn2(`[deps] Failed to pre-warm ${pkgName}: ${err}`);
3668
+ }
3669
+ })
3670
+ ).catch((err) => {
3671
+ logWarn2(`[deps] Pre-warming error: ${err}`);
3672
+ });
3673
+ }
3674
+ }
2425
3675
  return {
2426
3676
  server,
2427
3677
  port: actualPort,
@@ -2432,13 +3682,13 @@ async function startDevServer({
2432
3682
  }
2433
3683
 
2434
3684
  // src/cli/commands/analyze.ts
2435
- import fs8 from "fs";
2436
- import path10 from "path";
3685
+ import fs10 from "fs";
3686
+ import path13 from "path";
2437
3687
  function readGraphFromDisk(root) {
2438
- const file = path10.join(root, ".ionify", "graph.json");
2439
- if (!fs8.existsSync(file)) return null;
3688
+ const file = path13.join(root, ".ionify", "graph.json");
3689
+ if (!fs10.existsSync(file)) return null;
2440
3690
  try {
2441
- const raw = fs8.readFileSync(file, "utf8");
3691
+ const raw = fs10.readFileSync(file, "utf8");
2442
3692
  const snapshot = JSON.parse(raw);
2443
3693
  if (snapshot?.version !== 1 || !snapshot?.nodes) return null;
2444
3694
  return Object.entries(snapshot.nodes).map(([id, node]) => ({
@@ -2534,8 +3784,8 @@ async function runAnalyzeCommand(options = {}) {
2534
3784
  }
2535
3785
 
2536
3786
  // src/cli/commands/build.ts
2537
- import fs10 from "fs";
2538
- import path12 from "path";
3787
+ import fs12 from "fs";
3788
+ import path15 from "path";
2539
3789
 
2540
3790
  // src/cli/utils/optimization-level.ts
2541
3791
  function getOptimizationPreset(level) {
@@ -2624,9 +3874,9 @@ function resolveOptimizationLevel(configLevel, options = {}) {
2624
3874
  }
2625
3875
 
2626
3876
  // src/core/bundler.ts
2627
- import fs9 from "fs";
2628
- import path11 from "path";
2629
- import crypto3 from "crypto";
3877
+ import fs11 from "fs";
3878
+ import path14 from "path";
3879
+ import crypto4 from "crypto";
2630
3880
  function readGraphSnapshot() {
2631
3881
  if (native?.graphLoadMap) {
2632
3882
  try {
@@ -2641,13 +3891,13 @@ function readGraphSnapshot() {
2641
3891
  }));
2642
3892
  }
2643
3893
  } catch (err) {
2644
- logWarn(`Failed to load native graph: ${String(err)}`);
3894
+ logWarn2(`Failed to load native graph: ${String(err)}`);
2645
3895
  }
2646
3896
  }
2647
- const file = path11.join(process.cwd(), ".ionify", "graph.json");
2648
- if (!fs9.existsSync(file)) return [];
3897
+ const file = path14.join(process.cwd(), ".ionify", "graph.json");
3898
+ if (!fs11.existsSync(file)) return [];
2649
3899
  try {
2650
- const raw = fs9.readFileSync(file, "utf8");
3900
+ const raw = fs11.readFileSync(file, "utf8");
2651
3901
  const snapshot = JSON.parse(raw);
2652
3902
  if (snapshot?.version !== 1 || !snapshot?.nodes) return [];
2653
3903
  return Object.entries(snapshot.nodes).map(([id, node]) => ({
@@ -2656,20 +3906,20 @@ function readGraphSnapshot() {
2656
3906
  deps: Array.isArray(node.deps) ? node.deps : []
2657
3907
  }));
2658
3908
  } catch (err) {
2659
- logWarn(`Failed to read graph snapshot: ${String(err)}`);
3909
+ logWarn2(`Failed to read graph snapshot: ${String(err)}`);
2660
3910
  return [];
2661
3911
  }
2662
3912
  }
2663
3913
  var JS_EXTENSIONS2 = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx"]);
2664
3914
  var CSS_EXTENSIONS = /* @__PURE__ */ new Set([".css"]);
2665
3915
  function classifyModuleKind(id) {
2666
- const ext = path11.extname(id).toLowerCase();
3916
+ const ext = path14.extname(id).toLowerCase();
2667
3917
  if (CSS_EXTENSIONS.has(ext)) return "css";
2668
3918
  if (JS_EXTENSIONS2.has(ext)) return "js";
2669
3919
  return "asset";
2670
3920
  }
2671
3921
  var isNonEmptyString = (value) => typeof value === "string" && value.length > 0;
2672
- var toPosix = (p) => p.split(path11.sep).join("/");
3922
+ var toPosix = (p) => p.split(path14.sep).join("/");
2673
3923
  function minifyCss(input) {
2674
3924
  return input.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").replace(/\s*([{};:,])\s*/g, "$1").trim();
2675
3925
  }
@@ -2804,7 +4054,7 @@ function fallbackPlan(entries) {
2804
4054
  async function generateBuildPlan(entries, versionInputs) {
2805
4055
  const version = versionInputs ? computeGraphVersion(versionInputs) : void 0;
2806
4056
  logInfo(`Graph version: ${version || "default"}`);
2807
- const graphDbPath = path11.join(process.cwd(), ".ionify", "graph.db");
4057
+ const graphDbPath = path14.join(process.cwd(), ".ionify", "graph.db");
2808
4058
  ensureNativeGraph(graphDbPath, version);
2809
4059
  let moduleCount = 0;
2810
4060
  if (native?.graphLoadMap) {
@@ -2817,19 +4067,19 @@ async function generateBuildPlan(entries, versionInputs) {
2817
4067
  logInfo(`Loaded persisted graph with ${graphSize} modules`);
2818
4068
  }
2819
4069
  } catch (err) {
2820
- logWarn(`Failed to load persisted graph: ${String(err)}`);
4070
+ logWarn2(`Failed to load persisted graph: ${String(err)}`);
2821
4071
  }
2822
4072
  } else {
2823
- logWarn(`graphLoadMap not available, native binding: ${!!native}`);
4073
+ logWarn2(`graphLoadMap not available, native binding: ${!!native}`);
2824
4074
  }
2825
4075
  if (moduleCount === 0 && entries?.length && native) {
2826
- logWarn(`[Build] Graph is empty \u2014 rebuilding dependency graph from entries...`);
4076
+ logWarn2(`[Build] Graph is empty \u2014 rebuilding dependency graph from entries...`);
2827
4077
  const queue = [...entries];
2828
4078
  const seen = new Set(queue);
2829
4079
  while (queue.length) {
2830
4080
  const file = queue.shift();
2831
- if (!fs9.existsSync(file)) continue;
2832
- const code = fs9.readFileSync(file, "utf8");
4081
+ if (!fs11.existsSync(file)) continue;
4082
+ const code = fs11.readFileSync(file, "utf8");
2833
4083
  let hash = getCacheKey(code);
2834
4084
  let specs = [];
2835
4085
  if (native.parseModuleIr) {
@@ -2875,7 +4125,7 @@ async function generateBuildPlan(entries, versionInputs) {
2875
4125
  logInfo(`[Planner] Native plan returned: ${plan.entries.length} entries, ${plan.chunks.length} chunks in ${Date.now() - start}ms`);
2876
4126
  return normalizePlan(plan);
2877
4127
  } catch (err) {
2878
- logWarn(`plannerPlanBuild failed, falling back to JS planner: ${String(err)}`);
4128
+ logWarn2(`plannerPlanBuild failed, falling back to JS planner: ${String(err)}`);
2879
4129
  }
2880
4130
  }
2881
4131
  return fallbackPlan(entries);
@@ -2901,14 +4151,14 @@ async function writeBuildManifest(outputDir, plan, artifacts) {
2901
4151
  files: filesByChunk.get(chunk.id) ?? { js: [], css: [], assets: [] }
2902
4152
  }))
2903
4153
  };
2904
- const dir = path11.resolve(outputDir);
2905
- await fs9.promises.mkdir(dir, { recursive: true });
2906
- const file = path11.join(dir, "manifest.json");
2907
- await fs9.promises.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
4154
+ const dir = path14.resolve(outputDir);
4155
+ await fs11.promises.mkdir(dir, { recursive: true });
4156
+ const file = path14.join(dir, "manifest.json");
4157
+ await fs11.promises.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
2908
4158
  }
2909
4159
  async function emitChunks(outputDir, plan, moduleOutputs, opts) {
2910
4160
  if (!native?.buildChunks) {
2911
- logWarn("Native buildChunks binding is not available; using JS fallback emitter.");
4161
+ logWarn2("Native buildChunks binding is not available; using JS fallback emitter.");
2912
4162
  const rawArtifacts2 = buildJsFallbackArtifacts(plan, moduleOutputs);
2913
4163
  return emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifacts2);
2914
4164
  }
@@ -2931,7 +4181,7 @@ ${output.code}`);
2931
4181
  }
2932
4182
  for (const assetPath of chunk.assets) {
2933
4183
  try {
2934
- const data = fs9.readFileSync(assetPath);
4184
+ const data = fs11.readFileSync(assetPath);
2935
4185
  if (data.length < 4096) {
2936
4186
  const mime = "application/octet-stream";
2937
4187
  const inline = `data:${mime};base64,${data.toString("base64")}`;
@@ -2939,15 +4189,15 @@ ${output.code}`);
2939
4189
  export const __ionify_asset = "${inline}";`);
2940
4190
  continue;
2941
4191
  }
2942
- const hash = crypto3.createHash("sha256").update(data).digest("hex").slice(0, 16);
2943
- const ext = path11.extname(assetPath) || ".bin";
4192
+ const hash = crypto4.createHash("sha256").update(data).digest("hex").slice(0, 16);
4193
+ const ext = path14.extname(assetPath) || ".bin";
2944
4194
  const fileName = `assets/${hash}${ext}`;
2945
4195
  assets.push({
2946
4196
  source: assetPath,
2947
4197
  file_name: fileName
2948
4198
  });
2949
4199
  } catch {
2950
- const fileName = path11.basename(assetPath) || "asset";
4200
+ const fileName = path14.basename(assetPath) || "asset";
2951
4201
  assets.push({
2952
4202
  source: assetPath,
2953
4203
  file_name: fileName
@@ -2980,15 +4230,15 @@ function normalizeNativeArtifact(raw) {
2980
4230
  const map_bytes = typeof raw.map_bytes === "number" ? raw.map_bytes : map ? Buffer.byteLength(map, "utf8") : 0;
2981
4231
  const assets = Array.isArray(raw.assets) ? raw.assets.map((asset) => ({
2982
4232
  source: asset.source,
2983
- file_name: asset.file_name ?? asset.fileName ?? path11.basename(asset.source ?? "asset")
4233
+ file_name: asset.file_name ?? asset.fileName ?? path14.basename(asset.source ?? "asset")
2984
4234
  })) : [];
2985
4235
  return { id, file_name, code, map, assets, code_bytes, map_bytes };
2986
4236
  }
2987
4237
  async function emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifacts) {
2988
- const chunkDir = path11.join(outputDir, "chunks");
2989
- await fs9.promises.mkdir(chunkDir, { recursive: true });
2990
- const assetsDir = path11.join(outputDir, "assets");
2991
- await fs9.promises.mkdir(assetsDir, { recursive: true });
4238
+ const chunkDir = path14.join(outputDir, "chunks");
4239
+ await fs11.promises.mkdir(chunkDir, { recursive: true });
4240
+ const assetsDir = path14.join(outputDir, "assets");
4241
+ await fs11.promises.mkdir(assetsDir, { recursive: true });
2992
4242
  const enableSourceMaps = process.env.IONIFY_SOURCEMAPS === "true";
2993
4243
  const grouped = /* @__PURE__ */ new Map();
2994
4244
  for (const raw of rawArtifacts) {
@@ -3005,8 +4255,8 @@ async function emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifa
3005
4255
  if (!artifacts || !artifacts.length) {
3006
4256
  throw new Error(`Native bundler did not emit artifacts for ${chunk.id}`);
3007
4257
  }
3008
- const chunkOutDir = path11.join(chunkDir, chunk.id);
3009
- await fs9.promises.mkdir(chunkOutDir, { recursive: true });
4258
+ const chunkOutDir = path14.join(chunkDir, chunk.id);
4259
+ await fs11.promises.mkdir(chunkOutDir, { recursive: true });
3010
4260
  artifacts.sort((a, b) => {
3011
4261
  if (a.id === chunk.id) return -1;
3012
4262
  if (b.id === chunk.id) return 1;
@@ -3019,14 +4269,14 @@ async function emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifa
3019
4269
  const copyAssets = async (assets) => {
3020
4270
  for (const asset of assets) {
3021
4271
  if (!asset?.source) continue;
3022
- const relName = asset.file_name ?? path11.basename(asset.source);
3023
- const assetFile = path11.join(outputDir, relName);
4272
+ const relName = asset.file_name ?? path14.basename(asset.source);
4273
+ const assetFile = path14.join(outputDir, relName);
3024
4274
  if (assetWritten.has(assetFile)) continue;
3025
4275
  try {
3026
- const data = await fs9.promises.readFile(asset.source);
3027
- await fs9.promises.mkdir(path11.dirname(assetFile), { recursive: true });
3028
- await fs9.promises.writeFile(assetFile, data);
3029
- const rel = toPosix(path11.relative(outputDir, assetFile));
4276
+ const data = await fs11.promises.readFile(asset.source);
4277
+ await fs11.promises.mkdir(path14.dirname(assetFile), { recursive: true });
4278
+ await fs11.promises.writeFile(assetFile, data);
4279
+ const rel = toPosix(path14.relative(outputDir, assetFile));
3030
4280
  buildStats[rel] = {
3031
4281
  bytes: data.length,
3032
4282
  emitter: "native",
@@ -3035,7 +4285,7 @@ async function emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifa
3035
4285
  assetFiles.push(rel);
3036
4286
  assetWritten.add(assetFile);
3037
4287
  } catch (err) {
3038
- logWarn(`Failed to emit asset ${asset.source}: ${String(err)}`);
4288
+ logWarn2(`Failed to emit asset ${asset.source}: ${String(err)}`);
3039
4289
  }
3040
4290
  }
3041
4291
  };
@@ -3046,11 +4296,11 @@ async function emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifa
3046
4296
  const cssPieces = [];
3047
4297
  for (const cssPath of cssOrder) {
3048
4298
  let cssSource = moduleOutputs.get(cssPath)?.code;
3049
- if (!cssSource && fs9.existsSync(cssPath)) {
4299
+ if (!cssSource && fs11.existsSync(cssPath)) {
3050
4300
  try {
3051
- cssSource = await fs9.promises.readFile(cssPath, "utf8");
4301
+ cssSource = await fs11.promises.readFile(cssPath, "utf8");
3052
4302
  } catch (err) {
3053
- logWarn(`Failed to read CSS source ${cssPath}: ${String(err)}`);
4303
+ logWarn2(`Failed to read CSS source ${cssPath}: ${String(err)}`);
3054
4304
  }
3055
4305
  }
3056
4306
  if (!cssSource) continue;
@@ -3065,9 +4315,9 @@ async function emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifa
3065
4315
  const combinedCss = cssPieces.join("\n");
3066
4316
  const cssHash = getCacheKey(combinedCss).slice(0, 8);
3067
4317
  const cssFileName = `assets/${chunk.id}.${cssHash}.css`;
3068
- const cssFilePath = path11.join(outputDir, cssFileName);
3069
- await fs9.promises.writeFile(cssFilePath, combinedCss, "utf8");
3070
- cssFileRel = toPosix(path11.relative(outputDir, cssFilePath));
4318
+ const cssFilePath = path14.join(outputDir, cssFileName);
4319
+ await fs11.promises.writeFile(cssFilePath, combinedCss, "utf8");
4320
+ cssFileRel = toPosix(path14.relative(outputDir, cssFilePath));
3071
4321
  buildStats[cssFileRel] = {
3072
4322
  bytes: Buffer.byteLength(combinedCss),
3073
4323
  emitter: "native",
@@ -3077,11 +4327,11 @@ async function emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifa
3077
4327
  }
3078
4328
  }
3079
4329
  for (const artifact of artifacts) {
3080
- const nativeFile = path11.join(chunkOutDir, artifact.file_name);
4330
+ const nativeFile = path14.join(chunkOutDir, artifact.file_name);
3081
4331
  let nativeCode = artifact.code;
3082
4332
  if (cssFileRel) {
3083
- const absCss = path11.join(outputDir, cssFileRel);
3084
- const relCss = toPosix(path11.relative(path11.dirname(nativeFile), absCss));
4333
+ const absCss = path14.join(outputDir, cssFileRel);
4334
+ const relCss = toPosix(path14.relative(path14.dirname(nativeFile), absCss));
3085
4335
  const inject = `(()=>{const url=new URL(${JSON.stringify(
3086
4336
  relCss
3087
4337
  )},import.meta.url).toString();if(typeof document!=="undefined"&&!document.querySelector('link[data-ionify-css="'+url+'"]')){const l=document.createElement("link");l.rel="stylesheet";l.href=url;l.setAttribute("data-ionify-css",url);document.head.appendChild(l);}})();`;
@@ -3090,10 +4340,10 @@ ${nativeCode}`;
3090
4340
  }
3091
4341
  if (enableSourceMaps && artifact.map) {
3092
4342
  const mapFile = `${nativeFile}.map`;
3093
- await fs9.promises.writeFile(mapFile, artifact.map, "utf8");
4343
+ await fs11.promises.writeFile(mapFile, artifact.map, "utf8");
3094
4344
  nativeCode = `${nativeCode}
3095
- //# sourceMappingURL=${path11.basename(mapFile)}`;
3096
- const relMap = toPosix(path11.relative(outputDir, mapFile));
4345
+ //# sourceMappingURL=${path14.basename(mapFile)}`;
4346
+ const relMap = toPosix(path14.relative(outputDir, mapFile));
3097
4347
  buildStats[relMap] = {
3098
4348
  bytes: artifact.map_bytes,
3099
4349
  emitter: "native",
@@ -3101,8 +4351,8 @@ ${nativeCode}`;
3101
4351
  };
3102
4352
  jsFiles.push(relMap);
3103
4353
  }
3104
- await fs9.promises.writeFile(nativeFile, nativeCode, "utf8");
3105
- const relNative = toPosix(path11.relative(outputDir, nativeFile));
4354
+ await fs11.promises.writeFile(nativeFile, nativeCode, "utf8");
4355
+ const relNative = toPosix(path14.relative(outputDir, nativeFile));
3106
4356
  buildStats[relNative] = {
3107
4357
  bytes: artifact.code_bytes,
3108
4358
  emitter: "native",
@@ -3119,21 +4369,21 @@ ${nativeCode}`;
3119
4369
  const output = moduleOutputs.get(cssPath);
3120
4370
  if (output?.type === "css") {
3121
4371
  cssSources.push(output.code);
3122
- } else if (fs9.existsSync(cssPath)) {
4372
+ } else if (fs11.existsSync(cssPath)) {
3123
4373
  try {
3124
- cssSources.push(await fs9.promises.readFile(cssPath, "utf8"));
4374
+ cssSources.push(await fs11.promises.readFile(cssPath, "utf8"));
3125
4375
  } catch (err) {
3126
- logWarn(`Failed to read CSS source ${cssPath}: ${String(err)}`);
4376
+ logWarn2(`Failed to read CSS source ${cssPath}: ${String(err)}`);
3127
4377
  }
3128
4378
  }
3129
4379
  }
3130
4380
  if (cssSources.length) {
3131
4381
  const combinedCss = cssSources.join("\n\n");
3132
- const cssHash = crypto3.createHash("sha256").update(combinedCss).digest("hex").slice(0, 8);
4382
+ const cssHash = crypto4.createHash("sha256").update(combinedCss).digest("hex").slice(0, 8);
3133
4383
  const cssFileName = `${chunk.id}.${cssHash}.native.css`;
3134
- const cssFilePath = path11.join(chunkOutDir, cssFileName);
3135
- await fs9.promises.writeFile(cssFilePath, combinedCss, "utf8");
3136
- const relCss = path11.relative(outputDir, cssFilePath);
4384
+ const cssFilePath = path14.join(chunkOutDir, cssFileName);
4385
+ await fs11.promises.writeFile(cssFilePath, combinedCss, "utf8");
4386
+ const relCss = path14.relative(outputDir, cssFilePath);
3137
4387
  buildStats[relCss] = {
3138
4388
  bytes: Buffer.byteLength(combinedCss),
3139
4389
  emitter: "native",
@@ -3154,14 +4404,14 @@ ${nativeCode}`;
3154
4404
  return { artifacts: results, stats: buildStats };
3155
4405
  }
3156
4406
  async function writeAssetsManifest(outputDir, artifacts) {
3157
- const dir = path11.resolve(outputDir);
3158
- await fs9.promises.mkdir(dir, { recursive: true });
3159
- const file = path11.join(dir, "manifest.assets.json");
4407
+ const dir = path14.resolve(outputDir);
4408
+ await fs11.promises.mkdir(dir, { recursive: true });
4409
+ const file = path14.join(dir, "manifest.assets.json");
3160
4410
  const payload = {
3161
4411
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3162
4412
  chunks: artifacts
3163
4413
  };
3164
- await fs9.promises.writeFile(file, JSON.stringify(payload, null, 2), "utf8");
4414
+ await fs11.promises.writeFile(file, JSON.stringify(payload, null, 2), "utf8");
3165
4415
  }
3166
4416
 
3167
4417
  // src/core/worker/pool.ts
@@ -3203,7 +4453,7 @@ var TransformWorkerPool = class {
3203
4453
  this.dequeue(worker);
3204
4454
  });
3205
4455
  worker.on("error", (err) => {
3206
- logWarn(`Transform worker error: ${String(err)}`);
4456
+ logWarn2(`Transform worker error: ${String(err)}`);
3207
4457
  const item = this.active.get(id);
3208
4458
  if (item) {
3209
4459
  this.active.delete(id);
@@ -3218,7 +4468,7 @@ var TransformWorkerPool = class {
3218
4468
  this.queue.unshift(item);
3219
4469
  }
3220
4470
  if (!this.closed && code !== 0) {
3221
- logWarn(`Transform worker exited unexpectedly (${code}), respawning`);
4471
+ logWarn2(`Transform worker exited unexpectedly (${code}), respawning`);
3222
4472
  this.spawnWorker();
3223
4473
  }
3224
4474
  });
@@ -3293,6 +4543,7 @@ var TransformWorkerPool = class {
3293
4543
  async function runBuildCommand(options = {}) {
3294
4544
  try {
3295
4545
  const config = await loadIonifyConfig();
4546
+ const rootDir = config?.root || process.cwd();
3296
4547
  const optLevel = resolveOptimizationLevel(config?.optimizationLevel, {
3297
4548
  cliLevel: options.level,
3298
4549
  envLevel: process.env.IONIFY_OPTIMIZATION_LEVEL
@@ -3321,11 +4572,10 @@ async function runBuildCommand(options = {}) {
3321
4572
  combineEnv: process.env.IONIFY_SCOPE_HOIST_COMBINE
3322
4573
  });
3323
4574
  }
3324
- applyMinifierEnv(minifier);
3325
4575
  applyParserEnv(parserMode);
3326
- applyTreeshakeEnv(treeshake);
3327
- applyScopeHoistEnv(scopeHoist);
3328
- const entries = config?.entry ? [config.entry.startsWith("/") ? path12.join(process.cwd(), config.entry) : path12.resolve(process.cwd(), config.entry)] : void 0;
4576
+ const entries = config?.entry ? (Array.isArray(config.entry) ? config.entry : [config.entry]).map(
4577
+ (entry) => entry.startsWith("/") ? path15.join(rootDir, entry) : path15.resolve(rootDir, entry)
4578
+ ) : void 0;
3329
4579
  if (entries) {
3330
4580
  logInfo(`Build entries: ${entries.join(", ")}`);
3331
4581
  } else {
@@ -3367,11 +4617,11 @@ async function runBuildCommand(options = {}) {
3367
4617
  const moduleOutputs = /* @__PURE__ */ new Map();
3368
4618
  const pool = new TransformWorkerPool();
3369
4619
  try {
3370
- const jobs = Array.from(uniqueModules).filter((filePath) => fs10.existsSync(filePath)).map((filePath) => {
3371
- const code = fs10.readFileSync(filePath, "utf8");
4620
+ const jobs = Array.from(uniqueModules).filter((filePath) => fs12.existsSync(filePath)).map((filePath) => {
4621
+ const code = fs12.readFileSync(filePath, "utf8");
3372
4622
  const sourceHash = getCacheKey(code);
3373
4623
  const moduleHash = moduleHashes.get(filePath) ?? sourceHash;
3374
- const cacheKey = getCacheKey(`build-worker:v1:${path12.extname(filePath)}:${moduleHash}:${filePath}`);
4624
+ const cacheKey = getCacheKey(`build-worker:v1:${path15.extname(filePath)}:${moduleHash}:${filePath}`);
3375
4625
  const cached = readCache(cacheKey);
3376
4626
  if (cached) {
3377
4627
  try {
@@ -3379,13 +4629,13 @@ async function runBuildCommand(options = {}) {
3379
4629
  if (parsed?.code) {
3380
4630
  const transformedHash = getCacheKey(parsed.code);
3381
4631
  moduleHashes.set(filePath, transformedHash);
3382
- const casRoot2 = path12.join(process.cwd(), ".ionify", "cas");
4632
+ const casRoot2 = path15.join(rootDir, ".ionify", "cas");
3383
4633
  const cacheDir = getCasArtifactPath(casRoot2, configHash, transformedHash);
3384
- if (!fs10.existsSync(path12.join(cacheDir, "transformed.js"))) {
3385
- fs10.mkdirSync(cacheDir, { recursive: true });
3386
- fs10.writeFileSync(path12.join(cacheDir, "transformed.js"), parsed.code, "utf8");
4634
+ if (!fs12.existsSync(path15.join(cacheDir, "transformed.js"))) {
4635
+ fs12.mkdirSync(cacheDir, { recursive: true });
4636
+ fs12.writeFileSync(path15.join(cacheDir, "transformed.js"), parsed.code, "utf8");
3387
4637
  if (parsed.map) {
3388
- fs10.writeFileSync(path12.join(cacheDir, "transformed.js.map"), parsed.map, "utf8");
4638
+ fs12.writeFileSync(path15.join(cacheDir, "transformed.js.map"), parsed.map, "utf8");
3389
4639
  }
3390
4640
  }
3391
4641
  moduleOutputs.set(filePath, { code: parsed.code, type: parsed.type ?? "js" });
@@ -3397,7 +4647,7 @@ async function runBuildCommand(options = {}) {
3397
4647
  return {
3398
4648
  id: filePath,
3399
4649
  filePath,
3400
- ext: path12.extname(filePath),
4650
+ ext: path15.extname(filePath),
3401
4651
  code,
3402
4652
  cacheKey
3403
4653
  };
@@ -3420,13 +4670,13 @@ async function runBuildCommand(options = {}) {
3420
4670
  writeCache(job.cacheKey, Buffer.from(payload));
3421
4671
  const transformedHash = getCacheKey(result.code);
3422
4672
  const moduleHash = transformedHash;
3423
- const casRoot2 = path12.join(process.cwd(), ".ionify", "cas");
4673
+ const casRoot2 = path15.join(rootDir, ".ionify", "cas");
3424
4674
  const versionHash = configHash;
3425
4675
  const cacheDir = getCasArtifactPath(casRoot2, versionHash, moduleHash);
3426
- fs10.mkdirSync(cacheDir, { recursive: true });
3427
- fs10.writeFileSync(path12.join(cacheDir, "transformed.js"), result.code, "utf8");
4676
+ fs12.mkdirSync(cacheDir, { recursive: true });
4677
+ fs12.writeFileSync(path15.join(cacheDir, "transformed.js"), result.code, "utf8");
3428
4678
  if (result.map) {
3429
- fs10.writeFileSync(path12.join(cacheDir, "transformed.js.map"), result.map, "utf8");
4679
+ fs12.writeFileSync(path15.join(cacheDir, "transformed.js.map"), result.map, "utf8");
3430
4680
  }
3431
4681
  moduleHashes.set(job.filePath, moduleHash);
3432
4682
  for (const chunk of plan.chunks) {
@@ -3449,20 +4699,20 @@ async function runBuildCommand(options = {}) {
3449
4699
  }
3450
4700
  }
3451
4701
  }
3452
- const absOutDir = path12.resolve(outDir);
3453
- const casRoot = path12.join(process.cwd(), ".ionify", "cas");
4702
+ const absOutDir = path15.resolve(outDir);
4703
+ const casRoot = path15.join(rootDir, ".ionify", "cas");
3454
4704
  const { artifacts, stats } = await emitChunks(absOutDir, plan, moduleOutputs, {
3455
4705
  casRoot,
3456
4706
  versionHash: configHash
3457
4707
  });
3458
4708
  await writeBuildManifest(absOutDir, plan, artifacts);
3459
4709
  await writeAssetsManifest(absOutDir, artifacts);
3460
- await fs10.promises.writeFile(
3461
- path12.join(absOutDir, "build.stats.json"),
4710
+ await fs12.promises.writeFile(
4711
+ path15.join(absOutDir, "build.stats.json"),
3462
4712
  JSON.stringify(stats, null, 2),
3463
4713
  "utf8"
3464
4714
  );
3465
- logInfo(`Build plan generated \u2192 ${path12.join(absOutDir, "manifest.json")}`);
4715
+ logInfo(`Build plan generated \u2192 ${path15.join(absOutDir, "manifest.json")}`);
3466
4716
  logInfo(`Entries: ${plan.entries.length}, Chunks: ${plan.chunks.length}`);
3467
4717
  logInfo(`Modules transformed: ${moduleOutputs.size}`);
3468
4718
  } catch (err) {