@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/README.md +2 -2
- package/dist/{cache-Y4NMRSZO.js → cache-LH24ZR2B.js} +1 -1
- package/dist/{chunk-GOZUBOYH.js → chunk-QQSYYX7B.js} +3 -0
- package/dist/cli/index.cjs +1544 -283
- package/dist/cli/index.js +1532 -282
- package/dist/client/hmr.js +39 -3
- package/dist/client/overlay.js +140 -25
- package/dist/client/react-refresh-runtime.js +57 -9
- package/dist/index.d.cts +71 -0
- package/dist/index.d.ts +71 -0
- package/dist/ionify_core.node +0 -0
- package/package.json +3 -2
package/dist/cli/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
tryBundleNodeModule,
|
|
9
9
|
tryNativeTransform,
|
|
10
10
|
writeCache
|
|
11
|
-
} from "../chunk-
|
|
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
|
|
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
|
|
36
|
-
import
|
|
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
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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-
|
|
853
|
-
const
|
|
854
|
-
const
|
|
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 ?
|
|
862
|
-
const casMapFile = casDir ?
|
|
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 &&
|
|
940
|
+
if (casFile && fs13.existsSync(casFile)) {
|
|
873
941
|
try {
|
|
874
|
-
const code =
|
|
875
|
-
const map = casMapFile &&
|
|
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
|
-
|
|
913
|
-
|
|
980
|
+
fs13.mkdirSync(path16.dirname(casFile), { recursive: true });
|
|
981
|
+
fs13.writeFileSync(casFile, result.code, "utf8");
|
|
914
982
|
if (result.map && casMapFile) {
|
|
915
|
-
|
|
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
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
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
|
|
1282
|
-
if (
|
|
1283
|
-
output =
|
|
1707
|
+
const depsProxy = makeDepsProxyForFile(filePath, code);
|
|
1708
|
+
if (depsProxy) {
|
|
1709
|
+
output = depsProxy;
|
|
1284
1710
|
} else {
|
|
1285
|
-
|
|
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
|
|
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
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
1940
|
+
var registry2 = /* @__PURE__ */ new Set();
|
|
1401
1941
|
function registerLoader(registration) {
|
|
1402
|
-
|
|
1942
|
+
registry2.add(registration);
|
|
1403
1943
|
}
|
|
1404
1944
|
async function applyRegisteredLoaders(engine, config) {
|
|
1405
|
-
for (const registration of
|
|
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
|
-
|
|
1418
|
-
|
|
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
|
|
1437
|
-
import
|
|
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 =
|
|
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 =
|
|
1499
|
-
if (
|
|
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 =
|
|
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 =
|
|
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 ${
|
|
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
|
|
1570
|
-
import
|
|
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 =
|
|
1603
|
-
if (!
|
|
2156
|
+
const filePath = path11.resolve(rootDir, name);
|
|
2157
|
+
if (!fs8.existsSync(filePath) || !fs8.statSync(filePath).isFile()) {
|
|
1604
2158
|
continue;
|
|
1605
2159
|
}
|
|
1606
|
-
const contents =
|
|
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
|
|
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 =
|
|
1790
|
-
var CLIENT_DIR =
|
|
1791
|
-
var CLIENT_FALLBACK_DIR =
|
|
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 =
|
|
1794
|
-
if (
|
|
1795
|
-
return { filePath: primary, code:
|
|
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 =
|
|
1798
|
-
if (
|
|
1799
|
-
return { filePath: fallback, code:
|
|
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 =
|
|
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
|
|
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
|
-
|
|
1844
|
-
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
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 =
|
|
2131
|
-
if (
|
|
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 =
|
|
2140
|
-
if (
|
|
3230
|
+
const packageJson = path12.join(effectiveFsPath, "package.json");
|
|
3231
|
+
if (fs9.existsSync(packageJson)) {
|
|
2141
3232
|
try {
|
|
2142
|
-
const pkg = JSON.parse(
|
|
3233
|
+
const pkg = JSON.parse(fs9.readFileSync(packageJson, "utf8"));
|
|
2143
3234
|
if (pkg.main) {
|
|
2144
|
-
const mainFile =
|
|
2145
|
-
if (
|
|
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 =
|
|
2157
|
-
if (
|
|
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 (!
|
|
3261
|
+
if (!fs9.existsSync(effectiveFsPath)) {
|
|
2171
3262
|
res.statusCode = 404;
|
|
2172
3263
|
res.end("Not found");
|
|
2173
3264
|
return;
|
|
2174
3265
|
}
|
|
2175
|
-
const ext =
|
|
3266
|
+
const ext = path12.extname(effectiveFsPath);
|
|
2176
3267
|
if (isAssetExt(ext)) {
|
|
2177
3268
|
try {
|
|
2178
|
-
const data =
|
|
2179
|
-
const assetHash =
|
|
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
|
-
|
|
3286
|
+
fs9.createReadStream(effectiveFsPath).pipe(res);
|
|
2196
3287
|
return;
|
|
2197
3288
|
}
|
|
2198
3289
|
}
|
|
2199
3290
|
if (ext === ".css") {
|
|
2200
3291
|
try {
|
|
2201
|
-
const cssSource =
|
|
3292
|
+
const cssSource = fs9.readFileSync(effectiveFsPath, "utf8");
|
|
2202
3293
|
const isModule = "module" in q || /\.module\.css$/i.test(effectiveFsPath);
|
|
2203
|
-
const
|
|
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
|
|
2209
|
-
const
|
|
2210
|
-
|
|
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 (
|
|
3310
|
+
if (fs9.existsSync(casFile)) {
|
|
2213
3311
|
try {
|
|
2214
|
-
finalBuffer =
|
|
2215
|
-
|
|
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
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
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
|
-
|
|
2236
|
-
|
|
3365
|
+
fs9.mkdirSync(casDir, { recursive: true });
|
|
3366
|
+
fs9.writeFileSync(casFile, finalBuffer);
|
|
2237
3367
|
} catch {
|
|
2238
3368
|
}
|
|
2239
3369
|
}
|
|
2240
|
-
|
|
2241
|
-
|
|
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
|
-
|
|
3377
|
+
sendBuffer(req, res, 200, "text/css; charset=utf-8", finalBuffer, {
|
|
3378
|
+
etag,
|
|
3379
|
+
cacheControl: "no-cache"
|
|
3380
|
+
});
|
|
2244
3381
|
}
|
|
2245
|
-
|
|
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 =
|
|
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
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
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
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
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
|
-
|
|
2293
|
-
res
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
2436
|
-
import
|
|
3685
|
+
import fs10 from "fs";
|
|
3686
|
+
import path13 from "path";
|
|
2437
3687
|
function readGraphFromDisk(root) {
|
|
2438
|
-
const file =
|
|
2439
|
-
if (!
|
|
3688
|
+
const file = path13.join(root, ".ionify", "graph.json");
|
|
3689
|
+
if (!fs10.existsSync(file)) return null;
|
|
2440
3690
|
try {
|
|
2441
|
-
const raw =
|
|
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
|
|
2538
|
-
import
|
|
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
|
|
2628
|
-
import
|
|
2629
|
-
import
|
|
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
|
-
|
|
3894
|
+
logWarn2(`Failed to load native graph: ${String(err)}`);
|
|
2645
3895
|
}
|
|
2646
3896
|
}
|
|
2647
|
-
const file =
|
|
2648
|
-
if (!
|
|
3897
|
+
const file = path14.join(process.cwd(), ".ionify", "graph.json");
|
|
3898
|
+
if (!fs11.existsSync(file)) return [];
|
|
2649
3899
|
try {
|
|
2650
|
-
const raw =
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
4070
|
+
logWarn2(`Failed to load persisted graph: ${String(err)}`);
|
|
2821
4071
|
}
|
|
2822
4072
|
} else {
|
|
2823
|
-
|
|
4073
|
+
logWarn2(`graphLoadMap not available, native binding: ${!!native}`);
|
|
2824
4074
|
}
|
|
2825
4075
|
if (moduleCount === 0 && entries?.length && native) {
|
|
2826
|
-
|
|
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 (!
|
|
2832
|
-
const code =
|
|
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
|
-
|
|
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 =
|
|
2905
|
-
await
|
|
2906
|
-
const file =
|
|
2907
|
-
await
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
2943
|
-
const ext =
|
|
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 =
|
|
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 ??
|
|
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 =
|
|
2989
|
-
await
|
|
2990
|
-
const assetsDir =
|
|
2991
|
-
await
|
|
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 =
|
|
3009
|
-
await
|
|
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 ??
|
|
3023
|
-
const assetFile =
|
|
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
|
|
3027
|
-
await
|
|
3028
|
-
await
|
|
3029
|
-
const rel = toPosix(
|
|
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
|
-
|
|
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 &&
|
|
4299
|
+
if (!cssSource && fs11.existsSync(cssPath)) {
|
|
3050
4300
|
try {
|
|
3051
|
-
cssSource = await
|
|
4301
|
+
cssSource = await fs11.promises.readFile(cssPath, "utf8");
|
|
3052
4302
|
} catch (err) {
|
|
3053
|
-
|
|
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 =
|
|
3069
|
-
await
|
|
3070
|
-
cssFileRel = toPosix(
|
|
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 =
|
|
4330
|
+
const nativeFile = path14.join(chunkOutDir, artifact.file_name);
|
|
3081
4331
|
let nativeCode = artifact.code;
|
|
3082
4332
|
if (cssFileRel) {
|
|
3083
|
-
const absCss =
|
|
3084
|
-
const relCss = toPosix(
|
|
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
|
|
4343
|
+
await fs11.promises.writeFile(mapFile, artifact.map, "utf8");
|
|
3094
4344
|
nativeCode = `${nativeCode}
|
|
3095
|
-
//# sourceMappingURL=${
|
|
3096
|
-
const relMap = toPosix(
|
|
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
|
|
3105
|
-
const relNative = toPosix(
|
|
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 (
|
|
4372
|
+
} else if (fs11.existsSync(cssPath)) {
|
|
3123
4373
|
try {
|
|
3124
|
-
cssSources.push(await
|
|
4374
|
+
cssSources.push(await fs11.promises.readFile(cssPath, "utf8"));
|
|
3125
4375
|
} catch (err) {
|
|
3126
|
-
|
|
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 =
|
|
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 =
|
|
3135
|
-
await
|
|
3136
|
-
const relCss =
|
|
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 =
|
|
3158
|
-
await
|
|
3159
|
-
const file =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
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) =>
|
|
3371
|
-
const code =
|
|
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:${
|
|
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 =
|
|
4632
|
+
const casRoot2 = path15.join(rootDir, ".ionify", "cas");
|
|
3383
4633
|
const cacheDir = getCasArtifactPath(casRoot2, configHash, transformedHash);
|
|
3384
|
-
if (!
|
|
3385
|
-
|
|
3386
|
-
|
|
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
|
-
|
|
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:
|
|
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 =
|
|
4673
|
+
const casRoot2 = path15.join(rootDir, ".ionify", "cas");
|
|
3424
4674
|
const versionHash = configHash;
|
|
3425
4675
|
const cacheDir = getCasArtifactPath(casRoot2, versionHash, moduleHash);
|
|
3426
|
-
|
|
3427
|
-
|
|
4676
|
+
fs12.mkdirSync(cacheDir, { recursive: true });
|
|
4677
|
+
fs12.writeFileSync(path15.join(cacheDir, "transformed.js"), result.code, "utf8");
|
|
3428
4678
|
if (result.map) {
|
|
3429
|
-
|
|
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 =
|
|
3453
|
-
const casRoot =
|
|
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
|
|
3461
|
-
|
|
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 ${
|
|
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) {
|