@ionify/ionify 0.1.0 → 0.1.2
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 +13 -0
- package/dist/cas-FEOXFD7R.js +7 -0
- package/dist/chunk-GOZUBOYH.js +225 -0
- package/dist/chunk-X5UIMJDA.js +11 -0
- package/dist/cli/index.cjs +3677 -68
- package/dist/cli/index.js +3373 -62
- package/package.json +1 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
9
16
|
var __copyProps = (to, from, except, desc) => {
|
|
10
17
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
18
|
for (let key of __getOwnPropNames(from))
|
|
@@ -24,85 +31,2730 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
31
|
));
|
|
25
32
|
|
|
26
33
|
// node_modules/.pnpm/tsup@8.5.1_@swc+core@1.15.4_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js
|
|
27
|
-
var getImportMetaUrl
|
|
28
|
-
var
|
|
34
|
+
var getImportMetaUrl, importMetaUrl;
|
|
35
|
+
var init_cjs_shims = __esm({
|
|
36
|
+
"node_modules/.pnpm/tsup@8.5.1_@swc+core@1.15.4_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js"() {
|
|
37
|
+
"use strict";
|
|
38
|
+
getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
39
|
+
importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// src/core/version.ts
|
|
44
|
+
function normalizeTreeshake(treeshake) {
|
|
45
|
+
if (treeshake === false || treeshake === void 0 || treeshake === null) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
if (treeshake === true) {
|
|
49
|
+
return {
|
|
50
|
+
mode: "safe",
|
|
51
|
+
include: [],
|
|
52
|
+
exclude: []
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (typeof treeshake === "string") {
|
|
56
|
+
return {
|
|
57
|
+
mode: treeshake === "aggressive" ? "aggressive" : "safe",
|
|
58
|
+
include: [],
|
|
59
|
+
exclude: []
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
mode: treeshake.mode === "aggressive" ? "aggressive" : "safe",
|
|
64
|
+
include: Array.isArray(treeshake.include) ? [...treeshake.include].sort() : [],
|
|
65
|
+
exclude: Array.isArray(treeshake.exclude) ? [...treeshake.exclude].sort() : []
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function normalizeScopeHoist(scopeHoist) {
|
|
69
|
+
if (scopeHoist === false || scopeHoist === void 0 || scopeHoist === null) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (scopeHoist === true) {
|
|
73
|
+
return {
|
|
74
|
+
inlineFunctions: true,
|
|
75
|
+
constantFolding: true,
|
|
76
|
+
combineVariables: true
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
inlineFunctions: scopeHoist.inlineFunctions === true,
|
|
81
|
+
constantFolding: scopeHoist.constantFolding === true,
|
|
82
|
+
combineVariables: scopeHoist.combineVariables === true
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function computeCanonicalVersionInputs(config) {
|
|
86
|
+
const parserMode = config.parserMode || "hybrid";
|
|
87
|
+
const minifier = config.minifier || "auto";
|
|
88
|
+
const treeshake = normalizeTreeshake(config.treeshake);
|
|
89
|
+
const scopeHoist = normalizeScopeHoist(config.scopeHoist);
|
|
90
|
+
const plugins = Array.isArray(config.plugins) ? config.plugins.map((p) => typeof p === "string" ? p : p.name).filter((name) => typeof name === "string").sort() : [];
|
|
91
|
+
let entry = null;
|
|
92
|
+
if (config.entry) {
|
|
93
|
+
if (typeof config.entry === "string") {
|
|
94
|
+
entry = [config.entry];
|
|
95
|
+
} else if (Array.isArray(config.entry)) {
|
|
96
|
+
entry = [...config.entry].sort();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const cssOptions = config.cssOptions && Object.keys(config.cssOptions).length > 0 ? config.cssOptions : null;
|
|
100
|
+
const assetOptions = config.assetOptions && Object.keys(config.assetOptions).length > 0 ? config.assetOptions : null;
|
|
101
|
+
return {
|
|
102
|
+
parserMode,
|
|
103
|
+
minifier,
|
|
104
|
+
treeshake,
|
|
105
|
+
scopeHoist,
|
|
106
|
+
plugins,
|
|
107
|
+
entry,
|
|
108
|
+
cssOptions,
|
|
109
|
+
assetOptions
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function computeVersionHash(inputs) {
|
|
113
|
+
const json = JSON.stringify(inputs, Object.keys(inputs).sort());
|
|
114
|
+
const hash = (0, import_node_crypto.createHash)("sha256").update(json).digest("hex");
|
|
115
|
+
return hash.slice(0, 16);
|
|
116
|
+
}
|
|
117
|
+
var import_node_crypto;
|
|
118
|
+
var init_version = __esm({
|
|
119
|
+
"src/core/version.ts"() {
|
|
120
|
+
"use strict";
|
|
121
|
+
init_cjs_shims();
|
|
122
|
+
import_node_crypto = require("crypto");
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// src/native/index.ts
|
|
127
|
+
function resolveCandidates() {
|
|
128
|
+
const cwd = process.cwd();
|
|
129
|
+
const releaseDir = import_path.default.resolve(cwd, "target", "release");
|
|
130
|
+
const debugDir = import_path.default.resolve(cwd, "target", "debug");
|
|
131
|
+
const nativeDir = import_path.default.resolve(cwd, "native");
|
|
132
|
+
const moduleDir = import_path.default.dirname(new URL(importMetaUrl).pathname);
|
|
133
|
+
const packageNativeDir = import_path.default.resolve(moduleDir, "..", "native");
|
|
134
|
+
const packageDistDir = import_path.default.resolve(moduleDir, "..");
|
|
135
|
+
const platformFile = process.platform === "win32" ? "ionify_core.dll" : process.platform === "darwin" ? "libionify_core.dylib" : "libionify_core.so";
|
|
136
|
+
const candidates = [
|
|
137
|
+
// Installed package locations (checked first)
|
|
138
|
+
import_path.default.join(packageDistDir, "ionify_core.node"),
|
|
139
|
+
import_path.default.join(packageNativeDir, "ionify_core.node"),
|
|
140
|
+
// Development locations
|
|
141
|
+
import_path.default.join(nativeDir, "ionify_core.node"),
|
|
142
|
+
import_path.default.join(releaseDir, "ionify_core.node"),
|
|
143
|
+
import_path.default.join(releaseDir, platformFile),
|
|
144
|
+
import_path.default.join(debugDir, "ionify_core.node"),
|
|
145
|
+
import_path.default.join(debugDir, platformFile)
|
|
146
|
+
];
|
|
147
|
+
return candidates.filter((candidate) => {
|
|
148
|
+
try {
|
|
149
|
+
return import_fs.default.existsSync(candidate) && import_fs.default.statSync(candidate).isFile();
|
|
150
|
+
} catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function tryNativeTransform(mode, code, options) {
|
|
156
|
+
if (!nativeBinding) return null;
|
|
157
|
+
const wantsOxc = mode === "oxc" || mode === "hybrid";
|
|
158
|
+
const wantsSwc = mode === "swc" || mode === "hybrid";
|
|
159
|
+
if (wantsOxc && nativeBinding.parseAndTransformOxc) {
|
|
160
|
+
try {
|
|
161
|
+
return nativeBinding.parseAndTransformOxc(code, options);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
if (mode === "oxc") throw err;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (wantsSwc && nativeBinding.parseAndTransformSwc) {
|
|
167
|
+
try {
|
|
168
|
+
return nativeBinding.parseAndTransformSwc(code, options);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
if (mode === "swc") throw err;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
function ensureNativeGraph(graphPath, version) {
|
|
176
|
+
if (!nativeBinding?.graphInit) return;
|
|
177
|
+
try {
|
|
178
|
+
nativeBinding.graphInit(graphPath, version);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
console.error(`[Native] Failed to initialize graph: ${err}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function computeGraphVersion(inputs) {
|
|
184
|
+
const canonical = computeCanonicalVersionInputs(inputs);
|
|
185
|
+
return computeVersionHash(canonical);
|
|
186
|
+
}
|
|
187
|
+
function tryBundleNodeModule(filePath, code) {
|
|
188
|
+
if (!nativeBinding?.plannerPlanBuild || !nativeBinding?.buildChunks) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
const plan = nativeBinding.plannerPlanBuild([filePath]);
|
|
193
|
+
if (!plan || !plan.chunks || plan.chunks.length === 0) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
const artifacts = nativeBinding.buildChunks(plan);
|
|
197
|
+
if (artifacts && artifacts.length > 0 && artifacts[0].code) {
|
|
198
|
+
return artifacts[0].code;
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.warn(`[Ionify] Native bundler failed for ${filePath}:`, error);
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
var import_fs, import_path, import_module, nativeBinding, native;
|
|
206
|
+
var init_native = __esm({
|
|
207
|
+
"src/native/index.ts"() {
|
|
208
|
+
"use strict";
|
|
209
|
+
init_cjs_shims();
|
|
210
|
+
import_fs = __toESM(require("fs"), 1);
|
|
211
|
+
import_path = __toESM(require("path"), 1);
|
|
212
|
+
import_module = require("module");
|
|
213
|
+
init_version();
|
|
214
|
+
nativeBinding = null;
|
|
215
|
+
(() => {
|
|
216
|
+
const require2 = (0, import_module.createRequire)(importMetaUrl);
|
|
217
|
+
for (const candidate of resolveCandidates()) {
|
|
218
|
+
try {
|
|
219
|
+
const mod = require2(candidate);
|
|
220
|
+
if (mod) {
|
|
221
|
+
nativeBinding = mod;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
})();
|
|
228
|
+
native = nativeBinding;
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// src/core/cache.ts
|
|
233
|
+
var cache_exports = {};
|
|
234
|
+
__export(cache_exports, {
|
|
235
|
+
clearCache: () => clearCache,
|
|
236
|
+
getCacheKey: () => getCacheKey,
|
|
237
|
+
readCache: () => readCache,
|
|
238
|
+
writeCache: () => writeCache
|
|
239
|
+
});
|
|
240
|
+
function ensureCacheDir() {
|
|
241
|
+
if (!import_fs2.default.existsSync(CACHE_DIR)) {
|
|
242
|
+
import_fs2.default.mkdirSync(CACHE_DIR, { recursive: true });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function getCacheKey(content) {
|
|
246
|
+
if (native?.cacheHash) {
|
|
247
|
+
try {
|
|
248
|
+
return native.cacheHash(Buffer.from(content));
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return import_crypto.default.createHash("sha256").update(content).digest("hex");
|
|
253
|
+
}
|
|
254
|
+
function writeCache(hash, data) {
|
|
255
|
+
ensureCacheDir();
|
|
256
|
+
const target = import_path2.default.join(CACHE_DIR, hash);
|
|
257
|
+
import_fs2.default.writeFileSync(target, data);
|
|
258
|
+
}
|
|
259
|
+
function readCache(hash) {
|
|
260
|
+
const target = import_path2.default.join(CACHE_DIR, hash);
|
|
261
|
+
return import_fs2.default.existsSync(target) ? import_fs2.default.readFileSync(target) : null;
|
|
262
|
+
}
|
|
263
|
+
function clearCache() {
|
|
264
|
+
if (import_fs2.default.existsSync(CACHE_DIR)) {
|
|
265
|
+
import_fs2.default.rmSync(CACHE_DIR, { recursive: true, force: true });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
var import_fs2, import_path2, import_crypto, CACHE_DIR;
|
|
269
|
+
var init_cache = __esm({
|
|
270
|
+
"src/core/cache.ts"() {
|
|
271
|
+
"use strict";
|
|
272
|
+
init_cjs_shims();
|
|
273
|
+
import_fs2 = __toESM(require("fs"), 1);
|
|
274
|
+
import_path2 = __toESM(require("path"), 1);
|
|
275
|
+
import_crypto = __toESM(require("crypto"), 1);
|
|
276
|
+
init_native();
|
|
277
|
+
CACHE_DIR = import_path2.default.join(process.cwd(), ".ionify", "cache");
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// src/core/utils/cas.ts
|
|
282
|
+
var cas_exports = {};
|
|
283
|
+
__export(cas_exports, {
|
|
284
|
+
getCasArtifactPath: () => getCasArtifactPath
|
|
285
|
+
});
|
|
286
|
+
function getCasArtifactPath(casRoot, versionHash, moduleHash) {
|
|
287
|
+
return import_path7.default.join(casRoot, versionHash, moduleHash);
|
|
288
|
+
}
|
|
289
|
+
var import_path7;
|
|
290
|
+
var init_cas = __esm({
|
|
291
|
+
"src/core/utils/cas.ts"() {
|
|
292
|
+
"use strict";
|
|
293
|
+
init_cjs_shims();
|
|
294
|
+
import_path7 = __toESM(require("path"), 1);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
29
297
|
|
|
30
298
|
// src/cli/index.ts
|
|
299
|
+
init_cjs_shims();
|
|
31
300
|
var import_commander = require("commander");
|
|
32
301
|
|
|
33
302
|
// src/cli/utils/logger.ts
|
|
303
|
+
init_cjs_shims();
|
|
34
304
|
var import_chalk = __toESM(require("chalk"), 1);
|
|
35
305
|
function logInfo(message) {
|
|
36
306
|
console.log(import_chalk.default.cyan(`[Ionify] ${message}`));
|
|
37
307
|
}
|
|
38
|
-
function
|
|
39
|
-
console.
|
|
40
|
-
|
|
308
|
+
function logWarn(message) {
|
|
309
|
+
console.warn(import_chalk.default.yellow(`[Ionify] ${message}`));
|
|
310
|
+
}
|
|
311
|
+
function logError(message, err) {
|
|
312
|
+
console.error(import_chalk.default.red(`[Ionify] ${message}`));
|
|
313
|
+
if (err) console.error(err);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/cli/commands/dev.ts
|
|
317
|
+
init_cjs_shims();
|
|
318
|
+
var import_http = __toESM(require("http"), 1);
|
|
319
|
+
var import_url3 = __toESM(require("url"), 1);
|
|
320
|
+
var import_fs9 = __toESM(require("fs"), 1);
|
|
321
|
+
var import_path12 = __toESM(require("path"), 1);
|
|
322
|
+
var import_url4 = require("url");
|
|
323
|
+
var import_module3 = require("module");
|
|
324
|
+
init_cache();
|
|
325
|
+
|
|
326
|
+
// src/core/graph.ts
|
|
327
|
+
init_cjs_shims();
|
|
328
|
+
var import_fs3 = __toESM(require("fs"), 1);
|
|
329
|
+
var import_path3 = __toESM(require("path"), 1);
|
|
330
|
+
init_native();
|
|
331
|
+
var IONIFY_DIR = import_path3.default.join(process.cwd(), ".ionify");
|
|
332
|
+
var GRAPH_FILE = import_path3.default.join(IONIFY_DIR, "graph.json");
|
|
333
|
+
var GRAPH_DB_FILE = import_path3.default.join(IONIFY_DIR, "graph.db");
|
|
334
|
+
function ensureIonifyDir() {
|
|
335
|
+
if (!import_fs3.default.existsSync(IONIFY_DIR)) import_fs3.default.mkdirSync(IONIFY_DIR, { recursive: true });
|
|
336
|
+
}
|
|
337
|
+
var Graph = class {
|
|
338
|
+
nodes = /* @__PURE__ */ new Map();
|
|
339
|
+
dirty = false;
|
|
340
|
+
saveTimer = null;
|
|
341
|
+
native = native ?? null;
|
|
342
|
+
nativeFlushTimer = null;
|
|
343
|
+
queueSave() {
|
|
344
|
+
if (this.native) return;
|
|
345
|
+
this.dirty = true;
|
|
346
|
+
if (this.saveTimer) return;
|
|
347
|
+
this.saveTimer = setTimeout(() => this.save(), 300);
|
|
348
|
+
}
|
|
349
|
+
constructor(versionInputs) {
|
|
350
|
+
ensureIonifyDir();
|
|
351
|
+
if (this.native) {
|
|
352
|
+
const version = versionInputs ? computeGraphVersion(versionInputs) : void 0;
|
|
353
|
+
ensureNativeGraph(GRAPH_DB_FILE, version);
|
|
354
|
+
}
|
|
355
|
+
this.load();
|
|
356
|
+
}
|
|
357
|
+
load() {
|
|
358
|
+
if (this.native) {
|
|
359
|
+
try {
|
|
360
|
+
const snapshot = this.native.graphLoad();
|
|
361
|
+
for (const node of snapshot) {
|
|
362
|
+
const stat = import_fs3.default.existsSync(node.id) ? import_fs3.default.statSync(node.id) : null;
|
|
363
|
+
this.nodes.set(node.id, {
|
|
364
|
+
id: node.id,
|
|
365
|
+
hash: node.hash,
|
|
366
|
+
deps: node.deps,
|
|
367
|
+
dynamicDeps: node.dynamicDeps,
|
|
368
|
+
kind: node.kind,
|
|
369
|
+
configHash: node.config_hash ?? node.configHash ?? null,
|
|
370
|
+
mtimeMs: stat ? stat.mtimeMs : null
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
} catch {
|
|
374
|
+
this.loadFromDisk();
|
|
375
|
+
}
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
this.loadFromDisk();
|
|
379
|
+
}
|
|
380
|
+
loadFromDisk() {
|
|
381
|
+
if (!import_fs3.default.existsSync(GRAPH_FILE)) return;
|
|
382
|
+
try {
|
|
383
|
+
const raw = import_fs3.default.readFileSync(GRAPH_FILE, "utf8");
|
|
384
|
+
const snap = JSON.parse(raw);
|
|
385
|
+
if (snap.version === 1 && snap.nodes) {
|
|
386
|
+
for (const [id, node] of Object.entries(snap.nodes)) {
|
|
387
|
+
this.nodes.set(id, node);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
} catch {
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
scheduleNativeFlush() {
|
|
394
|
+
if (!this.native?.graphFlush) return;
|
|
395
|
+
if (this.nativeFlushTimer) return;
|
|
396
|
+
this.nativeFlushTimer = setTimeout(() => {
|
|
397
|
+
this.nativeFlushTimer = null;
|
|
398
|
+
try {
|
|
399
|
+
this.native?.graphFlush?.();
|
|
400
|
+
} catch {
|
|
401
|
+
}
|
|
402
|
+
}, 250);
|
|
403
|
+
}
|
|
404
|
+
scheduleSave() {
|
|
405
|
+
if (this.native) return;
|
|
406
|
+
this.queueSave();
|
|
407
|
+
}
|
|
408
|
+
save() {
|
|
409
|
+
if (this.native) return;
|
|
410
|
+
try {
|
|
411
|
+
const snap = {
|
|
412
|
+
version: 1,
|
|
413
|
+
nodes: Object.fromEntries(this.nodes.entries())
|
|
414
|
+
};
|
|
415
|
+
import_fs3.default.writeFileSync(GRAPH_FILE, JSON.stringify(snap, null, 2), "utf8");
|
|
416
|
+
this.dirty = false;
|
|
417
|
+
} catch {
|
|
418
|
+
} finally {
|
|
419
|
+
if (this.saveTimer) {
|
|
420
|
+
clearTimeout(this.saveTimer);
|
|
421
|
+
this.saveTimer = null;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/** Upsert a node and its deps; returns true if hash changed */
|
|
426
|
+
recordFile(absPath, contentHash, depsAbs, dynamicDeps, kind) {
|
|
427
|
+
const stat = import_fs3.default.existsSync(absPath) ? import_fs3.default.statSync(absPath) : null;
|
|
428
|
+
const mtimeMs = stat ? stat.mtimeMs : null;
|
|
429
|
+
const configHash = process.env.IONIFY_CONFIG_HASH || null;
|
|
430
|
+
const prev = this.nodes.get(absPath);
|
|
431
|
+
let changed = !prev || prev.hash !== contentHash;
|
|
432
|
+
const node = {
|
|
433
|
+
id: absPath,
|
|
434
|
+
hash: contentHash,
|
|
435
|
+
deps: Array.from(new Set(depsAbs)),
|
|
436
|
+
dynamicDeps: dynamicDeps ? Array.from(new Set(dynamicDeps)) : void 0,
|
|
437
|
+
kind: kind || this.inferKind(absPath),
|
|
438
|
+
configHash,
|
|
439
|
+
mtimeMs
|
|
440
|
+
};
|
|
441
|
+
this.nodes.set(absPath, node);
|
|
442
|
+
if (this.native) {
|
|
443
|
+
try {
|
|
444
|
+
changed = this.native.graphRecord(
|
|
445
|
+
absPath,
|
|
446
|
+
contentHash,
|
|
447
|
+
node.deps,
|
|
448
|
+
node.dynamicDeps || [],
|
|
449
|
+
node.kind,
|
|
450
|
+
node.configHash ?? null
|
|
451
|
+
);
|
|
452
|
+
this.scheduleNativeFlush();
|
|
453
|
+
} catch (err) {
|
|
454
|
+
console.error(`[Graph] Failed to record ${absPath}:`, err);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
this.scheduleSave();
|
|
458
|
+
return changed;
|
|
459
|
+
}
|
|
460
|
+
/** Infer module kind from file extension */
|
|
461
|
+
inferKind(absPath) {
|
|
462
|
+
const ext = import_path3.default.extname(absPath).toLowerCase();
|
|
463
|
+
if (/\.(module)\.css$/i.test(absPath)) return "css-module";
|
|
464
|
+
if ([".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx"].includes(ext)) return "js";
|
|
465
|
+
if (ext === ".css") return "css";
|
|
466
|
+
if ([".json"].includes(ext)) return "json";
|
|
467
|
+
return "asset";
|
|
468
|
+
}
|
|
469
|
+
getNode(absPath) {
|
|
470
|
+
return this.nodes.get(absPath);
|
|
471
|
+
}
|
|
472
|
+
getDeps(absPath) {
|
|
473
|
+
return this.nodes.get(absPath)?.deps ?? [];
|
|
474
|
+
}
|
|
475
|
+
/** Reverse edges: who depends on target? */
|
|
476
|
+
getDependents(targetAbs) {
|
|
477
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
478
|
+
if (this.native?.graphDependents) {
|
|
479
|
+
try {
|
|
480
|
+
for (const dep of this.native.graphDependents(targetAbs) ?? []) {
|
|
481
|
+
candidates.add(dep);
|
|
482
|
+
}
|
|
483
|
+
} catch {
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
for (const [id, node] of this.nodes) {
|
|
487
|
+
if (node.deps.includes(targetAbs)) candidates.add(id);
|
|
488
|
+
}
|
|
489
|
+
return Array.from(candidates);
|
|
490
|
+
}
|
|
491
|
+
/** Collect dependents recursively (breadth-first) */
|
|
492
|
+
collectDependentsDeep(targetAbs) {
|
|
493
|
+
const result = /* @__PURE__ */ new Set();
|
|
494
|
+
const queue = [targetAbs];
|
|
495
|
+
while (queue.length) {
|
|
496
|
+
const current = queue.shift();
|
|
497
|
+
for (const dep of this.getDependents(current)) {
|
|
498
|
+
if (!result.has(dep)) {
|
|
499
|
+
result.add(dep);
|
|
500
|
+
queue.push(dep);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return Array.from(result);
|
|
505
|
+
}
|
|
506
|
+
/** Includes changed files and all dependents */
|
|
507
|
+
collectAffected(changed) {
|
|
508
|
+
const result = /* @__PURE__ */ new Set();
|
|
509
|
+
let usedNative = false;
|
|
510
|
+
if (this.native?.graphCollectAffected) {
|
|
511
|
+
try {
|
|
512
|
+
const nativeList = this.native.graphCollectAffected(changed);
|
|
513
|
+
for (const item of nativeList ?? []) {
|
|
514
|
+
result.add(item);
|
|
515
|
+
}
|
|
516
|
+
usedNative = true;
|
|
517
|
+
} catch {
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
for (const target of changed) {
|
|
521
|
+
result.add(target);
|
|
522
|
+
}
|
|
523
|
+
if (!usedNative || result.size === 0) {
|
|
524
|
+
for (const target of changed) {
|
|
525
|
+
result.add(target);
|
|
526
|
+
for (const dep of this.collectDependentsDeep(target)) {
|
|
527
|
+
result.add(dep);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return Array.from(result);
|
|
532
|
+
}
|
|
533
|
+
/** Remove file from graph and clean up dependents lists */
|
|
534
|
+
removeFile(absPath) {
|
|
535
|
+
const existed = this.nodes.delete(absPath);
|
|
536
|
+
if (existed) {
|
|
537
|
+
for (const node of this.nodes.values()) {
|
|
538
|
+
if (node.deps.includes(absPath)) {
|
|
539
|
+
node.deps = node.deps.filter((dep) => dep !== absPath);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (this.native) {
|
|
543
|
+
try {
|
|
544
|
+
this.native.graphRemove(absPath);
|
|
545
|
+
this.scheduleNativeFlush();
|
|
546
|
+
} catch {
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
this.queueSave();
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
/** Persist immediately (e.g., on shutdown) */
|
|
553
|
+
flush() {
|
|
554
|
+
if (this.nativeFlushTimer) {
|
|
555
|
+
clearTimeout(this.nativeFlushTimer);
|
|
556
|
+
this.nativeFlushTimer = null;
|
|
557
|
+
}
|
|
558
|
+
if (this.native?.graphFlush) {
|
|
559
|
+
try {
|
|
560
|
+
this.native.graphFlush();
|
|
561
|
+
} catch {
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (this.dirty) this.save();
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
// src/core/resolver.ts
|
|
569
|
+
init_cjs_shims();
|
|
570
|
+
var import_fs4 = __toESM(require("fs"), 1);
|
|
571
|
+
var import_path4 = __toESM(require("path"), 1);
|
|
572
|
+
var import_module2 = require("module");
|
|
573
|
+
var import_url = require("url");
|
|
574
|
+
init_native();
|
|
575
|
+
var import_meta = {};
|
|
576
|
+
var SUPPORTED_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
|
|
577
|
+
var CONFIG_FILES = ["tsconfig.json", "jsconfig.json"];
|
|
578
|
+
var swc = null;
|
|
579
|
+
(() => {
|
|
580
|
+
try {
|
|
581
|
+
const require2 = (0, import_module2.createRequire)(importMetaUrl);
|
|
582
|
+
swc = require2("@swc/core");
|
|
583
|
+
} catch {
|
|
584
|
+
swc = null;
|
|
585
|
+
}
|
|
586
|
+
})();
|
|
587
|
+
function extractImports(source, filename = "inline.ts") {
|
|
588
|
+
if (native?.parseModuleIr) {
|
|
589
|
+
try {
|
|
590
|
+
const result = native.parseModuleIr(filename, source);
|
|
591
|
+
return result.dependencies.map((dep) => dep.specifier);
|
|
592
|
+
} catch {
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
const deps = /* @__PURE__ */ new Set();
|
|
596
|
+
const fallbackRegex = () => {
|
|
597
|
+
const re = /(?:import\s+(?:[^'"]+\s+from\s+)??['"]([^'"]+)['"])|(?:export\s+[^'"]+\s+from\s+['"]([^'"]+)['"])|(?:import\s*?\(\s*?['"]([^'"]+)['"]\s*?\))/g;
|
|
598
|
+
let m;
|
|
599
|
+
while (m = re.exec(source)) {
|
|
600
|
+
const spec = m[1] || m[2] || m[3];
|
|
601
|
+
if (spec) deps.add(spec);
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
try {
|
|
605
|
+
const parseSync = swc?.parseSync;
|
|
606
|
+
if (parseSync) {
|
|
607
|
+
const ast = parseSync(source, {
|
|
608
|
+
filename,
|
|
609
|
+
isModule: true,
|
|
610
|
+
target: "es2022",
|
|
611
|
+
syntax: "typescript",
|
|
612
|
+
tsx: true,
|
|
613
|
+
decorators: true,
|
|
614
|
+
dynamicImport: true
|
|
615
|
+
});
|
|
616
|
+
const visit = (node) => {
|
|
617
|
+
if (!node || typeof node !== "object") return;
|
|
618
|
+
const anyNode = node;
|
|
619
|
+
const type = anyNode.type;
|
|
620
|
+
if (type === "ImportDeclaration" && anyNode.source && typeof anyNode.source.value === "string") {
|
|
621
|
+
deps.add(anyNode.source.value);
|
|
622
|
+
} else if (type === "ExportAllDeclaration" && anyNode.source && typeof anyNode.source.value === "string") {
|
|
623
|
+
deps.add(anyNode.source.value);
|
|
624
|
+
} else if (type === "ExportNamedDeclaration" && anyNode.source && typeof anyNode.source.value === "string") {
|
|
625
|
+
deps.add(anyNode.source.value);
|
|
626
|
+
} else if (type === "CallExpression") {
|
|
627
|
+
const callee = anyNode.callee ?? {};
|
|
628
|
+
if (callee.type === "Import") {
|
|
629
|
+
const args = anyNode.arguments ?? [];
|
|
630
|
+
const first = args[0];
|
|
631
|
+
if (first && typeof first === "object") {
|
|
632
|
+
const expr = first.expression;
|
|
633
|
+
if (expr && expr.type === "StringLiteral" && typeof expr.value === "string") {
|
|
634
|
+
deps.add(expr.value);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
for (const value of Object.values(anyNode)) {
|
|
640
|
+
if (!value) continue;
|
|
641
|
+
if (Array.isArray(value)) {
|
|
642
|
+
for (const item of value) visit(item);
|
|
643
|
+
} else if (typeof value === "object") {
|
|
644
|
+
visit(value);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
visit(ast);
|
|
649
|
+
} else {
|
|
650
|
+
fallbackRegex();
|
|
651
|
+
}
|
|
652
|
+
} catch {
|
|
653
|
+
fallbackRegex();
|
|
654
|
+
}
|
|
655
|
+
if (!deps.size) {
|
|
656
|
+
fallbackRegex();
|
|
657
|
+
}
|
|
658
|
+
return Array.from(deps);
|
|
659
|
+
}
|
|
660
|
+
function tryFile(p) {
|
|
661
|
+
if (import_fs4.default.existsSync(p) && import_fs4.default.statSync(p).isFile()) return p;
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
function tryWithExt(p) {
|
|
665
|
+
if (tryFile(p)) return p;
|
|
666
|
+
for (const ext of SUPPORTED_EXTS) {
|
|
667
|
+
const cand = p.endsWith(ext) ? p : p + ext;
|
|
668
|
+
const found = tryFile(cand);
|
|
669
|
+
if (found) return found;
|
|
670
|
+
}
|
|
671
|
+
if (import_fs4.default.existsSync(p) && import_fs4.default.statSync(p).isDirectory()) {
|
|
672
|
+
const pkgPath = import_path4.default.join(p, "package.json");
|
|
673
|
+
if (import_fs4.default.existsSync(pkgPath)) {
|
|
674
|
+
try {
|
|
675
|
+
const pkg = JSON.parse(import_fs4.default.readFileSync(pkgPath, "utf8"));
|
|
676
|
+
if (pkg.main) {
|
|
677
|
+
const mainPath = import_path4.default.join(p, pkg.main);
|
|
678
|
+
const mainResolved = tryFile(mainPath) || tryWithExt(mainPath);
|
|
679
|
+
if (mainResolved) return mainResolved;
|
|
680
|
+
}
|
|
681
|
+
if (pkg.module) {
|
|
682
|
+
const modulePath = import_path4.default.join(p, pkg.module);
|
|
683
|
+
const moduleResolved = tryFile(modulePath) || tryWithExt(modulePath);
|
|
684
|
+
if (moduleResolved) return moduleResolved;
|
|
685
|
+
}
|
|
686
|
+
} catch {
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
for (const ext of SUPPORTED_EXTS) {
|
|
690
|
+
const idx = import_path4.default.join(p, "index" + ext);
|
|
691
|
+
const found = tryFile(idx);
|
|
692
|
+
if (found) return found;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
var cachedTsconfigAliases;
|
|
698
|
+
var customAliasEntries = [];
|
|
699
|
+
var resolvePathCache = /* @__PURE__ */ new Map();
|
|
700
|
+
function createAliasEntry(pattern, targets) {
|
|
701
|
+
const hasWildcard = pattern.includes("*");
|
|
702
|
+
if (hasWildcard) {
|
|
703
|
+
const escaped = pattern.replace(/[-/\\^$+?.()|[\]{}]/g, "\\$&");
|
|
704
|
+
const matcher = new RegExp(`^${escaped.replace(/\*/g, "(.*)")}$`);
|
|
705
|
+
return {
|
|
706
|
+
resolveCandidates(specifier) {
|
|
707
|
+
const match = matcher.exec(specifier);
|
|
708
|
+
if (!match) return [];
|
|
709
|
+
const wildcards = match.slice(1);
|
|
710
|
+
return targets.map((target) => {
|
|
711
|
+
if (!target.includes("*")) return target;
|
|
712
|
+
const segments = target.split("*");
|
|
713
|
+
let rebuilt = segments[0] ?? "";
|
|
714
|
+
for (let i = 1; i < segments.length; i++) {
|
|
715
|
+
const replacement = wildcards[i - 1] ?? wildcards[wildcards.length - 1] ?? "";
|
|
716
|
+
rebuilt += replacement + segments[i];
|
|
717
|
+
}
|
|
718
|
+
return rebuilt;
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
const normalizedPattern = pattern.endsWith("/") ? pattern.slice(0, -1) : pattern;
|
|
724
|
+
return {
|
|
725
|
+
resolveCandidates(specifier) {
|
|
726
|
+
if (specifier === normalizedPattern) {
|
|
727
|
+
return targets;
|
|
728
|
+
}
|
|
729
|
+
if (normalizedPattern && specifier.startsWith(normalizedPattern + "/")) {
|
|
730
|
+
const remainder = specifier.slice(normalizedPattern.length + 1);
|
|
731
|
+
return targets.map((target) => import_path4.default.join(target, remainder));
|
|
732
|
+
}
|
|
733
|
+
return [];
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
function buildAliasEntries(aliases, baseDir) {
|
|
738
|
+
const entries = [];
|
|
739
|
+
for (const [pattern, value] of Object.entries(aliases)) {
|
|
740
|
+
const replacements = Array.isArray(value) ? value : [value];
|
|
741
|
+
const targets = replacements.filter((rep) => typeof rep === "string" && rep.trim().length > 0).map(
|
|
742
|
+
(rep) => import_path4.default.isAbsolute(rep) ? rep : import_path4.default.resolve(baseDir, rep)
|
|
743
|
+
);
|
|
744
|
+
if (!targets.length) continue;
|
|
745
|
+
entries.push(createAliasEntry(pattern, targets));
|
|
746
|
+
}
|
|
747
|
+
return entries;
|
|
748
|
+
}
|
|
749
|
+
function loadTsconfigAliases() {
|
|
750
|
+
if (cachedTsconfigAliases !== void 0) {
|
|
751
|
+
return cachedTsconfigAliases ?? [];
|
|
752
|
+
}
|
|
753
|
+
const rootDir = process.cwd();
|
|
754
|
+
for (const configName of CONFIG_FILES) {
|
|
755
|
+
const candidate = import_path4.default.resolve(rootDir, configName);
|
|
756
|
+
if (!import_fs4.default.existsSync(candidate) || !import_fs4.default.statSync(candidate).isFile()) {
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
try {
|
|
760
|
+
const raw = import_fs4.default.readFileSync(candidate, "utf8");
|
|
761
|
+
const parsed = JSON.parse(raw);
|
|
762
|
+
const compilerOptions = parsed?.compilerOptions ?? {};
|
|
763
|
+
const baseUrl = compilerOptions.baseUrl ? import_path4.default.resolve(import_path4.default.dirname(candidate), compilerOptions.baseUrl) : import_path4.default.dirname(candidate);
|
|
764
|
+
const paths = compilerOptions.paths ?? {};
|
|
765
|
+
cachedTsconfigAliases = buildAliasEntries(paths, baseUrl);
|
|
766
|
+
return cachedTsconfigAliases;
|
|
767
|
+
} catch {
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
cachedTsconfigAliases = [];
|
|
771
|
+
return cachedTsconfigAliases;
|
|
772
|
+
}
|
|
773
|
+
function resolveFromEntries(entries, specifier) {
|
|
774
|
+
for (const entry of entries) {
|
|
775
|
+
const candidates = entry.resolveCandidates(specifier);
|
|
776
|
+
for (const candidate of candidates) {
|
|
777
|
+
const resolved = tryWithExt(candidate);
|
|
778
|
+
if (resolved) return resolved;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
function resolveWithAliases(specifier) {
|
|
784
|
+
const custom = resolveFromEntries(customAliasEntries, specifier);
|
|
785
|
+
if (custom) return custom;
|
|
786
|
+
const tsconfigEntries = loadTsconfigAliases();
|
|
787
|
+
return resolveFromEntries(tsconfigEntries, specifier);
|
|
788
|
+
}
|
|
789
|
+
function configureResolverAliases(aliases, baseDir) {
|
|
790
|
+
customAliasEntries = aliases ? buildAliasEntries(aliases, baseDir) : [];
|
|
791
|
+
}
|
|
792
|
+
function resolveImport(specifier, importerAbs) {
|
|
793
|
+
const cacheKey = `${importerAbs}\0${specifier}`;
|
|
794
|
+
if (resolvePathCache.has(cacheKey)) {
|
|
795
|
+
return resolvePathCache.get(cacheKey) ?? null;
|
|
796
|
+
}
|
|
797
|
+
if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
|
|
798
|
+
const aliasResolved = resolveWithAliases(specifier);
|
|
799
|
+
if (aliasResolved) {
|
|
800
|
+
resolvePathCache.set(cacheKey, aliasResolved);
|
|
801
|
+
return aliasResolved;
|
|
802
|
+
}
|
|
803
|
+
try {
|
|
804
|
+
const require2 = (0, import_module2.createRequire)(importerAbs);
|
|
805
|
+
const resolved2 = require2.resolve(specifier);
|
|
806
|
+
resolvePathCache.set(cacheKey, resolved2);
|
|
807
|
+
return resolved2;
|
|
808
|
+
} catch {
|
|
809
|
+
try {
|
|
810
|
+
const importerUrl = (0, import_url.pathToFileURL)(importerAbs).href;
|
|
811
|
+
const resolvedUrl = import_meta.resolve(specifier, importerUrl);
|
|
812
|
+
if (resolvedUrl.startsWith("file://")) {
|
|
813
|
+
const resolved2 = (0, import_url.fileURLToPath)(resolvedUrl);
|
|
814
|
+
resolvePathCache.set(cacheKey, resolved2);
|
|
815
|
+
return resolved2;
|
|
816
|
+
}
|
|
817
|
+
resolvePathCache.set(cacheKey, resolvedUrl);
|
|
818
|
+
return resolvedUrl;
|
|
819
|
+
} catch {
|
|
820
|
+
const nodeModulesPath = import_path4.default.join(import_path4.default.dirname(importerAbs), "node_modules", specifier);
|
|
821
|
+
const resolvedNodeModules = tryWithExt(nodeModulesPath);
|
|
822
|
+
if (resolvedNodeModules) {
|
|
823
|
+
resolvePathCache.set(cacheKey, resolvedNodeModules);
|
|
824
|
+
return resolvedNodeModules;
|
|
825
|
+
}
|
|
826
|
+
const srcPath = import_path4.default.join(process.cwd(), "src", specifier);
|
|
827
|
+
const resolvedSrc = tryWithExt(srcPath);
|
|
828
|
+
if (resolvedSrc) {
|
|
829
|
+
resolvePathCache.set(cacheKey, resolvedSrc);
|
|
830
|
+
return resolvedSrc;
|
|
831
|
+
}
|
|
832
|
+
const rootPath = import_path4.default.join(process.cwd(), specifier);
|
|
833
|
+
const resolvedRoot = tryWithExt(rootPath);
|
|
834
|
+
if (resolvedRoot) {
|
|
835
|
+
resolvePathCache.set(cacheKey, resolvedRoot);
|
|
836
|
+
return resolvedRoot;
|
|
837
|
+
}
|
|
838
|
+
resolvePathCache.set(cacheKey, null);
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
const baseDir = import_path4.default.dirname(importerAbs);
|
|
844
|
+
const target = import_path4.default.resolve(baseDir, specifier);
|
|
845
|
+
const resolved = tryWithExt(target);
|
|
846
|
+
resolvePathCache.set(cacheKey, resolved);
|
|
847
|
+
return resolved;
|
|
848
|
+
}
|
|
849
|
+
function resolveImports(specs, importerAbs) {
|
|
850
|
+
const abs = specs.map((s) => resolveImport(s, importerAbs)).filter((x) => !!x);
|
|
851
|
+
return Array.from(new Set(abs));
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// src/core/resolver/module-resolver.ts
|
|
855
|
+
init_cjs_shims();
|
|
856
|
+
var import_path5 = __toESM(require("path"), 1);
|
|
857
|
+
var import_fs5 = __toESM(require("fs"), 1);
|
|
858
|
+
var DEFAULT_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".json", ".mjs"];
|
|
859
|
+
var DEFAULT_CONDITIONS = ["import", "default"];
|
|
860
|
+
var DEFAULT_MAIN_FIELDS = ["module", "main"];
|
|
861
|
+
var ModuleResolver = class {
|
|
862
|
+
options;
|
|
863
|
+
rootDir;
|
|
864
|
+
constructor(rootDir, options = {}) {
|
|
865
|
+
this.rootDir = rootDir;
|
|
866
|
+
this.options = {
|
|
867
|
+
baseUrl: options.baseUrl || ".",
|
|
868
|
+
paths: options.paths || {},
|
|
869
|
+
extensions: options.extensions || DEFAULT_EXTENSIONS,
|
|
870
|
+
alias: options.alias || {},
|
|
871
|
+
conditions: options.conditions || DEFAULT_CONDITIONS,
|
|
872
|
+
mainFields: options.mainFields || DEFAULT_MAIN_FIELDS
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
resolve(importSpecifier, importer) {
|
|
876
|
+
if (import_path5.default.isAbsolute(importSpecifier)) {
|
|
877
|
+
return this.tryResolveFile(importSpecifier);
|
|
878
|
+
}
|
|
879
|
+
const aliasResolved = this.resolveAlias(importSpecifier);
|
|
880
|
+
if (aliasResolved) {
|
|
881
|
+
return this.tryResolveFile(aliasResolved);
|
|
882
|
+
}
|
|
883
|
+
if (importSpecifier.startsWith(".")) {
|
|
884
|
+
const resolvedPath = import_path5.default.resolve(import_path5.default.dirname(importer), importSpecifier);
|
|
885
|
+
return this.tryResolveFile(resolvedPath);
|
|
886
|
+
}
|
|
887
|
+
return this.resolveBareModule(importSpecifier, importer);
|
|
888
|
+
}
|
|
889
|
+
resolveAlias(specifier) {
|
|
890
|
+
for (const [alias, target] of Object.entries(this.options.alias)) {
|
|
891
|
+
if (specifier === alias || specifier.startsWith(`${alias}/`)) {
|
|
892
|
+
const relativePath = specifier.slice(alias.length);
|
|
893
|
+
const targets = Array.isArray(target) ? target : [target];
|
|
894
|
+
for (const t of targets) {
|
|
895
|
+
const resolved = import_path5.default.join(this.rootDir, t, relativePath);
|
|
896
|
+
if (import_fs5.default.existsSync(resolved)) {
|
|
897
|
+
return resolved;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
for (const [pattern, targets] of Object.entries(this.options.paths)) {
|
|
903
|
+
const wildcardIndex = pattern.indexOf("*");
|
|
904
|
+
if (wildcardIndex === -1) {
|
|
905
|
+
if (specifier === pattern) {
|
|
906
|
+
return import_path5.default.join(this.rootDir, this.options.baseUrl, targets[0]);
|
|
907
|
+
}
|
|
908
|
+
} else {
|
|
909
|
+
const prefix = pattern.slice(0, wildcardIndex);
|
|
910
|
+
const suffix = pattern.slice(wildcardIndex + 1);
|
|
911
|
+
if (specifier.startsWith(prefix) && specifier.endsWith(suffix)) {
|
|
912
|
+
const matchedPortion = specifier.slice(prefix.length, -suffix.length || void 0);
|
|
913
|
+
for (const target of targets) {
|
|
914
|
+
const resolved = import_path5.default.join(
|
|
915
|
+
this.rootDir,
|
|
916
|
+
this.options.baseUrl,
|
|
917
|
+
target.replace("*", matchedPortion)
|
|
918
|
+
);
|
|
919
|
+
if (import_fs5.default.existsSync(resolved)) {
|
|
920
|
+
return resolved;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
928
|
+
resolveBareModule(specifier, importer) {
|
|
929
|
+
const parts = specifier.split("/");
|
|
930
|
+
const packageName = parts[0].startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
931
|
+
const subpath = parts.slice(packageName.startsWith("@") ? 2 : 1).join("/");
|
|
932
|
+
let dir = import_path5.default.dirname(importer);
|
|
933
|
+
while (dir !== "/") {
|
|
934
|
+
const nodeModulesPath = import_path5.default.join(dir, "node_modules", packageName);
|
|
935
|
+
if (import_fs5.default.existsSync(nodeModulesPath)) {
|
|
936
|
+
if (subpath) {
|
|
937
|
+
return this.tryResolveFile(import_path5.default.join(nodeModulesPath, subpath));
|
|
938
|
+
}
|
|
939
|
+
return this.resolvePackageMain(nodeModulesPath);
|
|
940
|
+
}
|
|
941
|
+
dir = import_path5.default.dirname(dir);
|
|
942
|
+
}
|
|
943
|
+
return null;
|
|
944
|
+
}
|
|
945
|
+
resolvePackageMain(packageDir) {
|
|
946
|
+
const pkgJsonPath = import_path5.default.join(packageDir, "package.json");
|
|
947
|
+
if (import_fs5.default.existsSync(pkgJsonPath)) {
|
|
948
|
+
try {
|
|
949
|
+
const pkg = JSON.parse(import_fs5.default.readFileSync(pkgJsonPath, "utf8"));
|
|
950
|
+
if (pkg.exports) {
|
|
951
|
+
const resolved = this.resolveExports(pkg.exports, packageDir);
|
|
952
|
+
if (resolved) return resolved;
|
|
953
|
+
}
|
|
954
|
+
for (const field of this.options.mainFields) {
|
|
955
|
+
if (pkg[field]) {
|
|
956
|
+
const resolved = this.tryResolveFile(import_path5.default.join(packageDir, pkg[field]));
|
|
957
|
+
if (resolved) return resolved;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
} catch {
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
return this.tryResolveFile(import_path5.default.join(packageDir, "index"));
|
|
964
|
+
}
|
|
965
|
+
resolveExports(exports2, packageDir) {
|
|
966
|
+
if (typeof exports2 === "string") {
|
|
967
|
+
return this.tryResolveFile(import_path5.default.join(packageDir, exports2));
|
|
968
|
+
}
|
|
969
|
+
if (Array.isArray(exports2)) {
|
|
970
|
+
for (const exp of exports2) {
|
|
971
|
+
const resolved = this.resolveExports(exp, packageDir);
|
|
972
|
+
if (resolved) return resolved;
|
|
973
|
+
}
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
if (typeof exports2 === "object") {
|
|
977
|
+
for (const condition of this.options.conditions) {
|
|
978
|
+
if (condition in exports2) {
|
|
979
|
+
const resolved = this.resolveExports(exports2[condition], packageDir);
|
|
980
|
+
if (resolved) return resolved;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if ("default" in exports2) {
|
|
984
|
+
return this.resolveExports(exports2.default, packageDir);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
tryResolveFile(filepath) {
|
|
990
|
+
if (import_fs5.default.existsSync(filepath) && import_fs5.default.statSync(filepath).isFile()) {
|
|
991
|
+
return filepath;
|
|
992
|
+
}
|
|
993
|
+
for (const ext of this.options.extensions) {
|
|
994
|
+
const withExt = `${filepath}${ext}`;
|
|
995
|
+
if (import_fs5.default.existsSync(withExt) && import_fs5.default.statSync(withExt).isFile()) {
|
|
996
|
+
return withExt;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (import_fs5.default.existsSync(filepath) && import_fs5.default.statSync(filepath).isDirectory()) {
|
|
1000
|
+
for (const ext of this.options.extensions) {
|
|
1001
|
+
const indexFile = import_path5.default.join(filepath, `index${ext}`);
|
|
1002
|
+
if (import_fs5.default.existsSync(indexFile) && import_fs5.default.statSync(indexFile).isFile()) {
|
|
1003
|
+
return indexFile;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
// src/core/watcher.ts
|
|
1012
|
+
init_cjs_shims();
|
|
1013
|
+
var import_fs6 = __toESM(require("fs"), 1);
|
|
1014
|
+
var import_path6 = __toESM(require("path"), 1);
|
|
1015
|
+
var import_events = require("events");
|
|
1016
|
+
var IonifyWatcher = class extends import_events.EventEmitter {
|
|
1017
|
+
constructor(rootDir) {
|
|
1018
|
+
super();
|
|
1019
|
+
this.rootDir = rootDir;
|
|
1020
|
+
}
|
|
1021
|
+
watchers = /* @__PURE__ */ new Map();
|
|
1022
|
+
debounce = /* @__PURE__ */ new Map();
|
|
1023
|
+
polled = /* @__PURE__ */ new Set();
|
|
1024
|
+
watchFile(filePath) {
|
|
1025
|
+
const abs = import_path6.default.resolve(filePath);
|
|
1026
|
+
if (this.watchers.has(abs)) return;
|
|
1027
|
+
if (/(node_modules|\.git|\.ionify|dist)/.test(abs)) return;
|
|
1028
|
+
if (!import_fs6.default.existsSync(abs)) return;
|
|
1029
|
+
try {
|
|
1030
|
+
const dir = import_path6.default.dirname(abs);
|
|
1031
|
+
const watcher = import_fs6.default.watch(dir, (event, filename) => {
|
|
1032
|
+
if (!filename) return;
|
|
1033
|
+
const full = import_path6.default.join(dir, filename.toString());
|
|
1034
|
+
if (full !== abs) return;
|
|
1035
|
+
const now = Date.now();
|
|
1036
|
+
const last = this.debounce.get(abs) || 0;
|
|
1037
|
+
if (now - last < 100) return;
|
|
1038
|
+
this.debounce.set(abs, now);
|
|
1039
|
+
const exists = import_fs6.default.existsSync(abs);
|
|
1040
|
+
this.emit("change", abs, exists ? "changed" : "deleted");
|
|
1041
|
+
});
|
|
1042
|
+
this.watchers.set(abs, watcher);
|
|
1043
|
+
this.polled.add(abs);
|
|
1044
|
+
import_fs6.default.watchFile(abs, { interval: 5e3 }, (curr, prev) => {
|
|
1045
|
+
if (curr.mtimeMs !== prev.mtimeMs) {
|
|
1046
|
+
this.emit("change", abs, "changed");
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
} catch {
|
|
1050
|
+
this.polled.add(abs);
|
|
1051
|
+
import_fs6.default.watchFile(abs, { interval: 8e3 }, (curr, prev) => {
|
|
1052
|
+
if (curr.mtimeMs !== prev.mtimeMs) {
|
|
1053
|
+
this.emit("change", abs, "changed");
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
unwatchFile(filePath) {
|
|
1059
|
+
const abs = import_path6.default.resolve(filePath);
|
|
1060
|
+
const watcher = this.watchers.get(abs);
|
|
1061
|
+
if (watcher) watcher.close();
|
|
1062
|
+
import_fs6.default.unwatchFile(abs);
|
|
1063
|
+
this.watchers.delete(abs);
|
|
1064
|
+
this.polled.delete(abs);
|
|
1065
|
+
}
|
|
1066
|
+
closeAll() {
|
|
1067
|
+
for (const [abs, w] of this.watchers) {
|
|
1068
|
+
w.close();
|
|
1069
|
+
import_fs6.default.unwatchFile(abs);
|
|
1070
|
+
}
|
|
1071
|
+
this.watchers.clear();
|
|
1072
|
+
for (const abs of this.polled) {
|
|
1073
|
+
import_fs6.default.unwatchFile(abs);
|
|
1074
|
+
}
|
|
1075
|
+
this.polled.clear();
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
// src/core/transform.ts
|
|
1080
|
+
init_cjs_shims();
|
|
1081
|
+
var TransformCache = class {
|
|
1082
|
+
store = /* @__PURE__ */ new Map();
|
|
1083
|
+
hits = 0;
|
|
1084
|
+
misses = 0;
|
|
1085
|
+
maxEntries;
|
|
1086
|
+
constructor(maxEntries) {
|
|
1087
|
+
const envMax = process.env.IONIFY_DEV_TRANSFORM_CACHE_MAX;
|
|
1088
|
+
const parsedEnv = envMax ? parseInt(envMax, 10) : NaN;
|
|
1089
|
+
this.maxEntries = Number.isFinite(parsedEnv) ? parsedEnv : maxEntries ?? 5e3;
|
|
1090
|
+
}
|
|
1091
|
+
setMaxEntries(maxEntries) {
|
|
1092
|
+
this.maxEntries = maxEntries;
|
|
1093
|
+
this.prune();
|
|
1094
|
+
}
|
|
1095
|
+
get(key) {
|
|
1096
|
+
const entry = this.store.get(key);
|
|
1097
|
+
if (entry) {
|
|
1098
|
+
this.hits += 1;
|
|
1099
|
+
entry.timestamp = Date.now();
|
|
1100
|
+
return entry;
|
|
1101
|
+
}
|
|
1102
|
+
this.misses += 1;
|
|
1103
|
+
return null;
|
|
1104
|
+
}
|
|
1105
|
+
set(key, entry) {
|
|
1106
|
+
this.store.set(key, { ...entry, timestamp: Date.now() });
|
|
1107
|
+
this.prune();
|
|
1108
|
+
}
|
|
1109
|
+
prune(maxEntries) {
|
|
1110
|
+
const limit = maxEntries ?? this.maxEntries;
|
|
1111
|
+
if (this.store.size <= limit) return;
|
|
1112
|
+
const sorted = Array.from(this.store.entries()).sort(
|
|
1113
|
+
(a, b) => a[1].timestamp - b[1].timestamp
|
|
1114
|
+
);
|
|
1115
|
+
const removeCount = this.store.size - limit;
|
|
1116
|
+
for (let i = 0; i < removeCount; i++) {
|
|
1117
|
+
this.store.delete(sorted[i][0]);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
metrics() {
|
|
1121
|
+
return {
|
|
1122
|
+
hits: this.hits,
|
|
1123
|
+
misses: this.misses,
|
|
1124
|
+
size: this.store.size,
|
|
1125
|
+
max: this.maxEntries
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
var transformCache = new TransformCache();
|
|
1130
|
+
var TransformEngine = class {
|
|
1131
|
+
loaders = [];
|
|
1132
|
+
cacheEnabled;
|
|
1133
|
+
cacheVersion = "v1";
|
|
1134
|
+
casRoot;
|
|
1135
|
+
versionHash;
|
|
1136
|
+
constructor(options) {
|
|
1137
|
+
this.cacheEnabled = options?.cache ?? true;
|
|
1138
|
+
this.casRoot = options?.casRoot;
|
|
1139
|
+
this.versionHash = options?.versionHash;
|
|
1140
|
+
}
|
|
1141
|
+
useLoader(loader) {
|
|
1142
|
+
this.loaders.push(loader);
|
|
1143
|
+
this.loaders.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
1144
|
+
}
|
|
1145
|
+
async run(ctx) {
|
|
1146
|
+
const { getCacheKey: getCacheKey2 } = await Promise.resolve().then(() => (init_cache(), cache_exports));
|
|
1147
|
+
const path16 = await import("path");
|
|
1148
|
+
const fs13 = await import("fs");
|
|
1149
|
+
const { getCasArtifactPath: getCasArtifactPath2 } = await Promise.resolve().then(() => (init_cas(), cas_exports));
|
|
1150
|
+
const moduleHash = ctx.moduleHash || getCacheKey2(ctx.code);
|
|
1151
|
+
const loaderSig = this.loaders.map((l) => l.name || "loader").join("|");
|
|
1152
|
+
const loaderHash = getCacheKey2(loaderSig);
|
|
1153
|
+
const memKey = `${moduleHash}-${loaderHash}`;
|
|
1154
|
+
const casDir = this.casRoot && this.versionHash ? getCasArtifactPath2(this.casRoot, this.versionHash, moduleHash) : null;
|
|
1155
|
+
const casFile = casDir ? path16.join(casDir, "transformed.js") : null;
|
|
1156
|
+
const casMapFile = casDir ? path16.join(casDir, "transformed.js.map") : null;
|
|
1157
|
+
const debug = process.env.IONIFY_DEV_TRANSFORM_CACHE_DEBUG === "1";
|
|
1158
|
+
if (this.cacheEnabled) {
|
|
1159
|
+
const memHit = transformCache.get(memKey);
|
|
1160
|
+
if (memHit) {
|
|
1161
|
+
if (debug) {
|
|
1162
|
+
console.log(`[Dev Cache] HIT mem key=${memKey} size=${transformCache.metrics().size}`);
|
|
1163
|
+
}
|
|
1164
|
+
return { code: memHit.transformed, map: memHit.map };
|
|
1165
|
+
}
|
|
1166
|
+
if (casFile && fs13.existsSync(casFile)) {
|
|
1167
|
+
try {
|
|
1168
|
+
const code = fs13.readFileSync(casFile, "utf8");
|
|
1169
|
+
const map = casMapFile && fs13.existsSync(casMapFile) ? fs13.readFileSync(casMapFile, "utf8") : void 0;
|
|
1170
|
+
const parsed = { code, map };
|
|
1171
|
+
transformCache.set(memKey, {
|
|
1172
|
+
hash: moduleHash,
|
|
1173
|
+
loaderHash,
|
|
1174
|
+
transformed: parsed.code,
|
|
1175
|
+
map: parsed.map,
|
|
1176
|
+
timestamp: Date.now()
|
|
1177
|
+
});
|
|
1178
|
+
if (debug) {
|
|
1179
|
+
console.log(`[Dev Cache] HIT cas key=${memKey} size=${transformCache.metrics().size}`);
|
|
1180
|
+
}
|
|
1181
|
+
return parsed;
|
|
1182
|
+
} catch {
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
let working = { ...ctx };
|
|
1187
|
+
let result = { code: ctx.code };
|
|
1188
|
+
for (const loader of this.loaders) {
|
|
1189
|
+
if (!loader.test(working)) continue;
|
|
1190
|
+
const output = await loader.transform({ ...working, code: result.code });
|
|
1191
|
+
if (output && output.code !== void 0) {
|
|
1192
|
+
result = { ...result, ...output };
|
|
1193
|
+
working = { ...working, code: result.code };
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
if (this.cacheEnabled) {
|
|
1197
|
+
transformCache.set(memKey, {
|
|
1198
|
+
hash: moduleHash,
|
|
1199
|
+
loaderHash,
|
|
1200
|
+
transformed: result.code,
|
|
1201
|
+
map: result.map,
|
|
1202
|
+
timestamp: Date.now()
|
|
1203
|
+
});
|
|
1204
|
+
if (casFile) {
|
|
1205
|
+
try {
|
|
1206
|
+
fs13.mkdirSync(path16.dirname(casFile), { recursive: true });
|
|
1207
|
+
fs13.writeFileSync(casFile, result.code, "utf8");
|
|
1208
|
+
if (result.map && casMapFile) {
|
|
1209
|
+
fs13.writeFileSync(casMapFile, typeof result.map === "string" ? result.map : JSON.stringify(result.map), "utf8");
|
|
1210
|
+
}
|
|
1211
|
+
} catch {
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
if (debug) {
|
|
1215
|
+
const m = transformCache.metrics();
|
|
1216
|
+
console.log(`[Dev Cache] MISS stored key=${memKey} size=${m.size} hits=${m.hits} misses=${m.misses}`);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return result;
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
// src/core/hmr.ts
|
|
1224
|
+
init_cjs_shims();
|
|
1225
|
+
var HMRServer = class {
|
|
1226
|
+
clients = /* @__PURE__ */ new Set();
|
|
1227
|
+
pending = /* @__PURE__ */ new Map();
|
|
1228
|
+
nextId = 1;
|
|
1229
|
+
closed = false;
|
|
1230
|
+
/** Handle an incoming SSE subscription request */
|
|
1231
|
+
handleSSE(req, res) {
|
|
1232
|
+
if (this.closed) {
|
|
1233
|
+
res.writeHead(503);
|
|
1234
|
+
res.end();
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
res.writeHead(200, {
|
|
1238
|
+
"Content-Type": "text/event-stream",
|
|
1239
|
+
"Cache-Control": "no-cache, no-transform",
|
|
1240
|
+
Connection: "keep-alive",
|
|
1241
|
+
"X-Accel-Buffering": "no"
|
|
1242
|
+
});
|
|
1243
|
+
res.write(`event: ready
|
|
1244
|
+
data: "ok"
|
|
1245
|
+
|
|
1246
|
+
`);
|
|
1247
|
+
this.clients.add(res);
|
|
1248
|
+
req.on("close", () => {
|
|
1249
|
+
this.clients.delete(res);
|
|
1250
|
+
try {
|
|
1251
|
+
res.end();
|
|
1252
|
+
} catch {
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
send(event, payload) {
|
|
1257
|
+
const data = (event ? `event: ${event}
|
|
1258
|
+
` : "") + `data: ${JSON.stringify(payload)}
|
|
1259
|
+
|
|
1260
|
+
`;
|
|
1261
|
+
for (const client of this.clients) {
|
|
1262
|
+
try {
|
|
1263
|
+
client.write(data);
|
|
1264
|
+
} catch {
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
/** Broadcast a JSON event to all SSE clients */
|
|
1269
|
+
broadcast(payload) {
|
|
1270
|
+
this.send(null, payload);
|
|
1271
|
+
}
|
|
1272
|
+
broadcastEvent(event, payload) {
|
|
1273
|
+
this.send(event, payload);
|
|
1274
|
+
}
|
|
1275
|
+
queueUpdate(modules) {
|
|
1276
|
+
if (!modules.length) return null;
|
|
1277
|
+
const timestamp = Date.now();
|
|
1278
|
+
const id = `${timestamp}-${this.nextId++}`;
|
|
1279
|
+
const summary = {
|
|
1280
|
+
type: "update",
|
|
1281
|
+
id,
|
|
1282
|
+
timestamp,
|
|
1283
|
+
modules: modules.map(({ url: url2, hash, reason }) => ({ url: url2, hash, reason }))
|
|
1284
|
+
};
|
|
1285
|
+
this.pending.set(id, { summary, modules, createdAt: timestamp });
|
|
1286
|
+
this.broadcastEvent("update", summary);
|
|
1287
|
+
return summary;
|
|
1288
|
+
}
|
|
1289
|
+
consumeUpdate(id) {
|
|
1290
|
+
const pending = this.pending.get(id);
|
|
1291
|
+
if (pending) {
|
|
1292
|
+
this.pending.delete(id);
|
|
1293
|
+
}
|
|
1294
|
+
return pending;
|
|
1295
|
+
}
|
|
1296
|
+
broadcastError(payload) {
|
|
1297
|
+
this.broadcastEvent("error", { type: "error", ...payload });
|
|
1298
|
+
}
|
|
1299
|
+
close() {
|
|
1300
|
+
this.closed = true;
|
|
1301
|
+
for (const client of this.clients) {
|
|
1302
|
+
try {
|
|
1303
|
+
client.end();
|
|
1304
|
+
} catch {
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
this.clients.clear();
|
|
1308
|
+
this.pending.clear();
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
function injectHMRClient(html) {
|
|
1312
|
+
const tag = `<script type="module" src="/__ionify_hmr_client.js"></script>`;
|
|
1313
|
+
return html.includes("</body>") ? html.replace("</body>", `${tag}
|
|
1314
|
+
</body>`) : html + "\n" + tag;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
// src/core/loaders/css.ts
|
|
1318
|
+
init_cjs_shims();
|
|
1319
|
+
var import_path8 = __toESM(require("path"), 1);
|
|
1320
|
+
var import_crypto2 = __toESM(require("crypto"), 1);
|
|
1321
|
+
var import_postcss = __toESM(require("postcss"), 1);
|
|
1322
|
+
var import_postcss_load_config = __toESM(require("postcss-load-config"), 1);
|
|
1323
|
+
var import_postcss_modules = __toESM(require("postcss-modules"), 1);
|
|
1324
|
+
init_cache();
|
|
1325
|
+
var cachedConfig = null;
|
|
1326
|
+
var configFailed = false;
|
|
1327
|
+
async function getPostcssConfig(rootDir) {
|
|
1328
|
+
if (cachedConfig) return cachedConfig;
|
|
1329
|
+
if (configFailed) return { plugins: [], options: {} };
|
|
1330
|
+
try {
|
|
1331
|
+
const result = await (0, import_postcss_load_config.default)({}, rootDir);
|
|
1332
|
+
cachedConfig = {
|
|
1333
|
+
plugins: Array.isArray(result.plugins) ? result.plugins : [],
|
|
1334
|
+
options: result.options ?? {}
|
|
1335
|
+
};
|
|
1336
|
+
} catch {
|
|
1337
|
+
configFailed = true;
|
|
1338
|
+
cachedConfig = { plugins: [], options: {} };
|
|
1339
|
+
}
|
|
1340
|
+
return cachedConfig;
|
|
1341
|
+
}
|
|
1342
|
+
async function compileCss({
|
|
1343
|
+
code,
|
|
1344
|
+
filePath,
|
|
1345
|
+
rootDir,
|
|
1346
|
+
modules = false
|
|
1347
|
+
}) {
|
|
1348
|
+
const loaderHash = getCacheKey(JSON.stringify({ modules, filePath: filePath.replace(/\\+/g, "/") }));
|
|
1349
|
+
const contentHash = getCacheKey(code);
|
|
1350
|
+
const cacheKey = `${contentHash}-${loaderHash}`;
|
|
1351
|
+
const cached = transformCache.get(cacheKey);
|
|
1352
|
+
if (cached) {
|
|
1353
|
+
try {
|
|
1354
|
+
const parsed = JSON.parse(cached.transformed);
|
|
1355
|
+
return parsed;
|
|
1356
|
+
} catch {
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
const { plugins, options } = await getPostcssConfig(rootDir);
|
|
1360
|
+
const pipeline = [...plugins];
|
|
1361
|
+
let tokens;
|
|
1362
|
+
if (modules) {
|
|
1363
|
+
const scopedName = (name, filename) => {
|
|
1364
|
+
const relative = import_path8.default.relative(rootDir, filename || filePath).replace(/\\+/g, "/");
|
|
1365
|
+
const seed = import_crypto2.default.createHash("sha1").update(relative).digest("hex").slice(0, 6);
|
|
1366
|
+
return `${name}___${seed}`;
|
|
1367
|
+
};
|
|
1368
|
+
pipeline.push(
|
|
1369
|
+
(0, import_postcss_modules.default)({
|
|
1370
|
+
generateScopedName: scopedName,
|
|
1371
|
+
getJSON(_filename, json) {
|
|
1372
|
+
tokens = json;
|
|
1373
|
+
}
|
|
1374
|
+
})
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
const runner = (0, import_postcss.default)(pipeline);
|
|
1378
|
+
const result = await runner.process(code, {
|
|
1379
|
+
...options,
|
|
1380
|
+
from: filePath,
|
|
1381
|
+
map: false
|
|
1382
|
+
});
|
|
1383
|
+
const compiled = {
|
|
1384
|
+
css: result.css,
|
|
1385
|
+
tokens
|
|
1386
|
+
};
|
|
1387
|
+
transformCache.set(cacheKey, {
|
|
1388
|
+
hash: contentHash,
|
|
1389
|
+
loaderHash,
|
|
1390
|
+
transformed: JSON.stringify(compiled),
|
|
1391
|
+
timestamp: Date.now()
|
|
1392
|
+
});
|
|
1393
|
+
return compiled;
|
|
1394
|
+
}
|
|
1395
|
+
function renderCssModule({
|
|
1396
|
+
css,
|
|
1397
|
+
filePath,
|
|
1398
|
+
tokens
|
|
1399
|
+
}) {
|
|
1400
|
+
const cssJson = JSON.stringify(css);
|
|
1401
|
+
const styleId = `ionify-css-${getCacheKey(filePath).slice(0, 8)}`;
|
|
1402
|
+
const tokensJson = tokens ? JSON.stringify(tokens) : "null";
|
|
1403
|
+
return `
|
|
1404
|
+
const cssText = ${cssJson};
|
|
1405
|
+
const styleId = ${JSON.stringify(styleId)};
|
|
1406
|
+
let style = document.querySelector(\`style[data-ionify-id="\${styleId}"]\`);
|
|
1407
|
+
if (!style) {
|
|
1408
|
+
style = document.createElement("style");
|
|
1409
|
+
style.setAttribute("data-ionify-id", styleId);
|
|
1410
|
+
document.head.appendChild(style);
|
|
1411
|
+
}
|
|
1412
|
+
style.textContent = cssText;
|
|
1413
|
+
${tokens ? `const tokens = ${tokensJson};` : ""}
|
|
1414
|
+
export const css = cssText;
|
|
1415
|
+
${tokens ? `export const classes = tokens;
|
|
1416
|
+
export default tokens;` : `export default cssText;`}
|
|
1417
|
+
if (import.meta.hot) {
|
|
1418
|
+
import.meta.hot.accept();
|
|
1419
|
+
import.meta.hot.dispose(() => {
|
|
1420
|
+
const existing = document.querySelector(\`style[data-ionify-id="\${styleId}"]\`);
|
|
1421
|
+
if (existing) existing.remove();
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
`.trim();
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
// src/core/loaders/asset.ts
|
|
1428
|
+
init_cjs_shims();
|
|
1429
|
+
|
|
1430
|
+
// src/core/utils/public-path.ts
|
|
1431
|
+
init_cjs_shims();
|
|
1432
|
+
var import_path9 = __toESM(require("path"), 1);
|
|
1433
|
+
var MODULE_PREFIX = "/__ionify__/modules/";
|
|
1434
|
+
function publicPathForFile(rootDir, absPath) {
|
|
1435
|
+
const normalizedRoot = import_path9.default.resolve(rootDir);
|
|
1436
|
+
const normalizedFile = import_path9.default.resolve(absPath);
|
|
1437
|
+
if (normalizedFile.startsWith(normalizedRoot + import_path9.default.sep) || normalizedFile === normalizedRoot) {
|
|
1438
|
+
const relative = import_path9.default.relative(normalizedRoot, normalizedFile).split(import_path9.default.sep).join("/");
|
|
1439
|
+
return "/" + (relative.length ? relative : "");
|
|
1440
|
+
}
|
|
1441
|
+
const encoded = Buffer.from(normalizedFile).toString("base64url");
|
|
1442
|
+
return MODULE_PREFIX + encoded;
|
|
1443
|
+
}
|
|
1444
|
+
function decodePublicPath(rootDir, urlPath) {
|
|
1445
|
+
if (urlPath.startsWith(MODULE_PREFIX)) {
|
|
1446
|
+
const encoded = urlPath.slice(MODULE_PREFIX.length);
|
|
1447
|
+
try {
|
|
1448
|
+
const decoded = Buffer.from(encoded, "base64url").toString("utf8");
|
|
1449
|
+
return import_path9.default.resolve(decoded);
|
|
1450
|
+
} catch {
|
|
1451
|
+
return null;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
const normalizedRoot = import_path9.default.resolve(rootDir);
|
|
1455
|
+
const joined = import_path9.default.resolve(normalizedRoot, "." + urlPath);
|
|
1456
|
+
if (!joined.startsWith(normalizedRoot + import_path9.default.sep) && joined !== normalizedRoot) {
|
|
1457
|
+
return null;
|
|
1458
|
+
}
|
|
1459
|
+
return joined;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// src/core/loaders/asset.ts
|
|
1463
|
+
function assetAsModule(urlPath) {
|
|
1464
|
+
const safe = urlPath.replace(/"/g, "%22");
|
|
1465
|
+
return `export default "${safe}";`;
|
|
1466
|
+
}
|
|
1467
|
+
function isAssetExt(ext) {
|
|
1468
|
+
return [
|
|
1469
|
+
".png",
|
|
1470
|
+
".jpg",
|
|
1471
|
+
".jpeg",
|
|
1472
|
+
".gif",
|
|
1473
|
+
".svg",
|
|
1474
|
+
".ico",
|
|
1475
|
+
".webp",
|
|
1476
|
+
".avif",
|
|
1477
|
+
".woff",
|
|
1478
|
+
".woff2",
|
|
1479
|
+
".ttf",
|
|
1480
|
+
".otf",
|
|
1481
|
+
".eot"
|
|
1482
|
+
].includes(ext);
|
|
1483
|
+
}
|
|
1484
|
+
function contentTypeForAsset(ext) {
|
|
1485
|
+
switch (ext) {
|
|
1486
|
+
case ".png":
|
|
1487
|
+
return "image/png";
|
|
1488
|
+
case ".jpg":
|
|
1489
|
+
case ".jpeg":
|
|
1490
|
+
return "image/jpeg";
|
|
1491
|
+
case ".gif":
|
|
1492
|
+
return "image/gif";
|
|
1493
|
+
case ".svg":
|
|
1494
|
+
return "image/svg+xml";
|
|
1495
|
+
case ".ico":
|
|
1496
|
+
return "image/x-icon";
|
|
1497
|
+
case ".webp":
|
|
1498
|
+
return "image/webp";
|
|
1499
|
+
case ".avif":
|
|
1500
|
+
return "image/avif";
|
|
1501
|
+
case ".woff":
|
|
1502
|
+
return "font/woff";
|
|
1503
|
+
case ".woff2":
|
|
1504
|
+
return "font/woff2";
|
|
1505
|
+
case ".ttf":
|
|
1506
|
+
return "font/ttf";
|
|
1507
|
+
case ".otf":
|
|
1508
|
+
return "font/otf";
|
|
1509
|
+
case ".eot":
|
|
1510
|
+
return "application/vnd.ms-fontobject";
|
|
1511
|
+
default:
|
|
1512
|
+
return "application/octet-stream";
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
function normalizeUrlFromFs(rootDir, fsPath) {
|
|
1516
|
+
return publicPathForFile(rootDir, fsPath);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// src/core/loaders/registry.ts
|
|
1520
|
+
init_cjs_shims();
|
|
1521
|
+
|
|
1522
|
+
// src/core/loaders/js.ts
|
|
1523
|
+
init_cjs_shims();
|
|
1524
|
+
var import_core = require("@swc/core");
|
|
1525
|
+
var import_es_module_lexer = require("es-module-lexer");
|
|
1526
|
+
init_native();
|
|
1527
|
+
var JS_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx"]);
|
|
1528
|
+
function needsReactRefresh(ext) {
|
|
1529
|
+
if (ext === ".jsx" || ext === ".tsx") return true;
|
|
1530
|
+
if (!ext.endsWith("x")) return false;
|
|
1531
|
+
return false;
|
|
1532
|
+
}
|
|
1533
|
+
function shouldTransform(ext, filePath) {
|
|
1534
|
+
if (!JS_EXTENSIONS.has(ext)) return false;
|
|
1535
|
+
if (filePath.endsWith(".d.ts")) return false;
|
|
1536
|
+
return true;
|
|
1537
|
+
}
|
|
1538
|
+
async function swcTranspile(code, filePath, ext, reactRefresh) {
|
|
1539
|
+
const isTypeScript = ext === ".ts" || ext === ".tsx";
|
|
1540
|
+
const isTsx = ext === ".tsx";
|
|
1541
|
+
const isJsx = ext === ".jsx";
|
|
1542
|
+
const swcParser = isTypeScript ? {
|
|
1543
|
+
syntax: "typescript",
|
|
1544
|
+
tsx: isTsx,
|
|
1545
|
+
decorators: true,
|
|
1546
|
+
dynamicImport: true
|
|
1547
|
+
} : {
|
|
1548
|
+
syntax: "ecmascript",
|
|
1549
|
+
jsx: isJsx,
|
|
1550
|
+
decorators: true,
|
|
1551
|
+
dynamicImport: true
|
|
1552
|
+
};
|
|
1553
|
+
const result = await (0, import_core.transform)(code, {
|
|
1554
|
+
filename: filePath,
|
|
1555
|
+
jsc: {
|
|
1556
|
+
parser: swcParser,
|
|
1557
|
+
target: "es2022",
|
|
1558
|
+
transform: reactRefresh ? {
|
|
1559
|
+
react: {
|
|
1560
|
+
development: true,
|
|
1561
|
+
refresh: true,
|
|
1562
|
+
runtime: "automatic"
|
|
1563
|
+
}
|
|
1564
|
+
} : void 0
|
|
1565
|
+
},
|
|
1566
|
+
sourceMaps: false,
|
|
1567
|
+
module: {
|
|
1568
|
+
type: "es6"
|
|
1569
|
+
}
|
|
1570
|
+
});
|
|
1571
|
+
return result.code ?? code;
|
|
1572
|
+
}
|
|
1573
|
+
function currentMode() {
|
|
1574
|
+
const mode = (process.env.IONIFY_PARSER || "hybrid").toLowerCase();
|
|
1575
|
+
if (mode === "swc") return "swc";
|
|
1576
|
+
if (mode === "oxc") return "oxc";
|
|
1577
|
+
return "hybrid";
|
|
1578
|
+
}
|
|
1579
|
+
var jsLoader = {
|
|
1580
|
+
name: "js",
|
|
1581
|
+
order: 0,
|
|
1582
|
+
test: ({ ext, path: filePath }) => shouldTransform(ext, filePath),
|
|
1583
|
+
transform: async ({ path: filePath, code, ext }) => {
|
|
1584
|
+
const isNodeModules = filePath.includes("node_modules");
|
|
1585
|
+
let output = code;
|
|
1586
|
+
if (isNodeModules) {
|
|
1587
|
+
const bundled = tryBundleNodeModule(filePath, code);
|
|
1588
|
+
if (bundled) {
|
|
1589
|
+
output = bundled;
|
|
1590
|
+
} else {
|
|
1591
|
+
output = code;
|
|
1592
|
+
}
|
|
1593
|
+
} else {
|
|
1594
|
+
const reactRefresh = needsReactRefresh(ext);
|
|
1595
|
+
const mode = currentMode();
|
|
1596
|
+
const nativeResult = tryNativeTransform(mode, code, {
|
|
1597
|
+
filename: filePath,
|
|
1598
|
+
jsx: ext === ".jsx" || ext === ".tsx",
|
|
1599
|
+
typescript: ext === ".ts" || ext === ".tsx",
|
|
1600
|
+
react_refresh: reactRefresh
|
|
1601
|
+
});
|
|
1602
|
+
if (nativeResult) {
|
|
1603
|
+
output = nativeResult.code ?? code;
|
|
1604
|
+
} else {
|
|
1605
|
+
output = await swcTranspile(code, filePath, ext, reactRefresh);
|
|
1606
|
+
}
|
|
1607
|
+
if (reactRefresh) {
|
|
1608
|
+
const prologue = `import { setupReactRefresh } from "/__ionify_react_refresh.js";
|
|
1609
|
+
const __ionifyRefresh__ = setupReactRefresh(import.meta.hot, import.meta.url);
|
|
1610
|
+
`;
|
|
1611
|
+
const epilogue = `
|
|
1612
|
+
__ionifyRefresh__?.finalize?.();
|
|
1613
|
+
|
|
1614
|
+
if (import.meta.hot) {
|
|
1615
|
+
import.meta.hot.accept((newModule) => {
|
|
1616
|
+
__ionifyRefresh__?.refresh?.(newModule);
|
|
1617
|
+
});
|
|
1618
|
+
import.meta.hot.dispose(() => {
|
|
1619
|
+
__ionifyRefresh__?.dispose?.();
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
`;
|
|
1623
|
+
output = prologue + output + epilogue;
|
|
1624
|
+
} else {
|
|
1625
|
+
output += `
|
|
1626
|
+
if (import.meta.hot) {
|
|
1627
|
+
import.meta.hot.accept();
|
|
1628
|
+
}
|
|
1629
|
+
`;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
await import_es_module_lexer.init;
|
|
1633
|
+
const [imports] = (0, import_es_module_lexer.parse)(output);
|
|
1634
|
+
if (imports.length) {
|
|
1635
|
+
const rootDir = process.cwd();
|
|
1636
|
+
let rewritten = "";
|
|
1637
|
+
let lastIndex = 0;
|
|
1638
|
+
let mutated = false;
|
|
1639
|
+
for (const record of imports) {
|
|
1640
|
+
if (!record.n) continue;
|
|
1641
|
+
const spec = record.n;
|
|
1642
|
+
if (spec.startsWith("http://") || spec.startsWith("https://") || spec.startsWith(MODULE_PREFIX)) {
|
|
1643
|
+
continue;
|
|
1644
|
+
}
|
|
1645
|
+
let pathPart = spec;
|
|
1646
|
+
let suffix = "";
|
|
1647
|
+
const queryIndex = spec.indexOf("?");
|
|
1648
|
+
const hashIndex = spec.indexOf("#");
|
|
1649
|
+
const splitIndex = queryIndex === -1 ? hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
|
|
1650
|
+
if (splitIndex !== -1) {
|
|
1651
|
+
pathPart = spec.slice(0, splitIndex);
|
|
1652
|
+
suffix = spec.slice(splitIndex);
|
|
1653
|
+
}
|
|
1654
|
+
const resolved = resolveImport(pathPart, filePath);
|
|
1655
|
+
if (!resolved) continue;
|
|
1656
|
+
const resolvedExt = resolved.slice(resolved.lastIndexOf("."));
|
|
1657
|
+
let augmentedSuffix = suffix;
|
|
1658
|
+
if (resolvedExt === ".css" && !suffix) {
|
|
1659
|
+
augmentedSuffix = "?inline";
|
|
1660
|
+
}
|
|
1661
|
+
const assetExts = [
|
|
1662
|
+
".png",
|
|
1663
|
+
".jpg",
|
|
1664
|
+
".jpeg",
|
|
1665
|
+
".gif",
|
|
1666
|
+
".svg",
|
|
1667
|
+
".ico",
|
|
1668
|
+
".webp",
|
|
1669
|
+
".avif",
|
|
1670
|
+
".woff",
|
|
1671
|
+
".woff2",
|
|
1672
|
+
".ttf",
|
|
1673
|
+
".otf",
|
|
1674
|
+
".eot"
|
|
1675
|
+
];
|
|
1676
|
+
if (assetExts.includes(resolvedExt) && !suffix) {
|
|
1677
|
+
augmentedSuffix = "?import";
|
|
1678
|
+
}
|
|
1679
|
+
const replacementPath = publicPathForFile(rootDir, resolved);
|
|
1680
|
+
const replacement = replacementPath + augmentedSuffix;
|
|
1681
|
+
if (replacement === spec) continue;
|
|
1682
|
+
if (!mutated) {
|
|
1683
|
+
mutated = true;
|
|
1684
|
+
}
|
|
1685
|
+
if (record.t === 2) {
|
|
1686
|
+
rewritten += output.slice(lastIndex, record.s + 1);
|
|
1687
|
+
rewritten += replacement;
|
|
1688
|
+
rewritten += output[record.e - 1];
|
|
1689
|
+
lastIndex = record.e;
|
|
1690
|
+
} else {
|
|
1691
|
+
rewritten += output.slice(lastIndex, record.s);
|
|
1692
|
+
rewritten += replacement;
|
|
1693
|
+
lastIndex = record.e;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
if (mutated) {
|
|
1697
|
+
rewritten += output.slice(lastIndex);
|
|
1698
|
+
output = rewritten;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
return { code: output };
|
|
1702
|
+
}
|
|
1703
|
+
};
|
|
1704
|
+
|
|
1705
|
+
// src/core/loaders/registry.ts
|
|
1706
|
+
var registry = [];
|
|
1707
|
+
function registerLoader(registration) {
|
|
1708
|
+
registry.push(registration);
|
|
1709
|
+
}
|
|
1710
|
+
async function applyRegisteredLoaders(engine, config) {
|
|
1711
|
+
for (const registration of registry) {
|
|
1712
|
+
await registration(engine, config ?? null);
|
|
1713
|
+
}
|
|
1714
|
+
if (config?.plugins) {
|
|
1715
|
+
for (const plugin of config.plugins) {
|
|
1716
|
+
if (plugin.loaders) {
|
|
1717
|
+
for (const loader of plugin.loaders) {
|
|
1718
|
+
engine.useLoader(loader);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
if (plugin.setup) {
|
|
1722
|
+
const context = {
|
|
1723
|
+
registerLoader: (loader) => {
|
|
1724
|
+
engine.useLoader(loader);
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
await plugin.setup(context);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
if (config?.loaders) {
|
|
1732
|
+
for (const loader of config.loaders) {
|
|
1733
|
+
engine.useLoader(loader);
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
registerLoader((engine) => {
|
|
1738
|
+
engine.useLoader(jsLoader);
|
|
1739
|
+
});
|
|
1740
|
+
|
|
1741
|
+
// src/cli/utils/config.ts
|
|
1742
|
+
init_cjs_shims();
|
|
1743
|
+
var import_fs7 = __toESM(require("fs"), 1);
|
|
1744
|
+
var import_path10 = __toESM(require("path"), 1);
|
|
1745
|
+
var import_url2 = require("url");
|
|
1746
|
+
var import_esbuild = require("esbuild");
|
|
1747
|
+
var CONFIG_BASENAMES = [
|
|
1748
|
+
"ionify.config.ts",
|
|
1749
|
+
"ionify.config.mts",
|
|
1750
|
+
"ionify.config.js",
|
|
1751
|
+
"ionify.config.mjs",
|
|
1752
|
+
"ionify.config.cjs"
|
|
1753
|
+
];
|
|
1754
|
+
var cachedConfig2 = null;
|
|
1755
|
+
var configLoaded = false;
|
|
1756
|
+
async function bundleConfig(entry) {
|
|
1757
|
+
const absDir = import_path10.default.dirname(entry);
|
|
1758
|
+
const inlineIonifyPlugin = {
|
|
1759
|
+
name: "inline-ionify",
|
|
1760
|
+
setup(build2) {
|
|
1761
|
+
build2.onResolve({ filter: /^ionify$/ }, () => ({
|
|
1762
|
+
path: "ionify-virtual",
|
|
1763
|
+
namespace: "ionify-ns"
|
|
1764
|
+
}));
|
|
1765
|
+
build2.onLoad({ filter: /.*/, namespace: "ionify-ns" }, () => ({
|
|
1766
|
+
contents: `
|
|
1767
|
+
export function defineConfig(config) {
|
|
1768
|
+
return typeof config === 'function' ? config : () => config;
|
|
1769
|
+
}
|
|
1770
|
+
`,
|
|
1771
|
+
loader: "js"
|
|
1772
|
+
}));
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
const result = await (0, import_esbuild.build)({
|
|
1776
|
+
entryPoints: [entry],
|
|
1777
|
+
bundle: true,
|
|
1778
|
+
platform: "node",
|
|
1779
|
+
format: "esm",
|
|
1780
|
+
sourcemap: "inline",
|
|
1781
|
+
write: false,
|
|
1782
|
+
target: "node18",
|
|
1783
|
+
logLevel: "silent",
|
|
1784
|
+
absWorkingDir: absDir,
|
|
1785
|
+
plugins: [inlineIonifyPlugin]
|
|
1786
|
+
});
|
|
1787
|
+
const output = result.outputFiles?.[0];
|
|
1788
|
+
if (!output) throw new Error("Failed to bundle ionify config");
|
|
1789
|
+
const dirnameLiteral = JSON.stringify(absDir);
|
|
1790
|
+
const filenameLiteral = JSON.stringify(entry);
|
|
1791
|
+
const importMetaLiteral = JSON.stringify((0, import_url2.pathToFileURL)(entry).href);
|
|
1792
|
+
let contents = output.text;
|
|
1793
|
+
if (contents.includes("import.meta.url")) {
|
|
1794
|
+
contents = contents.replace(/import\.meta\.url/g, "__IONIFY_IMPORT_META_URL");
|
|
1795
|
+
contents = `const __IONIFY_IMPORT_META_URL = ${importMetaLiteral};
|
|
1796
|
+
${contents}`;
|
|
1797
|
+
}
|
|
1798
|
+
const preamble = `const __dirname = ${dirnameLiteral};
|
|
1799
|
+
const __filename = ${filenameLiteral};
|
|
1800
|
+
`;
|
|
1801
|
+
return preamble + contents;
|
|
1802
|
+
}
|
|
1803
|
+
function findConfigFile(cwd) {
|
|
1804
|
+
for (const name of CONFIG_BASENAMES) {
|
|
1805
|
+
const candidate = import_path10.default.resolve(cwd, name);
|
|
1806
|
+
if (import_fs7.default.existsSync(candidate) && import_fs7.default.statSync(candidate).isFile()) {
|
|
1807
|
+
return candidate;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
return null;
|
|
1811
|
+
}
|
|
1812
|
+
async function loadIonifyConfig(cwd = process.cwd()) {
|
|
1813
|
+
if (configLoaded) return cachedConfig2;
|
|
1814
|
+
configLoaded = true;
|
|
1815
|
+
const configPath = findConfigFile(cwd);
|
|
1816
|
+
if (!configPath) {
|
|
1817
|
+
cachedConfig2 = null;
|
|
1818
|
+
configureResolverAliases(void 0, cwd);
|
|
1819
|
+
return cachedConfig2;
|
|
1820
|
+
}
|
|
1821
|
+
try {
|
|
1822
|
+
const bundled = await bundleConfig(configPath);
|
|
1823
|
+
const dataUrl = `data:text/javascript;base64,${Buffer.from(bundled).toString("base64")}`;
|
|
1824
|
+
const imported = await import(dataUrl);
|
|
1825
|
+
let resolved = imported?.default ?? imported?.config ?? imported ?? null;
|
|
1826
|
+
if (resolved && typeof resolved === "function") {
|
|
1827
|
+
resolved = resolved({ mode: process.env.NODE_ENV || "development" });
|
|
1828
|
+
}
|
|
1829
|
+
if (resolved && typeof resolved?.then === "function") {
|
|
1830
|
+
resolved = await resolved;
|
|
1831
|
+
}
|
|
1832
|
+
if (resolved && typeof resolved === "object") {
|
|
1833
|
+
cachedConfig2 = resolved;
|
|
1834
|
+
const baseDir = import_path10.default.dirname(configPath);
|
|
1835
|
+
const aliases = resolved?.resolve?.alias;
|
|
1836
|
+
if (aliases && typeof aliases === "object") {
|
|
1837
|
+
configureResolverAliases(aliases, baseDir);
|
|
1838
|
+
} else {
|
|
1839
|
+
configureResolverAliases(void 0, baseDir);
|
|
1840
|
+
}
|
|
1841
|
+
logInfo(`Loaded ionify config from ${import_path10.default.relative(cwd, configPath)}`);
|
|
1842
|
+
} else {
|
|
1843
|
+
throw new Error("Config did not export an object");
|
|
1844
|
+
}
|
|
1845
|
+
} catch (err) {
|
|
1846
|
+
logError("Failed to load ionify.config", err);
|
|
1847
|
+
cachedConfig2 = null;
|
|
1848
|
+
configureResolverAliases(void 0, cwd);
|
|
1849
|
+
}
|
|
1850
|
+
return cachedConfig2;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
// src/cli/utils/minifier.ts
|
|
1854
|
+
init_cjs_shims();
|
|
1855
|
+
function normalize(value) {
|
|
1856
|
+
if (value === "oxc" || value === "swc" || value === "auto") return value;
|
|
1857
|
+
if (typeof value === "string") {
|
|
1858
|
+
const v = value.toLowerCase();
|
|
1859
|
+
if (v === "oxc" || v === "swc" || v === "auto") return v;
|
|
1860
|
+
}
|
|
1861
|
+
return null;
|
|
1862
|
+
}
|
|
1863
|
+
function resolveMinifier(config, opts = {}) {
|
|
1864
|
+
const fromCli = normalize(opts.cliFlag);
|
|
1865
|
+
if (fromCli) return fromCli;
|
|
1866
|
+
const fromEnv = normalize(opts.envVar);
|
|
1867
|
+
if (fromEnv) return fromEnv;
|
|
1868
|
+
const fromConfig = normalize(config?.minifier);
|
|
1869
|
+
if (fromConfig) return fromConfig;
|
|
1870
|
+
return "auto";
|
|
1871
|
+
}
|
|
1872
|
+
function applyMinifierEnv(choice) {
|
|
1873
|
+
process.env.IONIFY_MINIFIER = choice;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
// src/cli/utils/env.ts
|
|
1877
|
+
init_cjs_shims();
|
|
1878
|
+
var import_fs8 = __toESM(require("fs"), 1);
|
|
1879
|
+
var import_path11 = __toESM(require("path"), 1);
|
|
1880
|
+
function parseValue(raw) {
|
|
1881
|
+
let value = raw.trim();
|
|
1882
|
+
if (!value) return "";
|
|
1883
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
1884
|
+
value = value.slice(1, -1);
|
|
1885
|
+
}
|
|
1886
|
+
value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r");
|
|
1887
|
+
return value;
|
|
1888
|
+
}
|
|
1889
|
+
function parseEnvFile(source) {
|
|
1890
|
+
const env = {};
|
|
1891
|
+
const lines = source.split(/\r?\n/);
|
|
1892
|
+
for (const line of lines) {
|
|
1893
|
+
const trimmed = line.trim();
|
|
1894
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1895
|
+
const match = trimmed.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_\.]*)\s*=\s*(.*)$/);
|
|
1896
|
+
if (!match) continue;
|
|
1897
|
+
const [, key, rest] = match;
|
|
1898
|
+
env[key] = parseValue(rest);
|
|
1899
|
+
}
|
|
1900
|
+
return env;
|
|
1901
|
+
}
|
|
1902
|
+
function loadEnv(mode = "development", rootDir = process.cwd()) {
|
|
1903
|
+
const candidates = [
|
|
1904
|
+
".env",
|
|
1905
|
+
".env.local",
|
|
1906
|
+
`.env.${mode}`,
|
|
1907
|
+
`.env.${mode}.local`
|
|
1908
|
+
];
|
|
1909
|
+
const merged = {};
|
|
1910
|
+
for (const name of candidates) {
|
|
1911
|
+
const filePath = import_path11.default.resolve(rootDir, name);
|
|
1912
|
+
if (!import_fs8.default.existsSync(filePath) || !import_fs8.default.statSync(filePath).isFile()) {
|
|
1913
|
+
continue;
|
|
1914
|
+
}
|
|
1915
|
+
const contents = import_fs8.default.readFileSync(filePath, "utf8");
|
|
1916
|
+
const parsed = parseEnvFile(contents);
|
|
1917
|
+
Object.assign(merged, parsed);
|
|
1918
|
+
}
|
|
1919
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
1920
|
+
process.env[key] = value;
|
|
1921
|
+
}
|
|
1922
|
+
return {
|
|
1923
|
+
...merged
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
// src/cli/utils/treeshake.ts
|
|
1928
|
+
init_cjs_shims();
|
|
1929
|
+
var DEFAULT_RESOLUTION = {
|
|
1930
|
+
mode: "safe",
|
|
1931
|
+
include: [],
|
|
1932
|
+
exclude: []
|
|
1933
|
+
};
|
|
1934
|
+
function parseMode(value) {
|
|
1935
|
+
if (!value) return null;
|
|
1936
|
+
switch (value.toLowerCase()) {
|
|
1937
|
+
case "off":
|
|
1938
|
+
case "false":
|
|
1939
|
+
return "off";
|
|
1940
|
+
case "aggressive":
|
|
1941
|
+
return "aggressive";
|
|
1942
|
+
case "safe":
|
|
1943
|
+
case "true":
|
|
1944
|
+
return "safe";
|
|
1945
|
+
default:
|
|
1946
|
+
return null;
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
function normalizeList(value) {
|
|
1950
|
+
if (!value) return [];
|
|
1951
|
+
if (Array.isArray(value)) {
|
|
1952
|
+
return value.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
1953
|
+
}
|
|
1954
|
+
return [];
|
|
1955
|
+
}
|
|
1956
|
+
function parseEnvList(raw) {
|
|
1957
|
+
if (!raw || !raw.trim()) return null;
|
|
1958
|
+
try {
|
|
1959
|
+
const parsed = JSON.parse(raw);
|
|
1960
|
+
return normalizeList(parsed);
|
|
1961
|
+
} catch {
|
|
1962
|
+
return null;
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
function extractConfigObject(value) {
|
|
1966
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1967
|
+
return value;
|
|
1968
|
+
}
|
|
1969
|
+
return null;
|
|
1970
|
+
}
|
|
1971
|
+
function resolveTreeshake(input, options = {}) {
|
|
1972
|
+
let resolved = { ...DEFAULT_RESOLUTION };
|
|
1973
|
+
const objectValue = extractConfigObject(input);
|
|
1974
|
+
if (objectValue) {
|
|
1975
|
+
resolved.include = normalizeList(objectValue.include);
|
|
1976
|
+
resolved.exclude = normalizeList(objectValue.exclude);
|
|
1977
|
+
if (objectValue.mode) {
|
|
1978
|
+
const objectMode = parseMode(objectValue.mode);
|
|
1979
|
+
if (objectMode) {
|
|
1980
|
+
resolved.mode = objectMode;
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
} else if (typeof input === "boolean") {
|
|
1984
|
+
resolved.mode = input ? "safe" : "off";
|
|
1985
|
+
} else if (typeof input === "string") {
|
|
1986
|
+
resolved.mode = parseMode(input) ?? DEFAULT_RESOLUTION.mode;
|
|
1987
|
+
}
|
|
1988
|
+
const envMode = parseMode(options.envMode);
|
|
1989
|
+
if (envMode) {
|
|
1990
|
+
resolved.mode = envMode;
|
|
1991
|
+
}
|
|
1992
|
+
const includeOverride = parseEnvList(options.includeEnv);
|
|
1993
|
+
if (includeOverride) {
|
|
1994
|
+
resolved.include = includeOverride;
|
|
1995
|
+
}
|
|
1996
|
+
const excludeOverride = parseEnvList(options.excludeEnv);
|
|
1997
|
+
if (excludeOverride) {
|
|
1998
|
+
resolved.exclude = excludeOverride;
|
|
1999
|
+
}
|
|
2000
|
+
return resolved;
|
|
2001
|
+
}
|
|
2002
|
+
function applyTreeshakeEnv(resolved) {
|
|
2003
|
+
process.env.IONIFY_TREESHAKE = resolved.mode;
|
|
2004
|
+
process.env.IONIFY_TREESHAKE_INCLUDE = JSON.stringify(resolved.include);
|
|
2005
|
+
process.env.IONIFY_TREESHAKE_EXCLUDE = JSON.stringify(resolved.exclude);
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
// src/cli/utils/scope-hoist.ts
|
|
2009
|
+
init_cjs_shims();
|
|
2010
|
+
var DEFAULT_SCOPE_HOIST = {
|
|
2011
|
+
enable: true,
|
|
2012
|
+
inlineFunctions: true,
|
|
2013
|
+
constantFolding: true,
|
|
2014
|
+
combineVariables: true
|
|
2015
|
+
};
|
|
2016
|
+
function parseBool(value) {
|
|
2017
|
+
if (value === true || value === false) return value;
|
|
2018
|
+
if (typeof value === "string") {
|
|
2019
|
+
const normalized = value.toLowerCase();
|
|
2020
|
+
if (["true", "1", "yes", "on", "enable"].includes(normalized)) return true;
|
|
2021
|
+
if (["false", "0", "no", "off", "disable"].includes(normalized)) return false;
|
|
2022
|
+
}
|
|
2023
|
+
return null;
|
|
2024
|
+
}
|
|
2025
|
+
function parseEnvFlag(value) {
|
|
2026
|
+
if (!value) return null;
|
|
2027
|
+
return parseBool(value);
|
|
2028
|
+
}
|
|
2029
|
+
function resolveScopeHoist(configValue, options = {}) {
|
|
2030
|
+
let resolved = { ...DEFAULT_SCOPE_HOIST };
|
|
2031
|
+
const scopeConfig = configValue;
|
|
2032
|
+
if (typeof scopeConfig === "boolean") {
|
|
2033
|
+
resolved.enable = scopeConfig;
|
|
2034
|
+
} else if (scopeConfig && typeof scopeConfig === "object") {
|
|
2035
|
+
resolved.enable = true;
|
|
2036
|
+
if (scopeConfig.inlineFunctions !== void 0) {
|
|
2037
|
+
resolved.inlineFunctions = !!scopeConfig.inlineFunctions;
|
|
2038
|
+
}
|
|
2039
|
+
if (scopeConfig.constantFolding !== void 0) {
|
|
2040
|
+
resolved.constantFolding = !!scopeConfig.constantFolding;
|
|
2041
|
+
}
|
|
2042
|
+
if (scopeConfig.combineVariables !== void 0) {
|
|
2043
|
+
resolved.combineVariables = !!scopeConfig.combineVariables;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
const envMode = parseEnvFlag(options.envMode);
|
|
2047
|
+
if (envMode !== null) {
|
|
2048
|
+
resolved.enable = envMode;
|
|
2049
|
+
}
|
|
2050
|
+
const inlineEnv = parseEnvFlag(options.inlineEnv);
|
|
2051
|
+
if (inlineEnv !== null) {
|
|
2052
|
+
resolved.inlineFunctions = inlineEnv;
|
|
2053
|
+
} else if (!resolved.enable) {
|
|
2054
|
+
resolved.inlineFunctions = false;
|
|
2055
|
+
}
|
|
2056
|
+
const constantEnv = parseEnvFlag(options.constantEnv);
|
|
2057
|
+
if (constantEnv !== null) {
|
|
2058
|
+
resolved.constantFolding = constantEnv;
|
|
2059
|
+
} else if (!resolved.enable) {
|
|
2060
|
+
resolved.constantFolding = false;
|
|
2061
|
+
}
|
|
2062
|
+
const combineEnv = parseEnvFlag(options.combineEnv);
|
|
2063
|
+
if (combineEnv !== null) {
|
|
2064
|
+
resolved.combineVariables = combineEnv;
|
|
2065
|
+
} else if (!resolved.enable) {
|
|
2066
|
+
resolved.combineVariables = false;
|
|
2067
|
+
}
|
|
2068
|
+
return resolved;
|
|
2069
|
+
}
|
|
2070
|
+
function applyScopeHoistEnv(result) {
|
|
2071
|
+
process.env.IONIFY_SCOPE_HOIST = result.enable ? "true" : "false";
|
|
2072
|
+
process.env.IONIFY_SCOPE_HOIST_INLINE = result.inlineFunctions ? "true" : "false";
|
|
2073
|
+
process.env.IONIFY_SCOPE_HOIST_CONST = result.constantFolding ? "true" : "false";
|
|
2074
|
+
process.env.IONIFY_SCOPE_HOIST_COMBINE = result.combineVariables ? "true" : "false";
|
|
41
2075
|
}
|
|
42
2076
|
|
|
43
|
-
// src/cli/
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
2077
|
+
// src/cli/utils/parser.ts
|
|
2078
|
+
init_cjs_shims();
|
|
2079
|
+
function normalize2(mode) {
|
|
2080
|
+
if (typeof mode !== "string") return null;
|
|
2081
|
+
const lower = mode.toLowerCase();
|
|
2082
|
+
if (lower === "swc") return "swc";
|
|
2083
|
+
if (lower === "hybrid") return "hybrid";
|
|
2084
|
+
if (lower === "oxc") return "oxc";
|
|
2085
|
+
return null;
|
|
2086
|
+
}
|
|
2087
|
+
function resolveParser(config, opts) {
|
|
2088
|
+
const envRaw = opts?.envMode ?? process.env.IONIFY_PARSER;
|
|
2089
|
+
const env = normalize2(envRaw);
|
|
2090
|
+
if (env) return env;
|
|
2091
|
+
const fromConfig = normalize2(config?.parser);
|
|
2092
|
+
return fromConfig ?? "hybrid";
|
|
2093
|
+
}
|
|
2094
|
+
function applyParserEnv(mode) {
|
|
2095
|
+
process.env.IONIFY_PARSER = mode;
|
|
2096
|
+
}
|
|
54
2097
|
|
|
55
|
-
// src/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
2098
|
+
// src/cli/commands/dev.ts
|
|
2099
|
+
init_cas();
|
|
2100
|
+
init_native();
|
|
2101
|
+
var import_crypto3 = __toESM(require("crypto"), 1);
|
|
2102
|
+
var __filename2 = (0, import_url4.fileURLToPath)(importMetaUrl);
|
|
2103
|
+
var __dirname = import_path12.default.dirname(__filename2);
|
|
2104
|
+
var CLIENT_DIR = import_path12.default.resolve(__dirname, "../client");
|
|
2105
|
+
var CLIENT_FALLBACK_DIR = import_path12.default.resolve(process.cwd(), "src/client");
|
|
2106
|
+
function readClientAssetFile(fileName) {
|
|
2107
|
+
const primary = import_path12.default.join(CLIENT_DIR, fileName);
|
|
2108
|
+
if (import_fs9.default.existsSync(primary)) {
|
|
2109
|
+
return { filePath: primary, code: import_fs9.default.readFileSync(primary, "utf8") };
|
|
2110
|
+
}
|
|
2111
|
+
const fallback = import_path12.default.join(CLIENT_FALLBACK_DIR, fileName);
|
|
2112
|
+
if (import_fs9.default.existsSync(fallback)) {
|
|
2113
|
+
return { filePath: fallback, code: import_fs9.default.readFileSync(fallback, "utf8") };
|
|
2114
|
+
}
|
|
2115
|
+
throw new Error(`Missing Ionify client asset: ${fileName}`);
|
|
2116
|
+
}
|
|
2117
|
+
function readClientAsset(fileName) {
|
|
2118
|
+
return readClientAssetFile(fileName).code;
|
|
2119
|
+
}
|
|
2120
|
+
function guessContentType(filePath) {
|
|
2121
|
+
const ext = import_path12.default.extname(filePath);
|
|
2122
|
+
if (ext === ".html") return "text/html; charset=utf-8";
|
|
2123
|
+
if (ext === ".css") return "text/css; charset=utf-8";
|
|
2124
|
+
if (ext === ".json") return "application/json; charset=utf-8";
|
|
2125
|
+
if ([".mjs", ".js", ".ts", ".tsx", ".jsx", ".cjs", ".mts", ".cts"].includes(ext))
|
|
2126
|
+
return "application/javascript; charset=utf-8";
|
|
2127
|
+
if ([".wasm"].includes(ext))
|
|
2128
|
+
return "application/wasm";
|
|
2129
|
+
if ([".map"].includes(ext))
|
|
2130
|
+
return "application/json; charset=utf-8";
|
|
2131
|
+
return "text/plain; charset=utf-8";
|
|
2132
|
+
}
|
|
2133
|
+
async function startDevServer({
|
|
2134
|
+
port = 5173,
|
|
2135
|
+
enableSignalHandlers = true
|
|
2136
|
+
} = {}) {
|
|
2137
|
+
const rootDir = process.cwd();
|
|
2138
|
+
const watcher = new IonifyWatcher(rootDir);
|
|
2139
|
+
const cacheDebug = process.env.IONIFY_DEV_TRANSFORM_CACHE_DEBUG === "1";
|
|
2140
|
+
const userConfig = await loadIonifyConfig();
|
|
2141
|
+
const minifier = resolveMinifier(userConfig, { envVar: process.env.IONIFY_MINIFIER });
|
|
2142
|
+
applyMinifierEnv(minifier);
|
|
2143
|
+
const parserMode = resolveParser(userConfig, { envMode: process.env.IONIFY_PARSER });
|
|
2144
|
+
applyParserEnv(parserMode);
|
|
2145
|
+
const treeshake = resolveTreeshake(userConfig?.treeshake, {
|
|
2146
|
+
envMode: process.env.IONIFY_TREESHAKE,
|
|
2147
|
+
includeEnv: process.env.IONIFY_TREESHAKE_INCLUDE,
|
|
2148
|
+
excludeEnv: process.env.IONIFY_TREESHAKE_EXCLUDE
|
|
2149
|
+
});
|
|
2150
|
+
applyTreeshakeEnv(treeshake);
|
|
2151
|
+
const scopeHoist = resolveScopeHoist(userConfig?.scopeHoist, {
|
|
2152
|
+
envMode: process.env.IONIFY_SCOPE_HOIST,
|
|
2153
|
+
inlineEnv: process.env.IONIFY_SCOPE_HOIST_INLINE,
|
|
2154
|
+
constantEnv: process.env.IONIFY_SCOPE_HOIST_CONST,
|
|
2155
|
+
combineEnv: process.env.IONIFY_SCOPE_HOIST_COMBINE
|
|
2156
|
+
});
|
|
2157
|
+
applyScopeHoistEnv(scopeHoist);
|
|
2158
|
+
const resolvedEntry = userConfig?.entry ? userConfig.entry.startsWith("/") ? import_path12.default.join(rootDir, userConfig.entry) : import_path12.default.resolve(rootDir, userConfig.entry) : void 0;
|
|
2159
|
+
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;
|
|
2160
|
+
const rawVersionInputs = {
|
|
2161
|
+
parserMode,
|
|
2162
|
+
minifier,
|
|
2163
|
+
treeshake,
|
|
2164
|
+
scopeHoist,
|
|
2165
|
+
plugins: pluginNames,
|
|
2166
|
+
entry: resolvedEntry ? [resolvedEntry] : null,
|
|
2167
|
+
cssOptions: userConfig?.css,
|
|
2168
|
+
assetOptions: userConfig?.assets ?? userConfig?.asset
|
|
2169
|
+
};
|
|
2170
|
+
const configHash = computeGraphVersion(rawVersionInputs);
|
|
2171
|
+
logInfo(`[Dev] Version hash: ${configHash}`);
|
|
2172
|
+
process.env.IONIFY_CONFIG_HASH = configHash;
|
|
2173
|
+
const casRoot = import_path12.default.join(rootDir, ".ionify", "cas");
|
|
2174
|
+
const transformer = new TransformEngine({ casRoot, versionHash: configHash });
|
|
2175
|
+
const graph = new Graph(rawVersionInputs);
|
|
2176
|
+
if (native?.initAstCache) {
|
|
2177
|
+
const versionHash = JSON.stringify(rawVersionInputs);
|
|
2178
|
+
native.initAstCache(versionHash);
|
|
2179
|
+
logInfo(`AST cache initialized with version hash`);
|
|
2180
|
+
if (native?.astCacheWarmup) {
|
|
2181
|
+
try {
|
|
2182
|
+
native.astCacheWarmup();
|
|
2183
|
+
} catch (err) {
|
|
2184
|
+
logWarn(`AST cache warmup skipped: ${err}`);
|
|
2185
|
+
}
|
|
81
2186
|
}
|
|
2187
|
+
if (native?.astCacheStats) {
|
|
2188
|
+
try {
|
|
2189
|
+
const stats = native.astCacheStats();
|
|
2190
|
+
const entries = stats.total_entries ?? stats.totalEntries ?? 0;
|
|
2191
|
+
const sizeBytes = stats.total_size_bytes ?? stats.totalSizeBytes ?? 0;
|
|
2192
|
+
const hits = stats.total_hits ?? stats.totalHits ?? 0;
|
|
2193
|
+
const hitRate = stats.hit_rate ?? stats.hitRate ?? 0;
|
|
2194
|
+
logInfo(`[AST Cache] entries=${entries}, size=${sizeBytes} bytes, hits=${hits}, hitRate=${hitRate}`);
|
|
2195
|
+
} catch {
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
const moduleResolver = new ModuleResolver(rootDir, {
|
|
2200
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".mjs"],
|
|
2201
|
+
conditions: ["import", "default"],
|
|
2202
|
+
mainFields: ["module", "main"],
|
|
2203
|
+
...userConfig?.resolve || {}
|
|
82
2204
|
});
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
(
|
|
86
|
-
|
|
87
|
-
|
|
2205
|
+
await applyRegisteredLoaders(transformer, userConfig);
|
|
2206
|
+
const hmr = new HMRServer();
|
|
2207
|
+
const envFromFiles = loadEnv("development", rootDir);
|
|
2208
|
+
process.env.NODE_ENV = process.env.NODE_ENV ?? "development";
|
|
2209
|
+
process.env.MODE = process.env.MODE ?? "development";
|
|
2210
|
+
const envValues = {
|
|
2211
|
+
...envFromFiles,
|
|
2212
|
+
NODE_ENV: process.env.NODE_ENV,
|
|
2213
|
+
MODE: process.env.MODE
|
|
2214
|
+
};
|
|
2215
|
+
const envPlaceholderPattern = /%([A-Z0-9_]+)%/g;
|
|
2216
|
+
const envEnabledExts = /* @__PURE__ */ new Set([
|
|
2217
|
+
".html",
|
|
2218
|
+
".js",
|
|
2219
|
+
".mjs",
|
|
2220
|
+
".cjs",
|
|
2221
|
+
".ts",
|
|
2222
|
+
".tsx",
|
|
2223
|
+
".jsx"
|
|
2224
|
+
]);
|
|
2225
|
+
const applyEnvPlaceholders = (input, extname) => {
|
|
2226
|
+
if (!envEnabledExts.has(extname)) return input;
|
|
2227
|
+
return input.replace(envPlaceholderPattern, (match, key) => {
|
|
2228
|
+
if (key === "NODE_ENV" || key === "MODE" || key.startsWith("VITE_") || key.startsWith("IONIFY_")) {
|
|
2229
|
+
const replacement = envValues[key];
|
|
2230
|
+
return replacement !== void 0 ? replacement : match;
|
|
2231
|
+
}
|
|
2232
|
+
return match;
|
|
2233
|
+
});
|
|
2234
|
+
};
|
|
2235
|
+
const parseJsonBody = async (req) => {
|
|
2236
|
+
const chunks = [];
|
|
2237
|
+
await new Promise((resolve, reject) => {
|
|
2238
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
2239
|
+
req.on("end", () => resolve());
|
|
2240
|
+
req.on("error", (err) => reject(err));
|
|
2241
|
+
});
|
|
2242
|
+
if (!chunks.length) return null;
|
|
2243
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
2244
|
+
if (!raw.trim()) return null;
|
|
2245
|
+
return JSON.parse(raw);
|
|
2246
|
+
};
|
|
2247
|
+
const sendJson = (res, status, payload) => {
|
|
2248
|
+
const body = JSON.stringify(payload);
|
|
2249
|
+
res.writeHead(status, {
|
|
2250
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
2251
|
+
"Cache-Control": "no-store"
|
|
2252
|
+
});
|
|
2253
|
+
res.end(body);
|
|
2254
|
+
};
|
|
2255
|
+
const buildUpdatePayload = async (modules) => {
|
|
2256
|
+
const updates = [];
|
|
2257
|
+
for (const mod of modules) {
|
|
2258
|
+
const exists = import_fs9.default.existsSync(mod.absPath);
|
|
2259
|
+
if (mod.reason === "deleted" || !exists) {
|
|
2260
|
+
graph.removeFile(mod.absPath);
|
|
2261
|
+
watcher.unwatchFile(mod.absPath);
|
|
2262
|
+
updates.push({
|
|
2263
|
+
url: mod.url,
|
|
2264
|
+
hash: null,
|
|
2265
|
+
deps: [],
|
|
2266
|
+
reason: mod.reason,
|
|
2267
|
+
status: "deleted"
|
|
2268
|
+
});
|
|
2269
|
+
continue;
|
|
2270
|
+
}
|
|
2271
|
+
watcher.watchFile(mod.absPath);
|
|
2272
|
+
let code;
|
|
2273
|
+
try {
|
|
2274
|
+
code = import_fs9.default.readFileSync(mod.absPath, "utf8");
|
|
2275
|
+
} catch (err) {
|
|
2276
|
+
logError("Failed to read module during HMR apply", err);
|
|
2277
|
+
throw err;
|
|
2278
|
+
}
|
|
2279
|
+
let hash;
|
|
2280
|
+
let specs;
|
|
2281
|
+
if (native?.parseModuleIr) {
|
|
2282
|
+
try {
|
|
2283
|
+
const ir = native.parseModuleIr(mod.absPath, code);
|
|
2284
|
+
hash = ir.hash;
|
|
2285
|
+
specs = ir.dependencies.map((dep) => dep.specifier);
|
|
2286
|
+
} catch {
|
|
2287
|
+
hash = getCacheKey(code);
|
|
2288
|
+
specs = extractImports(code, mod.absPath);
|
|
2289
|
+
}
|
|
2290
|
+
} else {
|
|
2291
|
+
hash = getCacheKey(code);
|
|
2292
|
+
specs = extractImports(code, mod.absPath);
|
|
2293
|
+
}
|
|
2294
|
+
const depsAbs = resolveImports(specs, mod.absPath);
|
|
2295
|
+
graph.recordFile(mod.absPath, hash, depsAbs);
|
|
2296
|
+
for (const dep of depsAbs) {
|
|
2297
|
+
watcher.watchFile(dep);
|
|
2298
|
+
}
|
|
2299
|
+
const result = await transformer.run({
|
|
2300
|
+
path: mod.absPath,
|
|
2301
|
+
code,
|
|
2302
|
+
ext: import_path12.default.extname(mod.absPath),
|
|
2303
|
+
moduleHash: hash
|
|
2304
|
+
});
|
|
2305
|
+
const transformed = result.code;
|
|
2306
|
+
const envApplied = applyEnvPlaceholders(
|
|
2307
|
+
transformed,
|
|
2308
|
+
import_path12.default.extname(mod.absPath)
|
|
2309
|
+
);
|
|
2310
|
+
updates.push({
|
|
2311
|
+
url: mod.url,
|
|
2312
|
+
hash,
|
|
2313
|
+
deps: depsAbs.map((dep) => normalizeUrlFromFs(rootDir, dep)),
|
|
2314
|
+
reason: mod.reason,
|
|
2315
|
+
status: "updated",
|
|
2316
|
+
code: envApplied
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
return updates;
|
|
2320
|
+
};
|
|
2321
|
+
const server = import_http.default.createServer(async (req, res) => {
|
|
88
2322
|
try {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
2323
|
+
const parsed = import_url3.default.parse(req.url || "/", true);
|
|
2324
|
+
let reqPath = parsed.pathname || "/";
|
|
2325
|
+
try {
|
|
2326
|
+
reqPath = decodeURIComponent(reqPath);
|
|
2327
|
+
} catch {
|
|
93
2328
|
}
|
|
94
|
-
|
|
2329
|
+
const q = parsed.query || {};
|
|
2330
|
+
if (reqPath === "/__ionify_hmr") {
|
|
2331
|
+
hmr.handleSSE(req, res);
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
if (reqPath === "/__ionify_hmr_client.js") {
|
|
2335
|
+
res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
|
|
2336
|
+
res.end(readClientAsset("hmr.js"));
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
if (reqPath === "/__ionify_overlay.js") {
|
|
2340
|
+
res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
|
|
2341
|
+
res.end(readClientAsset("overlay.js"));
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
if (reqPath === "/__ionify_react_refresh.js") {
|
|
2345
|
+
try {
|
|
2346
|
+
const asset = readClientAssetFile("react-refresh-runtime.js");
|
|
2347
|
+
let reactRefreshPath;
|
|
2348
|
+
try {
|
|
2349
|
+
const projectRequire = (0, import_module3.createRequire)(import_path12.default.join(rootDir, "package.json"));
|
|
2350
|
+
reactRefreshPath = projectRequire.resolve("react-refresh/runtime");
|
|
2351
|
+
} catch (err) {
|
|
2352
|
+
logError("Failed to resolve react-refresh/runtime", err);
|
|
2353
|
+
res.statusCode = 500;
|
|
2354
|
+
res.end("Failed to resolve react-refresh/runtime. Make sure react-refresh is installed.");
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2357
|
+
const reactRefreshUrl = normalizeUrlFromFs(rootDir, reactRefreshPath);
|
|
2358
|
+
let code2 = asset.code.replace(
|
|
2359
|
+
'import RefreshRuntime from "react-refresh/runtime"',
|
|
2360
|
+
`import RefreshRuntime from "${reactRefreshUrl}"`
|
|
2361
|
+
);
|
|
2362
|
+
res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
|
|
2363
|
+
res.end(code2);
|
|
2364
|
+
} catch (err) {
|
|
2365
|
+
logError("Failed to serve react refresh runtime", err);
|
|
2366
|
+
res.statusCode = 500;
|
|
2367
|
+
res.end("Internal Server Error");
|
|
2368
|
+
}
|
|
2369
|
+
return;
|
|
2370
|
+
}
|
|
2371
|
+
if (reqPath === "/__ionify_hmr/apply") {
|
|
2372
|
+
if (req.method !== "POST") {
|
|
2373
|
+
res.writeHead(405, { Allow: "POST" });
|
|
2374
|
+
res.end("Method Not Allowed");
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
2377
|
+
let body;
|
|
2378
|
+
try {
|
|
2379
|
+
body = await parseJsonBody(req);
|
|
2380
|
+
} catch (err) {
|
|
2381
|
+
logError("Invalid JSON body for HMR apply", err);
|
|
2382
|
+
sendJson(res, 400, { error: "Invalid JSON body" });
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2385
|
+
const id = typeof body?.id === "string" ? body.id : null;
|
|
2386
|
+
if (!id) {
|
|
2387
|
+
sendJson(res, 400, { error: "Missing update id" });
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
const pending = hmr.consumeUpdate(id);
|
|
2391
|
+
if (!pending) {
|
|
2392
|
+
sendJson(res, 404, { error: "Update not found", id });
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
2395
|
+
try {
|
|
2396
|
+
const modules = await buildUpdatePayload(pending.modules);
|
|
2397
|
+
sendJson(res, 200, {
|
|
2398
|
+
type: "update",
|
|
2399
|
+
id: pending.summary.id,
|
|
2400
|
+
timestamp: Date.now(),
|
|
2401
|
+
modules
|
|
2402
|
+
});
|
|
2403
|
+
} catch (err) {
|
|
2404
|
+
logError("Failed to build HMR update payload", err);
|
|
2405
|
+
hmr.broadcastError({
|
|
2406
|
+
id,
|
|
2407
|
+
message: "Failed to compile update; falling back to full reload"
|
|
2408
|
+
});
|
|
2409
|
+
sendJson(res, 500, { error: "Failed to compile update", id });
|
|
2410
|
+
}
|
|
2411
|
+
return;
|
|
2412
|
+
}
|
|
2413
|
+
if (reqPath === "/__ionify_hmr/error") {
|
|
2414
|
+
if (req.method !== "POST") {
|
|
2415
|
+
res.writeHead(405, { Allow: "POST" });
|
|
2416
|
+
res.end("Method Not Allowed");
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
let body;
|
|
2420
|
+
try {
|
|
2421
|
+
body = await parseJsonBody(req);
|
|
2422
|
+
} catch {
|
|
2423
|
+
body = null;
|
|
2424
|
+
}
|
|
2425
|
+
const id = typeof body?.id === "string" ? body.id : void 0;
|
|
2426
|
+
const message = typeof body?.message === "string" ? body.message : "Unknown HMR error";
|
|
2427
|
+
logError(`[HMR] client reported error${id ? ` ${id}` : ""}: ${message}`);
|
|
2428
|
+
hmr.broadcastError({ id, message });
|
|
2429
|
+
sendJson(res, 200, { ok: true });
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
const fsPath = decodePublicPath(rootDir, reqPath);
|
|
2433
|
+
if (!fsPath) {
|
|
2434
|
+
res.statusCode = 404;
|
|
2435
|
+
res.end("Not found");
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
let effectiveFsPath = fsPath;
|
|
2439
|
+
let effectiveUrlPath = reqPath;
|
|
2440
|
+
if (import_fs9.default.existsSync(effectiveFsPath) && import_fs9.default.statSync(effectiveFsPath).isDirectory()) {
|
|
2441
|
+
const indexExtensions = [".html", ".js", ".ts", ".tsx", ".jsx"];
|
|
2442
|
+
let found = false;
|
|
2443
|
+
for (const ext2 of indexExtensions) {
|
|
2444
|
+
const indexFile = import_path12.default.join(effectiveFsPath, `index${ext2}`);
|
|
2445
|
+
if (import_fs9.default.existsSync(indexFile)) {
|
|
2446
|
+
effectiveFsPath = indexFile;
|
|
2447
|
+
effectiveUrlPath = effectiveUrlPath.endsWith("/") ? `${effectiveUrlPath}index${ext2}` : `${effectiveUrlPath}/index${ext2}`;
|
|
2448
|
+
found = true;
|
|
2449
|
+
break;
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
if (!found) {
|
|
2453
|
+
const packageJson = import_path12.default.join(effectiveFsPath, "package.json");
|
|
2454
|
+
if (import_fs9.default.existsSync(packageJson)) {
|
|
2455
|
+
try {
|
|
2456
|
+
const pkg = JSON.parse(import_fs9.default.readFileSync(packageJson, "utf8"));
|
|
2457
|
+
if (pkg.main) {
|
|
2458
|
+
const mainFile = import_path12.default.join(effectiveFsPath, pkg.main);
|
|
2459
|
+
if (import_fs9.default.existsSync(mainFile)) {
|
|
2460
|
+
effectiveFsPath = mainFile;
|
|
2461
|
+
found = true;
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
} catch (e) {
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
if (!found) {
|
|
2469
|
+
for (const ext2 of indexExtensions) {
|
|
2470
|
+
const moduleFile = import_path12.default.join(effectiveFsPath, `module${ext2}`);
|
|
2471
|
+
if (import_fs9.default.existsSync(moduleFile)) {
|
|
2472
|
+
effectiveFsPath = moduleFile;
|
|
2473
|
+
found = true;
|
|
2474
|
+
break;
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
if (!found) {
|
|
2479
|
+
res.statusCode = 404;
|
|
2480
|
+
res.end("Module not found");
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
if (!import_fs9.default.existsSync(effectiveFsPath)) {
|
|
2485
|
+
res.statusCode = 404;
|
|
2486
|
+
res.end("Not found");
|
|
2487
|
+
return;
|
|
2488
|
+
}
|
|
2489
|
+
const ext = import_path12.default.extname(effectiveFsPath);
|
|
2490
|
+
if (isAssetExt(ext)) {
|
|
2491
|
+
try {
|
|
2492
|
+
const data = import_fs9.default.readFileSync(effectiveFsPath);
|
|
2493
|
+
const assetHash = import_crypto3.default.createHash("sha256").update(data).digest("hex");
|
|
2494
|
+
const kind = "asset";
|
|
2495
|
+
const changed2 = graph.recordFile(effectiveFsPath, assetHash, [], [], kind);
|
|
2496
|
+
watcher.watchFile(effectiveFsPath);
|
|
2497
|
+
if (changed2) {
|
|
2498
|
+
logInfo(`[Graph] Asset updated: ${effectiveFsPath}`);
|
|
2499
|
+
}
|
|
2500
|
+
} catch {
|
|
2501
|
+
}
|
|
2502
|
+
if ("import" in q) {
|
|
2503
|
+
const js = assetAsModule(normalizeUrlFromFs(rootDir, effectiveFsPath));
|
|
2504
|
+
res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
|
|
2505
|
+
res.end(js);
|
|
2506
|
+
return;
|
|
2507
|
+
} else {
|
|
2508
|
+
res.writeHead(200, { "Content-Type": contentTypeForAsset(ext) });
|
|
2509
|
+
import_fs9.default.createReadStream(effectiveFsPath).pipe(res);
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
if (ext === ".css") {
|
|
2514
|
+
try {
|
|
2515
|
+
const cssSource = import_fs9.default.readFileSync(effectiveFsPath, "utf8");
|
|
2516
|
+
const isModule = "module" in q || /\.module\.css$/i.test(effectiveFsPath);
|
|
2517
|
+
const isInline = "inline" in q;
|
|
2518
|
+
const mode = isModule ? "css:module" : isInline ? "css:inline" : "css:raw";
|
|
2519
|
+
const contentHash = getCacheKey(cssSource);
|
|
2520
|
+
watcher.watchFile(effectiveFsPath);
|
|
2521
|
+
const kind = isModule ? "css-module" : "css";
|
|
2522
|
+
const changed2 = graph.recordFile(effectiveFsPath, contentHash, [], [], kind);
|
|
2523
|
+
const casDir = getCasArtifactPath(casRoot, configHash, contentHash);
|
|
2524
|
+
const casFile = import_path12.default.join(casDir, "transformed.js");
|
|
2525
|
+
let finalBuffer = null;
|
|
2526
|
+
if (import_fs9.default.existsSync(casFile)) {
|
|
2527
|
+
try {
|
|
2528
|
+
finalBuffer = import_fs9.default.readFileSync(casFile);
|
|
2529
|
+
res.setHeader("X-Ionify-Cache", "HIT");
|
|
2530
|
+
} catch {
|
|
2531
|
+
finalBuffer = null;
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
if (!finalBuffer) {
|
|
2535
|
+
const { css: compiledCss, tokens } = await compileCss({
|
|
2536
|
+
code: cssSource,
|
|
2537
|
+
filePath: effectiveFsPath,
|
|
2538
|
+
rootDir,
|
|
2539
|
+
modules: isModule
|
|
2540
|
+
});
|
|
2541
|
+
const body = isModule || isInline ? renderCssModule({
|
|
2542
|
+
css: compiledCss,
|
|
2543
|
+
filePath: effectiveFsPath,
|
|
2544
|
+
tokens: isModule ? tokens ?? {} : void 0
|
|
2545
|
+
}) : compiledCss;
|
|
2546
|
+
finalBuffer = Buffer.from(body, "utf8");
|
|
2547
|
+
res.setHeader("X-Ionify-Cache", "MISS");
|
|
2548
|
+
try {
|
|
2549
|
+
import_fs9.default.mkdirSync(casDir, { recursive: true });
|
|
2550
|
+
import_fs9.default.writeFileSync(casFile, finalBuffer);
|
|
2551
|
+
} catch {
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
if (isModule || isInline) {
|
|
2555
|
+
res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
|
|
2556
|
+
} else {
|
|
2557
|
+
res.writeHead(200, { "Content-Type": "text/css; charset=utf-8" });
|
|
2558
|
+
}
|
|
2559
|
+
res.end(finalBuffer);
|
|
2560
|
+
logInfo(`Served: ${effectiveUrlPath} deps:0 ${changed2 ? "(updated)" : "(cached)"}`);
|
|
2561
|
+
return;
|
|
2562
|
+
} catch (err) {
|
|
2563
|
+
logError("Failed to process CSS", err);
|
|
2564
|
+
res.statusCode = 500;
|
|
2565
|
+
res.end("Failed to process CSS");
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
const code = import_fs9.default.readFileSync(effectiveFsPath, "utf8");
|
|
2570
|
+
let hash;
|
|
2571
|
+
let specs;
|
|
2572
|
+
if (native?.parseModuleIr) {
|
|
2573
|
+
try {
|
|
2574
|
+
const ir = native.parseModuleIr(effectiveFsPath, code);
|
|
2575
|
+
hash = ir.hash;
|
|
2576
|
+
specs = ir.dependencies.map((dep) => dep.specifier);
|
|
2577
|
+
} catch {
|
|
2578
|
+
hash = getCacheKey(code);
|
|
2579
|
+
specs = extractImports(code, effectiveFsPath);
|
|
2580
|
+
}
|
|
2581
|
+
} else {
|
|
2582
|
+
hash = getCacheKey(code);
|
|
2583
|
+
specs = extractImports(code, effectiveFsPath);
|
|
2584
|
+
}
|
|
2585
|
+
const depsAbs = resolveImports(specs, effectiveFsPath);
|
|
2586
|
+
const changed = graph.recordFile(effectiveFsPath, hash, depsAbs);
|
|
2587
|
+
watcher.watchFile(effectiveFsPath);
|
|
2588
|
+
for (const dep of depsAbs) {
|
|
2589
|
+
watcher.watchFile(dep);
|
|
2590
|
+
}
|
|
2591
|
+
const result = await transformer.run({
|
|
2592
|
+
path: effectiveFsPath,
|
|
2593
|
+
code,
|
|
2594
|
+
ext,
|
|
2595
|
+
moduleHash: hash
|
|
2596
|
+
});
|
|
2597
|
+
const transformedCode = result.code;
|
|
2598
|
+
res.setHeader("X-Ionify-Cache", changed ? "MISS" : "HIT");
|
|
2599
|
+
const envApplied = applyEnvPlaceholders(transformedCode, ext);
|
|
2600
|
+
if (import_path12.default.extname(effectiveFsPath) === ".html") {
|
|
2601
|
+
const injected = injectHMRClient(envApplied);
|
|
2602
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
2603
|
+
res.end(injected);
|
|
2604
|
+
} else {
|
|
2605
|
+
const finalBuffer = Buffer.from(envApplied);
|
|
2606
|
+
res.setHeader("Content-Type", guessContentType(effectiveFsPath));
|
|
2607
|
+
res.end(finalBuffer);
|
|
2608
|
+
}
|
|
2609
|
+
logInfo(`Served: ${effectiveUrlPath} deps:${depsAbs.length} ${changed ? "(updated)" : "(cached)"}`);
|
|
2610
|
+
if (cacheDebug) {
|
|
2611
|
+
const m = transformCache.metrics();
|
|
2612
|
+
logInfo(`[Ionify][Dev Cache] hits:${m.hits} misses:${m.misses} size:${m.size}`);
|
|
2613
|
+
}
|
|
2614
|
+
} catch (err) {
|
|
2615
|
+
logError("Error serving request:", err);
|
|
2616
|
+
res.statusCode = 500;
|
|
2617
|
+
res.end("Internal Server Error");
|
|
2618
|
+
}
|
|
2619
|
+
});
|
|
2620
|
+
watcher.on("change", (file, status) => {
|
|
2621
|
+
logInfo(`[Watcher] ${status}: ${file}`);
|
|
2622
|
+
const affected = graph.collectAffected([file]);
|
|
2623
|
+
if (!affected.includes(file)) {
|
|
2624
|
+
affected.unshift(file);
|
|
2625
|
+
}
|
|
2626
|
+
const modules = [];
|
|
2627
|
+
for (const absPath of affected) {
|
|
2628
|
+
const reason = absPath === file ? status === "deleted" ? "deleted" : "changed" : "dependent";
|
|
2629
|
+
let hash = null;
|
|
2630
|
+
if (reason !== "deleted") {
|
|
2631
|
+
if (absPath === file) {
|
|
2632
|
+
try {
|
|
2633
|
+
const code = import_fs9.default.readFileSync(absPath, "utf8");
|
|
2634
|
+
hash = getCacheKey(code);
|
|
2635
|
+
} catch {
|
|
2636
|
+
hash = graph.getNode(absPath)?.hash ?? null;
|
|
2637
|
+
}
|
|
2638
|
+
} else {
|
|
2639
|
+
hash = graph.getNode(absPath)?.hash ?? null;
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
modules.push({
|
|
2643
|
+
absPath,
|
|
2644
|
+
url: normalizeUrlFromFs(rootDir, absPath),
|
|
2645
|
+
hash,
|
|
2646
|
+
reason
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
const summary = hmr.queueUpdate(modules);
|
|
2650
|
+
if (summary) {
|
|
2651
|
+
logInfo(
|
|
2652
|
+
`[HMR] update ${summary.id} -> ${summary.modules.length} module(s) queued`
|
|
2653
|
+
);
|
|
2654
|
+
}
|
|
2655
|
+
if (status === "deleted") {
|
|
2656
|
+
graph.removeFile(file);
|
|
2657
|
+
watcher.unwatchFile(file);
|
|
2658
|
+
}
|
|
2659
|
+
});
|
|
2660
|
+
let closingPromise = null;
|
|
2661
|
+
let cleanedUp = false;
|
|
2662
|
+
const signalHandlers = [];
|
|
2663
|
+
const cleanup = (force = false) => {
|
|
2664
|
+
if (cleanedUp) return;
|
|
2665
|
+
cleanedUp = true;
|
|
2666
|
+
if (force) {
|
|
2667
|
+
server.getConnections((err, count) => {
|
|
2668
|
+
if (!err && count > 0) {
|
|
2669
|
+
server.closeAllConnections();
|
|
2670
|
+
}
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2673
|
+
try {
|
|
2674
|
+
watcher.closeAll();
|
|
2675
|
+
} catch (err) {
|
|
2676
|
+
logError("Error closing watcher:", err);
|
|
95
2677
|
}
|
|
2678
|
+
try {
|
|
2679
|
+
hmr.close();
|
|
2680
|
+
} catch (err) {
|
|
2681
|
+
logError("Error closing HMR:", err);
|
|
2682
|
+
}
|
|
2683
|
+
graph.flush();
|
|
2684
|
+
for (const { event, handler } of signalHandlers) {
|
|
2685
|
+
process.off(event, handler);
|
|
2686
|
+
}
|
|
2687
|
+
};
|
|
2688
|
+
server.on("close", () => cleanup(false));
|
|
2689
|
+
const shutdown = async (exitProcess) => {
|
|
2690
|
+
if (!closingPromise) {
|
|
2691
|
+
closingPromise = new Promise((resolve, reject) => {
|
|
2692
|
+
const timeoutId = setTimeout(() => {
|
|
2693
|
+
logInfo("Server shutdown taking too long, forcing cleanup...");
|
|
2694
|
+
cleanup(true);
|
|
2695
|
+
resolve();
|
|
2696
|
+
}, 3e3);
|
|
2697
|
+
server.close((err) => {
|
|
2698
|
+
clearTimeout(timeoutId);
|
|
2699
|
+
if (err) {
|
|
2700
|
+
logError("Error during server shutdown:", err);
|
|
2701
|
+
reject(err);
|
|
2702
|
+
} else {
|
|
2703
|
+
resolve();
|
|
2704
|
+
}
|
|
2705
|
+
});
|
|
2706
|
+
});
|
|
2707
|
+
}
|
|
2708
|
+
try {
|
|
2709
|
+
await Promise.race([
|
|
2710
|
+
closingPromise,
|
|
2711
|
+
new Promise(
|
|
2712
|
+
(_, reject) => setTimeout(() => reject(new Error("Shutdown timeout")), 5e3)
|
|
2713
|
+
)
|
|
2714
|
+
]);
|
|
2715
|
+
} catch (err) {
|
|
2716
|
+
logError("Shutdown error:", err);
|
|
2717
|
+
cleanup(true);
|
|
2718
|
+
}
|
|
2719
|
+
if (exitProcess) {
|
|
2720
|
+
setTimeout(() => process.exit(0), 100);
|
|
2721
|
+
}
|
|
2722
|
+
};
|
|
2723
|
+
if (enableSignalHandlers) {
|
|
2724
|
+
const onSignal = () => {
|
|
2725
|
+
void shutdown(true);
|
|
2726
|
+
};
|
|
2727
|
+
process.on("SIGINT", onSignal);
|
|
2728
|
+
process.on("SIGTERM", onSignal);
|
|
2729
|
+
signalHandlers.push({ event: "SIGINT", handler: onSignal });
|
|
2730
|
+
signalHandlers.push({ event: "SIGTERM", handler: onSignal });
|
|
96
2731
|
}
|
|
97
|
-
|
|
98
|
-
|
|
2732
|
+
await new Promise((resolve) => {
|
|
2733
|
+
server.listen(port, () => resolve());
|
|
2734
|
+
});
|
|
2735
|
+
const address = server.address();
|
|
2736
|
+
const actualPort = address && typeof address === "object" && address?.port ? address.port : port;
|
|
2737
|
+
logInfo(`Ionify Dev Server (Phase 2) at http://localhost:${actualPort}`);
|
|
2738
|
+
logInfo(`HMR listening at /__ionify_hmr (SSE)`);
|
|
2739
|
+
return {
|
|
2740
|
+
server,
|
|
2741
|
+
port: actualPort,
|
|
2742
|
+
close: async () => {
|
|
2743
|
+
await shutdown(false);
|
|
2744
|
+
}
|
|
2745
|
+
};
|
|
2746
|
+
}
|
|
99
2747
|
|
|
100
2748
|
// src/cli/commands/analyze.ts
|
|
2749
|
+
init_cjs_shims();
|
|
2750
|
+
var import_fs10 = __toESM(require("fs"), 1);
|
|
2751
|
+
var import_path13 = __toESM(require("path"), 1);
|
|
2752
|
+
init_native();
|
|
101
2753
|
function readGraphFromDisk(root) {
|
|
102
|
-
const file =
|
|
103
|
-
if (!
|
|
2754
|
+
const file = import_path13.default.join(root, ".ionify", "graph.json");
|
|
2755
|
+
if (!import_fs10.default.existsSync(file)) return null;
|
|
104
2756
|
try {
|
|
105
|
-
const raw =
|
|
2757
|
+
const raw = import_fs10.default.readFileSync(file, "utf8");
|
|
106
2758
|
const snapshot = JSON.parse(raw);
|
|
107
2759
|
if (snapshot?.version !== 1 || !snapshot?.nodes) return null;
|
|
108
2760
|
return Object.entries(snapshot.nodes).map(([id, node]) => ({
|
|
@@ -197,23 +2849,980 @@ async function runAnalyzeCommand(options = {}) {
|
|
|
197
2849
|
}
|
|
198
2850
|
}
|
|
199
2851
|
|
|
2852
|
+
// src/cli/commands/build.ts
|
|
2853
|
+
init_cjs_shims();
|
|
2854
|
+
var import_fs12 = __toESM(require("fs"), 1);
|
|
2855
|
+
var import_path15 = __toESM(require("path"), 1);
|
|
2856
|
+
init_native();
|
|
2857
|
+
init_cas();
|
|
2858
|
+
|
|
2859
|
+
// src/cli/utils/optimization-level.ts
|
|
2860
|
+
init_cjs_shims();
|
|
2861
|
+
function getOptimizationPreset(level) {
|
|
2862
|
+
switch (level) {
|
|
2863
|
+
case 0:
|
|
2864
|
+
return {
|
|
2865
|
+
minifier: "swc",
|
|
2866
|
+
treeshake: {
|
|
2867
|
+
mode: "off",
|
|
2868
|
+
include: [],
|
|
2869
|
+
exclude: []
|
|
2870
|
+
},
|
|
2871
|
+
scopeHoist: {
|
|
2872
|
+
enable: false,
|
|
2873
|
+
inlineFunctions: false,
|
|
2874
|
+
constantFolding: false,
|
|
2875
|
+
combineVariables: false
|
|
2876
|
+
}
|
|
2877
|
+
};
|
|
2878
|
+
case 1:
|
|
2879
|
+
return {
|
|
2880
|
+
minifier: "oxc",
|
|
2881
|
+
treeshake: {
|
|
2882
|
+
mode: "safe",
|
|
2883
|
+
include: [],
|
|
2884
|
+
exclude: []
|
|
2885
|
+
},
|
|
2886
|
+
scopeHoist: {
|
|
2887
|
+
enable: true,
|
|
2888
|
+
inlineFunctions: true,
|
|
2889
|
+
constantFolding: false,
|
|
2890
|
+
combineVariables: false
|
|
2891
|
+
}
|
|
2892
|
+
};
|
|
2893
|
+
case 2:
|
|
2894
|
+
return {
|
|
2895
|
+
minifier: "oxc",
|
|
2896
|
+
treeshake: {
|
|
2897
|
+
mode: "safe",
|
|
2898
|
+
include: [],
|
|
2899
|
+
exclude: []
|
|
2900
|
+
},
|
|
2901
|
+
scopeHoist: {
|
|
2902
|
+
enable: true,
|
|
2903
|
+
inlineFunctions: true,
|
|
2904
|
+
constantFolding: true,
|
|
2905
|
+
combineVariables: true
|
|
2906
|
+
}
|
|
2907
|
+
};
|
|
2908
|
+
case 3:
|
|
2909
|
+
return {
|
|
2910
|
+
minifier: "oxc",
|
|
2911
|
+
treeshake: {
|
|
2912
|
+
mode: "aggressive",
|
|
2913
|
+
include: [],
|
|
2914
|
+
exclude: []
|
|
2915
|
+
},
|
|
2916
|
+
scopeHoist: {
|
|
2917
|
+
enable: true,
|
|
2918
|
+
inlineFunctions: true,
|
|
2919
|
+
constantFolding: true,
|
|
2920
|
+
combineVariables: true
|
|
2921
|
+
}
|
|
2922
|
+
};
|
|
2923
|
+
default:
|
|
2924
|
+
return getOptimizationPreset(2);
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
function resolveOptimizationLevel(configLevel, options = {}) {
|
|
2928
|
+
if (options.cliLevel !== void 0) {
|
|
2929
|
+
const parsed = typeof options.cliLevel === "number" ? options.cliLevel : parseInt(options.cliLevel, 10);
|
|
2930
|
+
if ([0, 1, 2, 3].includes(parsed)) {
|
|
2931
|
+
return parsed;
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
if (options.envLevel) {
|
|
2935
|
+
const parsed = parseInt(options.envLevel, 10);
|
|
2936
|
+
if ([0, 1, 2, 3].includes(parsed)) {
|
|
2937
|
+
return parsed;
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
if (configLevel !== void 0 && [0, 1, 2, 3].includes(configLevel)) {
|
|
2941
|
+
return configLevel;
|
|
2942
|
+
}
|
|
2943
|
+
return null;
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
// src/core/bundler.ts
|
|
2947
|
+
init_cjs_shims();
|
|
2948
|
+
var import_fs11 = __toESM(require("fs"), 1);
|
|
2949
|
+
var import_path14 = __toESM(require("path"), 1);
|
|
2950
|
+
var import_crypto4 = __toESM(require("crypto"), 1);
|
|
2951
|
+
init_native();
|
|
2952
|
+
init_cache();
|
|
2953
|
+
function readGraphSnapshot() {
|
|
2954
|
+
if (native?.graphLoadMap) {
|
|
2955
|
+
try {
|
|
2956
|
+
const nativeMap = native.graphLoadMap();
|
|
2957
|
+
if (nativeMap && Object.keys(nativeMap).length > 0) {
|
|
2958
|
+
return Object.values(nativeMap).map((node) => ({
|
|
2959
|
+
id: node.id,
|
|
2960
|
+
hash: node.hash,
|
|
2961
|
+
deps: node.deps || [],
|
|
2962
|
+
dynamicDeps: node.dynamicDeps || [],
|
|
2963
|
+
kind: node.kind
|
|
2964
|
+
}));
|
|
2965
|
+
}
|
|
2966
|
+
} catch (err) {
|
|
2967
|
+
logWarn(`Failed to load native graph: ${String(err)}`);
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
const file = import_path14.default.join(process.cwd(), ".ionify", "graph.json");
|
|
2971
|
+
if (!import_fs11.default.existsSync(file)) return [];
|
|
2972
|
+
try {
|
|
2973
|
+
const raw = import_fs11.default.readFileSync(file, "utf8");
|
|
2974
|
+
const snapshot = JSON.parse(raw);
|
|
2975
|
+
if (snapshot?.version !== 1 || !snapshot?.nodes) return [];
|
|
2976
|
+
return Object.entries(snapshot.nodes).map(([id, node]) => ({
|
|
2977
|
+
id,
|
|
2978
|
+
hash: typeof node.hash === "string" ? node.hash : null,
|
|
2979
|
+
deps: Array.isArray(node.deps) ? node.deps : []
|
|
2980
|
+
}));
|
|
2981
|
+
} catch (err) {
|
|
2982
|
+
logWarn(`Failed to read graph snapshot: ${String(err)}`);
|
|
2983
|
+
return [];
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
var JS_EXTENSIONS2 = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx"]);
|
|
2987
|
+
var CSS_EXTENSIONS = /* @__PURE__ */ new Set([".css"]);
|
|
2988
|
+
function classifyModuleKind(id) {
|
|
2989
|
+
const ext = import_path14.default.extname(id).toLowerCase();
|
|
2990
|
+
if (CSS_EXTENSIONS.has(ext)) return "css";
|
|
2991
|
+
if (JS_EXTENSIONS2.has(ext)) return "js";
|
|
2992
|
+
return "asset";
|
|
2993
|
+
}
|
|
2994
|
+
var isNonEmptyString = (value) => typeof value === "string" && value.length > 0;
|
|
2995
|
+
var toPosix = (p) => p.split(import_path14.default.sep).join("/");
|
|
2996
|
+
function minifyCss(input) {
|
|
2997
|
+
return input.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").replace(/\s*([{};:,])\s*/g, "$1").trim();
|
|
2998
|
+
}
|
|
2999
|
+
function orderCssModules(chunk) {
|
|
3000
|
+
const cssModules = chunk.modules.filter((m) => m.kind === "css");
|
|
3001
|
+
const cssSet = new Set(cssModules.map((m) => m.id));
|
|
3002
|
+
const adj = /* @__PURE__ */ new Map();
|
|
3003
|
+
for (const mod of cssModules) {
|
|
3004
|
+
const deps = [...mod.deps || [], ...mod.dynamicDeps || []].filter((d) => cssSet.has(d));
|
|
3005
|
+
deps.sort();
|
|
3006
|
+
adj.set(mod.id, deps);
|
|
3007
|
+
}
|
|
3008
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3009
|
+
const temp = /* @__PURE__ */ new Set();
|
|
3010
|
+
const ordered = [];
|
|
3011
|
+
const dfs = (id) => {
|
|
3012
|
+
if (visited.has(id) || temp.has(id)) return;
|
|
3013
|
+
temp.add(id);
|
|
3014
|
+
const edges = adj.get(id) || [];
|
|
3015
|
+
for (const dep of edges) dfs(dep);
|
|
3016
|
+
temp.delete(id);
|
|
3017
|
+
visited.add(id);
|
|
3018
|
+
ordered.push(id);
|
|
3019
|
+
};
|
|
3020
|
+
const sorted = [...cssModules.map((m) => m.id)].sort();
|
|
3021
|
+
for (const id of sorted) {
|
|
3022
|
+
dfs(id);
|
|
3023
|
+
}
|
|
3024
|
+
return ordered;
|
|
3025
|
+
}
|
|
3026
|
+
function normalizeModules(rawModules) {
|
|
3027
|
+
const modules = [];
|
|
3028
|
+
for (const raw of rawModules) {
|
|
3029
|
+
if (typeof raw === "string") {
|
|
3030
|
+
modules.push({
|
|
3031
|
+
id: raw,
|
|
3032
|
+
hash: null,
|
|
3033
|
+
kind: classifyModuleKind(raw),
|
|
3034
|
+
deps: [],
|
|
3035
|
+
dynamicDeps: []
|
|
3036
|
+
});
|
|
3037
|
+
continue;
|
|
3038
|
+
}
|
|
3039
|
+
if (!raw || typeof raw !== "object") continue;
|
|
3040
|
+
const id = typeof raw.id === "string" ? raw.id : null;
|
|
3041
|
+
if (!id) continue;
|
|
3042
|
+
const rawKind = typeof raw.kind === "string" ? raw.kind : classifyModuleKind(id);
|
|
3043
|
+
const kind = rawKind === "css" || rawKind === "asset" ? rawKind : "js";
|
|
3044
|
+
const deps = Array.isArray(raw.deps) ? raw.deps.filter(isNonEmptyString) : [];
|
|
3045
|
+
const dynamicSource = Array.isArray(raw.dynamicDeps) ? raw.dynamicDeps : Array.isArray(raw.dynamic_deps) ? raw.dynamic_deps : [];
|
|
3046
|
+
const dynamicDeps = dynamicSource.filter(isNonEmptyString);
|
|
3047
|
+
const hash = typeof raw.hash === "string" && raw.hash.length ? raw.hash : null;
|
|
3048
|
+
modules.push({
|
|
3049
|
+
id,
|
|
3050
|
+
hash,
|
|
3051
|
+
kind,
|
|
3052
|
+
deps,
|
|
3053
|
+
dynamicDeps
|
|
3054
|
+
});
|
|
3055
|
+
}
|
|
3056
|
+
return modules;
|
|
3057
|
+
}
|
|
3058
|
+
function normalizePlan(plan) {
|
|
3059
|
+
const entries = Array.isArray(plan?.entries) ? Array.from(new Set(plan.entries.filter(isNonEmptyString))) : [];
|
|
3060
|
+
const rawChunks = Array.isArray(plan?.chunks) ? plan.chunks : [];
|
|
3061
|
+
const normalizedChunks = rawChunks.map((chunk, index) => {
|
|
3062
|
+
const id = typeof chunk?.id === "string" && chunk.id.length ? chunk.id : `chunk-${index}`;
|
|
3063
|
+
const modules = normalizeModules(Array.isArray(chunk?.modules) ? chunk.modules : []);
|
|
3064
|
+
const consumersRaw = Array.isArray(chunk?.consumers) ? chunk.consumers.filter(isNonEmptyString) : null;
|
|
3065
|
+
const cssRaw = Array.isArray(chunk?.css) ? chunk.css.filter(isNonEmptyString) : null;
|
|
3066
|
+
const assetsRaw = Array.isArray(chunk?.assets) ? chunk.assets.filter(isNonEmptyString) : null;
|
|
3067
|
+
const consumers = consumersRaw && consumersRaw.length ? Array.from(new Set(consumersRaw)) : [...entries];
|
|
3068
|
+
const inferredCss = cssRaw && cssRaw.length ? cssRaw : modules.filter((m) => m.kind === "css").map((m) => m.id);
|
|
3069
|
+
const inferredAssets = assetsRaw && assetsRaw.length ? assetsRaw : modules.filter((m) => m.kind === "asset").map((m) => m.id);
|
|
3070
|
+
return {
|
|
3071
|
+
id,
|
|
3072
|
+
modules,
|
|
3073
|
+
entry: chunk?.entry === true,
|
|
3074
|
+
shared: chunk?.shared === true,
|
|
3075
|
+
consumers,
|
|
3076
|
+
css: inferredCss,
|
|
3077
|
+
assets: inferredAssets
|
|
3078
|
+
};
|
|
3079
|
+
});
|
|
3080
|
+
return {
|
|
3081
|
+
entries,
|
|
3082
|
+
chunks: normalizedChunks
|
|
3083
|
+
};
|
|
3084
|
+
}
|
|
3085
|
+
function fallbackPlan(entries) {
|
|
3086
|
+
const nodes = readGraphSnapshot();
|
|
3087
|
+
logInfo(`[Fallback] modules: ${nodes.length}, entries: ${entries?.length ?? 0}`);
|
|
3088
|
+
logInfo(`[Fallback] module IDs: ${nodes.map((n) => n.id).join(", ")}`);
|
|
3089
|
+
logInfo(`[Fallback] entry IDs: ${entries?.join(", ") ?? "none"}`);
|
|
3090
|
+
const modules = nodes.map((n) => n.id);
|
|
3091
|
+
const deps = /* @__PURE__ */ new Set();
|
|
3092
|
+
for (const node of nodes) {
|
|
3093
|
+
for (const dep of node.deps) deps.add(dep);
|
|
3094
|
+
}
|
|
3095
|
+
let finalEntries = entries && entries.length ? [...entries] : modules.filter((m) => !deps.has(m));
|
|
3096
|
+
if (!finalEntries.length && modules.length) {
|
|
3097
|
+
finalEntries = [modules[0]];
|
|
3098
|
+
}
|
|
3099
|
+
const nodeMap = new Map(nodes.map((node) => [node.id, node]));
|
|
3100
|
+
const planModules = modules.map((id) => {
|
|
3101
|
+
const node = nodeMap.get(id);
|
|
3102
|
+
return {
|
|
3103
|
+
id,
|
|
3104
|
+
hash: node?.hash ?? null,
|
|
3105
|
+
kind: node?.kind ?? classifyModuleKind(id),
|
|
3106
|
+
deps: node?.deps ?? [],
|
|
3107
|
+
dynamicDeps: node?.dynamicDeps ?? []
|
|
3108
|
+
};
|
|
3109
|
+
});
|
|
3110
|
+
const css = planModules.filter((m) => m.kind === "css").map((m) => m.id);
|
|
3111
|
+
const assets = planModules.filter((m) => m.kind === "asset").map((m) => m.id);
|
|
3112
|
+
return normalizePlan({
|
|
3113
|
+
entries: finalEntries,
|
|
3114
|
+
chunks: [
|
|
3115
|
+
{
|
|
3116
|
+
id: "chunk-main",
|
|
3117
|
+
modules: planModules,
|
|
3118
|
+
entry: true,
|
|
3119
|
+
shared: false,
|
|
3120
|
+
consumers: finalEntries,
|
|
3121
|
+
css,
|
|
3122
|
+
assets
|
|
3123
|
+
}
|
|
3124
|
+
]
|
|
3125
|
+
});
|
|
3126
|
+
}
|
|
3127
|
+
async function generateBuildPlan(entries, versionInputs) {
|
|
3128
|
+
const version = versionInputs ? computeGraphVersion(versionInputs) : void 0;
|
|
3129
|
+
logInfo(`Graph version: ${version || "default"}`);
|
|
3130
|
+
const graphDbPath = import_path14.default.join(process.cwd(), ".ionify", "graph.db");
|
|
3131
|
+
ensureNativeGraph(graphDbPath, version);
|
|
3132
|
+
let moduleCount = 0;
|
|
3133
|
+
if (native?.graphLoadMap) {
|
|
3134
|
+
try {
|
|
3135
|
+
const persistedGraph = native.graphLoadMap();
|
|
3136
|
+
const graphSize = persistedGraph ? Object.keys(persistedGraph).length : 0;
|
|
3137
|
+
moduleCount = graphSize;
|
|
3138
|
+
logInfo(`Native graph loaded: ${graphSize} modules`);
|
|
3139
|
+
if (persistedGraph && graphSize > 0) {
|
|
3140
|
+
logInfo(`Loaded persisted graph with ${graphSize} modules`);
|
|
3141
|
+
}
|
|
3142
|
+
} catch (err) {
|
|
3143
|
+
logWarn(`Failed to load persisted graph: ${String(err)}`);
|
|
3144
|
+
}
|
|
3145
|
+
} else {
|
|
3146
|
+
logWarn(`graphLoadMap not available, native binding: ${!!native}`);
|
|
3147
|
+
}
|
|
3148
|
+
if (moduleCount === 0 && entries?.length && native) {
|
|
3149
|
+
logWarn(`[Build] Graph is empty \u2014 rebuilding dependency graph from entries...`);
|
|
3150
|
+
const queue = [...entries];
|
|
3151
|
+
const seen = new Set(queue);
|
|
3152
|
+
while (queue.length) {
|
|
3153
|
+
const file = queue.shift();
|
|
3154
|
+
if (!import_fs11.default.existsSync(file)) continue;
|
|
3155
|
+
const code = import_fs11.default.readFileSync(file, "utf8");
|
|
3156
|
+
let hash = getCacheKey(code);
|
|
3157
|
+
let specs = [];
|
|
3158
|
+
if (native.parseModuleIr) {
|
|
3159
|
+
try {
|
|
3160
|
+
const ir = native.parseModuleIr(file, code);
|
|
3161
|
+
hash = ir.hash;
|
|
3162
|
+
specs = ir.dependencies.map((d) => d.specifier);
|
|
3163
|
+
} catch {
|
|
3164
|
+
specs = extractImports(code, file);
|
|
3165
|
+
}
|
|
3166
|
+
} else {
|
|
3167
|
+
specs = extractImports(code, file);
|
|
3168
|
+
}
|
|
3169
|
+
const depsAbs = resolveImports(specs, file);
|
|
3170
|
+
if (typeof native.graphRecord === "function") {
|
|
3171
|
+
native.graphRecord(file, hash, depsAbs, [], "module");
|
|
3172
|
+
} else if (typeof native.recordFile === "function") {
|
|
3173
|
+
native.recordFile(file, hash, depsAbs, [], "module");
|
|
3174
|
+
}
|
|
3175
|
+
for (const dep of depsAbs) {
|
|
3176
|
+
if (!seen.has(dep)) {
|
|
3177
|
+
seen.add(dep);
|
|
3178
|
+
queue.push(dep);
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
try {
|
|
3183
|
+
if (typeof native.loadModulesCount === "function") {
|
|
3184
|
+
moduleCount = native.loadModulesCount() ?? moduleCount;
|
|
3185
|
+
} else if (native.graphLoadMap) {
|
|
3186
|
+
const persistedGraph = native.graphLoadMap();
|
|
3187
|
+
moduleCount = persistedGraph ? Object.keys(persistedGraph).length : moduleCount;
|
|
3188
|
+
}
|
|
3189
|
+
} catch {
|
|
3190
|
+
}
|
|
3191
|
+
logInfo(`[Build] Dependency graph rebuilt: ${moduleCount} modules`);
|
|
3192
|
+
}
|
|
3193
|
+
if (native?.plannerPlanBuild) {
|
|
3194
|
+
try {
|
|
3195
|
+
const start = Date.now();
|
|
3196
|
+
logInfo(`[Planner] Calling native plannerPlanBuild with ${entries?.length ?? 0} entries`);
|
|
3197
|
+
const plan = native.plannerPlanBuild(entries ?? []);
|
|
3198
|
+
logInfo(`[Planner] Native plan returned: ${plan.entries.length} entries, ${plan.chunks.length} chunks in ${Date.now() - start}ms`);
|
|
3199
|
+
return normalizePlan(plan);
|
|
3200
|
+
} catch (err) {
|
|
3201
|
+
logWarn(`plannerPlanBuild failed, falling back to JS planner: ${String(err)}`);
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
return fallbackPlan(entries);
|
|
3205
|
+
}
|
|
3206
|
+
async function writeBuildManifest(outputDir, plan, artifacts) {
|
|
3207
|
+
const filesByChunk = /* @__PURE__ */ new Map();
|
|
3208
|
+
for (const artifact of artifacts) {
|
|
3209
|
+
filesByChunk.set(artifact.id, artifact.files);
|
|
3210
|
+
}
|
|
3211
|
+
const manifest = {
|
|
3212
|
+
entries: plan.entries,
|
|
3213
|
+
chunks: plan.chunks.map((chunk) => ({
|
|
3214
|
+
id: chunk.id,
|
|
3215
|
+
entry: chunk.entry,
|
|
3216
|
+
shared: chunk.shared,
|
|
3217
|
+
consumers: chunk.consumers,
|
|
3218
|
+
modules: chunk.modules.map((mod) => ({
|
|
3219
|
+
id: mod.id,
|
|
3220
|
+
kind: mod.kind,
|
|
3221
|
+
deps: mod.deps,
|
|
3222
|
+
dynamicDeps: mod.dynamicDeps
|
|
3223
|
+
})),
|
|
3224
|
+
files: filesByChunk.get(chunk.id) ?? { js: [], css: [], assets: [] }
|
|
3225
|
+
}))
|
|
3226
|
+
};
|
|
3227
|
+
const dir = import_path14.default.resolve(outputDir);
|
|
3228
|
+
await import_fs11.default.promises.mkdir(dir, { recursive: true });
|
|
3229
|
+
const file = import_path14.default.join(dir, "manifest.json");
|
|
3230
|
+
await import_fs11.default.promises.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
3231
|
+
}
|
|
3232
|
+
async function emitChunks(outputDir, plan, moduleOutputs, opts) {
|
|
3233
|
+
if (!native?.buildChunks) {
|
|
3234
|
+
logWarn("Native buildChunks binding is not available; using JS fallback emitter.");
|
|
3235
|
+
const rawArtifacts2 = buildJsFallbackArtifacts(plan, moduleOutputs);
|
|
3236
|
+
return emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifacts2);
|
|
3237
|
+
}
|
|
3238
|
+
const start = Date.now();
|
|
3239
|
+
const rawArtifacts = native.buildChunks(plan, opts?.casRoot, opts?.versionHash) ?? [];
|
|
3240
|
+
logInfo(`[Bundler] buildChunks completed in ${Date.now() - start}ms (native)`);
|
|
3241
|
+
return emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifacts);
|
|
3242
|
+
}
|
|
3243
|
+
function buildJsFallbackArtifacts(plan, moduleOutputs) {
|
|
3244
|
+
const artifacts = [];
|
|
3245
|
+
for (const chunk of plan.chunks) {
|
|
3246
|
+
const jsParts = [];
|
|
3247
|
+
const assets = [];
|
|
3248
|
+
for (const mod of chunk.modules) {
|
|
3249
|
+
const output = moduleOutputs.get(mod.id);
|
|
3250
|
+
if (output?.type === "js") {
|
|
3251
|
+
jsParts.push(`// ${mod.id}
|
|
3252
|
+
${output.code}`);
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
for (const assetPath of chunk.assets) {
|
|
3256
|
+
try {
|
|
3257
|
+
const data = import_fs11.default.readFileSync(assetPath);
|
|
3258
|
+
if (data.length < 4096) {
|
|
3259
|
+
const mime = "application/octet-stream";
|
|
3260
|
+
const inline = `data:${mime};base64,${data.toString("base64")}`;
|
|
3261
|
+
jsParts.push(`// ${assetPath}
|
|
3262
|
+
export const __ionify_asset = "${inline}";`);
|
|
3263
|
+
continue;
|
|
3264
|
+
}
|
|
3265
|
+
const hash = import_crypto4.default.createHash("sha256").update(data).digest("hex").slice(0, 16);
|
|
3266
|
+
const ext = import_path14.default.extname(assetPath) || ".bin";
|
|
3267
|
+
const fileName = `assets/${hash}${ext}`;
|
|
3268
|
+
assets.push({
|
|
3269
|
+
source: assetPath,
|
|
3270
|
+
file_name: fileName
|
|
3271
|
+
});
|
|
3272
|
+
} catch {
|
|
3273
|
+
const fileName = import_path14.default.basename(assetPath) || "asset";
|
|
3274
|
+
assets.push({
|
|
3275
|
+
source: assetPath,
|
|
3276
|
+
file_name: fileName
|
|
3277
|
+
});
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
const code = jsParts.length ? jsParts.join("\n\n") : `// Ionify JS fallback for ${chunk.id}
|
|
3281
|
+
export default {};`;
|
|
3282
|
+
artifacts.push({
|
|
3283
|
+
id: chunk.id,
|
|
3284
|
+
file_name: `${chunk.id}.fallback.js`,
|
|
3285
|
+
code,
|
|
3286
|
+
map: null,
|
|
3287
|
+
assets,
|
|
3288
|
+
code_bytes: Buffer.byteLength(code, "utf8"),
|
|
3289
|
+
map_bytes: 0
|
|
3290
|
+
});
|
|
3291
|
+
}
|
|
3292
|
+
return artifacts;
|
|
3293
|
+
}
|
|
3294
|
+
function normalizeNativeArtifact(raw) {
|
|
3295
|
+
const id = raw.id;
|
|
3296
|
+
if (!id) {
|
|
3297
|
+
throw new Error("Native artifact missing id");
|
|
3298
|
+
}
|
|
3299
|
+
const file_name = raw.file_name ?? `${id.replace(/::/g, ".")}.native.js`;
|
|
3300
|
+
const code = raw.code ?? "";
|
|
3301
|
+
const map = raw.map ?? null;
|
|
3302
|
+
const code_bytes = typeof raw.code_bytes === "number" ? raw.code_bytes : Buffer.byteLength(code, "utf8");
|
|
3303
|
+
const map_bytes = typeof raw.map_bytes === "number" ? raw.map_bytes : map ? Buffer.byteLength(map, "utf8") : 0;
|
|
3304
|
+
const assets = Array.isArray(raw.assets) ? raw.assets.map((asset) => ({
|
|
3305
|
+
source: asset.source,
|
|
3306
|
+
file_name: asset.file_name ?? asset.fileName ?? import_path14.default.basename(asset.source ?? "asset")
|
|
3307
|
+
})) : [];
|
|
3308
|
+
return { id, file_name, code, map, assets, code_bytes, map_bytes };
|
|
3309
|
+
}
|
|
3310
|
+
async function emitChunksFromArtifacts(outputDir, plan, moduleOutputs, rawArtifacts) {
|
|
3311
|
+
const chunkDir = import_path14.default.join(outputDir, "chunks");
|
|
3312
|
+
await import_fs11.default.promises.mkdir(chunkDir, { recursive: true });
|
|
3313
|
+
const assetsDir = import_path14.default.join(outputDir, "assets");
|
|
3314
|
+
await import_fs11.default.promises.mkdir(assetsDir, { recursive: true });
|
|
3315
|
+
const enableSourceMaps = process.env.IONIFY_SOURCEMAPS === "true";
|
|
3316
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
3317
|
+
for (const raw of rawArtifacts) {
|
|
3318
|
+
const artifact = normalizeNativeArtifact(raw);
|
|
3319
|
+
const baseId = artifact.id.split("::")[0] ?? artifact.id;
|
|
3320
|
+
const bucket = grouped.get(baseId);
|
|
3321
|
+
if (bucket) bucket.push(artifact);
|
|
3322
|
+
else grouped.set(baseId, [artifact]);
|
|
3323
|
+
}
|
|
3324
|
+
const buildStats = {};
|
|
3325
|
+
const results = [];
|
|
3326
|
+
for (const chunk of plan.chunks) {
|
|
3327
|
+
const artifacts = grouped.get(chunk.id);
|
|
3328
|
+
if (!artifacts || !artifacts.length) {
|
|
3329
|
+
throw new Error(`Native bundler did not emit artifacts for ${chunk.id}`);
|
|
3330
|
+
}
|
|
3331
|
+
const chunkOutDir = import_path14.default.join(chunkDir, chunk.id);
|
|
3332
|
+
await import_fs11.default.promises.mkdir(chunkOutDir, { recursive: true });
|
|
3333
|
+
artifacts.sort((a, b) => {
|
|
3334
|
+
if (a.id === chunk.id) return -1;
|
|
3335
|
+
if (b.id === chunk.id) return 1;
|
|
3336
|
+
return a.id.localeCompare(b.id);
|
|
3337
|
+
});
|
|
3338
|
+
const jsFiles = [];
|
|
3339
|
+
const cssFiles = [];
|
|
3340
|
+
const assetFiles = [];
|
|
3341
|
+
const assetWritten = /* @__PURE__ */ new Set();
|
|
3342
|
+
const copyAssets = async (assets) => {
|
|
3343
|
+
for (const asset of assets) {
|
|
3344
|
+
if (!asset?.source) continue;
|
|
3345
|
+
const relName = asset.file_name ?? import_path14.default.basename(asset.source);
|
|
3346
|
+
const assetFile = import_path14.default.join(outputDir, relName);
|
|
3347
|
+
if (assetWritten.has(assetFile)) continue;
|
|
3348
|
+
try {
|
|
3349
|
+
const data = await import_fs11.default.promises.readFile(asset.source);
|
|
3350
|
+
await import_fs11.default.promises.mkdir(import_path14.default.dirname(assetFile), { recursive: true });
|
|
3351
|
+
await import_fs11.default.promises.writeFile(assetFile, data);
|
|
3352
|
+
const rel = toPosix(import_path14.default.relative(outputDir, assetFile));
|
|
3353
|
+
buildStats[rel] = {
|
|
3354
|
+
bytes: data.length,
|
|
3355
|
+
emitter: "native",
|
|
3356
|
+
type: "asset"
|
|
3357
|
+
};
|
|
3358
|
+
assetFiles.push(rel);
|
|
3359
|
+
assetWritten.add(assetFile);
|
|
3360
|
+
} catch (err) {
|
|
3361
|
+
logWarn(`Failed to emit asset ${asset.source}: ${String(err)}`);
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
};
|
|
3365
|
+
const cssOrder = orderCssModules(chunk);
|
|
3366
|
+
let cssFileRel = null;
|
|
3367
|
+
if (cssOrder.length) {
|
|
3368
|
+
const seenCss = /* @__PURE__ */ new Set();
|
|
3369
|
+
const cssPieces = [];
|
|
3370
|
+
for (const cssPath of cssOrder) {
|
|
3371
|
+
let cssSource = moduleOutputs.get(cssPath)?.code;
|
|
3372
|
+
if (!cssSource && import_fs11.default.existsSync(cssPath)) {
|
|
3373
|
+
try {
|
|
3374
|
+
cssSource = await import_fs11.default.promises.readFile(cssPath, "utf8");
|
|
3375
|
+
} catch (err) {
|
|
3376
|
+
logWarn(`Failed to read CSS source ${cssPath}: ${String(err)}`);
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
if (!cssSource) continue;
|
|
3380
|
+
const minified = minifyCss(cssSource);
|
|
3381
|
+
if (!minified.length) continue;
|
|
3382
|
+
const key = getCacheKey(minified);
|
|
3383
|
+
if (seenCss.has(key)) continue;
|
|
3384
|
+
seenCss.add(key);
|
|
3385
|
+
cssPieces.push(minified);
|
|
3386
|
+
}
|
|
3387
|
+
if (cssPieces.length) {
|
|
3388
|
+
const combinedCss = cssPieces.join("\n");
|
|
3389
|
+
const cssHash = getCacheKey(combinedCss).slice(0, 8);
|
|
3390
|
+
const cssFileName = `assets/${chunk.id}.${cssHash}.css`;
|
|
3391
|
+
const cssFilePath = import_path14.default.join(outputDir, cssFileName);
|
|
3392
|
+
await import_fs11.default.promises.writeFile(cssFilePath, combinedCss, "utf8");
|
|
3393
|
+
cssFileRel = toPosix(import_path14.default.relative(outputDir, cssFilePath));
|
|
3394
|
+
buildStats[cssFileRel] = {
|
|
3395
|
+
bytes: Buffer.byteLength(combinedCss),
|
|
3396
|
+
emitter: "native",
|
|
3397
|
+
type: "css"
|
|
3398
|
+
};
|
|
3399
|
+
cssFiles.push(cssFileRel);
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
for (const artifact of artifacts) {
|
|
3403
|
+
const nativeFile = import_path14.default.join(chunkOutDir, artifact.file_name);
|
|
3404
|
+
let nativeCode = artifact.code;
|
|
3405
|
+
if (cssFileRel) {
|
|
3406
|
+
const absCss = import_path14.default.join(outputDir, cssFileRel);
|
|
3407
|
+
const relCss = toPosix(import_path14.default.relative(import_path14.default.dirname(nativeFile), absCss));
|
|
3408
|
+
const inject = `(()=>{const url=new URL(${JSON.stringify(
|
|
3409
|
+
relCss
|
|
3410
|
+
)},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);}})();`;
|
|
3411
|
+
nativeCode = `${inject}
|
|
3412
|
+
${nativeCode}`;
|
|
3413
|
+
}
|
|
3414
|
+
if (enableSourceMaps && artifact.map) {
|
|
3415
|
+
const mapFile = `${nativeFile}.map`;
|
|
3416
|
+
await import_fs11.default.promises.writeFile(mapFile, artifact.map, "utf8");
|
|
3417
|
+
nativeCode = `${nativeCode}
|
|
3418
|
+
//# sourceMappingURL=${import_path14.default.basename(mapFile)}`;
|
|
3419
|
+
const relMap = toPosix(import_path14.default.relative(outputDir, mapFile));
|
|
3420
|
+
buildStats[relMap] = {
|
|
3421
|
+
bytes: artifact.map_bytes,
|
|
3422
|
+
emitter: "native",
|
|
3423
|
+
type: "map"
|
|
3424
|
+
};
|
|
3425
|
+
jsFiles.push(relMap);
|
|
3426
|
+
}
|
|
3427
|
+
await import_fs11.default.promises.writeFile(nativeFile, nativeCode, "utf8");
|
|
3428
|
+
const relNative = toPosix(import_path14.default.relative(outputDir, nativeFile));
|
|
3429
|
+
buildStats[relNative] = {
|
|
3430
|
+
bytes: artifact.code_bytes,
|
|
3431
|
+
emitter: "native",
|
|
3432
|
+
type: "js"
|
|
3433
|
+
};
|
|
3434
|
+
jsFiles.push(relNative);
|
|
3435
|
+
await copyAssets(artifact.assets);
|
|
3436
|
+
}
|
|
3437
|
+
if (chunk.css.length) {
|
|
3438
|
+
const seenCss = /* @__PURE__ */ new Set();
|
|
3439
|
+
const cssSources = [];
|
|
3440
|
+
for (const cssPath of chunk.css) {
|
|
3441
|
+
if (!seenCss.add(cssPath)) continue;
|
|
3442
|
+
const output = moduleOutputs.get(cssPath);
|
|
3443
|
+
if (output?.type === "css") {
|
|
3444
|
+
cssSources.push(output.code);
|
|
3445
|
+
} else if (import_fs11.default.existsSync(cssPath)) {
|
|
3446
|
+
try {
|
|
3447
|
+
cssSources.push(await import_fs11.default.promises.readFile(cssPath, "utf8"));
|
|
3448
|
+
} catch (err) {
|
|
3449
|
+
logWarn(`Failed to read CSS source ${cssPath}: ${String(err)}`);
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
if (cssSources.length) {
|
|
3454
|
+
const combinedCss = cssSources.join("\n\n");
|
|
3455
|
+
const cssHash = import_crypto4.default.createHash("sha256").update(combinedCss).digest("hex").slice(0, 8);
|
|
3456
|
+
const cssFileName = `${chunk.id}.${cssHash}.native.css`;
|
|
3457
|
+
const cssFilePath = import_path14.default.join(chunkOutDir, cssFileName);
|
|
3458
|
+
await import_fs11.default.promises.writeFile(cssFilePath, combinedCss, "utf8");
|
|
3459
|
+
const relCss = import_path14.default.relative(outputDir, cssFilePath);
|
|
3460
|
+
buildStats[relCss] = {
|
|
3461
|
+
bytes: Buffer.byteLength(combinedCss),
|
|
3462
|
+
emitter: "native",
|
|
3463
|
+
type: "css"
|
|
3464
|
+
};
|
|
3465
|
+
cssFiles.push(relCss);
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
results.push({
|
|
3469
|
+
id: chunk.id,
|
|
3470
|
+
files: {
|
|
3471
|
+
js: jsFiles,
|
|
3472
|
+
css: cssFiles,
|
|
3473
|
+
assets: assetFiles
|
|
3474
|
+
}
|
|
3475
|
+
});
|
|
3476
|
+
}
|
|
3477
|
+
return { artifacts: results, stats: buildStats };
|
|
3478
|
+
}
|
|
3479
|
+
async function writeAssetsManifest(outputDir, artifacts) {
|
|
3480
|
+
const dir = import_path14.default.resolve(outputDir);
|
|
3481
|
+
await import_fs11.default.promises.mkdir(dir, { recursive: true });
|
|
3482
|
+
const file = import_path14.default.join(dir, "manifest.assets.json");
|
|
3483
|
+
const payload = {
|
|
3484
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3485
|
+
chunks: artifacts
|
|
3486
|
+
};
|
|
3487
|
+
await import_fs11.default.promises.writeFile(file, JSON.stringify(payload, null, 2), "utf8");
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
// src/core/worker/pool.ts
|
|
3491
|
+
init_cjs_shims();
|
|
3492
|
+
var import_worker_threads = require("worker_threads");
|
|
3493
|
+
var import_os = __toESM(require("os"), 1);
|
|
3494
|
+
var import_url5 = require("url");
|
|
3495
|
+
var workerPath = (0, import_url5.fileURLToPath)(new URL("./worker.cjs", importMetaUrl));
|
|
3496
|
+
var TransformWorkerPool = class {
|
|
3497
|
+
workers = [];
|
|
3498
|
+
queue = [];
|
|
3499
|
+
active = /* @__PURE__ */ new Map();
|
|
3500
|
+
callbacks = /* @__PURE__ */ new Map();
|
|
3501
|
+
waiters = [];
|
|
3502
|
+
pendingBytes = 0;
|
|
3503
|
+
closed = false;
|
|
3504
|
+
size;
|
|
3505
|
+
maxQueueBytes;
|
|
3506
|
+
constructor(options = {}) {
|
|
3507
|
+
const cpuDefault = Math.max(1, import_os.default.cpus().length - 1);
|
|
3508
|
+
this.size = Math.max(1, options.size ?? cpuDefault);
|
|
3509
|
+
this.maxQueueBytes = options.maxQueueBytes;
|
|
3510
|
+
for (let i = 0; i < this.size; i++) {
|
|
3511
|
+
this.spawnWorker();
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
spawnWorker() {
|
|
3515
|
+
const worker = new import_worker_threads.Worker(workerPath, { env: process.env });
|
|
3516
|
+
const id = worker.threadId;
|
|
3517
|
+
worker.on("message", (message) => {
|
|
3518
|
+
const item = this.active.get(id);
|
|
3519
|
+
if (item) {
|
|
3520
|
+
this.active.delete(id);
|
|
3521
|
+
this.pendingBytes -= item.size;
|
|
3522
|
+
this.resolveWaiters();
|
|
3523
|
+
}
|
|
3524
|
+
const cb = message ? this.callbacks.get(message.id) : void 0;
|
|
3525
|
+
if (message && cb) cb(message);
|
|
3526
|
+
if (message) this.callbacks.delete(message.id);
|
|
3527
|
+
this.dequeue(worker);
|
|
3528
|
+
});
|
|
3529
|
+
worker.on("error", (err) => {
|
|
3530
|
+
logWarn(`Transform worker error: ${String(err)}`);
|
|
3531
|
+
const item = this.active.get(id);
|
|
3532
|
+
if (item) {
|
|
3533
|
+
this.active.delete(id);
|
|
3534
|
+
this.queue.unshift(item);
|
|
3535
|
+
}
|
|
3536
|
+
this.spawnWorker();
|
|
3537
|
+
});
|
|
3538
|
+
worker.on("exit", (code) => {
|
|
3539
|
+
const item = this.active.get(id);
|
|
3540
|
+
if (item) {
|
|
3541
|
+
this.active.delete(id);
|
|
3542
|
+
this.queue.unshift(item);
|
|
3543
|
+
}
|
|
3544
|
+
if (!this.closed && code !== 0) {
|
|
3545
|
+
logWarn(`Transform worker exited unexpectedly (${code}), respawning`);
|
|
3546
|
+
this.spawnWorker();
|
|
3547
|
+
}
|
|
3548
|
+
});
|
|
3549
|
+
this.workers.push(worker);
|
|
3550
|
+
}
|
|
3551
|
+
dequeue(worker) {
|
|
3552
|
+
if (this.queue.length === 0) return;
|
|
3553
|
+
const item = this.queue.shift();
|
|
3554
|
+
this.active.set(worker.threadId, item);
|
|
3555
|
+
worker.postMessage(item.job);
|
|
3556
|
+
}
|
|
3557
|
+
resolveWaiters() {
|
|
3558
|
+
if (!this.maxQueueBytes) return;
|
|
3559
|
+
while (this.waiters.length && this.pendingBytes < this.maxQueueBytes) {
|
|
3560
|
+
const resolve = this.waiters.shift();
|
|
3561
|
+
resolve && resolve();
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3564
|
+
async run(job) {
|
|
3565
|
+
if (this.closed) {
|
|
3566
|
+
throw new Error("Worker pool already closed");
|
|
3567
|
+
}
|
|
3568
|
+
const size = Buffer.byteLength(job.code, "utf8");
|
|
3569
|
+
if (this.maxQueueBytes) {
|
|
3570
|
+
while (this.pendingBytes + size > this.maxQueueBytes) {
|
|
3571
|
+
await new Promise((resolve) => this.waiters.push(resolve));
|
|
3572
|
+
await new Promise((r) => setTimeout(r, 50 + Math.random() * 100));
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
this.pendingBytes += size;
|
|
3576
|
+
return new Promise((resolve) => {
|
|
3577
|
+
this.callbacks.set(job.id, resolve);
|
|
3578
|
+
const idleWorker = this.workers.find((w) => !this.active.has(w.threadId));
|
|
3579
|
+
const item = { job, size };
|
|
3580
|
+
if (idleWorker) {
|
|
3581
|
+
this.active.set(idleWorker.threadId, item);
|
|
3582
|
+
idleWorker.postMessage(job);
|
|
3583
|
+
} else {
|
|
3584
|
+
this.queue.push(item);
|
|
3585
|
+
}
|
|
3586
|
+
});
|
|
3587
|
+
}
|
|
3588
|
+
async runMany(jobs) {
|
|
3589
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
3590
|
+
await Promise.all(
|
|
3591
|
+
jobs.map(async (job) => {
|
|
3592
|
+
const res = await this.run(job);
|
|
3593
|
+
resultMap.set(job.id, res);
|
|
3594
|
+
})
|
|
3595
|
+
);
|
|
3596
|
+
return jobs.map((job) => resultMap.get(job.id));
|
|
3597
|
+
}
|
|
3598
|
+
async close() {
|
|
3599
|
+
this.closed = true;
|
|
3600
|
+
await Promise.all(this.workers.map((worker) => worker.terminate()));
|
|
3601
|
+
this.workers = [];
|
|
3602
|
+
this.queue = [];
|
|
3603
|
+
this.active.clear();
|
|
3604
|
+
this.callbacks.clear();
|
|
3605
|
+
this.waiters.forEach((resolve) => resolve());
|
|
3606
|
+
this.waiters = [];
|
|
3607
|
+
this.pendingBytes = 0;
|
|
3608
|
+
}
|
|
3609
|
+
async drain() {
|
|
3610
|
+
while (!this.closed && (this.queue.length || this.active.size)) {
|
|
3611
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
};
|
|
3615
|
+
|
|
3616
|
+
// src/cli/commands/build.ts
|
|
3617
|
+
init_cache();
|
|
3618
|
+
async function runBuildCommand(options = {}) {
|
|
3619
|
+
try {
|
|
3620
|
+
const config = await loadIonifyConfig();
|
|
3621
|
+
const optLevel = resolveOptimizationLevel(config?.optimizationLevel, {
|
|
3622
|
+
cliLevel: options.level,
|
|
3623
|
+
envLevel: process.env.IONIFY_OPTIMIZATION_LEVEL
|
|
3624
|
+
});
|
|
3625
|
+
let minifier;
|
|
3626
|
+
const parserMode = resolveParser(config, { envMode: process.env.IONIFY_PARSER });
|
|
3627
|
+
let treeshake;
|
|
3628
|
+
let scopeHoist;
|
|
3629
|
+
if (optLevel !== null) {
|
|
3630
|
+
const preset = getOptimizationPreset(optLevel);
|
|
3631
|
+
minifier = preset.minifier;
|
|
3632
|
+
treeshake = preset.treeshake;
|
|
3633
|
+
scopeHoist = preset.scopeHoist;
|
|
3634
|
+
logInfo(`Using optimization level ${optLevel} (preset)`);
|
|
3635
|
+
} else {
|
|
3636
|
+
minifier = resolveMinifier(config, { envVar: process.env.IONIFY_MINIFIER });
|
|
3637
|
+
treeshake = resolveTreeshake(config?.treeshake, {
|
|
3638
|
+
envMode: process.env.IONIFY_TREESHAKE,
|
|
3639
|
+
includeEnv: process.env.IONIFY_TREESHAKE_INCLUDE,
|
|
3640
|
+
excludeEnv: process.env.IONIFY_TREESHAKE_EXCLUDE
|
|
3641
|
+
});
|
|
3642
|
+
scopeHoist = resolveScopeHoist(config?.scopeHoist, {
|
|
3643
|
+
envMode: process.env.IONIFY_SCOPE_HOIST,
|
|
3644
|
+
inlineEnv: process.env.IONIFY_SCOPE_HOIST_INLINE,
|
|
3645
|
+
constantEnv: process.env.IONIFY_SCOPE_HOIST_CONST,
|
|
3646
|
+
combineEnv: process.env.IONIFY_SCOPE_HOIST_COMBINE
|
|
3647
|
+
});
|
|
3648
|
+
}
|
|
3649
|
+
applyMinifierEnv(minifier);
|
|
3650
|
+
applyParserEnv(parserMode);
|
|
3651
|
+
applyTreeshakeEnv(treeshake);
|
|
3652
|
+
applyScopeHoistEnv(scopeHoist);
|
|
3653
|
+
const entries = config?.entry ? [config.entry.startsWith("/") ? import_path15.default.join(process.cwd(), config.entry) : import_path15.default.resolve(process.cwd(), config.entry)] : void 0;
|
|
3654
|
+
if (entries) {
|
|
3655
|
+
logInfo(`Build entries: ${entries.join(", ")}`);
|
|
3656
|
+
} else {
|
|
3657
|
+
logInfo(`No entries in config, planner will infer from graph`);
|
|
3658
|
+
}
|
|
3659
|
+
const pluginNames = Array.isArray(config?.plugins) ? config.plugins.map((p) => typeof p === "string" ? p : p?.name).filter((name) => typeof name === "string" && name.length > 0) : void 0;
|
|
3660
|
+
const rawVersionInputs = {
|
|
3661
|
+
parserMode,
|
|
3662
|
+
minifier,
|
|
3663
|
+
treeshake,
|
|
3664
|
+
scopeHoist,
|
|
3665
|
+
plugins: pluginNames,
|
|
3666
|
+
entry: entries ?? null,
|
|
3667
|
+
cssOptions: config?.css,
|
|
3668
|
+
assetOptions: config?.assets ?? config?.asset
|
|
3669
|
+
};
|
|
3670
|
+
const configHash = computeGraphVersion(rawVersionInputs);
|
|
3671
|
+
logInfo(`[Build] Version hash: ${configHash}`);
|
|
3672
|
+
process.env.IONIFY_CONFIG_HASH = configHash;
|
|
3673
|
+
if (native?.initAstCache) {
|
|
3674
|
+
const versionHash = JSON.stringify(rawVersionInputs);
|
|
3675
|
+
native.initAstCache(versionHash);
|
|
3676
|
+
logInfo(`AST cache initialized with version hash`);
|
|
3677
|
+
}
|
|
3678
|
+
const plan = await generateBuildPlan(entries, rawVersionInputs);
|
|
3679
|
+
const outDir = options.outDir || "dist";
|
|
3680
|
+
const moduleHashes = /* @__PURE__ */ new Map();
|
|
3681
|
+
for (const chunk of plan.chunks) {
|
|
3682
|
+
for (const mod of chunk.modules) {
|
|
3683
|
+
if (mod.hash) {
|
|
3684
|
+
moduleHashes.set(mod.id, mod.hash);
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
const uniqueModules = /* @__PURE__ */ new Set();
|
|
3689
|
+
for (const chunk of plan.chunks) {
|
|
3690
|
+
for (const mod of chunk.modules) uniqueModules.add(mod.id);
|
|
3691
|
+
}
|
|
3692
|
+
const moduleOutputs = /* @__PURE__ */ new Map();
|
|
3693
|
+
const pool = new TransformWorkerPool();
|
|
3694
|
+
try {
|
|
3695
|
+
const jobs = Array.from(uniqueModules).filter((filePath) => import_fs12.default.existsSync(filePath)).map((filePath) => {
|
|
3696
|
+
const code = import_fs12.default.readFileSync(filePath, "utf8");
|
|
3697
|
+
const sourceHash = getCacheKey(code);
|
|
3698
|
+
const moduleHash = moduleHashes.get(filePath) ?? sourceHash;
|
|
3699
|
+
const cacheKey = getCacheKey(`build-worker:v1:${import_path15.default.extname(filePath)}:${moduleHash}:${filePath}`);
|
|
3700
|
+
const cached = readCache(cacheKey);
|
|
3701
|
+
if (cached) {
|
|
3702
|
+
try {
|
|
3703
|
+
const parsed = JSON.parse(cached.toString("utf8"));
|
|
3704
|
+
if (parsed?.code) {
|
|
3705
|
+
const transformedHash = getCacheKey(parsed.code);
|
|
3706
|
+
moduleHashes.set(filePath, transformedHash);
|
|
3707
|
+
const casRoot2 = import_path15.default.join(process.cwd(), ".ionify", "cas");
|
|
3708
|
+
const cacheDir = getCasArtifactPath(casRoot2, configHash, transformedHash);
|
|
3709
|
+
if (!import_fs12.default.existsSync(import_path15.default.join(cacheDir, "transformed.js"))) {
|
|
3710
|
+
import_fs12.default.mkdirSync(cacheDir, { recursive: true });
|
|
3711
|
+
import_fs12.default.writeFileSync(import_path15.default.join(cacheDir, "transformed.js"), parsed.code, "utf8");
|
|
3712
|
+
if (parsed.map) {
|
|
3713
|
+
import_fs12.default.writeFileSync(import_path15.default.join(cacheDir, "transformed.js.map"), parsed.map, "utf8");
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
moduleOutputs.set(filePath, { code: parsed.code, type: parsed.type ?? "js" });
|
|
3717
|
+
return null;
|
|
3718
|
+
}
|
|
3719
|
+
} catch {
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
return {
|
|
3723
|
+
id: filePath,
|
|
3724
|
+
filePath,
|
|
3725
|
+
ext: import_path15.default.extname(filePath),
|
|
3726
|
+
code,
|
|
3727
|
+
cacheKey
|
|
3728
|
+
};
|
|
3729
|
+
}).filter((job) => !!job);
|
|
3730
|
+
const results = await pool.runMany(
|
|
3731
|
+
jobs.map((job) => ({
|
|
3732
|
+
id: job.id,
|
|
3733
|
+
filePath: job.filePath,
|
|
3734
|
+
ext: job.ext,
|
|
3735
|
+
code: job.code
|
|
3736
|
+
}))
|
|
3737
|
+
);
|
|
3738
|
+
for (let i = 0; i < results.length; i++) {
|
|
3739
|
+
const result = results[i];
|
|
3740
|
+
const job = jobs[i];
|
|
3741
|
+
if (result.error) {
|
|
3742
|
+
throw new Error(`Transform failed for ${result.filePath}: ${result.error}`);
|
|
3743
|
+
}
|
|
3744
|
+
const payload = JSON.stringify({ code: result.code, map: result.map, type: result.type });
|
|
3745
|
+
writeCache(job.cacheKey, Buffer.from(payload));
|
|
3746
|
+
const transformedHash = getCacheKey(result.code);
|
|
3747
|
+
const moduleHash = transformedHash;
|
|
3748
|
+
const casRoot2 = import_path15.default.join(process.cwd(), ".ionify", "cas");
|
|
3749
|
+
const versionHash = configHash;
|
|
3750
|
+
const cacheDir = getCasArtifactPath(casRoot2, versionHash, moduleHash);
|
|
3751
|
+
import_fs12.default.mkdirSync(cacheDir, { recursive: true });
|
|
3752
|
+
import_fs12.default.writeFileSync(import_path15.default.join(cacheDir, "transformed.js"), result.code, "utf8");
|
|
3753
|
+
if (result.map) {
|
|
3754
|
+
import_fs12.default.writeFileSync(import_path15.default.join(cacheDir, "transformed.js.map"), result.map, "utf8");
|
|
3755
|
+
}
|
|
3756
|
+
moduleHashes.set(job.filePath, moduleHash);
|
|
3757
|
+
for (const chunk of plan.chunks) {
|
|
3758
|
+
for (const mod of chunk.modules) {
|
|
3759
|
+
if (mod.id === job.filePath) {
|
|
3760
|
+
mod.hash = moduleHash;
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
moduleOutputs.set(result.filePath, { code: result.code, type: result.type });
|
|
3765
|
+
}
|
|
3766
|
+
} finally {
|
|
3767
|
+
await pool.close();
|
|
3768
|
+
}
|
|
3769
|
+
for (const chunk of plan.chunks) {
|
|
3770
|
+
for (const mod of chunk.modules) {
|
|
3771
|
+
const updatedHash = moduleHashes.get(mod.id);
|
|
3772
|
+
if (updatedHash) {
|
|
3773
|
+
mod.hash = updatedHash;
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
const absOutDir = import_path15.default.resolve(outDir);
|
|
3778
|
+
const casRoot = import_path15.default.join(process.cwd(), ".ionify", "cas");
|
|
3779
|
+
const { artifacts, stats } = await emitChunks(absOutDir, plan, moduleOutputs, {
|
|
3780
|
+
casRoot,
|
|
3781
|
+
versionHash: configHash
|
|
3782
|
+
});
|
|
3783
|
+
await writeBuildManifest(absOutDir, plan, artifacts);
|
|
3784
|
+
await writeAssetsManifest(absOutDir, artifacts);
|
|
3785
|
+
await import_fs12.default.promises.writeFile(
|
|
3786
|
+
import_path15.default.join(absOutDir, "build.stats.json"),
|
|
3787
|
+
JSON.stringify(stats, null, 2),
|
|
3788
|
+
"utf8"
|
|
3789
|
+
);
|
|
3790
|
+
logInfo(`Build plan generated \u2192 ${import_path15.default.join(absOutDir, "manifest.json")}`);
|
|
3791
|
+
logInfo(`Entries: ${plan.entries.length}, Chunks: ${plan.chunks.length}`);
|
|
3792
|
+
logInfo(`Modules transformed: ${moduleOutputs.size}`);
|
|
3793
|
+
} catch (err) {
|
|
3794
|
+
logError("ionify build failed", err);
|
|
3795
|
+
throw err;
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
|
|
200
3799
|
// src/cli/index.ts
|
|
201
3800
|
var program = new import_commander.Command();
|
|
202
|
-
program.name("ionify").description("Ionify \u2013 Instant, Intelligent, Unified Build Engine").version("0.1
|
|
203
|
-
program.command("dev").description("Start Ionify development server
|
|
204
|
-
|
|
205
|
-
|
|
3801
|
+
program.name("ionify").description("Ionify \u2013 Instant, Intelligent, Unified Build Engine").version("0.0.1");
|
|
3802
|
+
program.command("dev").description("Start Ionify development server").option("-p, --port <port>", "Port to run the server on", "5173").action(async (options) => {
|
|
3803
|
+
try {
|
|
3804
|
+
const port = parseInt(options.port, 10);
|
|
3805
|
+
await startDevServer({ port });
|
|
3806
|
+
} catch (err) {
|
|
3807
|
+
logError("Failed to start dev server", err);
|
|
3808
|
+
process.exit(1);
|
|
3809
|
+
}
|
|
206
3810
|
});
|
|
207
|
-
program.command("build").description("
|
|
208
|
-
|
|
209
|
-
|
|
3811
|
+
program.command("build").description("Create production build using Ionify bundler").option("-o, --out-dir <dir>", "Output directory", "dist").action(async (options) => {
|
|
3812
|
+
try {
|
|
3813
|
+
await runBuildCommand({ outDir: options.outDir });
|
|
3814
|
+
} catch {
|
|
3815
|
+
process.exit(1);
|
|
3816
|
+
}
|
|
210
3817
|
});
|
|
211
|
-
program.command("
|
|
3818
|
+
program.command("migrate").description("Migrate from Vite/Rollup config (not implemented yet)").action(() => logInfo("Migrate command coming soon..."));
|
|
3819
|
+
program.command("analyze").description("Inspect cached dependency graph stats").option("--json", "Output summary as JSON").option("-l, --limit <count>", "Limit list outputs", "10").action(async (options) => {
|
|
212
3820
|
try {
|
|
213
|
-
|
|
3821
|
+
const limit = parseInt(options.limit ?? "10", 10);
|
|
3822
|
+
await runAnalyzeCommand({ json: !!options.json, limit: Number.isFinite(limit) ? limit : 10 });
|
|
214
3823
|
} catch (err) {
|
|
215
|
-
logError(
|
|
3824
|
+
logError("Analyzer failed", err);
|
|
216
3825
|
process.exit(1);
|
|
217
3826
|
}
|
|
218
3827
|
});
|
|
219
|
-
program.parse();
|
|
3828
|
+
program.parse(process.argv);
|