@powerhousedao/builder-tools 6.2.0-dev.5 → 6.2.0-dev.50
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 +110 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +879 -78
- package/dist/index.mjs.map +1 -1
- package/package.json +28 -7
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
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";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import MagicString from "magic-string";
|
|
4
8
|
import { readFile, writeFile } from "node:fs/promises";
|
|
5
|
-
import path, { join, resolve } from "node:path";
|
|
6
9
|
import { cwd } from "node:process";
|
|
7
10
|
import { DEFAULT_CONNECT_CONFIG, buildRuntimeConfig, deepMerge, loadConnectEnv, normalizeBasePath, phConnectRuntimeConfigSchema, powerhousePackageSchema, setConnectEnv } from "@powerhousedao/shared/connect";
|
|
8
11
|
import { getConfig } from "@powerhousedao/config/node";
|
|
@@ -16,6 +19,546 @@ const IMPORT_SCRIPT_FILE = "external-packages.js";
|
|
|
16
19
|
const LOCAL_PACKAGE_ID = "ph:local-package";
|
|
17
20
|
const PH_DIR_NAME = ".ph";
|
|
18
21
|
//#endregion
|
|
22
|
+
//#region connect-utils/vite-plugins/dynamic-base.ts
|
|
23
|
+
/**
|
|
24
|
+
* Placeholder base used when Connect is built in dynamic-base mode. The Vite
|
|
25
|
+
* `base` option is set to this token; this plugin rewrites it in the emitted
|
|
26
|
+
* output so the effective base is resolved at serve time from a global instead
|
|
27
|
+
* of being baked at build time.
|
|
28
|
+
*
|
|
29
|
+
* Trailing slash matches `normalizeBasePath` output, so the token sits in the
|
|
30
|
+
* same syntactic position as a concrete base would.
|
|
31
|
+
*/
|
|
32
|
+
const DYNAMIC_BASE_PLACEHOLDER = "/__PH_DYNAMIC_BASE__/";
|
|
33
|
+
/**
|
|
34
|
+
* Global the runtime (ph-clint proxy) must set before the entry bundle loads.
|
|
35
|
+
* Value is the normalized deploy base, e.g. "/myagent/" or "/". The JS rewrite
|
|
36
|
+
* below resolves all asset / lazy-chunk / BASE_URL references against it.
|
|
37
|
+
*/
|
|
38
|
+
const RUNTIME_GLOBAL = "globalThis.__PH_DYNAMIC_BASE__";
|
|
39
|
+
const BASE_EXPR = `(${RUNTIME_GLOBAL}||"/")`;
|
|
40
|
+
function workerPrelude(stripPrefix) {
|
|
41
|
+
return `${RUNTIME_GLOBAL}=self.location.pathname.replace(/${escapeForRegExp(stripPrefix).replace(/\//g, "\\/")}assets\\/[^/]*$/,"");\n`;
|
|
42
|
+
}
|
|
43
|
+
const PLACEHOLDER_LITERAL = new RegExp(`(["'\`])${escapeForRegExp(DYNAMIC_BASE_PLACEHOLDER)}((?:(?!\\1)[^$])*)\\1`, "g");
|
|
44
|
+
/**
|
|
45
|
+
* Rewrites the placeholder base in emitted JS chunks to a runtime expression so
|
|
46
|
+
* one built `dist/connect` serves under any subpath. Rolldown-native: per-match
|
|
47
|
+
* MagicString edits in `renderChunk`, no AST splicing (the SWC byte-offset
|
|
48
|
+
* splicing in vite-plugin-dynamic-base corrupts Rolldown chunks). Each edited
|
|
49
|
+
* chunk returns a hires sourcemap that the bundler composes into the chunk's
|
|
50
|
+
* map chain, so the emitted `.map` files track the rewrite (and the worker
|
|
51
|
+
* prelude's line shift).
|
|
52
|
+
*
|
|
53
|
+
* What gets rewritten in JS, all of which emit the base as a quoted string
|
|
54
|
+
* literal beginning with the placeholder:
|
|
55
|
+
* - asset URLs (`new URL("/__PH_DYNAMIC_BASE__/assets/x.png", ...)`)
|
|
56
|
+
* - dynamic-import / lazy-chunk preload URLs
|
|
57
|
+
* - the inlined `import.meta.env.BASE_URL` (drives Connect's router basename
|
|
58
|
+
* and BASE_URL-relative fetches such as `${BASE_URL}ph-packages.json`)
|
|
59
|
+
*
|
|
60
|
+
* A literal `"/__PH_DYNAMIC_BASE__/foo"` becomes `((globalThis.__PH_DYNAMIC_BASE__||"/")+"foo")`;
|
|
61
|
+
* a bare `"/__PH_DYNAMIC_BASE__/"` (the BASE_URL value) becomes `(globalThis.__PH_DYNAMIC_BASE__||"/")`.
|
|
62
|
+
*
|
|
63
|
+
* HTML is left untouched: the entry `<script>`/`<link>` tags keep the literal
|
|
64
|
+
* placeholder so the proxy substitutes it with the concrete base at serve time
|
|
65
|
+
* (the same proxy also sets the runtime global for the JS rewrite). See the
|
|
66
|
+
* plugin's module doc / report for the exact serve-time contract.
|
|
67
|
+
*
|
|
68
|
+
* CSS `url(...)` references resolve relative to the stylesheet's own URL, so
|
|
69
|
+
* they need no rewrite once the stylesheet itself is loaded from the right
|
|
70
|
+
* prefix (which the HTML substitution handles).
|
|
71
|
+
*/
|
|
72
|
+
function connectDynamicBasePlugin(options = {}) {
|
|
73
|
+
return {
|
|
74
|
+
name: "ph-connect-dynamic-base",
|
|
75
|
+
enforce: "post",
|
|
76
|
+
renderChunk(code, chunk) {
|
|
77
|
+
if (!code.includes("/__PH_DYNAMIC_BASE__/")) return null;
|
|
78
|
+
const s = new MagicString(code);
|
|
79
|
+
for (const match of code.matchAll(PLACEHOLDER_LITERAL)) {
|
|
80
|
+
const rest = match[2];
|
|
81
|
+
s.overwrite(match.index, match.index + match[0].length, rest.length === 0 ? BASE_EXPR : `(${BASE_EXPR}+${JSON.stringify(rest)})`);
|
|
82
|
+
}
|
|
83
|
+
if (options.forWorker) {
|
|
84
|
+
s.prepend(workerPrelude(options.workerStripPrefix ?? ""));
|
|
85
|
+
this.info(`dynamic-base: worker prelude prepended to ${chunk.fileName}`);
|
|
86
|
+
}
|
|
87
|
+
if (!s.hasChanged()) return null;
|
|
88
|
+
return {
|
|
89
|
+
code: s.toString(),
|
|
90
|
+
map: s.generateMap({ hires: true })
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
generateBundle(_options, bundle) {
|
|
94
|
+
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}`);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function escapeForRegExp(s) {
|
|
99
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region connect-utils/externalize-vendor.ts
|
|
103
|
+
/**
|
|
104
|
+
* Prebuild the heavy, stable Connect dependencies into a static ESM "vendor"
|
|
105
|
+
* bundle so the dev server never runs (or holds) the dependency optimizer /
|
|
106
|
+
* module graph for them.
|
|
107
|
+
*
|
|
108
|
+
* The reactor-project preview dev-optimizes a large UI graph (Connect itself,
|
|
109
|
+
* design-system, document-engineering, reactor-browser, …) every session —
|
|
110
|
+
* ~1–2 GB resident — even though those libs don't change; only the project's
|
|
111
|
+
* editors do. This module builds them ONCE with `vite build` (in a throwaway
|
|
112
|
+
* subprocess that exits, freeing the build's peak memory): `vite build` handles
|
|
113
|
+
* CSS/asset imports, web workers, and WASM (Connect's in-browser PGlite ships
|
|
114
|
+
* all three), and a multi-entry build with `preserveEntrySignatures: 'strict'`
|
|
115
|
+
* dedupes shared code into shared chunks. Entries for CJS deps re-export the
|
|
116
|
+
* module's named API explicitly (so `import { createRoot }` works); ESM deps
|
|
117
|
+
* use `export *`. The build runs under a dynamic-base placeholder + vendor
|
|
118
|
+
* segment (`connectDynamicBasePlugin`), so emitted asset/chunk URLs
|
|
119
|
+
* (`new URL(…, import.meta.url)` for .wasm/.data/workers) carry the placeholder
|
|
120
|
+
* and resolve at serve time to `<deploy-base>__vendor__/`, while the vendored
|
|
121
|
+
* Connect's `import.meta.env.BASE_URL` resolves to the deploy base.
|
|
122
|
+
*
|
|
123
|
+
* The React family stays EXTERNAL (see `VENDOR_EXTERNAL`); `esmExternalRequirePlugin`
|
|
124
|
+
* owns that externalization and rewrites CJS `require("react")` → import. Vendor
|
|
125
|
+
* chunks keep bare React imports, which the dev import map resolves to Vite's
|
|
126
|
+
* pre-bundled React (`devReactImportmapPlugin`) — one React instance across the
|
|
127
|
+
* vendor, the project's editors, and CDN-loaded editors.
|
|
128
|
+
*
|
|
129
|
+
* `devReactImportmapPlugin` consumes the result: it externalizes these
|
|
130
|
+
* specifiers in the long-lived dev server, points the page import map at the
|
|
131
|
+
* vendor URLs, and serves the bundle. With Connect vendored, the dev server only
|
|
132
|
+
* processes the project's own `main` + local package; HMR for them is unaffected.
|
|
133
|
+
*/
|
|
134
|
+
/**
|
|
135
|
+
* The stable Connect libraries worth prebuilding. The React family is NOT here —
|
|
136
|
+
* it's externalized from the build (see `VENDOR_EXTERNAL`) so the vendor shares
|
|
137
|
+
* the dev server's single React instance via the import map.
|
|
138
|
+
*/
|
|
139
|
+
const DEFAULT_VENDOR_INCLUDE = [
|
|
140
|
+
"@powerhousedao/connect",
|
|
141
|
+
"document-model",
|
|
142
|
+
"zod",
|
|
143
|
+
"@powerhousedao/design-system/connect",
|
|
144
|
+
"@powerhousedao/reactor-browser",
|
|
145
|
+
"@powerhousedao/document-engineering"
|
|
146
|
+
];
|
|
147
|
+
/**
|
|
148
|
+
* React-family specifiers kept external to the vendor build. The vendor's chunks
|
|
149
|
+
* emit bare imports for these; `devReactImportmapPlugin`'s import map resolves
|
|
150
|
+
* them to Vite's pre-bundled React, so there is exactly one React instance.
|
|
151
|
+
*/
|
|
152
|
+
const VENDOR_EXTERNAL = [
|
|
153
|
+
"react",
|
|
154
|
+
"react-dom",
|
|
155
|
+
"react-dom/client",
|
|
156
|
+
"react/jsx-runtime",
|
|
157
|
+
"react/jsx-dev-runtime",
|
|
158
|
+
"ph-bundled-packages-virtual"
|
|
159
|
+
];
|
|
160
|
+
/** URL prefix the vendor bundle is served under by the dev middleware. */
|
|
161
|
+
const VENDOR_URL_PREFIX = "/__vendor__/";
|
|
162
|
+
const VENDOR_DYNAMIC_BASE = `${DYNAMIC_BASE_PLACEHOLDER}${VENDOR_URL_PREFIX.replace(/^\/+/, "")}`;
|
|
163
|
+
/**
|
|
164
|
+
* Build the prebuilt vendor (once, in a throwaway subprocess) and return its dir
|
|
165
|
+
* + import map. Idempotent: reuses a cached vendor built for the same dep set.
|
|
166
|
+
* Returns null on any failure (caller falls back to a normal dev server).
|
|
167
|
+
*/
|
|
168
|
+
async function prebuildConnectVendor(options) {
|
|
169
|
+
const include = expandIncludeSubpaths(options.dirname, options.include ?? DEFAULT_VENDOR_INCLUDE);
|
|
170
|
+
const external = options.external ?? VENDOR_EXTERNAL;
|
|
171
|
+
const vendorDir = options.vendorDir ?? join(options.dirname, "node_modules/.ph-vendor");
|
|
172
|
+
const importMapPath = join(vendorDir, "import-map.json");
|
|
173
|
+
const versionDigest = resolveVersionDigest(options.dirname, [...include, ...external]);
|
|
174
|
+
try {
|
|
175
|
+
const hit = readCacheHit(importMapPath, include, external, versionDigest);
|
|
176
|
+
if (hit) return {
|
|
177
|
+
vendorDir,
|
|
178
|
+
imports: hit
|
|
179
|
+
};
|
|
180
|
+
const lockDir = `${vendorDir}.lock`;
|
|
181
|
+
const lock = acquireLock(lockDir);
|
|
182
|
+
if (!lock) {
|
|
183
|
+
const imports = await waitForCacheHit(importMapPath, include, external, versionDigest, lockDir);
|
|
184
|
+
return imports ? {
|
|
185
|
+
vendorDir,
|
|
186
|
+
imports
|
|
187
|
+
} : null;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const raced = readCacheHit(importMapPath, include, external, versionDigest);
|
|
191
|
+
if (raced) return {
|
|
192
|
+
vendorDir,
|
|
193
|
+
imports: raced
|
|
194
|
+
};
|
|
195
|
+
const imports = await buildVendorAtomic(options.dirname, vendorDir, include, external, versionDigest);
|
|
196
|
+
return imports ? {
|
|
197
|
+
vendorDir,
|
|
198
|
+
imports
|
|
199
|
+
} : null;
|
|
200
|
+
} finally {
|
|
201
|
+
releaseLock(lock);
|
|
202
|
+
}
|
|
203
|
+
} catch (err) {
|
|
204
|
+
if (options.errorRef) options.errorRef.message = err instanceof Error ? err.message : String(err);
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function readCacheHit(importMapPath, include, external, versionDigest) {
|
|
209
|
+
if (!existsSync(importMapPath)) return null;
|
|
210
|
+
try {
|
|
211
|
+
const cached = JSON.parse(readFileSync(importMapPath, "utf8"));
|
|
212
|
+
if (sameSet(cached.include, include) && sameSet(cached.external, external) && cached.versionDigest === versionDigest) return cached.imports;
|
|
213
|
+
} catch {}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
function resolveVersionDigest(dirname, specs) {
|
|
217
|
+
const seen = /* @__PURE__ */ new Map();
|
|
218
|
+
for (const spec of specs) {
|
|
219
|
+
const { pkg } = parsePkg(spec);
|
|
220
|
+
if (seen.has(pkg)) continue;
|
|
221
|
+
let version = "missing";
|
|
222
|
+
try {
|
|
223
|
+
const pkgRoot = realpathSync(join(dirname, "node_modules", pkg));
|
|
224
|
+
const meta = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8"));
|
|
225
|
+
version = String(meta.version ?? "unknown");
|
|
226
|
+
} catch {}
|
|
227
|
+
seen.set(pkg, version);
|
|
228
|
+
}
|
|
229
|
+
const h = createHash("sha256");
|
|
230
|
+
const workerHash = createHash("sha256").update(VENDOR_BUILD_WORKER).digest("hex");
|
|
231
|
+
h.update(`worker:${workerHash}\n`);
|
|
232
|
+
for (const pkg of [...seen.keys()].sort()) h.update(`${pkg}@${seen.get(pkg)}\n`);
|
|
233
|
+
return h.digest("hex").slice(0, 16);
|
|
234
|
+
}
|
|
235
|
+
function sameSet(a, b) {
|
|
236
|
+
if (!a || a.length !== b.length) return false;
|
|
237
|
+
const s = new Set(a);
|
|
238
|
+
return b.every((x) => s.has(x));
|
|
239
|
+
}
|
|
240
|
+
const LOCK_STALE_MS = 5 * 6e4;
|
|
241
|
+
const LOCK_HEARTBEAT_MS = 6e4;
|
|
242
|
+
const ownerFile = (lockDir) => join(lockDir, "owner");
|
|
243
|
+
function lockIsStale(lockDir) {
|
|
244
|
+
try {
|
|
245
|
+
return Date.now() - statSync(ownerFile(lockDir)).mtimeMs > LOCK_STALE_MS;
|
|
246
|
+
} catch {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function acquireLock(lockDir) {
|
|
251
|
+
let made = false;
|
|
252
|
+
try {
|
|
253
|
+
mkdirSync(lockDir);
|
|
254
|
+
made = true;
|
|
255
|
+
} catch {
|
|
256
|
+
if (lockIsStale(lockDir)) try {
|
|
257
|
+
rmSync(lockDir, {
|
|
258
|
+
recursive: true,
|
|
259
|
+
force: true
|
|
260
|
+
});
|
|
261
|
+
mkdirSync(lockDir);
|
|
262
|
+
made = true;
|
|
263
|
+
} catch {}
|
|
264
|
+
}
|
|
265
|
+
if (!made) return null;
|
|
266
|
+
const token = `${process.pid}-${randomUUID()}`;
|
|
267
|
+
writeFileSync(ownerFile(lockDir), token);
|
|
268
|
+
const timer = setInterval(() => {
|
|
269
|
+
try {
|
|
270
|
+
if (readFileSync(ownerFile(lockDir), "utf8") === token) writeFileSync(ownerFile(lockDir), token);
|
|
271
|
+
else clearInterval(timer);
|
|
272
|
+
} catch {
|
|
273
|
+
clearInterval(timer);
|
|
274
|
+
}
|
|
275
|
+
}, LOCK_HEARTBEAT_MS);
|
|
276
|
+
timer.unref();
|
|
277
|
+
return {
|
|
278
|
+
dir: lockDir,
|
|
279
|
+
token,
|
|
280
|
+
timer
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function releaseLock(lock) {
|
|
284
|
+
clearInterval(lock.timer);
|
|
285
|
+
try {
|
|
286
|
+
if (readFileSync(ownerFile(lock.dir), "utf8") === lock.token) rmSync(lock.dir, {
|
|
287
|
+
recursive: true,
|
|
288
|
+
force: true
|
|
289
|
+
});
|
|
290
|
+
} catch {}
|
|
291
|
+
}
|
|
292
|
+
async function waitForCacheHit(importMapPath, include, external, versionDigest, lockDir) {
|
|
293
|
+
const deadline = Date.now() + LOCK_STALE_MS;
|
|
294
|
+
while (Date.now() < deadline) {
|
|
295
|
+
const hit = readCacheHit(importMapPath, include, external, versionDigest);
|
|
296
|
+
if (hit) return hit;
|
|
297
|
+
if (!existsSync(lockDir)) return readCacheHit(importMapPath, include, external, versionDigest);
|
|
298
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
function importTarget$1(value) {
|
|
303
|
+
if (typeof value === "string") return value;
|
|
304
|
+
if (!value || typeof value !== "object") return null;
|
|
305
|
+
const o = value;
|
|
306
|
+
for (const c of [
|
|
307
|
+
"browser",
|
|
308
|
+
"import",
|
|
309
|
+
"module",
|
|
310
|
+
"default"
|
|
311
|
+
]) if (c in o) {
|
|
312
|
+
const t = importTarget$1(o[c]);
|
|
313
|
+
if (t) return t;
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
function parsePkg(spec) {
|
|
318
|
+
if (spec.startsWith("@")) {
|
|
319
|
+
const parts = spec.split("/");
|
|
320
|
+
return {
|
|
321
|
+
pkg: parts.slice(0, 2).join("/"),
|
|
322
|
+
sub: parts.slice(2).join("/")
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
const i = spec.indexOf("/");
|
|
326
|
+
return i === -1 ? {
|
|
327
|
+
pkg: spec,
|
|
328
|
+
sub: ""
|
|
329
|
+
} : {
|
|
330
|
+
pkg: spec.slice(0, i),
|
|
331
|
+
sub: spec.slice(i + 1)
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Connect imports the heavy libs by subpath too (e.g.
|
|
336
|
+
* `@powerhousedao/design-system/connect/toast`, `zod/v4/core`). Those bare
|
|
337
|
+
* imports need their own import-map entry, so expand each listed spec to its
|
|
338
|
+
* package's concrete (non-wildcard, JS) subpath exports that share the spec's
|
|
339
|
+
* prefix. Unresolvable / CSS / JSON targets are skipped. Failures leave the
|
|
340
|
+
* original spec untouched.
|
|
341
|
+
*/
|
|
342
|
+
function expandIncludeSubpaths(dirname, include) {
|
|
343
|
+
const out = new Set(include);
|
|
344
|
+
for (const spec of include) {
|
|
345
|
+
const { pkg, sub } = parsePkg(spec);
|
|
346
|
+
let exp;
|
|
347
|
+
let pkgRoot;
|
|
348
|
+
try {
|
|
349
|
+
pkgRoot = realpathSync(join(dirname, "node_modules", pkg));
|
|
350
|
+
exp = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8")).exports;
|
|
351
|
+
} catch {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (!exp || typeof exp !== "object") continue;
|
|
355
|
+
const expMap = exp;
|
|
356
|
+
const prefixKey = sub ? `./${sub}` : ".";
|
|
357
|
+
for (const key of Object.keys(expMap)) {
|
|
358
|
+
if (key.includes("*") || key === "./package.json") continue;
|
|
359
|
+
if (/\.(css|json|scss)$/.test(key)) continue;
|
|
360
|
+
if (!(prefixKey === "." ? key === "." || key.startsWith("./") : key === prefixKey || key.startsWith(`${prefixKey}/`))) continue;
|
|
361
|
+
const target = importTarget$1(expMap[key]);
|
|
362
|
+
if (!target || /\.(css|json|scss)$/.test(target)) continue;
|
|
363
|
+
if (!existsSync(join(pkgRoot, target))) continue;
|
|
364
|
+
out.add(key === "." ? pkg : pkg + key.slice(1));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return [...out];
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Build the vendor into a unique temp dir, then atomically swap it into place,
|
|
371
|
+
* so a concurrent reader never sees a partial bundle or import-map.json. Runs
|
|
372
|
+
* the build in a throwaway subprocess (its peak memory is reclaimed on exit);
|
|
373
|
+
* the parent augments the import map with the version digest and does the swap.
|
|
374
|
+
* Returns the published import map, or null on failure.
|
|
375
|
+
*/
|
|
376
|
+
async function buildVendorAtomic(dirname$2, vendorDir, include, external, versionDigest) {
|
|
377
|
+
const parent = dirname(vendorDir);
|
|
378
|
+
mkdirSync(parent, { recursive: true });
|
|
379
|
+
const tmpDir = mkdtempSync(join(parent, ".ph-vendor.tmp-"));
|
|
380
|
+
try {
|
|
381
|
+
const { ok, stderr } = await runBuildWorker(dirname$2, tmpDir, include, external);
|
|
382
|
+
const tmpMap = join(tmpDir, "import-map.json");
|
|
383
|
+
if (!ok || !existsSync(tmpMap)) {
|
|
384
|
+
const detail = stderr.trim();
|
|
385
|
+
throw new Error(`vendor build failed${detail ? `:\n${detail}` : " (no output captured)"}`);
|
|
386
|
+
}
|
|
387
|
+
const meta = JSON.parse(readFileSync(tmpMap, "utf8"));
|
|
388
|
+
meta.versionDigest = versionDigest;
|
|
389
|
+
writeFileSync(tmpMap, JSON.stringify(meta, null, 2));
|
|
390
|
+
const oldDir = `${vendorDir}.old-${process.pid}-${Date.now()}`;
|
|
391
|
+
if (existsSync(vendorDir)) renameSync(vendorDir, oldDir);
|
|
392
|
+
renameSync(tmpDir, vendorDir);
|
|
393
|
+
rmSync(oldDir, {
|
|
394
|
+
recursive: true,
|
|
395
|
+
force: true
|
|
396
|
+
});
|
|
397
|
+
return meta.imports;
|
|
398
|
+
} catch (err) {
|
|
399
|
+
rmSync(tmpDir, {
|
|
400
|
+
recursive: true,
|
|
401
|
+
force: true
|
|
402
|
+
});
|
|
403
|
+
throw err;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Spawn a throwaway worker that generates one entry per specifier (CJS deps get
|
|
408
|
+
* explicit named re-exports, ESM deps `export *`), `vite build`s them into the
|
|
409
|
+
* given out dir, and writes the import map. Captures stdout/stderr so a failure
|
|
410
|
+
* is debuggable; the normal path stays quiet.
|
|
411
|
+
*/
|
|
412
|
+
function runBuildWorker(dirname, outDir, include, external) {
|
|
413
|
+
const workerPath = join(outDir, "build-worker.mjs");
|
|
414
|
+
writeFileSync(workerPath, VENDOR_BUILD_WORKER);
|
|
415
|
+
const selfModulePath = fileURLToPath(import.meta.url);
|
|
416
|
+
return new Promise((resolve) => {
|
|
417
|
+
const child = spawn(process.execPath, [
|
|
418
|
+
workerPath,
|
|
419
|
+
dirname,
|
|
420
|
+
outDir,
|
|
421
|
+
JSON.stringify(include),
|
|
422
|
+
VENDOR_URL_PREFIX,
|
|
423
|
+
JSON.stringify(external),
|
|
424
|
+
VENDOR_DYNAMIC_BASE,
|
|
425
|
+
selfModulePath
|
|
426
|
+
], {
|
|
427
|
+
cwd: dirname,
|
|
428
|
+
stdio: [
|
|
429
|
+
"ignore",
|
|
430
|
+
"pipe",
|
|
431
|
+
"pipe"
|
|
432
|
+
]
|
|
433
|
+
});
|
|
434
|
+
let stderr = "";
|
|
435
|
+
child.stdout.on("data", (d) => {
|
|
436
|
+
stderr += String(d);
|
|
437
|
+
});
|
|
438
|
+
child.stderr.on("data", (d) => {
|
|
439
|
+
stderr += String(d);
|
|
440
|
+
});
|
|
441
|
+
child.on("exit", (code) => resolve({
|
|
442
|
+
ok: code === 0,
|
|
443
|
+
stderr
|
|
444
|
+
}));
|
|
445
|
+
child.on("error", (e) => resolve({
|
|
446
|
+
ok: false,
|
|
447
|
+
stderr: String(e)
|
|
448
|
+
}));
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* The vendor build worker, written to disk and run as a subprocess. Loads
|
|
453
|
+
* `vite` from the project and `connectDynamicBasePlugin` from builder-tools' own
|
|
454
|
+
* built bundle (selfModulePath). argv: dirname, vendorDir, includeJSON,
|
|
455
|
+
* urlPrefix, externalJSON, dynamicBase, selfModulePath.
|
|
456
|
+
*/
|
|
457
|
+
const VENDOR_BUILD_WORKER = `
|
|
458
|
+
import { createRequire } from 'node:module';
|
|
459
|
+
import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
460
|
+
import { join, isAbsolute } from 'node:path';
|
|
461
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
462
|
+
const [dirname, vendorDir, includeJSON, urlPrefix, externalJSON, dynamicBase, selfModulePath] = process.argv.slice(2);
|
|
463
|
+
const include = JSON.parse(includeJSON);
|
|
464
|
+
const external = JSON.parse(externalJSON ?? '[]');
|
|
465
|
+
const externalSet = new Set(external);
|
|
466
|
+
const reqProj = createRequire(join(dirname, 'noop.js'));
|
|
467
|
+
const { build, esmExternalRequirePlugin } = await import(reqProj.resolve('vite'));
|
|
468
|
+
// Load the dynamic-base plugin from builder-tools' own built bundle (passed as
|
|
469
|
+
// an absolute path) — it isn't resolvable as a bare specifier from the worker.
|
|
470
|
+
const { connectDynamicBasePlugin, DYNAMIC_BASE_PLACEHOLDER } = await import(pathToFileURL(selfModulePath));
|
|
471
|
+
const srcDir = join(vendorDir, '.entries');
|
|
472
|
+
mkdirSync(srcDir, { recursive: true });
|
|
473
|
+
const entryName = (spec) => spec.replace(/[^\\w]+/g, '_');
|
|
474
|
+
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(' '));
|
|
475
|
+
const input = {};
|
|
476
|
+
for (const spec of include) {
|
|
477
|
+
const name = entryName(spec);
|
|
478
|
+
let src;
|
|
479
|
+
try {
|
|
480
|
+
// Import the bare spec (not reqProj.resolve(spec)) so Node picks the same
|
|
481
|
+
// import/browser-condition module the build resolves — resolving first can
|
|
482
|
+
// pick a CJS sibling whose default-export shape differs from the ESM build.
|
|
483
|
+
const ns = await import(spec);
|
|
484
|
+
// Only fall back to re-exporting from default when the module exposes NO
|
|
485
|
+
// top-level named exports (a true CJS-interop module). If it does (e.g. zod
|
|
486
|
+
// exposes \`z\`), \`export *\` captures them; destructuring default would miss
|
|
487
|
+
// top-level names that aren't keys of the default object.
|
|
488
|
+
const named = Object.keys(ns).filter((k) => k !== 'default');
|
|
489
|
+
const cjs = named.length === 0 && ns.default && typeof ns.default === 'object';
|
|
490
|
+
if (cjs) {
|
|
491
|
+
const names = Object.keys(ns.default).filter((k) => k !== 'default' && k !== '__esModule' && /^[A-Za-z_$][\\w$]*$/.test(k));
|
|
492
|
+
const plain = names.filter((n) => !RESERVED.has(n));
|
|
493
|
+
const reserved = names.filter((n) => RESERVED.has(n));
|
|
494
|
+
src = 'import d from ' + JSON.stringify(spec) + ';\\nexport default d;\\n'
|
|
495
|
+
+ (plain.length ? 'export const { ' + plain.join(', ') + ' } = d;\\n' : '');
|
|
496
|
+
// reserved words are invalid as const-binding names but valid as export
|
|
497
|
+
// aliases (export { x as enum }).
|
|
498
|
+
reserved.forEach((n, i) => {
|
|
499
|
+
src += 'const __r' + i + ' = d[' + JSON.stringify(n) + '];\\nexport { __r' + i + ' as ' + n + ' };\\n';
|
|
500
|
+
});
|
|
501
|
+
} else {
|
|
502
|
+
src = 'export * from ' + JSON.stringify(spec) + ';\\n'
|
|
503
|
+
+ (('default' in ns) ? 'export { default } from ' + JSON.stringify(spec) + ';\\n' : '');
|
|
504
|
+
}
|
|
505
|
+
} catch {
|
|
506
|
+
src = 'export * from ' + JSON.stringify(spec) + ';\\n';
|
|
507
|
+
}
|
|
508
|
+
const file = join(srcDir, name + '.js');
|
|
509
|
+
writeFileSync(file, src);
|
|
510
|
+
input[name] = file;
|
|
511
|
+
}
|
|
512
|
+
// Prefer the bundler's browser-condition-aware resolution; fall back to resolving
|
|
513
|
+
// from the worker (real install path) only for Rolldown's realpath-anchoring bug.
|
|
514
|
+
const phResolveCache = new Map();
|
|
515
|
+
const phVendorResolve = {
|
|
516
|
+
name: 'ph-vendor-resolve', enforce: 'pre',
|
|
517
|
+
async resolveId(source, importer, options) {
|
|
518
|
+
if (externalSet.has(source)) return null;
|
|
519
|
+
if (source[0] === '\\0' || source[0] === '.' || isAbsolute(source)) return null;
|
|
520
|
+
if (source.startsWith('node:') || source.startsWith('data:')) return null;
|
|
521
|
+
let viaBundler = null;
|
|
522
|
+
try { viaBundler = await this.resolve(source, importer, { ...options, skipSelf: true }); } catch {}
|
|
523
|
+
if (viaBundler) return viaBundler;
|
|
524
|
+
if (phResolveCache.has(source)) return phResolveCache.get(source);
|
|
525
|
+
let resolved = null;
|
|
526
|
+
try { resolved = fileURLToPath(import.meta.resolve(source)); } catch {}
|
|
527
|
+
phResolveCache.set(source, resolved);
|
|
528
|
+
return resolved;
|
|
529
|
+
},
|
|
530
|
+
};
|
|
531
|
+
await build({
|
|
532
|
+
root: dirname, configFile: false, logLevel: 'error',
|
|
533
|
+
// Dynamic-base placeholder + vendor segment: connectDynamicBasePlugin rewrites
|
|
534
|
+
// emitted chunk/asset URLs to resolve against the deploy base at serve time.
|
|
535
|
+
base: dynamicBase,
|
|
536
|
+
define: {
|
|
537
|
+
'process.env.NODE_ENV': '"development"',
|
|
538
|
+
// BASE_URL resolves to the deploy base (not the vendor prefix) so vendored
|
|
539
|
+
// Connect's router basename + BASE_URL-relative fetches use the right path.
|
|
540
|
+
'import.meta.env.BASE_URL': JSON.stringify(DYNAMIC_BASE_PLACEHOLDER),
|
|
541
|
+
},
|
|
542
|
+
// phVendorResolve (pre) resolves bares from the worker; esmExternalRequirePlugin owns
|
|
543
|
+
// react/virtual externalization; connectDynamicBasePlugin (post) rewrites the placeholder base.
|
|
544
|
+
plugins: [phVendorResolve, esmExternalRequirePlugin({ external }), connectDynamicBasePlugin()],
|
|
545
|
+
// pglite ships web workers as ES-module chunks. workerStripPrefix is the vendor
|
|
546
|
+
// segment so the worker recovers the deploy base, not the vendor prefix.
|
|
547
|
+
worker: { format: 'es', plugins: () => [connectDynamicBasePlugin({ forWorker: true, workerStripPrefix: urlPrefix.replace(/^\\/+/, '') })] },
|
|
548
|
+
build: {
|
|
549
|
+
outDir: vendorDir, emptyOutDir: false, minify: false, target: 'esnext', cssCodeSplit: true,
|
|
550
|
+
rollupOptions: {
|
|
551
|
+
input, preserveEntrySignatures: 'strict',
|
|
552
|
+
output: { format: 'es', entryFileNames: '[name].js', chunkFileNames: 'chunks/[name]-[hash].js', assetFileNames: 'assets/[name]-[hash][extname]' },
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
});
|
|
556
|
+
rmSync(srcDir, { recursive: true, force: true });
|
|
557
|
+
const imports = {};
|
|
558
|
+
for (const spec of include) imports[spec] = urlPrefix + entryName(spec) + '.js';
|
|
559
|
+
writeFileSync(join(vendorDir, 'import-map.json'), JSON.stringify({ include, external, imports }, null, 2));
|
|
560
|
+
`;
|
|
561
|
+
//#endregion
|
|
19
562
|
//#region connect-utils/helpers.ts
|
|
20
563
|
const DEFAULT_CONNECT_OUTDIR = ".ph/connect-build/dist/";
|
|
21
564
|
function resolveViteConfigPath(options) {
|
|
@@ -246,6 +789,54 @@ const runtimeConfigSchema = {
|
|
|
246
789
|
connect: phConnectRuntimeConfigSchema
|
|
247
790
|
}
|
|
248
791
|
};
|
|
792
|
+
const RESOLVED_VIRTUAL_ID = "\0virtual:ph-bundled-packages-virtual";
|
|
793
|
+
const BUNDLED_PACKAGES_DEV_URL = "@id/" + RESOLVED_VIRTUAL_ID.replace("\0", "__x00__");
|
|
794
|
+
function readBundledPackageVersion(projectRoot, name) {
|
|
795
|
+
try {
|
|
796
|
+
const raw = fs.readFileSync(path.join(projectRoot, "node_modules", name, "package.json"), "utf-8");
|
|
797
|
+
const pkg = JSON.parse(raw);
|
|
798
|
+
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
799
|
+
} catch {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
function makeRegisterModule(packages, projectRoot) {
|
|
804
|
+
if (packages.length === 0) return "export default () => {};\n";
|
|
805
|
+
const imports = [];
|
|
806
|
+
const calls = [];
|
|
807
|
+
packages.forEach((name, i) => {
|
|
808
|
+
const moduleName = `pkg${i}`;
|
|
809
|
+
const version = readBundledPackageVersion(projectRoot, name);
|
|
810
|
+
imports.push(`import * as ${moduleName} from ${JSON.stringify(name)};`);
|
|
811
|
+
imports.push(`import ${JSON.stringify(`${name}/style.css`)};`);
|
|
812
|
+
calls.push(` pm.addLocalPackage(${JSON.stringify(name)}, ${moduleName}, ${JSON.stringify(version)});`);
|
|
813
|
+
});
|
|
814
|
+
return `${imports.join("\n")}\n\nexport default function register(pm) {\n${calls.join("\n")}\n};\n`;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Emits a virtual module `ph-bundled-packages-virtual` whose default export
|
|
818
|
+
* is a `register(packageManager)` function. When called at runtime (from
|
|
819
|
+
* Connect's bootstrap), it registers each bundled package with the package
|
|
820
|
+
* manager the same way Common/Vetra are registered — meaning they work
|
|
821
|
+
* offline without the registry being reachable.
|
|
822
|
+
*
|
|
823
|
+
* When the list is empty, the module exports a no-op function so Connect's
|
|
824
|
+
* bootstrap code can always import it unconditionally.
|
|
825
|
+
*/
|
|
826
|
+
function phBundledPackagesPlugin(options) {
|
|
827
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
828
|
+
const moduleSource = makeRegisterModule(options.packages, projectRoot);
|
|
829
|
+
return {
|
|
830
|
+
name: "vite-plugin-ph-bundled-packages",
|
|
831
|
+
enforce: "pre",
|
|
832
|
+
resolveId(id) {
|
|
833
|
+
if (id === "ph-bundled-packages-virtual") return RESOLVED_VIRTUAL_ID;
|
|
834
|
+
},
|
|
835
|
+
load(id) {
|
|
836
|
+
if (id === "\0virtual:ph-bundled-packages-virtual") return moduleSource;
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
}
|
|
249
840
|
//#endregion
|
|
250
841
|
//#region connect-utils/vite-plugins/dev-external-react.ts
|
|
251
842
|
const REACT_DEPS = [
|
|
@@ -257,9 +848,36 @@ const REACT_DEPS = [
|
|
|
257
848
|
];
|
|
258
849
|
const SHIM_PATH = "__ph/dev-react-shim/";
|
|
259
850
|
const VITE_DEPS_PATH = "node_modules/.vite/deps";
|
|
851
|
+
const VENDOR_MIME = {
|
|
852
|
+
".css": "text/css",
|
|
853
|
+
".map": "application/json",
|
|
854
|
+
".json": "application/json",
|
|
855
|
+
".wasm": "application/wasm",
|
|
856
|
+
".data": "application/octet-stream",
|
|
857
|
+
".woff2": "font/woff2",
|
|
858
|
+
".woff": "font/woff",
|
|
859
|
+
".ttf": "font/ttf",
|
|
860
|
+
".svg": "image/svg+xml",
|
|
861
|
+
".png": "image/png",
|
|
862
|
+
".jpg": "image/jpeg",
|
|
863
|
+
".jpeg": "image/jpeg",
|
|
864
|
+
".gif": "image/gif",
|
|
865
|
+
".webp": "image/webp"
|
|
866
|
+
};
|
|
867
|
+
const VENDOR_MODE = process.env.PH_CONNECT_EXTERNALIZE_VENDOR === "1";
|
|
868
|
+
const VENDOR_EXTRA = (process.env.PH_CONNECT_VENDOR_EXTRA ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
869
|
+
const HEAVY_LIBS = [...DEFAULT_VENDOR_INCLUDE, ...VENDOR_EXTRA];
|
|
260
870
|
function withBase(base, p) {
|
|
261
871
|
return `${base}${p}`.replace(/\/{2,}/g, "/");
|
|
262
872
|
}
|
|
873
|
+
function externalizePlugin(externals) {
|
|
874
|
+
try {
|
|
875
|
+
const mod = createRequire(import.meta.url)("vite-plugin-externalize-dependencies");
|
|
876
|
+
return (mod.default ?? mod)({ externals });
|
|
877
|
+
} catch {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
263
881
|
/**
|
|
264
882
|
* Dev-only sibling of `esmExternalRequirePlugin`. The build path externalizes
|
|
265
883
|
* React via Rolldown so an importmap hands the same React instance to both
|
|
@@ -268,7 +886,7 @@ function withBase(base, p) {
|
|
|
268
886
|
* export — so a CDN editor that does `import { lazy } from "react"` would
|
|
269
887
|
* fail with "no named export 'lazy'".
|
|
270
888
|
*
|
|
271
|
-
* This plugin:
|
|
889
|
+
* This plugin owns the page import map. It always:
|
|
272
890
|
* 1. Forces React into `optimizeDeps.include` so the optimizer always knows
|
|
273
891
|
* about it.
|
|
274
892
|
* 2. Serves a shim per React module at a stable URL. Each shim imports
|
|
@@ -276,14 +894,46 @@ function withBase(base, p) {
|
|
|
276
894
|
* and re-exports React's named members so editors importing
|
|
277
895
|
* `{ lazy }`, `{ jsx }`, etc. work.
|
|
278
896
|
* 3. Rewrites the page importmap to point at those shim URLs.
|
|
897
|
+
*
|
|
898
|
+
* When PH_CONNECT_EXTERNALIZE_VENDOR=1 it additionally prebuilds the heavy
|
|
899
|
+
* stable libs (design-system, reactor-browser, …) into a static vendor bundle,
|
|
900
|
+
* externalizes them from the dev server, serves the bundle, and adds them to
|
|
901
|
+
* the SAME import map. The vendor's React imports stay bare → resolve through
|
|
902
|
+
* the React shims above → one React instance across Connect, the vendor, and
|
|
903
|
+
* CDN editors.
|
|
279
904
|
*/
|
|
280
|
-
function devReactImportmapPlugin() {
|
|
905
|
+
async function devReactImportmapPlugin(projectRoot = process.cwd()) {
|
|
281
906
|
let namedExports = /* @__PURE__ */ new Map();
|
|
282
907
|
let base = "/";
|
|
283
|
-
|
|
908
|
+
let vendor = null;
|
|
909
|
+
if (VENDOR_MODE) {
|
|
910
|
+
const errorRef = {};
|
|
911
|
+
vendor = await prebuildConnectVendor({
|
|
912
|
+
dirname: projectRoot,
|
|
913
|
+
include: HEAVY_LIBS,
|
|
914
|
+
errorRef
|
|
915
|
+
});
|
|
916
|
+
if (!vendor) {
|
|
917
|
+
const detail = errorRef.message ? `: ${errorRef.message}` : "";
|
|
918
|
+
console.warn(`[connect] PH_CONNECT_EXTERNALIZE_VENDOR set but vendor prebuild failed; falling back to dep-optimizing the heavy libs${detail}`);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
const ext = vendor ? externalizePlugin([(id) => id in vendor.imports]) : void 0;
|
|
922
|
+
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)");
|
|
923
|
+
const vendorActive = !!(vendor && ext);
|
|
924
|
+
const main = {
|
|
284
925
|
name: "ph-dev-react-importmap",
|
|
285
926
|
apply: "serve",
|
|
286
|
-
config
|
|
927
|
+
config(cfg) {
|
|
928
|
+
cfg.optimizeDeps ??= {};
|
|
929
|
+
const include = new Set(cfg.optimizeDeps.include ?? []);
|
|
930
|
+
REACT_DEPS.forEach((d) => include.add(d));
|
|
931
|
+
if (vendorActive) {
|
|
932
|
+
HEAVY_LIBS.forEach((d) => include.delete(d));
|
|
933
|
+
cfg.optimizeDeps.exclude = [...new Set([...cfg.optimizeDeps.exclude ?? [], ...HEAVY_LIBS])];
|
|
934
|
+
}
|
|
935
|
+
cfg.optimizeDeps.include = [...include];
|
|
936
|
+
},
|
|
287
937
|
configResolved(config) {
|
|
288
938
|
base = config.base;
|
|
289
939
|
},
|
|
@@ -297,6 +947,30 @@ function devReactImportmapPlugin() {
|
|
|
297
947
|
return [id, []];
|
|
298
948
|
}
|
|
299
949
|
}));
|
|
950
|
+
if (vendorActive) {
|
|
951
|
+
const vendorDir = vendor.vendorDir;
|
|
952
|
+
const vendorPrefixes = [withBase(base, VENDOR_URL_PREFIX), VENDOR_URL_PREFIX];
|
|
953
|
+
server.middlewares.use((req, res, next) => {
|
|
954
|
+
const url = (req.url ?? "").split("?")[0];
|
|
955
|
+
const prefix = vendorPrefixes.find((p) => url.startsWith(p));
|
|
956
|
+
if (!prefix) return next();
|
|
957
|
+
const name = url.slice(prefix.length).replace(/^\/+/, "");
|
|
958
|
+
const file = path.join(vendorDir, name);
|
|
959
|
+
const rel = path.relative(vendorDir, file);
|
|
960
|
+
if (!name || rel.startsWith("..") || path.isAbsolute(rel)) return next();
|
|
961
|
+
const stream = createReadStream(file);
|
|
962
|
+
stream.on("error", () => {
|
|
963
|
+
if (!res.headersSent) next();
|
|
964
|
+
});
|
|
965
|
+
stream.once("open", () => {
|
|
966
|
+
const ext = file.slice(file.lastIndexOf("."));
|
|
967
|
+
res.setHeader("Content-Type", VENDOR_MIME[ext] ?? "text/javascript");
|
|
968
|
+
const hashed = name.startsWith("chunks/") || name.startsWith("assets/");
|
|
969
|
+
res.setHeader("Cache-Control", hashed ? "public, max-age=31536000, immutable" : "no-cache");
|
|
970
|
+
stream.pipe(res);
|
|
971
|
+
});
|
|
972
|
+
});
|
|
973
|
+
}
|
|
300
974
|
const shimPrefixes = [withBase(base, SHIM_PATH), `/${SHIM_PATH}`];
|
|
301
975
|
server.middlewares.use((req, res, next) => {
|
|
302
976
|
const prefix = shimPrefixes.find((p) => req.url?.startsWith(p));
|
|
@@ -324,10 +998,17 @@ function devReactImportmapPlugin() {
|
|
|
324
998
|
if (!browserHash) return;
|
|
325
999
|
const shimPrefix = withBase(base, SHIM_PATH);
|
|
326
1000
|
const imports = Object.fromEntries(REACT_DEPS.map((id) => [id, `${shimPrefix}${id}.js?v=${browserHash}`]));
|
|
327
|
-
|
|
1001
|
+
let dynamicBaseScript = "";
|
|
1002
|
+
if (vendorActive) {
|
|
1003
|
+
for (const [spec, url] of Object.entries(vendor.imports)) imports[spec] = withBase(base, url);
|
|
1004
|
+
if (vendor.imports["@powerhousedao/connect"]) imports["ph-bundled-packages-virtual"] = withBase(base, BUNDLED_PACKAGES_DEV_URL);
|
|
1005
|
+
dynamicBaseScript = `<script>globalThis.__PH_DYNAMIC_BASE__=${JSON.stringify(base)};<\/script>\n`;
|
|
1006
|
+
}
|
|
1007
|
+
return html.replace(/<script type="importmap">[\s\S]*?<\/script>/, `${dynamicBaseScript}<script type="importmap">${JSON.stringify({ imports }, null, 2)}<\/script>`);
|
|
328
1008
|
}
|
|
329
1009
|
}
|
|
330
1010
|
};
|
|
1011
|
+
return vendorActive ? [ext, main] : main;
|
|
331
1012
|
}
|
|
332
1013
|
//#endregion
|
|
333
1014
|
//#region connect-utils/vite-plugins/favicon.ts
|
|
@@ -365,56 +1046,6 @@ function connectFaviconPlugin() {
|
|
|
365
1046
|
};
|
|
366
1047
|
}
|
|
367
1048
|
//#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
1049
|
//#region connect-utils/vite-plugins/ph-config.ts
|
|
419
1050
|
function readProjectPackageInfo(projectRoot) {
|
|
420
1051
|
if (!projectRoot) return null;
|
|
@@ -475,14 +1106,174 @@ function phConfigPlugin(options) {
|
|
|
475
1106
|
};
|
|
476
1107
|
}
|
|
477
1108
|
//#endregion
|
|
1109
|
+
//#region connect-utils/vite-plugins/react-self-host.ts
|
|
1110
|
+
const REACT_URL_DIR = "__react__";
|
|
1111
|
+
const REACT_PACKAGES = ["react", "react-dom"];
|
|
1112
|
+
function importTarget(value) {
|
|
1113
|
+
if (typeof value === "string") return value;
|
|
1114
|
+
if (!value || typeof value !== "object") return null;
|
|
1115
|
+
const o = value;
|
|
1116
|
+
for (const c of [
|
|
1117
|
+
"browser",
|
|
1118
|
+
"import",
|
|
1119
|
+
"module",
|
|
1120
|
+
"default"
|
|
1121
|
+
]) if (c in o) {
|
|
1122
|
+
const t = importTarget(o[c]);
|
|
1123
|
+
if (t) return t;
|
|
1124
|
+
}
|
|
1125
|
+
return null;
|
|
1126
|
+
}
|
|
1127
|
+
function resolveReactEntries(dirname$1) {
|
|
1128
|
+
const require = createRequire(join(dirname$1, "noop.js"));
|
|
1129
|
+
const out = {};
|
|
1130
|
+
for (const pkg of REACT_PACKAGES) {
|
|
1131
|
+
let pkgRoot;
|
|
1132
|
+
let exp;
|
|
1133
|
+
try {
|
|
1134
|
+
const pkgJsonPath = require.resolve(`${pkg}/package.json`);
|
|
1135
|
+
pkgRoot = dirname(pkgJsonPath);
|
|
1136
|
+
exp = JSON.parse(readFileSync(pkgJsonPath, "utf8")).exports;
|
|
1137
|
+
} catch {
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1140
|
+
if (!exp || typeof exp !== "object") continue;
|
|
1141
|
+
for (const key of Object.keys(exp)) {
|
|
1142
|
+
if (key === "./package.json" || key.includes("*")) continue;
|
|
1143
|
+
const target = importTarget(exp[key]);
|
|
1144
|
+
if (!target || /\.(json|css)$/.test(target)) continue;
|
|
1145
|
+
if (/\.(node|bun)\.js$/.test(target)) continue;
|
|
1146
|
+
const file = join(pkgRoot, target);
|
|
1147
|
+
if (!existsSync(file)) continue;
|
|
1148
|
+
out[key === "." ? pkg : `${pkg}${key.slice(1)}`] = file;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return out;
|
|
1152
|
+
}
|
|
1153
|
+
function reactSelfHostPlugin(options) {
|
|
1154
|
+
const entries = resolveReactEntries(options.dirname);
|
|
1155
|
+
let absOutDir = resolve(options.dirname, "dist");
|
|
1156
|
+
let importMap = {};
|
|
1157
|
+
return {
|
|
1158
|
+
name: "ph-react-self-host",
|
|
1159
|
+
apply: "build",
|
|
1160
|
+
configResolved(config) {
|
|
1161
|
+
absOutDir = resolve(config.root, config.build.outDir);
|
|
1162
|
+
importMap = Object.fromEntries(Object.keys(entries).map((spec) => [spec, `${config.base}${REACT_URL_DIR}/${spec}.js`]));
|
|
1163
|
+
for (const pkg of REACT_PACKAGES) importMap[`${pkg}/`] = `${config.base}${REACT_URL_DIR}/${pkg}/`;
|
|
1164
|
+
},
|
|
1165
|
+
transformIndexHtml() {
|
|
1166
|
+
if (!Object.keys(importMap).length) return;
|
|
1167
|
+
return [{
|
|
1168
|
+
tag: "script",
|
|
1169
|
+
attrs: { type: "importmap" },
|
|
1170
|
+
children: JSON.stringify({ imports: importMap }),
|
|
1171
|
+
injectTo: "head-prepend"
|
|
1172
|
+
}];
|
|
1173
|
+
},
|
|
1174
|
+
async closeBundle() {
|
|
1175
|
+
if (!Object.keys(entries).length) return;
|
|
1176
|
+
const { ok, stderr } = await runReactBuild(options.dirname, join(absOutDir, REACT_URL_DIR), entries, options.dev ? "development" : "production");
|
|
1177
|
+
if (!ok) this.error(`react self-host build failed${stderr.trim() ? `:\n${stderr.trim()}` : ""}`);
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
function runReactBuild(dirname, outDir, entries, nodeEnv) {
|
|
1182
|
+
mkdirSync(outDir, { recursive: true });
|
|
1183
|
+
const workerPath = join(outDir, "build-worker.mjs");
|
|
1184
|
+
writeFileSync(workerPath, REACT_BUILD_WORKER);
|
|
1185
|
+
return new Promise((resolvePromise) => {
|
|
1186
|
+
const child = spawn(process.execPath, [
|
|
1187
|
+
workerPath,
|
|
1188
|
+
dirname,
|
|
1189
|
+
outDir,
|
|
1190
|
+
JSON.stringify(entries),
|
|
1191
|
+
nodeEnv
|
|
1192
|
+
], {
|
|
1193
|
+
cwd: dirname,
|
|
1194
|
+
stdio: [
|
|
1195
|
+
"ignore",
|
|
1196
|
+
"pipe",
|
|
1197
|
+
"pipe"
|
|
1198
|
+
]
|
|
1199
|
+
});
|
|
1200
|
+
let out = "";
|
|
1201
|
+
child.stdout.on("data", (d) => out += String(d));
|
|
1202
|
+
child.stderr.on("data", (d) => out += String(d));
|
|
1203
|
+
const done = (r) => {
|
|
1204
|
+
rmSync(workerPath, { force: true });
|
|
1205
|
+
resolvePromise(r);
|
|
1206
|
+
};
|
|
1207
|
+
child.on("exit", (code) => done({
|
|
1208
|
+
ok: code === 0,
|
|
1209
|
+
stderr: out
|
|
1210
|
+
}));
|
|
1211
|
+
child.on("error", (e) => done({
|
|
1212
|
+
ok: false,
|
|
1213
|
+
stderr: String(e)
|
|
1214
|
+
}));
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
const REACT_BUILD_WORKER = `
|
|
1218
|
+
import { createRequire } from 'node:module';
|
|
1219
|
+
import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
1220
|
+
import { join } from 'node:path';
|
|
1221
|
+
import { pathToFileURL } from 'node:url';
|
|
1222
|
+
const [dirname, outDir, entriesJSON, nodeEnv] = process.argv.slice(2);
|
|
1223
|
+
const entries = JSON.parse(entriesJSON);
|
|
1224
|
+
const reqProj = createRequire(join(dirname, 'noop.js'));
|
|
1225
|
+
const { build } = await import(reqProj.resolve('vite'));
|
|
1226
|
+
const srcDir = join(outDir, '.entries');
|
|
1227
|
+
mkdirSync(srcDir, { recursive: true });
|
|
1228
|
+
const entryName = (spec) => spec.replace(/[^\\w]+/g, '_');
|
|
1229
|
+
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(' '));
|
|
1230
|
+
const input = {};
|
|
1231
|
+
for (const [spec, file] of Object.entries(entries)) {
|
|
1232
|
+
const name = entryName(spec);
|
|
1233
|
+
let names = [];
|
|
1234
|
+
try {
|
|
1235
|
+
const ns = await import(pathToFileURL(file).href);
|
|
1236
|
+
const obj = (ns.default && typeof ns.default === 'object') ? ns.default : ns;
|
|
1237
|
+
names = Object.keys(obj).filter((k) => k !== 'default' && k !== '__esModule' && /^[A-Za-z_$][\\w$]*$/.test(k));
|
|
1238
|
+
} catch {}
|
|
1239
|
+
const plain = names.filter((n) => !RESERVED.has(n));
|
|
1240
|
+
const reserved = names.filter((n) => RESERVED.has(n));
|
|
1241
|
+
let src = 'import __m from ' + JSON.stringify(spec) + ';\\nexport default __m;\\n';
|
|
1242
|
+
if (plain.length) src += 'export const { ' + plain.join(', ') + ' } = __m;\\n';
|
|
1243
|
+
reserved.forEach((n, i) => {
|
|
1244
|
+
src += 'const __r' + i + ' = __m[' + JSON.stringify(n) + '];\\nexport { __r' + i + ' as ' + n + ' };\\n';
|
|
1245
|
+
});
|
|
1246
|
+
const entryFile = join(srcDir, name + '.js');
|
|
1247
|
+
writeFileSync(entryFile, src);
|
|
1248
|
+
// Key by the full spec so the output path mirrors the subpath
|
|
1249
|
+
// (react-dom/client -> react-dom/client.js), matching the catch-all import map.
|
|
1250
|
+
input[spec] = entryFile;
|
|
1251
|
+
}
|
|
1252
|
+
try {
|
|
1253
|
+
await build({
|
|
1254
|
+
root: dirname, configFile: false, logLevel: 'error',
|
|
1255
|
+
base: './', publicDir: false,
|
|
1256
|
+
define: { 'process.env.NODE_ENV': JSON.stringify(nodeEnv) },
|
|
1257
|
+
resolve: { conditions: ['browser', 'import', 'module', 'default'] },
|
|
1258
|
+
build: {
|
|
1259
|
+
outDir, emptyOutDir: false, minify: nodeEnv === 'production', target: 'esnext',
|
|
1260
|
+
rollupOptions: {
|
|
1261
|
+
input, preserveEntrySignatures: 'strict',
|
|
1262
|
+
output: { format: 'es', entryFileNames: '[name].js', chunkFileNames: 'chunks/[name]-[hash].js', assetFileNames: 'assets/[name]-[hash][extname]' },
|
|
1263
|
+
},
|
|
1264
|
+
},
|
|
1265
|
+
});
|
|
1266
|
+
rmSync(srcDir, { recursive: true, force: true });
|
|
1267
|
+
// Force exit: rolldown can leave native worker threads alive that keep the
|
|
1268
|
+
// event loop open, hanging the parent's spawn() promise indefinitely.
|
|
1269
|
+
process.exit(0);
|
|
1270
|
+
} catch (err) {
|
|
1271
|
+
console.error(err instanceof Error ? (err.stack ?? err.message) : String(err));
|
|
1272
|
+
process.exit(1);
|
|
1273
|
+
}
|
|
1274
|
+
`;
|
|
1275
|
+
//#endregion
|
|
478
1276
|
//#region connect-utils/vite-config.ts
|
|
479
|
-
const REACT_VERSION = "19.2.0";
|
|
480
|
-
const REACT_IMPORTMAP_IMPORTS = {
|
|
481
|
-
react: `https://esm.sh/react@${REACT_VERSION}`,
|
|
482
|
-
"react/": `https://esm.sh/react@${REACT_VERSION}/`,
|
|
483
|
-
"react-dom": `https://esm.sh/react-dom@${REACT_VERSION}`,
|
|
484
|
-
"react-dom/": `https://esm.sh/react-dom@${REACT_VERSION}/`
|
|
485
|
-
};
|
|
486
1277
|
function getConnectHtmlTags(options = {}) {
|
|
487
1278
|
const { registryUrl, injectTo = "head" } = options;
|
|
488
1279
|
return [
|
|
@@ -490,7 +1281,7 @@ function getConnectHtmlTags(options = {}) {
|
|
|
490
1281
|
tag: "meta",
|
|
491
1282
|
attrs: {
|
|
492
1283
|
"http-equiv": "Content-Security-Policy",
|
|
493
|
-
content: `script-src 'self' 'unsafe-inline' 'unsafe-eval'
|
|
1284
|
+
content: `script-src 'self' 'unsafe-inline' 'unsafe-eval'${registryUrl ? " " + registryUrl : ""}; worker-src 'self' blob:; object-src 'none'; base-uri 'self';`
|
|
494
1285
|
},
|
|
495
1286
|
injectTo
|
|
496
1287
|
},
|
|
@@ -622,17 +1413,18 @@ function getConnectBaseViteConfig(options) {
|
|
|
622
1413
|
const release = process.env.WORKSPACE_VERSION ?? process.env.npm_package_version ?? env.PH_CONNECT_VERSION;
|
|
623
1414
|
const uploadSentrySourcemaps = authToken && org && project;
|
|
624
1415
|
const connectHtmlTags = getConnectHtmlTags({ registryUrl: phPackageRegistryUrl });
|
|
1416
|
+
const devImportmapTag = mode === "development" ? [{
|
|
1417
|
+
tag: "script",
|
|
1418
|
+
attrs: { type: "importmap" },
|
|
1419
|
+
children: JSON.stringify({ imports: {} }),
|
|
1420
|
+
injectTo: "head-prepend"
|
|
1421
|
+
}] : [];
|
|
625
1422
|
const plugins = [
|
|
626
1423
|
tailwind(),
|
|
627
1424
|
react(),
|
|
628
1425
|
createHtmlPlugin({
|
|
629
1426
|
minify: false,
|
|
630
|
-
inject: { tags: [...connectHtmlTags,
|
|
631
|
-
tag: "script",
|
|
632
|
-
attrs: { type: "importmap" },
|
|
633
|
-
children: JSON.stringify({ imports: REACT_IMPORTMAP_IMPORTS }, null, 2),
|
|
634
|
-
injectTo: "head-prepend"
|
|
635
|
-
}] }
|
|
1427
|
+
inject: { tags: [...connectHtmlTags, ...devImportmapTag] }
|
|
636
1428
|
})
|
|
637
1429
|
];
|
|
638
1430
|
if (uploadSentrySourcemaps) plugins.push(import("@sentry/vite-plugin").then(({ sentryVitePlugin }) => sentryVitePlugin({
|
|
@@ -646,7 +1438,8 @@ function getConnectBaseViteConfig(options) {
|
|
|
646
1438
|
bundleSizeOptimizations: { excludeDebugStatements: true },
|
|
647
1439
|
reactComponentAnnotation: { enabled: true }
|
|
648
1440
|
})));
|
|
649
|
-
const
|
|
1441
|
+
const isDebug = process.env.LOG_LEVEL === "debug" || phConfig.connect?.app?.logLevel === "debug";
|
|
1442
|
+
const customLogger = isDebug ? void 0 : viteLogger({ silence: {
|
|
650
1443
|
warnings: ["@import must precede all other statements (besides @charset or empty @layer)"],
|
|
651
1444
|
errors: ["Unterminated string literal"]
|
|
652
1445
|
} });
|
|
@@ -670,7 +1463,7 @@ function getConnectBaseViteConfig(options) {
|
|
|
670
1463
|
return {
|
|
671
1464
|
configFile: false,
|
|
672
1465
|
mode,
|
|
673
|
-
base: connectBasePath ? normalizeBasePath(connectBasePath) : void 0,
|
|
1466
|
+
base: options.dynamicBase ? DYNAMIC_BASE_PLACEHOLDER : connectBasePath ? normalizeBasePath(connectBasePath) : void 0,
|
|
674
1467
|
server: {
|
|
675
1468
|
watch: { ignored: ["**/backup-documents/**", "**/.ph/**"] },
|
|
676
1469
|
fs: { allow: [searchForWorkspaceRoot(options.dirname), ...linkedRoots] }
|
|
@@ -704,16 +1497,24 @@ function getConnectBaseViteConfig(options) {
|
|
|
704
1497
|
packages: localPackagesFromConfig,
|
|
705
1498
|
projectRoot: options.dirname
|
|
706
1499
|
}),
|
|
707
|
-
devReactImportmapPlugin(),
|
|
1500
|
+
devReactImportmapPlugin(options.dirname),
|
|
708
1501
|
...plugins,
|
|
709
1502
|
esmExternalRequirePlugin({ external: reactExternal }),
|
|
710
|
-
|
|
1503
|
+
reactSelfHostPlugin({
|
|
1504
|
+
dirname: options.dirname,
|
|
1505
|
+
dev: mode !== "production" || isDebug
|
|
1506
|
+
}),
|
|
1507
|
+
connectFaviconPlugin(),
|
|
1508
|
+
...options.dynamicBase ? [connectDynamicBasePlugin()] : []
|
|
711
1509
|
],
|
|
712
|
-
worker: {
|
|
1510
|
+
worker: {
|
|
1511
|
+
format: "es",
|
|
1512
|
+
...options.dynamicBase ? { plugins: () => [connectDynamicBasePlugin({ forWorker: true })] } : {}
|
|
1513
|
+
},
|
|
713
1514
|
build: { sourcemap: true }
|
|
714
1515
|
};
|
|
715
1516
|
}
|
|
716
1517
|
//#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 };
|
|
1518
|
+
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
1519
|
|
|
719
1520
|
//# sourceMappingURL=index.mjs.map
|