@powerhousedao/builder-tools 6.2.0-dev.9 → 6.2.0-rc.0
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 +48 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +872 -164
- package/dist/index.mjs.map +1 -1
- package/package.json +27 -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";
|
|
@@ -10,13 +13,552 @@ import tailwind from "@tailwindcss/vite";
|
|
|
10
13
|
import react from "@vitejs/plugin-react";
|
|
11
14
|
import { createLogger, esmExternalRequirePlugin, loadEnv, searchForWorkspaceRoot } from "vite";
|
|
12
15
|
import { createHtmlPlugin } from "vite-plugin-html";
|
|
13
|
-
import MagicString from "magic-string";
|
|
14
16
|
//#region connect-utils/constants.ts
|
|
15
17
|
const EXTERNAL_PACKAGES_IMPORT = "PH:EXTERNAL_PACKAGES";
|
|
16
18
|
const IMPORT_SCRIPT_FILE = "external-packages.js";
|
|
17
19
|
const LOCAL_PACKAGE_ID = "ph:local-package";
|
|
18
20
|
const PH_DIR_NAME = ".ph";
|
|
19
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
|
|
20
562
|
//#region connect-utils/helpers.ts
|
|
21
563
|
const DEFAULT_CONNECT_OUTDIR = ".ph/connect-build/dist/";
|
|
22
564
|
function resolveViteConfigPath(options) {
|
|
@@ -247,6 +789,54 @@ const runtimeConfigSchema = {
|
|
|
247
789
|
connect: phConnectRuntimeConfigSchema
|
|
248
790
|
}
|
|
249
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
|
+
}
|
|
250
840
|
//#endregion
|
|
251
841
|
//#region connect-utils/vite-plugins/dev-external-react.ts
|
|
252
842
|
const REACT_DEPS = [
|
|
@@ -258,9 +848,36 @@ const REACT_DEPS = [
|
|
|
258
848
|
];
|
|
259
849
|
const SHIM_PATH = "__ph/dev-react-shim/";
|
|
260
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];
|
|
261
870
|
function withBase(base, p) {
|
|
262
871
|
return `${base}${p}`.replace(/\/{2,}/g, "/");
|
|
263
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
|
+
}
|
|
264
881
|
/**
|
|
265
882
|
* Dev-only sibling of `esmExternalRequirePlugin`. The build path externalizes
|
|
266
883
|
* React via Rolldown so an importmap hands the same React instance to both
|
|
@@ -269,7 +886,7 @@ function withBase(base, p) {
|
|
|
269
886
|
* export — so a CDN editor that does `import { lazy } from "react"` would
|
|
270
887
|
* fail with "no named export 'lazy'".
|
|
271
888
|
*
|
|
272
|
-
* This plugin:
|
|
889
|
+
* This plugin owns the page import map. It always:
|
|
273
890
|
* 1. Forces React into `optimizeDeps.include` so the optimizer always knows
|
|
274
891
|
* about it.
|
|
275
892
|
* 2. Serves a shim per React module at a stable URL. Each shim imports
|
|
@@ -277,14 +894,46 @@ function withBase(base, p) {
|
|
|
277
894
|
* and re-exports React's named members so editors importing
|
|
278
895
|
* `{ lazy }`, `{ jsx }`, etc. work.
|
|
279
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.
|
|
280
904
|
*/
|
|
281
|
-
function devReactImportmapPlugin() {
|
|
905
|
+
async function devReactImportmapPlugin(projectRoot = process.cwd()) {
|
|
282
906
|
let namedExports = /* @__PURE__ */ new Map();
|
|
283
907
|
let base = "/";
|
|
284
|
-
|
|
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 = {
|
|
285
925
|
name: "ph-dev-react-importmap",
|
|
286
926
|
apply: "serve",
|
|
287
|
-
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
|
+
},
|
|
288
937
|
configResolved(config) {
|
|
289
938
|
base = config.base;
|
|
290
939
|
},
|
|
@@ -298,6 +947,30 @@ function devReactImportmapPlugin() {
|
|
|
298
947
|
return [id, []];
|
|
299
948
|
}
|
|
300
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
|
+
}
|
|
301
974
|
const shimPrefixes = [withBase(base, SHIM_PATH), `/${SHIM_PATH}`];
|
|
302
975
|
server.middlewares.use((req, res, next) => {
|
|
303
976
|
const prefix = shimPrefixes.find((p) => req.url?.startsWith(p));
|
|
@@ -325,98 +998,17 @@ function devReactImportmapPlugin() {
|
|
|
325
998
|
if (!browserHash) return;
|
|
326
999
|
const shimPrefix = withBase(base, SHIM_PATH);
|
|
327
1000
|
const imports = Object.fromEntries(REACT_DEPS.map((id) => [id, `${shimPrefix}${id}.js?v=${browserHash}`]));
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Placeholder base used when Connect is built in dynamic-base mode. The Vite
|
|
337
|
-
* `base` option is set to this token; this plugin rewrites it in the emitted
|
|
338
|
-
* output so the effective base is resolved at serve time from a global instead
|
|
339
|
-
* of being baked at build time.
|
|
340
|
-
*
|
|
341
|
-
* Trailing slash matches `normalizeBasePath` output, so the token sits in the
|
|
342
|
-
* same syntactic position as a concrete base would.
|
|
343
|
-
*/
|
|
344
|
-
const DYNAMIC_BASE_PLACEHOLDER = "/__PH_DYNAMIC_BASE__/";
|
|
345
|
-
/**
|
|
346
|
-
* Global the runtime (ph-clint proxy) must set before the entry bundle loads.
|
|
347
|
-
* Value is the normalized deploy base, e.g. "/myagent/" or "/". The JS rewrite
|
|
348
|
-
* below resolves all asset / lazy-chunk / BASE_URL references against it.
|
|
349
|
-
*/
|
|
350
|
-
const RUNTIME_GLOBAL = "globalThis.__PH_DYNAMIC_BASE__";
|
|
351
|
-
const BASE_EXPR = `(${RUNTIME_GLOBAL}||"/")`;
|
|
352
|
-
/**
|
|
353
|
-
* Worker scope has its own global object; the proxy injects the runtime global
|
|
354
|
-
* into the main-thread `<head>` only, so a worker chunk's rewritten references
|
|
355
|
-
* would fall back to "/" and fetch siblings (pglite .data/.wasm) without the
|
|
356
|
-
* deploy prefix. Prepended to worker chunks, this defines the global in worker
|
|
357
|
-
* scope from the worker's own script URL: the worker loads from
|
|
358
|
-
* `<base>assets/<name>-<hash>.js`, so stripping `assets/<file>` off
|
|
359
|
-
* `self.location.pathname` yields the same trailing-slash base the main thread
|
|
360
|
-
* holds ("/myagent/" or "/").
|
|
361
|
-
*/
|
|
362
|
-
const WORKER_PRELUDE = `${RUNTIME_GLOBAL}=self.location.pathname.replace(/assets\\/[^/]*$/,"");\n`;
|
|
363
|
-
const PLACEHOLDER_LITERAL = new RegExp(`(["'\`])${escapeForRegExp(DYNAMIC_BASE_PLACEHOLDER)}((?:(?!\\1)[^$])*)\\1`, "g");
|
|
364
|
-
/**
|
|
365
|
-
* Rewrites the placeholder base in emitted JS chunks to a runtime expression so
|
|
366
|
-
* one built `dist/connect` serves under any subpath. Rolldown-native: per-match
|
|
367
|
-
* MagicString edits in `renderChunk`, no AST splicing (the SWC byte-offset
|
|
368
|
-
* splicing in vite-plugin-dynamic-base corrupts Rolldown chunks). Each edited
|
|
369
|
-
* chunk returns a hires sourcemap that the bundler composes into the chunk's
|
|
370
|
-
* map chain, so the emitted `.map` files track the rewrite (and the worker
|
|
371
|
-
* prelude's line shift).
|
|
372
|
-
*
|
|
373
|
-
* What gets rewritten in JS, all of which emit the base as a quoted string
|
|
374
|
-
* literal beginning with the placeholder:
|
|
375
|
-
* - asset URLs (`new URL("/__PH_DYNAMIC_BASE__/assets/x.png", ...)`)
|
|
376
|
-
* - dynamic-import / lazy-chunk preload URLs
|
|
377
|
-
* - the inlined `import.meta.env.BASE_URL` (drives Connect's router basename
|
|
378
|
-
* and BASE_URL-relative fetches such as `${BASE_URL}ph-packages.json`)
|
|
379
|
-
*
|
|
380
|
-
* A literal `"/__PH_DYNAMIC_BASE__/foo"` becomes `((globalThis.__PH_DYNAMIC_BASE__||"/")+"foo")`;
|
|
381
|
-
* a bare `"/__PH_DYNAMIC_BASE__/"` (the BASE_URL value) becomes `(globalThis.__PH_DYNAMIC_BASE__||"/")`.
|
|
382
|
-
*
|
|
383
|
-
* HTML is left untouched: the entry `<script>`/`<link>` tags keep the literal
|
|
384
|
-
* placeholder so the proxy substitutes it with the concrete base at serve time
|
|
385
|
-
* (the same proxy also sets the runtime global for the JS rewrite). See the
|
|
386
|
-
* plugin's module doc / report for the exact serve-time contract.
|
|
387
|
-
*
|
|
388
|
-
* CSS `url(...)` references resolve relative to the stylesheet's own URL, so
|
|
389
|
-
* they need no rewrite once the stylesheet itself is loaded from the right
|
|
390
|
-
* prefix (which the HTML substitution handles).
|
|
391
|
-
*/
|
|
392
|
-
function connectDynamicBasePlugin(options = {}) {
|
|
393
|
-
return {
|
|
394
|
-
name: "ph-connect-dynamic-base",
|
|
395
|
-
enforce: "post",
|
|
396
|
-
renderChunk(code, chunk) {
|
|
397
|
-
if (!code.includes("/__PH_DYNAMIC_BASE__/")) return null;
|
|
398
|
-
const s = new MagicString(code);
|
|
399
|
-
for (const match of code.matchAll(PLACEHOLDER_LITERAL)) {
|
|
400
|
-
const rest = match[2];
|
|
401
|
-
s.overwrite(match.index, match.index + match[0].length, rest.length === 0 ? BASE_EXPR : `(${BASE_EXPR}+${JSON.stringify(rest)})`);
|
|
402
|
-
}
|
|
403
|
-
if (options.forWorker) {
|
|
404
|
-
s.prepend(WORKER_PRELUDE);
|
|
405
|
-
this.info(`dynamic-base: worker prelude prepended to ${chunk.fileName}`);
|
|
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>`);
|
|
406
1008
|
}
|
|
407
|
-
if (!s.hasChanged()) return null;
|
|
408
|
-
return {
|
|
409
|
-
code: s.toString(),
|
|
410
|
-
map: s.generateMap({ hires: true })
|
|
411
|
-
};
|
|
412
|
-
},
|
|
413
|
-
generateBundle(_options, bundle) {
|
|
414
|
-
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}`);
|
|
415
1009
|
}
|
|
416
1010
|
};
|
|
417
|
-
|
|
418
|
-
function escapeForRegExp(s) {
|
|
419
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1011
|
+
return vendorActive ? [ext, main] : main;
|
|
420
1012
|
}
|
|
421
1013
|
//#endregion
|
|
422
1014
|
//#region connect-utils/vite-plugins/favicon.ts
|
|
@@ -454,56 +1046,6 @@ function connectFaviconPlugin() {
|
|
|
454
1046
|
};
|
|
455
1047
|
}
|
|
456
1048
|
//#endregion
|
|
457
|
-
//#region connect-utils/vite-plugins/ph-bundled-packages.ts
|
|
458
|
-
const VIRTUAL_ID = "ph-bundled-packages-virtual";
|
|
459
|
-
const RESOLVED_VIRTUAL_ID = "\0virtual:" + VIRTUAL_ID;
|
|
460
|
-
function readBundledPackageVersion(projectRoot, name) {
|
|
461
|
-
try {
|
|
462
|
-
const raw = fs.readFileSync(path.join(projectRoot, "node_modules", name, "package.json"), "utf-8");
|
|
463
|
-
const pkg = JSON.parse(raw);
|
|
464
|
-
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
465
|
-
} catch {
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
function makeRegisterModule(packages, projectRoot) {
|
|
470
|
-
if (packages.length === 0) return "export default () => {};\n";
|
|
471
|
-
const imports = [];
|
|
472
|
-
const calls = [];
|
|
473
|
-
packages.forEach((name, i) => {
|
|
474
|
-
const moduleName = `pkg${i}`;
|
|
475
|
-
const version = readBundledPackageVersion(projectRoot, name);
|
|
476
|
-
imports.push(`import * as ${moduleName} from ${JSON.stringify(name)};`);
|
|
477
|
-
imports.push(`import ${JSON.stringify(`${name}/style.css`)};`);
|
|
478
|
-
calls.push(` pm.addLocalPackage(${JSON.stringify(name)}, ${moduleName}, ${JSON.stringify(version)});`);
|
|
479
|
-
});
|
|
480
|
-
return `${imports.join("\n")}\n\nexport default function register(pm) {\n${calls.join("\n")}\n};\n`;
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* Emits a virtual module `ph-bundled-packages-virtual` whose default export
|
|
484
|
-
* is a `register(packageManager)` function. When called at runtime (from
|
|
485
|
-
* Connect's bootstrap), it registers each bundled package with the package
|
|
486
|
-
* manager the same way Common/Vetra are registered — meaning they work
|
|
487
|
-
* offline without the registry being reachable.
|
|
488
|
-
*
|
|
489
|
-
* When the list is empty, the module exports a no-op function so Connect's
|
|
490
|
-
* bootstrap code can always import it unconditionally.
|
|
491
|
-
*/
|
|
492
|
-
function phBundledPackagesPlugin(options) {
|
|
493
|
-
const projectRoot = options.projectRoot ?? process.cwd();
|
|
494
|
-
const moduleSource = makeRegisterModule(options.packages, projectRoot);
|
|
495
|
-
return {
|
|
496
|
-
name: "vite-plugin-ph-bundled-packages",
|
|
497
|
-
enforce: "pre",
|
|
498
|
-
resolveId(id) {
|
|
499
|
-
if (id === VIRTUAL_ID) return RESOLVED_VIRTUAL_ID;
|
|
500
|
-
},
|
|
501
|
-
load(id) {
|
|
502
|
-
if (id === RESOLVED_VIRTUAL_ID) return moduleSource;
|
|
503
|
-
}
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
//#endregion
|
|
507
1049
|
//#region connect-utils/vite-plugins/ph-config.ts
|
|
508
1050
|
function readProjectPackageInfo(projectRoot) {
|
|
509
1051
|
if (!projectRoot) return null;
|
|
@@ -564,14 +1106,174 @@ function phConfigPlugin(options) {
|
|
|
564
1106
|
};
|
|
565
1107
|
}
|
|
566
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
|
|
567
1276
|
//#region connect-utils/vite-config.ts
|
|
568
|
-
const REACT_VERSION = "19.2.0";
|
|
569
|
-
const REACT_IMPORTMAP_IMPORTS = {
|
|
570
|
-
react: `https://esm.sh/react@${REACT_VERSION}`,
|
|
571
|
-
"react/": `https://esm.sh/react@${REACT_VERSION}/`,
|
|
572
|
-
"react-dom": `https://esm.sh/react-dom@${REACT_VERSION}`,
|
|
573
|
-
"react-dom/": `https://esm.sh/react-dom@${REACT_VERSION}/`
|
|
574
|
-
};
|
|
575
1277
|
function getConnectHtmlTags(options = {}) {
|
|
576
1278
|
const { registryUrl, injectTo = "head" } = options;
|
|
577
1279
|
return [
|
|
@@ -579,7 +1281,7 @@ function getConnectHtmlTags(options = {}) {
|
|
|
579
1281
|
tag: "meta",
|
|
580
1282
|
attrs: {
|
|
581
1283
|
"http-equiv": "Content-Security-Policy",
|
|
582
|
-
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';`
|
|
583
1285
|
},
|
|
584
1286
|
injectTo
|
|
585
1287
|
},
|
|
@@ -711,17 +1413,18 @@ function getConnectBaseViteConfig(options) {
|
|
|
711
1413
|
const release = process.env.WORKSPACE_VERSION ?? process.env.npm_package_version ?? env.PH_CONNECT_VERSION;
|
|
712
1414
|
const uploadSentrySourcemaps = authToken && org && project;
|
|
713
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
|
+
}] : [];
|
|
714
1422
|
const plugins = [
|
|
715
1423
|
tailwind(),
|
|
716
1424
|
react(),
|
|
717
1425
|
createHtmlPlugin({
|
|
718
1426
|
minify: false,
|
|
719
|
-
inject: { tags: [...connectHtmlTags,
|
|
720
|
-
tag: "script",
|
|
721
|
-
attrs: { type: "importmap" },
|
|
722
|
-
children: JSON.stringify({ imports: REACT_IMPORTMAP_IMPORTS }, null, 2),
|
|
723
|
-
injectTo: "head-prepend"
|
|
724
|
-
}] }
|
|
1427
|
+
inject: { tags: [...connectHtmlTags, ...devImportmapTag] }
|
|
725
1428
|
})
|
|
726
1429
|
];
|
|
727
1430
|
if (uploadSentrySourcemaps) plugins.push(import("@sentry/vite-plugin").then(({ sentryVitePlugin }) => sentryVitePlugin({
|
|
@@ -735,7 +1438,8 @@ function getConnectBaseViteConfig(options) {
|
|
|
735
1438
|
bundleSizeOptimizations: { excludeDebugStatements: true },
|
|
736
1439
|
reactComponentAnnotation: { enabled: true }
|
|
737
1440
|
})));
|
|
738
|
-
const
|
|
1441
|
+
const isDebug = process.env.LOG_LEVEL === "debug" || phConfig.connect?.app?.logLevel === "debug";
|
|
1442
|
+
const customLogger = isDebug ? void 0 : viteLogger({ silence: {
|
|
739
1443
|
warnings: ["@import must precede all other statements (besides @charset or empty @layer)"],
|
|
740
1444
|
errors: ["Unterminated string literal"]
|
|
741
1445
|
} });
|
|
@@ -793,9 +1497,13 @@ function getConnectBaseViteConfig(options) {
|
|
|
793
1497
|
packages: localPackagesFromConfig,
|
|
794
1498
|
projectRoot: options.dirname
|
|
795
1499
|
}),
|
|
796
|
-
devReactImportmapPlugin(),
|
|
1500
|
+
devReactImportmapPlugin(options.dirname),
|
|
797
1501
|
...plugins,
|
|
798
1502
|
esmExternalRequirePlugin({ external: reactExternal }),
|
|
1503
|
+
reactSelfHostPlugin({
|
|
1504
|
+
dirname: options.dirname,
|
|
1505
|
+
dev: mode !== "production" || isDebug
|
|
1506
|
+
}),
|
|
799
1507
|
connectFaviconPlugin(),
|
|
800
1508
|
...options.dynamicBase ? [connectDynamicBasePlugin()] : []
|
|
801
1509
|
],
|
|
@@ -807,6 +1515,6 @@ function getConnectBaseViteConfig(options) {
|
|
|
807
1515
|
};
|
|
808
1516
|
}
|
|
809
1517
|
//#endregion
|
|
810
|
-
export { DEFAULT_CONNECT_OUTDIR, DYNAMIC_BASE_PLACEHOLDER, EXTERNAL_PACKAGES_IMPORT, IMPORT_SCRIPT_FILE, LOCAL_PACKAGE_ID, PH_DIR_NAME, RUNTIME_CONFIG_SCHEMA_ID, RUNTIME_CONFIG_SCHEMA_URL, appendToHtmlHead, backupIndexHtml, connectDynamicBasePlugin, 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 };
|
|
811
1519
|
|
|
812
1520
|
//# sourceMappingURL=index.mjs.map
|