@powerhousedao/builder-tools 6.2.0-dev.2 → 6.2.0-dev.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +90 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +687 -62
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -6
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { exec, execSync } from "node:child_process";
|
|
3
|
-
import
|
|
2
|
+
import { exec, execSync, spawn } from "node:child_process";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
import fs, { createReadStream, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
5
|
+
import path, { dirname, join, resolve } from "node:path";
|
|
4
6
|
import { readFile, writeFile } from "node:fs/promises";
|
|
5
|
-
import path, { join, resolve } from "node:path";
|
|
6
7
|
import { cwd } from "node:process";
|
|
7
8
|
import { DEFAULT_CONNECT_CONFIG, buildRuntimeConfig, deepMerge, loadConnectEnv, normalizeBasePath, phConnectRuntimeConfigSchema, powerhousePackageSchema, setConnectEnv } from "@powerhousedao/shared/connect";
|
|
8
9
|
import { getConfig } from "@powerhousedao/config/node";
|
|
@@ -10,12 +11,462 @@ import tailwind from "@tailwindcss/vite";
|
|
|
10
11
|
import react from "@vitejs/plugin-react";
|
|
11
12
|
import { createLogger, esmExternalRequirePlugin, loadEnv, searchForWorkspaceRoot } from "vite";
|
|
12
13
|
import { createHtmlPlugin } from "vite-plugin-html";
|
|
14
|
+
import MagicString from "magic-string";
|
|
13
15
|
//#region connect-utils/constants.ts
|
|
14
16
|
const EXTERNAL_PACKAGES_IMPORT = "PH:EXTERNAL_PACKAGES";
|
|
15
17
|
const IMPORT_SCRIPT_FILE = "external-packages.js";
|
|
16
18
|
const LOCAL_PACKAGE_ID = "ph:local-package";
|
|
17
19
|
const PH_DIR_NAME = ".ph";
|
|
18
20
|
//#endregion
|
|
21
|
+
//#region connect-utils/externalize-vendor.ts
|
|
22
|
+
/**
|
|
23
|
+
* Prebuild the heavy, stable Connect dependencies into a static ESM "vendor"
|
|
24
|
+
* bundle so the dev server never runs (or holds) the dependency optimizer /
|
|
25
|
+
* module graph for them.
|
|
26
|
+
*
|
|
27
|
+
* The reactor-project preview dev-optimizes a large UI graph (Connect itself,
|
|
28
|
+
* design-system, document-engineering, reactor-browser, …) every session —
|
|
29
|
+
* ~1–2 GB resident — even though those libs don't change; only the project's
|
|
30
|
+
* editors do. This module builds them ONCE with `vite build` (in a throwaway
|
|
31
|
+
* subprocess that exits, freeing the build's peak memory): `vite build` handles
|
|
32
|
+
* CSS/asset imports, web workers, and WASM (Connect's in-browser PGlite ships
|
|
33
|
+
* all three), and a multi-entry build with `preserveEntrySignatures: 'strict'`
|
|
34
|
+
* dedupes shared code into shared chunks. Entries for CJS deps re-export the
|
|
35
|
+
* module's named API explicitly (so `import { createRoot }` works); ESM deps
|
|
36
|
+
* use `export *`. The build runs under `base = VENDOR_URL_PREFIX` so emitted
|
|
37
|
+
* asset URLs (`new URL(…, import.meta.url)` for .wasm/.data/workers) resolve to
|
|
38
|
+
* the dev middleware, not the SPA root.
|
|
39
|
+
*
|
|
40
|
+
* The React family stays EXTERNAL (see `VENDOR_EXTERNAL`); `esmExternalRequirePlugin`
|
|
41
|
+
* owns that externalization and rewrites CJS `require("react")` → import. Vendor
|
|
42
|
+
* chunks keep bare React imports, which the dev import map resolves to Vite's
|
|
43
|
+
* pre-bundled React (`devReactImportmapPlugin`) — one React instance across the
|
|
44
|
+
* vendor, the project's editors, and CDN-loaded editors.
|
|
45
|
+
*
|
|
46
|
+
* `devReactImportmapPlugin` consumes the result: it externalizes these
|
|
47
|
+
* specifiers in the long-lived dev server, points the page import map at the
|
|
48
|
+
* vendor URLs, and serves the bundle. With Connect vendored, the dev server only
|
|
49
|
+
* processes the project's own `main` + local package; HMR for them is unaffected.
|
|
50
|
+
*/
|
|
51
|
+
/**
|
|
52
|
+
* The stable Connect libraries worth prebuilding. The React family is NOT here —
|
|
53
|
+
* it's externalized from the build (see `VENDOR_EXTERNAL`) so the vendor shares
|
|
54
|
+
* the dev server's single React instance via the import map.
|
|
55
|
+
*/
|
|
56
|
+
const DEFAULT_VENDOR_INCLUDE = [
|
|
57
|
+
"@powerhousedao/connect",
|
|
58
|
+
"document-model",
|
|
59
|
+
"zod",
|
|
60
|
+
"@powerhousedao/design-system/connect",
|
|
61
|
+
"@powerhousedao/reactor-browser",
|
|
62
|
+
"@powerhousedao/document-engineering"
|
|
63
|
+
];
|
|
64
|
+
/**
|
|
65
|
+
* React-family specifiers kept external to the vendor build. The vendor's chunks
|
|
66
|
+
* emit bare imports for these; `devReactImportmapPlugin`'s import map resolves
|
|
67
|
+
* them to Vite's pre-bundled React, so there is exactly one React instance.
|
|
68
|
+
*/
|
|
69
|
+
const VENDOR_EXTERNAL = [
|
|
70
|
+
"react",
|
|
71
|
+
"react-dom",
|
|
72
|
+
"react-dom/client",
|
|
73
|
+
"react/jsx-runtime",
|
|
74
|
+
"react/jsx-dev-runtime",
|
|
75
|
+
"ph-bundled-packages-virtual"
|
|
76
|
+
];
|
|
77
|
+
/** URL prefix the vendor bundle is served under by the dev middleware. */
|
|
78
|
+
const VENDOR_URL_PREFIX = "/__vendor__/";
|
|
79
|
+
/**
|
|
80
|
+
* Build the prebuilt vendor (once, in a throwaway subprocess) and return its dir
|
|
81
|
+
* + import map. Idempotent: reuses a cached vendor built for the same dep set.
|
|
82
|
+
* Returns null on any failure (caller falls back to a normal dev server).
|
|
83
|
+
*/
|
|
84
|
+
async function prebuildConnectVendor(options) {
|
|
85
|
+
const include = expandIncludeSubpaths(options.dirname, options.include ?? DEFAULT_VENDOR_INCLUDE);
|
|
86
|
+
const external = options.external ?? VENDOR_EXTERNAL;
|
|
87
|
+
const vendorDir = options.vendorDir ?? join(options.dirname, "node_modules/.ph-vendor");
|
|
88
|
+
const importMapPath = join(vendorDir, "import-map.json");
|
|
89
|
+
const versionDigest = resolveVersionDigest(options.dirname, [...include, ...external]);
|
|
90
|
+
try {
|
|
91
|
+
const hit = readCacheHit(importMapPath, include, external, versionDigest);
|
|
92
|
+
if (hit) return {
|
|
93
|
+
vendorDir,
|
|
94
|
+
imports: hit
|
|
95
|
+
};
|
|
96
|
+
const lockDir = `${vendorDir}.lock`;
|
|
97
|
+
const lock = acquireLock(lockDir);
|
|
98
|
+
if (!lock) {
|
|
99
|
+
const imports = await waitForCacheHit(importMapPath, include, external, versionDigest, lockDir);
|
|
100
|
+
return imports ? {
|
|
101
|
+
vendorDir,
|
|
102
|
+
imports
|
|
103
|
+
} : null;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const raced = readCacheHit(importMapPath, include, external, versionDigest);
|
|
107
|
+
if (raced) return {
|
|
108
|
+
vendorDir,
|
|
109
|
+
imports: raced
|
|
110
|
+
};
|
|
111
|
+
const imports = await buildVendorAtomic(options.dirname, vendorDir, include, external, versionDigest);
|
|
112
|
+
return imports ? {
|
|
113
|
+
vendorDir,
|
|
114
|
+
imports
|
|
115
|
+
} : null;
|
|
116
|
+
} finally {
|
|
117
|
+
releaseLock(lock);
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
if (options.errorRef) options.errorRef.message = err instanceof Error ? err.message : String(err);
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function readCacheHit(importMapPath, include, external, versionDigest) {
|
|
125
|
+
if (!existsSync(importMapPath)) return null;
|
|
126
|
+
try {
|
|
127
|
+
const cached = JSON.parse(readFileSync(importMapPath, "utf8"));
|
|
128
|
+
if (sameSet(cached.include, include) && sameSet(cached.external, external) && cached.versionDigest === versionDigest) return cached.imports;
|
|
129
|
+
} catch {}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
function resolveVersionDigest(dirname, specs) {
|
|
133
|
+
const seen = /* @__PURE__ */ new Map();
|
|
134
|
+
for (const spec of specs) {
|
|
135
|
+
const { pkg } = parsePkg(spec);
|
|
136
|
+
if (seen.has(pkg)) continue;
|
|
137
|
+
let version = "missing";
|
|
138
|
+
try {
|
|
139
|
+
const pkgRoot = realpathSync(join(dirname, "node_modules", pkg));
|
|
140
|
+
const meta = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8"));
|
|
141
|
+
version = String(meta.version ?? "unknown");
|
|
142
|
+
} catch {}
|
|
143
|
+
seen.set(pkg, version);
|
|
144
|
+
}
|
|
145
|
+
const h = createHash("sha256");
|
|
146
|
+
const workerHash = createHash("sha256").update(VENDOR_BUILD_WORKER).digest("hex");
|
|
147
|
+
h.update(`worker:${workerHash}\n`);
|
|
148
|
+
for (const pkg of [...seen.keys()].sort()) h.update(`${pkg}@${seen.get(pkg)}\n`);
|
|
149
|
+
return h.digest("hex").slice(0, 16);
|
|
150
|
+
}
|
|
151
|
+
function sameSet(a, b) {
|
|
152
|
+
if (!a || a.length !== b.length) return false;
|
|
153
|
+
const s = new Set(a);
|
|
154
|
+
return b.every((x) => s.has(x));
|
|
155
|
+
}
|
|
156
|
+
const LOCK_STALE_MS = 5 * 6e4;
|
|
157
|
+
const LOCK_HEARTBEAT_MS = 6e4;
|
|
158
|
+
const ownerFile = (lockDir) => join(lockDir, "owner");
|
|
159
|
+
function lockIsStale(lockDir) {
|
|
160
|
+
try {
|
|
161
|
+
return Date.now() - statSync(ownerFile(lockDir)).mtimeMs > LOCK_STALE_MS;
|
|
162
|
+
} catch {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function acquireLock(lockDir) {
|
|
167
|
+
let made = false;
|
|
168
|
+
try {
|
|
169
|
+
mkdirSync(lockDir);
|
|
170
|
+
made = true;
|
|
171
|
+
} catch {
|
|
172
|
+
if (lockIsStale(lockDir)) try {
|
|
173
|
+
rmSync(lockDir, {
|
|
174
|
+
recursive: true,
|
|
175
|
+
force: true
|
|
176
|
+
});
|
|
177
|
+
mkdirSync(lockDir);
|
|
178
|
+
made = true;
|
|
179
|
+
} catch {}
|
|
180
|
+
}
|
|
181
|
+
if (!made) return null;
|
|
182
|
+
const token = `${process.pid}-${randomUUID()}`;
|
|
183
|
+
writeFileSync(ownerFile(lockDir), token);
|
|
184
|
+
const timer = setInterval(() => {
|
|
185
|
+
try {
|
|
186
|
+
if (readFileSync(ownerFile(lockDir), "utf8") === token) writeFileSync(ownerFile(lockDir), token);
|
|
187
|
+
else clearInterval(timer);
|
|
188
|
+
} catch {
|
|
189
|
+
clearInterval(timer);
|
|
190
|
+
}
|
|
191
|
+
}, LOCK_HEARTBEAT_MS);
|
|
192
|
+
timer.unref();
|
|
193
|
+
return {
|
|
194
|
+
dir: lockDir,
|
|
195
|
+
token,
|
|
196
|
+
timer
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function releaseLock(lock) {
|
|
200
|
+
clearInterval(lock.timer);
|
|
201
|
+
try {
|
|
202
|
+
if (readFileSync(ownerFile(lock.dir), "utf8") === lock.token) rmSync(lock.dir, {
|
|
203
|
+
recursive: true,
|
|
204
|
+
force: true
|
|
205
|
+
});
|
|
206
|
+
} catch {}
|
|
207
|
+
}
|
|
208
|
+
async function waitForCacheHit(importMapPath, include, external, versionDigest, lockDir) {
|
|
209
|
+
const deadline = Date.now() + LOCK_STALE_MS;
|
|
210
|
+
while (Date.now() < deadline) {
|
|
211
|
+
const hit = readCacheHit(importMapPath, include, external, versionDigest);
|
|
212
|
+
if (hit) return hit;
|
|
213
|
+
if (!existsSync(lockDir)) return readCacheHit(importMapPath, include, external, versionDigest);
|
|
214
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
function importTarget(value) {
|
|
219
|
+
if (typeof value === "string") return value;
|
|
220
|
+
if (!value || typeof value !== "object") return null;
|
|
221
|
+
const o = value;
|
|
222
|
+
for (const c of [
|
|
223
|
+
"browser",
|
|
224
|
+
"import",
|
|
225
|
+
"module",
|
|
226
|
+
"default"
|
|
227
|
+
]) if (c in o) {
|
|
228
|
+
const t = importTarget(o[c]);
|
|
229
|
+
if (t) return t;
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
function parsePkg(spec) {
|
|
234
|
+
if (spec.startsWith("@")) {
|
|
235
|
+
const parts = spec.split("/");
|
|
236
|
+
return {
|
|
237
|
+
pkg: parts.slice(0, 2).join("/"),
|
|
238
|
+
sub: parts.slice(2).join("/")
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
const i = spec.indexOf("/");
|
|
242
|
+
return i === -1 ? {
|
|
243
|
+
pkg: spec,
|
|
244
|
+
sub: ""
|
|
245
|
+
} : {
|
|
246
|
+
pkg: spec.slice(0, i),
|
|
247
|
+
sub: spec.slice(i + 1)
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Connect imports the heavy libs by subpath too (e.g.
|
|
252
|
+
* `@powerhousedao/design-system/connect/toast`, `zod/v4/core`). Those bare
|
|
253
|
+
* imports need their own import-map entry, so expand each listed spec to its
|
|
254
|
+
* package's concrete (non-wildcard, JS) subpath exports that share the spec's
|
|
255
|
+
* prefix. Unresolvable / CSS / JSON targets are skipped. Failures leave the
|
|
256
|
+
* original spec untouched.
|
|
257
|
+
*/
|
|
258
|
+
function expandIncludeSubpaths(dirname, include) {
|
|
259
|
+
const out = new Set(include);
|
|
260
|
+
for (const spec of include) {
|
|
261
|
+
const { pkg, sub } = parsePkg(spec);
|
|
262
|
+
let exp;
|
|
263
|
+
let pkgRoot;
|
|
264
|
+
try {
|
|
265
|
+
pkgRoot = realpathSync(join(dirname, "node_modules", pkg));
|
|
266
|
+
exp = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8")).exports;
|
|
267
|
+
} catch {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (!exp || typeof exp !== "object") continue;
|
|
271
|
+
const expMap = exp;
|
|
272
|
+
const prefixKey = sub ? `./${sub}` : ".";
|
|
273
|
+
for (const key of Object.keys(expMap)) {
|
|
274
|
+
if (key.includes("*") || key === "./package.json") continue;
|
|
275
|
+
if (/\.(css|json|scss)$/.test(key)) continue;
|
|
276
|
+
if (!(prefixKey === "." ? key === "." || key.startsWith("./") : key === prefixKey || key.startsWith(`${prefixKey}/`))) continue;
|
|
277
|
+
const target = importTarget(expMap[key]);
|
|
278
|
+
if (!target || /\.(css|json|scss)$/.test(target)) continue;
|
|
279
|
+
if (!existsSync(join(pkgRoot, target))) continue;
|
|
280
|
+
out.add(key === "." ? pkg : pkg + key.slice(1));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return [...out];
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Build the vendor into a unique temp dir, then atomically swap it into place,
|
|
287
|
+
* so a concurrent reader never sees a partial bundle or import-map.json. Runs
|
|
288
|
+
* the build in a throwaway subprocess (its peak memory is reclaimed on exit);
|
|
289
|
+
* the parent augments the import map with the version digest and does the swap.
|
|
290
|
+
* Returns the published import map, or null on failure.
|
|
291
|
+
*/
|
|
292
|
+
async function buildVendorAtomic(dirname$1, vendorDir, include, external, versionDigest) {
|
|
293
|
+
const parent = dirname(vendorDir);
|
|
294
|
+
mkdirSync(parent, { recursive: true });
|
|
295
|
+
const tmpDir = mkdtempSync(join(parent, ".ph-vendor.tmp-"));
|
|
296
|
+
try {
|
|
297
|
+
const { ok, stderr } = await runBuildWorker(dirname$1, tmpDir, include, external);
|
|
298
|
+
const tmpMap = join(tmpDir, "import-map.json");
|
|
299
|
+
if (!ok || !existsSync(tmpMap)) {
|
|
300
|
+
const detail = stderr.trim();
|
|
301
|
+
throw new Error(`vendor build failed${detail ? `:\n${detail}` : " (no output captured)"}`);
|
|
302
|
+
}
|
|
303
|
+
const meta = JSON.parse(readFileSync(tmpMap, "utf8"));
|
|
304
|
+
meta.versionDigest = versionDigest;
|
|
305
|
+
writeFileSync(tmpMap, JSON.stringify(meta, null, 2));
|
|
306
|
+
const oldDir = `${vendorDir}.old-${process.pid}-${Date.now()}`;
|
|
307
|
+
if (existsSync(vendorDir)) renameSync(vendorDir, oldDir);
|
|
308
|
+
renameSync(tmpDir, vendorDir);
|
|
309
|
+
rmSync(oldDir, {
|
|
310
|
+
recursive: true,
|
|
311
|
+
force: true
|
|
312
|
+
});
|
|
313
|
+
return meta.imports;
|
|
314
|
+
} catch (err) {
|
|
315
|
+
rmSync(tmpDir, {
|
|
316
|
+
recursive: true,
|
|
317
|
+
force: true
|
|
318
|
+
});
|
|
319
|
+
throw err;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Spawn a throwaway worker that generates one entry per specifier (CJS deps get
|
|
324
|
+
* explicit named re-exports, ESM deps `export *`), `vite build`s them into the
|
|
325
|
+
* given out dir, and writes the import map. Captures stdout/stderr so a failure
|
|
326
|
+
* is debuggable; the normal path stays quiet.
|
|
327
|
+
*/
|
|
328
|
+
function runBuildWorker(dirname, outDir, include, external) {
|
|
329
|
+
const workerPath = join(outDir, "build-worker.mjs");
|
|
330
|
+
writeFileSync(workerPath, VENDOR_BUILD_WORKER);
|
|
331
|
+
return new Promise((resolve) => {
|
|
332
|
+
const child = spawn(process.execPath, [
|
|
333
|
+
workerPath,
|
|
334
|
+
dirname,
|
|
335
|
+
outDir,
|
|
336
|
+
JSON.stringify(include),
|
|
337
|
+
VENDOR_URL_PREFIX,
|
|
338
|
+
JSON.stringify(external)
|
|
339
|
+
], {
|
|
340
|
+
cwd: dirname,
|
|
341
|
+
stdio: [
|
|
342
|
+
"ignore",
|
|
343
|
+
"pipe",
|
|
344
|
+
"pipe"
|
|
345
|
+
]
|
|
346
|
+
});
|
|
347
|
+
let stderr = "";
|
|
348
|
+
child.stdout.on("data", (d) => {
|
|
349
|
+
stderr += String(d);
|
|
350
|
+
});
|
|
351
|
+
child.stderr.on("data", (d) => {
|
|
352
|
+
stderr += String(d);
|
|
353
|
+
});
|
|
354
|
+
child.on("exit", (code) => resolve({
|
|
355
|
+
ok: code === 0,
|
|
356
|
+
stderr
|
|
357
|
+
}));
|
|
358
|
+
child.on("error", (e) => resolve({
|
|
359
|
+
ok: false,
|
|
360
|
+
stderr: String(e)
|
|
361
|
+
}));
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* The vendor build worker, written to disk and run as a subprocess. Self-contained
|
|
366
|
+
* (only Node + the project's `vite`). argv: dirname, vendorDir, includeJSON, urlPrefix.
|
|
367
|
+
*/
|
|
368
|
+
const VENDOR_BUILD_WORKER = `
|
|
369
|
+
import { createRequire } from 'node:module';
|
|
370
|
+
import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
371
|
+
import { join, isAbsolute } from 'node:path';
|
|
372
|
+
import { fileURLToPath } from 'node:url';
|
|
373
|
+
const [dirname, vendorDir, includeJSON, urlPrefix, externalJSON] = process.argv.slice(2);
|
|
374
|
+
const include = JSON.parse(includeJSON);
|
|
375
|
+
const external = JSON.parse(externalJSON ?? '[]');
|
|
376
|
+
const externalSet = new Set(external);
|
|
377
|
+
const reqProj = createRequire(join(dirname, 'noop.js'));
|
|
378
|
+
const { build, esmExternalRequirePlugin } = await import(reqProj.resolve('vite'));
|
|
379
|
+
const srcDir = join(vendorDir, '.entries');
|
|
380
|
+
mkdirSync(srcDir, { recursive: true });
|
|
381
|
+
const entryName = (spec) => spec.replace(/[^\\w]+/g, '_');
|
|
382
|
+
const RESERVED = new Set('enum void null function in instanceof typeof new delete do if else return switch case break continue for while this true false class const let var default export import extends super with yield debugger finally throw try catch await implements interface package private protected public static eval arguments'.split(' '));
|
|
383
|
+
const input = {};
|
|
384
|
+
for (const spec of include) {
|
|
385
|
+
const name = entryName(spec);
|
|
386
|
+
let src;
|
|
387
|
+
try {
|
|
388
|
+
// Import the bare spec (not reqProj.resolve(spec)) so Node picks the same
|
|
389
|
+
// import/browser-condition module the build resolves — resolving first can
|
|
390
|
+
// pick a CJS sibling whose default-export shape differs from the ESM build.
|
|
391
|
+
const ns = await import(spec);
|
|
392
|
+
// Only fall back to re-exporting from default when the module exposes NO
|
|
393
|
+
// top-level named exports (a true CJS-interop module). If it does (e.g. zod
|
|
394
|
+
// exposes \`z\`), \`export *\` captures them; destructuring default would miss
|
|
395
|
+
// top-level names that aren't keys of the default object.
|
|
396
|
+
const named = Object.keys(ns).filter((k) => k !== 'default');
|
|
397
|
+
const cjs = named.length === 0 && ns.default && typeof ns.default === 'object';
|
|
398
|
+
if (cjs) {
|
|
399
|
+
const names = Object.keys(ns.default).filter((k) => k !== 'default' && k !== '__esModule' && /^[A-Za-z_$][\\w$]*$/.test(k));
|
|
400
|
+
const plain = names.filter((n) => !RESERVED.has(n));
|
|
401
|
+
const reserved = names.filter((n) => RESERVED.has(n));
|
|
402
|
+
src = 'import d from ' + JSON.stringify(spec) + ';\\nexport default d;\\n'
|
|
403
|
+
+ (plain.length ? 'export const { ' + plain.join(', ') + ' } = d;\\n' : '');
|
|
404
|
+
// reserved words are invalid as const-binding names but valid as export
|
|
405
|
+
// aliases (export { x as enum }).
|
|
406
|
+
reserved.forEach((n, i) => {
|
|
407
|
+
src += 'const __r' + i + ' = d[' + JSON.stringify(n) + '];\\nexport { __r' + i + ' as ' + n + ' };\\n';
|
|
408
|
+
});
|
|
409
|
+
} else {
|
|
410
|
+
src = 'export * from ' + JSON.stringify(spec) + ';\\n'
|
|
411
|
+
+ (('default' in ns) ? 'export { default } from ' + JSON.stringify(spec) + ';\\n' : '');
|
|
412
|
+
}
|
|
413
|
+
} catch {
|
|
414
|
+
src = 'export * from ' + JSON.stringify(spec) + ';\\n';
|
|
415
|
+
}
|
|
416
|
+
const file = join(srcDir, name + '.js');
|
|
417
|
+
writeFileSync(file, src);
|
|
418
|
+
input[name] = file;
|
|
419
|
+
}
|
|
420
|
+
// Prefer the bundler's browser-condition-aware resolution; fall back to resolving
|
|
421
|
+
// from the worker (real install path) only for Rolldown's realpath-anchoring bug.
|
|
422
|
+
const phResolveCache = new Map();
|
|
423
|
+
const phVendorResolve = {
|
|
424
|
+
name: 'ph-vendor-resolve', enforce: 'pre',
|
|
425
|
+
async resolveId(source, importer, options) {
|
|
426
|
+
if (externalSet.has(source)) return null;
|
|
427
|
+
if (source[0] === '\\0' || source[0] === '.' || isAbsolute(source)) return null;
|
|
428
|
+
if (source.startsWith('node:') || source.startsWith('data:')) return null;
|
|
429
|
+
let viaBundler = null;
|
|
430
|
+
try { viaBundler = await this.resolve(source, importer, { ...options, skipSelf: true }); } catch {}
|
|
431
|
+
if (viaBundler) return viaBundler;
|
|
432
|
+
if (phResolveCache.has(source)) return phResolveCache.get(source);
|
|
433
|
+
let resolved = null;
|
|
434
|
+
try { resolved = fileURLToPath(import.meta.resolve(source)); } catch {}
|
|
435
|
+
phResolveCache.set(source, resolved);
|
|
436
|
+
return resolved;
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
await build({
|
|
440
|
+
root: dirname, configFile: false, logLevel: 'error',
|
|
441
|
+
// Emit asset URLs (pglite .wasm/.data, workers via \`new URL(..., import.meta.url)\`)
|
|
442
|
+
// under the vendor prefix so they resolve to the middleware, not the SPA root.
|
|
443
|
+
base: urlPrefix,
|
|
444
|
+
define: { 'process.env.NODE_ENV': '"development"' },
|
|
445
|
+
// phVendorResolve runs first (pre) so bare specifiers resolve from the worker,
|
|
446
|
+
// not the importing module's realpath. esmExternalRequirePlugin then OWNS
|
|
447
|
+
// externalization of \`external\` (react family + dev virtuals): it marks them
|
|
448
|
+
// external AND rewrites CJS require("react") to import (Rolldown keeps bare
|
|
449
|
+
// require() for external ids, which fails in the browser). It must be the sole
|
|
450
|
+
// externalizer — also listing them in rollupOptions.external prevents the
|
|
451
|
+
// require() rewrite (see vite-config.ts).
|
|
452
|
+
plugins: [phVendorResolve, esmExternalRequirePlugin({ external })],
|
|
453
|
+
// pglite (and any worker dep) ships web workers; emit them as ES-module asset
|
|
454
|
+
// chunks into the vendor so the middleware can serve them.
|
|
455
|
+
worker: { format: 'es' },
|
|
456
|
+
build: {
|
|
457
|
+
outDir: vendorDir, emptyOutDir: false, minify: false, target: 'esnext', cssCodeSplit: true,
|
|
458
|
+
rollupOptions: {
|
|
459
|
+
input, preserveEntrySignatures: 'strict',
|
|
460
|
+
output: { format: 'es', entryFileNames: '[name].js', chunkFileNames: 'chunks/[name]-[hash].js', assetFileNames: 'assets/[name]-[hash][extname]' },
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
rmSync(srcDir, { recursive: true, force: true });
|
|
465
|
+
const imports = {};
|
|
466
|
+
for (const spec of include) imports[spec] = urlPrefix + entryName(spec) + '.js';
|
|
467
|
+
writeFileSync(join(vendorDir, 'import-map.json'), JSON.stringify({ include, external, imports }, null, 2));
|
|
468
|
+
`;
|
|
469
|
+
//#endregion
|
|
19
470
|
//#region connect-utils/helpers.ts
|
|
20
471
|
const DEFAULT_CONNECT_OUTDIR = ".ph/connect-build/dist/";
|
|
21
472
|
function resolveViteConfigPath(options) {
|
|
@@ -246,6 +697,54 @@ const runtimeConfigSchema = {
|
|
|
246
697
|
connect: phConnectRuntimeConfigSchema
|
|
247
698
|
}
|
|
248
699
|
};
|
|
700
|
+
const RESOLVED_VIRTUAL_ID = "\0virtual:ph-bundled-packages-virtual";
|
|
701
|
+
const BUNDLED_PACKAGES_DEV_URL = "@id/" + RESOLVED_VIRTUAL_ID.replace("\0", "__x00__");
|
|
702
|
+
function readBundledPackageVersion(projectRoot, name) {
|
|
703
|
+
try {
|
|
704
|
+
const raw = fs.readFileSync(path.join(projectRoot, "node_modules", name, "package.json"), "utf-8");
|
|
705
|
+
const pkg = JSON.parse(raw);
|
|
706
|
+
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
707
|
+
} catch {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
function makeRegisterModule(packages, projectRoot) {
|
|
712
|
+
if (packages.length === 0) return "export default () => {};\n";
|
|
713
|
+
const imports = [];
|
|
714
|
+
const calls = [];
|
|
715
|
+
packages.forEach((name, i) => {
|
|
716
|
+
const moduleName = `pkg${i}`;
|
|
717
|
+
const version = readBundledPackageVersion(projectRoot, name);
|
|
718
|
+
imports.push(`import * as ${moduleName} from ${JSON.stringify(name)};`);
|
|
719
|
+
imports.push(`import ${JSON.stringify(`${name}/style.css`)};`);
|
|
720
|
+
calls.push(` pm.addLocalPackage(${JSON.stringify(name)}, ${moduleName}, ${JSON.stringify(version)});`);
|
|
721
|
+
});
|
|
722
|
+
return `${imports.join("\n")}\n\nexport default function register(pm) {\n${calls.join("\n")}\n};\n`;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Emits a virtual module `ph-bundled-packages-virtual` whose default export
|
|
726
|
+
* is a `register(packageManager)` function. When called at runtime (from
|
|
727
|
+
* Connect's bootstrap), it registers each bundled package with the package
|
|
728
|
+
* manager the same way Common/Vetra are registered — meaning they work
|
|
729
|
+
* offline without the registry being reachable.
|
|
730
|
+
*
|
|
731
|
+
* When the list is empty, the module exports a no-op function so Connect's
|
|
732
|
+
* bootstrap code can always import it unconditionally.
|
|
733
|
+
*/
|
|
734
|
+
function phBundledPackagesPlugin(options) {
|
|
735
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
736
|
+
const moduleSource = makeRegisterModule(options.packages, projectRoot);
|
|
737
|
+
return {
|
|
738
|
+
name: "vite-plugin-ph-bundled-packages",
|
|
739
|
+
enforce: "pre",
|
|
740
|
+
resolveId(id) {
|
|
741
|
+
if (id === "ph-bundled-packages-virtual") return RESOLVED_VIRTUAL_ID;
|
|
742
|
+
},
|
|
743
|
+
load(id) {
|
|
744
|
+
if (id === "\0virtual:ph-bundled-packages-virtual") return moduleSource;
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
}
|
|
249
748
|
//#endregion
|
|
250
749
|
//#region connect-utils/vite-plugins/dev-external-react.ts
|
|
251
750
|
const REACT_DEPS = [
|
|
@@ -257,9 +756,36 @@ const REACT_DEPS = [
|
|
|
257
756
|
];
|
|
258
757
|
const SHIM_PATH = "__ph/dev-react-shim/";
|
|
259
758
|
const VITE_DEPS_PATH = "node_modules/.vite/deps";
|
|
759
|
+
const VENDOR_MIME = {
|
|
760
|
+
".css": "text/css",
|
|
761
|
+
".map": "application/json",
|
|
762
|
+
".json": "application/json",
|
|
763
|
+
".wasm": "application/wasm",
|
|
764
|
+
".data": "application/octet-stream",
|
|
765
|
+
".woff2": "font/woff2",
|
|
766
|
+
".woff": "font/woff",
|
|
767
|
+
".ttf": "font/ttf",
|
|
768
|
+
".svg": "image/svg+xml",
|
|
769
|
+
".png": "image/png",
|
|
770
|
+
".jpg": "image/jpeg",
|
|
771
|
+
".jpeg": "image/jpeg",
|
|
772
|
+
".gif": "image/gif",
|
|
773
|
+
".webp": "image/webp"
|
|
774
|
+
};
|
|
775
|
+
const VENDOR_MODE = process.env.PH_CONNECT_EXTERNALIZE_VENDOR === "1";
|
|
776
|
+
const VENDOR_EXTRA = (process.env.PH_CONNECT_VENDOR_EXTRA ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
777
|
+
const HEAVY_LIBS = [...DEFAULT_VENDOR_INCLUDE, ...VENDOR_EXTRA];
|
|
260
778
|
function withBase(base, p) {
|
|
261
779
|
return `${base}${p}`.replace(/\/{2,}/g, "/");
|
|
262
780
|
}
|
|
781
|
+
function externalizePlugin(externals) {
|
|
782
|
+
try {
|
|
783
|
+
const mod = createRequire(import.meta.url)("vite-plugin-externalize-dependencies");
|
|
784
|
+
return (mod.default ?? mod)({ externals });
|
|
785
|
+
} catch {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
263
789
|
/**
|
|
264
790
|
* Dev-only sibling of `esmExternalRequirePlugin`. The build path externalizes
|
|
265
791
|
* React via Rolldown so an importmap hands the same React instance to both
|
|
@@ -268,7 +794,7 @@ function withBase(base, p) {
|
|
|
268
794
|
* export — so a CDN editor that does `import { lazy } from "react"` would
|
|
269
795
|
* fail with "no named export 'lazy'".
|
|
270
796
|
*
|
|
271
|
-
* This plugin:
|
|
797
|
+
* This plugin owns the page import map. It always:
|
|
272
798
|
* 1. Forces React into `optimizeDeps.include` so the optimizer always knows
|
|
273
799
|
* about it.
|
|
274
800
|
* 2. Serves a shim per React module at a stable URL. Each shim imports
|
|
@@ -276,14 +802,46 @@ function withBase(base, p) {
|
|
|
276
802
|
* and re-exports React's named members so editors importing
|
|
277
803
|
* `{ lazy }`, `{ jsx }`, etc. work.
|
|
278
804
|
* 3. Rewrites the page importmap to point at those shim URLs.
|
|
805
|
+
*
|
|
806
|
+
* When PH_CONNECT_EXTERNALIZE_VENDOR=1 it additionally prebuilds the heavy
|
|
807
|
+
* stable libs (design-system, reactor-browser, …) into a static vendor bundle,
|
|
808
|
+
* externalizes them from the dev server, serves the bundle, and adds them to
|
|
809
|
+
* the SAME import map. The vendor's React imports stay bare → resolve through
|
|
810
|
+
* the React shims above → one React instance across Connect, the vendor, and
|
|
811
|
+
* CDN editors.
|
|
279
812
|
*/
|
|
280
|
-
function devReactImportmapPlugin() {
|
|
813
|
+
async function devReactImportmapPlugin(projectRoot = process.cwd()) {
|
|
281
814
|
let namedExports = /* @__PURE__ */ new Map();
|
|
282
815
|
let base = "/";
|
|
283
|
-
|
|
816
|
+
let vendor = null;
|
|
817
|
+
if (VENDOR_MODE) {
|
|
818
|
+
const errorRef = {};
|
|
819
|
+
vendor = await prebuildConnectVendor({
|
|
820
|
+
dirname: projectRoot,
|
|
821
|
+
include: HEAVY_LIBS,
|
|
822
|
+
errorRef
|
|
823
|
+
});
|
|
824
|
+
if (!vendor) {
|
|
825
|
+
const detail = errorRef.message ? `: ${errorRef.message}` : "";
|
|
826
|
+
console.warn(`[connect] PH_CONNECT_EXTERNALIZE_VENDOR set but vendor prebuild failed; falling back to dep-optimizing the heavy libs${detail}`);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
const ext = vendor ? externalizePlugin([(id) => id in vendor.imports]) : void 0;
|
|
830
|
+
if (vendor && !ext) console.warn("[connect] PH_CONNECT_EXTERNALIZE_VENDOR set and vendor prebuilt, but vite-plugin-externalize-dependencies is not installed; falling back to dep-optimizing the heavy libs (install it to enable the vendor)");
|
|
831
|
+
const vendorActive = !!(vendor && ext);
|
|
832
|
+
const main = {
|
|
284
833
|
name: "ph-dev-react-importmap",
|
|
285
834
|
apply: "serve",
|
|
286
|
-
config
|
|
835
|
+
config(cfg) {
|
|
836
|
+
cfg.optimizeDeps ??= {};
|
|
837
|
+
const include = new Set(cfg.optimizeDeps.include ?? []);
|
|
838
|
+
REACT_DEPS.forEach((d) => include.add(d));
|
|
839
|
+
if (vendorActive) {
|
|
840
|
+
HEAVY_LIBS.forEach((d) => include.delete(d));
|
|
841
|
+
cfg.optimizeDeps.exclude = [...new Set([...cfg.optimizeDeps.exclude ?? [], ...HEAVY_LIBS])];
|
|
842
|
+
}
|
|
843
|
+
cfg.optimizeDeps.include = [...include];
|
|
844
|
+
},
|
|
287
845
|
configResolved(config) {
|
|
288
846
|
base = config.base;
|
|
289
847
|
},
|
|
@@ -297,6 +855,26 @@ function devReactImportmapPlugin() {
|
|
|
297
855
|
return [id, []];
|
|
298
856
|
}
|
|
299
857
|
}));
|
|
858
|
+
if (vendorActive) {
|
|
859
|
+
const vendorDir = vendor.vendorDir;
|
|
860
|
+
server.middlewares.use(VENDOR_URL_PREFIX, (req, res, next) => {
|
|
861
|
+
const name = (req.url ?? "").split("?")[0].replace(/^\/+/, "");
|
|
862
|
+
const file = path.join(vendorDir, name);
|
|
863
|
+
const rel = path.relative(vendorDir, file);
|
|
864
|
+
if (!name || rel.startsWith("..") || path.isAbsolute(rel)) return next();
|
|
865
|
+
const stream = createReadStream(file);
|
|
866
|
+
stream.on("error", () => {
|
|
867
|
+
if (!res.headersSent) next();
|
|
868
|
+
});
|
|
869
|
+
stream.once("open", () => {
|
|
870
|
+
const ext = file.slice(file.lastIndexOf("."));
|
|
871
|
+
res.setHeader("Content-Type", VENDOR_MIME[ext] ?? "text/javascript");
|
|
872
|
+
const hashed = name.startsWith("chunks/") || name.startsWith("assets/");
|
|
873
|
+
res.setHeader("Cache-Control", hashed ? "public, max-age=31536000, immutable" : "no-cache");
|
|
874
|
+
stream.pipe(res);
|
|
875
|
+
});
|
|
876
|
+
});
|
|
877
|
+
}
|
|
300
878
|
const shimPrefixes = [withBase(base, SHIM_PATH), `/${SHIM_PATH}`];
|
|
301
879
|
server.middlewares.use((req, res, next) => {
|
|
302
880
|
const prefix = shimPrefixes.find((p) => req.url?.startsWith(p));
|
|
@@ -324,10 +902,103 @@ function devReactImportmapPlugin() {
|
|
|
324
902
|
if (!browserHash) return;
|
|
325
903
|
const shimPrefix = withBase(base, SHIM_PATH);
|
|
326
904
|
const imports = Object.fromEntries(REACT_DEPS.map((id) => [id, `${shimPrefix}${id}.js?v=${browserHash}`]));
|
|
905
|
+
if (vendorActive) {
|
|
906
|
+
Object.assign(imports, vendor.imports);
|
|
907
|
+
if (vendor.imports["@powerhousedao/connect"]) imports["ph-bundled-packages-virtual"] = withBase(base, BUNDLED_PACKAGES_DEV_URL);
|
|
908
|
+
}
|
|
327
909
|
return html.replace(/<script type="importmap">[\s\S]*?<\/script>/, `<script type="importmap">${JSON.stringify({ imports }, null, 2)}<\/script>`);
|
|
328
910
|
}
|
|
329
911
|
}
|
|
330
912
|
};
|
|
913
|
+
return vendorActive ? [ext, main] : main;
|
|
914
|
+
}
|
|
915
|
+
//#endregion
|
|
916
|
+
//#region connect-utils/vite-plugins/dynamic-base.ts
|
|
917
|
+
/**
|
|
918
|
+
* Placeholder base used when Connect is built in dynamic-base mode. The Vite
|
|
919
|
+
* `base` option is set to this token; this plugin rewrites it in the emitted
|
|
920
|
+
* output so the effective base is resolved at serve time from a global instead
|
|
921
|
+
* of being baked at build time.
|
|
922
|
+
*
|
|
923
|
+
* Trailing slash matches `normalizeBasePath` output, so the token sits in the
|
|
924
|
+
* same syntactic position as a concrete base would.
|
|
925
|
+
*/
|
|
926
|
+
const DYNAMIC_BASE_PLACEHOLDER = "/__PH_DYNAMIC_BASE__/";
|
|
927
|
+
/**
|
|
928
|
+
* Global the runtime (ph-clint proxy) must set before the entry bundle loads.
|
|
929
|
+
* Value is the normalized deploy base, e.g. "/myagent/" or "/". The JS rewrite
|
|
930
|
+
* below resolves all asset / lazy-chunk / BASE_URL references against it.
|
|
931
|
+
*/
|
|
932
|
+
const RUNTIME_GLOBAL = "globalThis.__PH_DYNAMIC_BASE__";
|
|
933
|
+
const BASE_EXPR = `(${RUNTIME_GLOBAL}||"/")`;
|
|
934
|
+
/**
|
|
935
|
+
* Worker scope has its own global object; the proxy injects the runtime global
|
|
936
|
+
* into the main-thread `<head>` only, so a worker chunk's rewritten references
|
|
937
|
+
* would fall back to "/" and fetch siblings (pglite .data/.wasm) without the
|
|
938
|
+
* deploy prefix. Prepended to worker chunks, this defines the global in worker
|
|
939
|
+
* scope from the worker's own script URL: the worker loads from
|
|
940
|
+
* `<base>assets/<name>-<hash>.js`, so stripping `assets/<file>` off
|
|
941
|
+
* `self.location.pathname` yields the same trailing-slash base the main thread
|
|
942
|
+
* holds ("/myagent/" or "/").
|
|
943
|
+
*/
|
|
944
|
+
const WORKER_PRELUDE = `${RUNTIME_GLOBAL}=self.location.pathname.replace(/assets\\/[^/]*$/,"");\n`;
|
|
945
|
+
const PLACEHOLDER_LITERAL = new RegExp(`(["'\`])${escapeForRegExp(DYNAMIC_BASE_PLACEHOLDER)}((?:(?!\\1)[^$])*)\\1`, "g");
|
|
946
|
+
/**
|
|
947
|
+
* Rewrites the placeholder base in emitted JS chunks to a runtime expression so
|
|
948
|
+
* one built `dist/connect` serves under any subpath. Rolldown-native: per-match
|
|
949
|
+
* MagicString edits in `renderChunk`, no AST splicing (the SWC byte-offset
|
|
950
|
+
* splicing in vite-plugin-dynamic-base corrupts Rolldown chunks). Each edited
|
|
951
|
+
* chunk returns a hires sourcemap that the bundler composes into the chunk's
|
|
952
|
+
* map chain, so the emitted `.map` files track the rewrite (and the worker
|
|
953
|
+
* prelude's line shift).
|
|
954
|
+
*
|
|
955
|
+
* What gets rewritten in JS, all of which emit the base as a quoted string
|
|
956
|
+
* literal beginning with the placeholder:
|
|
957
|
+
* - asset URLs (`new URL("/__PH_DYNAMIC_BASE__/assets/x.png", ...)`)
|
|
958
|
+
* - dynamic-import / lazy-chunk preload URLs
|
|
959
|
+
* - the inlined `import.meta.env.BASE_URL` (drives Connect's router basename
|
|
960
|
+
* and BASE_URL-relative fetches such as `${BASE_URL}ph-packages.json`)
|
|
961
|
+
*
|
|
962
|
+
* A literal `"/__PH_DYNAMIC_BASE__/foo"` becomes `((globalThis.__PH_DYNAMIC_BASE__||"/")+"foo")`;
|
|
963
|
+
* a bare `"/__PH_DYNAMIC_BASE__/"` (the BASE_URL value) becomes `(globalThis.__PH_DYNAMIC_BASE__||"/")`.
|
|
964
|
+
*
|
|
965
|
+
* HTML is left untouched: the entry `<script>`/`<link>` tags keep the literal
|
|
966
|
+
* placeholder so the proxy substitutes it with the concrete base at serve time
|
|
967
|
+
* (the same proxy also sets the runtime global for the JS rewrite). See the
|
|
968
|
+
* plugin's module doc / report for the exact serve-time contract.
|
|
969
|
+
*
|
|
970
|
+
* CSS `url(...)` references resolve relative to the stylesheet's own URL, so
|
|
971
|
+
* they need no rewrite once the stylesheet itself is loaded from the right
|
|
972
|
+
* prefix (which the HTML substitution handles).
|
|
973
|
+
*/
|
|
974
|
+
function connectDynamicBasePlugin(options = {}) {
|
|
975
|
+
return {
|
|
976
|
+
name: "ph-connect-dynamic-base",
|
|
977
|
+
enforce: "post",
|
|
978
|
+
renderChunk(code, chunk) {
|
|
979
|
+
if (!code.includes("/__PH_DYNAMIC_BASE__/")) return null;
|
|
980
|
+
const s = new MagicString(code);
|
|
981
|
+
for (const match of code.matchAll(PLACEHOLDER_LITERAL)) {
|
|
982
|
+
const rest = match[2];
|
|
983
|
+
s.overwrite(match.index, match.index + match[0].length, rest.length === 0 ? BASE_EXPR : `(${BASE_EXPR}+${JSON.stringify(rest)})`);
|
|
984
|
+
}
|
|
985
|
+
if (options.forWorker) {
|
|
986
|
+
s.prepend(WORKER_PRELUDE);
|
|
987
|
+
this.info(`dynamic-base: worker prelude prepended to ${chunk.fileName}`);
|
|
988
|
+
}
|
|
989
|
+
if (!s.hasChanged()) return null;
|
|
990
|
+
return {
|
|
991
|
+
code: s.toString(),
|
|
992
|
+
map: s.generateMap({ hires: true })
|
|
993
|
+
};
|
|
994
|
+
},
|
|
995
|
+
generateBundle(_options, bundle) {
|
|
996
|
+
for (const file of Object.values(bundle)) if ((file.type === "chunk" ? file.code : file.fileName.endsWith(".css") && typeof file.source === "string" ? file.source : void 0)?.includes("/__PH_DYNAMIC_BASE__/")) this.error(`dynamic-base: unrewritten placeholder ${DYNAMIC_BASE_PLACEHOLDER} remains in ${file.fileName}`);
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
function escapeForRegExp(s) {
|
|
1001
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
331
1002
|
}
|
|
332
1003
|
//#endregion
|
|
333
1004
|
//#region connect-utils/vite-plugins/favicon.ts
|
|
@@ -365,56 +1036,6 @@ function connectFaviconPlugin() {
|
|
|
365
1036
|
};
|
|
366
1037
|
}
|
|
367
1038
|
//#endregion
|
|
368
|
-
//#region connect-utils/vite-plugins/ph-bundled-packages.ts
|
|
369
|
-
const VIRTUAL_ID = "ph-bundled-packages-virtual";
|
|
370
|
-
const RESOLVED_VIRTUAL_ID = "\0virtual:" + VIRTUAL_ID;
|
|
371
|
-
function readBundledPackageVersion(projectRoot, name) {
|
|
372
|
-
try {
|
|
373
|
-
const raw = fs.readFileSync(path.join(projectRoot, "node_modules", name, "package.json"), "utf-8");
|
|
374
|
-
const pkg = JSON.parse(raw);
|
|
375
|
-
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
376
|
-
} catch {
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
function makeRegisterModule(packages, projectRoot) {
|
|
381
|
-
if (packages.length === 0) return "export default () => {};\n";
|
|
382
|
-
const imports = [];
|
|
383
|
-
const calls = [];
|
|
384
|
-
packages.forEach((name, i) => {
|
|
385
|
-
const moduleName = `pkg${i}`;
|
|
386
|
-
const version = readBundledPackageVersion(projectRoot, name);
|
|
387
|
-
imports.push(`import * as ${moduleName} from ${JSON.stringify(name)};`);
|
|
388
|
-
imports.push(`import ${JSON.stringify(`${name}/style.css`)};`);
|
|
389
|
-
calls.push(` pm.addLocalPackage(${JSON.stringify(name)}, ${moduleName}, ${JSON.stringify(version)});`);
|
|
390
|
-
});
|
|
391
|
-
return `${imports.join("\n")}\n\nexport default function register(pm) {\n${calls.join("\n")}\n};\n`;
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* Emits a virtual module `ph-bundled-packages-virtual` whose default export
|
|
395
|
-
* is a `register(packageManager)` function. When called at runtime (from
|
|
396
|
-
* Connect's bootstrap), it registers each bundled package with the package
|
|
397
|
-
* manager the same way Common/Vetra are registered — meaning they work
|
|
398
|
-
* offline without the registry being reachable.
|
|
399
|
-
*
|
|
400
|
-
* When the list is empty, the module exports a no-op function so Connect's
|
|
401
|
-
* bootstrap code can always import it unconditionally.
|
|
402
|
-
*/
|
|
403
|
-
function phBundledPackagesPlugin(options) {
|
|
404
|
-
const projectRoot = options.projectRoot ?? process.cwd();
|
|
405
|
-
const moduleSource = makeRegisterModule(options.packages, projectRoot);
|
|
406
|
-
return {
|
|
407
|
-
name: "vite-plugin-ph-bundled-packages",
|
|
408
|
-
enforce: "pre",
|
|
409
|
-
resolveId(id) {
|
|
410
|
-
if (id === VIRTUAL_ID) return RESOLVED_VIRTUAL_ID;
|
|
411
|
-
},
|
|
412
|
-
load(id) {
|
|
413
|
-
if (id === RESOLVED_VIRTUAL_ID) return moduleSource;
|
|
414
|
-
}
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
//#endregion
|
|
418
1039
|
//#region connect-utils/vite-plugins/ph-config.ts
|
|
419
1040
|
function readProjectPackageInfo(projectRoot) {
|
|
420
1041
|
if (!projectRoot) return null;
|
|
@@ -670,7 +1291,7 @@ function getConnectBaseViteConfig(options) {
|
|
|
670
1291
|
return {
|
|
671
1292
|
configFile: false,
|
|
672
1293
|
mode,
|
|
673
|
-
base: connectBasePath ? normalizeBasePath(connectBasePath) : void 0,
|
|
1294
|
+
base: options.dynamicBase ? DYNAMIC_BASE_PLACEHOLDER : connectBasePath ? normalizeBasePath(connectBasePath) : void 0,
|
|
674
1295
|
server: {
|
|
675
1296
|
watch: { ignored: ["**/backup-documents/**", "**/.ph/**"] },
|
|
676
1297
|
fs: { allow: [searchForWorkspaceRoot(options.dirname), ...linkedRoots] }
|
|
@@ -704,16 +1325,20 @@ function getConnectBaseViteConfig(options) {
|
|
|
704
1325
|
packages: localPackagesFromConfig,
|
|
705
1326
|
projectRoot: options.dirname
|
|
706
1327
|
}),
|
|
707
|
-
devReactImportmapPlugin(),
|
|
1328
|
+
devReactImportmapPlugin(options.dirname),
|
|
708
1329
|
...plugins,
|
|
709
1330
|
esmExternalRequirePlugin({ external: reactExternal }),
|
|
710
|
-
connectFaviconPlugin()
|
|
1331
|
+
connectFaviconPlugin(),
|
|
1332
|
+
...options.dynamicBase ? [connectDynamicBasePlugin()] : []
|
|
711
1333
|
],
|
|
712
|
-
worker: {
|
|
1334
|
+
worker: {
|
|
1335
|
+
format: "es",
|
|
1336
|
+
...options.dynamicBase ? { plugins: () => [connectDynamicBasePlugin({ forWorker: true })] } : {}
|
|
1337
|
+
},
|
|
713
1338
|
build: { sourcemap: true }
|
|
714
1339
|
};
|
|
715
1340
|
}
|
|
716
1341
|
//#endregion
|
|
717
|
-
export { DEFAULT_CONNECT_OUTDIR, EXTERNAL_PACKAGES_IMPORT, IMPORT_SCRIPT_FILE, LOCAL_PACKAGE_ID, PH_DIR_NAME, RUNTIME_CONFIG_SCHEMA_ID, RUNTIME_CONFIG_SCHEMA_URL, appendToHtmlHead, backupIndexHtml, copyConnect, ensureNodeVersion, getConnectBaseViteConfig, getConnectHtmlTags, makeImportScriptFromPackages, phConfigPlugin, prependToHtmlHead, readJsonFile, removeBase64EnvValues, resolveConnectBundle, resolveConnectPackageJson, resolveConnectPublicDir, resolvePackage, resolveViteConfigPath, runShellScriptPlugin, runTsc, runtimeConfigSchema, stripVersionFromPackage };
|
|
1342
|
+
export { DEFAULT_CONNECT_OUTDIR, DEFAULT_VENDOR_INCLUDE, DYNAMIC_BASE_PLACEHOLDER, EXTERNAL_PACKAGES_IMPORT, IMPORT_SCRIPT_FILE, LOCAL_PACKAGE_ID, PH_DIR_NAME, RUNTIME_CONFIG_SCHEMA_ID, RUNTIME_CONFIG_SCHEMA_URL, VENDOR_EXTERNAL, VENDOR_URL_PREFIX, appendToHtmlHead, backupIndexHtml, connectDynamicBasePlugin, copyConnect, ensureNodeVersion, getConnectBaseViteConfig, getConnectHtmlTags, makeImportScriptFromPackages, phConfigPlugin, prebuildConnectVendor, prependToHtmlHead, readJsonFile, removeBase64EnvValues, resolveConnectBundle, resolveConnectPackageJson, resolveConnectPublicDir, resolvePackage, resolveViteConfigPath, runShellScriptPlugin, runTsc, runtimeConfigSchema, stripVersionFromPackage };
|
|
718
1343
|
|
|
719
1344
|
//# sourceMappingURL=index.mjs.map
|