@pylonsync/functions 0.3.278 → 0.3.280
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/package.json +3 -2
- package/src/ssr-client-bundler.ts +79 -0
- package/src/ssr-runtime.ts +10 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pylonsync/functions",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.280",
|
|
4
4
|
"description": "TypeScript function runtime for pylon — defines server-side queries, mutations, and actions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"types": "./src/index.ts",
|
|
11
11
|
"default": "./src/index.ts"
|
|
12
12
|
},
|
|
13
|
-
"./runtime": "./src/runtime.ts"
|
|
13
|
+
"./runtime": "./src/runtime.ts",
|
|
14
|
+
"./client-bundler": "./src/ssr-client-bundler.ts"
|
|
14
15
|
},
|
|
15
16
|
"bin": {
|
|
16
17
|
"pylon-functions-runtime": "src/runtime.ts"
|
|
@@ -742,6 +742,48 @@ export interface BuildOutput {
|
|
|
742
742
|
*/
|
|
743
743
|
let _inflightBuild: Promise<BuildOutput> | null = null;
|
|
744
744
|
|
|
745
|
+
/**
|
|
746
|
+
* If a complete prebuilt client-build is present on disk (Pylon Cloud: shipped
|
|
747
|
+
* in the artifact by the builder, marked with `.prebuilt` written last), return
|
|
748
|
+
* its manifest + outdir so callers skip the rebuild entirely. Returns null when
|
|
749
|
+
* there's no prebuilt bundle (dev, or a deploy that didn't pre-build) — callers
|
|
750
|
+
* then build normally.
|
|
751
|
+
*/
|
|
752
|
+
async function _prebuiltBundle(): Promise<BuildOutput | null> {
|
|
753
|
+
const fsMod: any = await import("node:fs");
|
|
754
|
+
const pathMod: any = await import("node:path");
|
|
755
|
+
const fs = fsMod.default ?? fsMod;
|
|
756
|
+
const path = pathMod.default ?? pathMod;
|
|
757
|
+
const outdir = path.join(process.cwd(), ".pylon", "client-build");
|
|
758
|
+
const manifestPath = path.join(outdir, "manifest.json");
|
|
759
|
+
if (!fs.existsSync(manifestPath)) return null;
|
|
760
|
+
|
|
761
|
+
// Reuse a prebuilt bundle when it's explicitly marked (`.prebuilt`) OR when
|
|
762
|
+
// its manifest already targets an ABSOLUTE (CDN) `public_prefix`. The builder
|
|
763
|
+
// bakes an absolute prefix only when it pre-built for the CDN, and it
|
|
764
|
+
// published the hashed assets under THOSE exact hashes — so the runtime must
|
|
765
|
+
// serve this exact manifest verbatim (a local rebuild emits different hashes
|
|
766
|
+
// that would 404 on the CDN). The manifest is a regular file that always
|
|
767
|
+
// ships with the build; keying on it (not just the `.prebuilt` dotfile, which
|
|
768
|
+
// can be dropped in transit) makes reuse robust. A same-origin
|
|
769
|
+
// `/_pylon/build/` manifest is a normal dev/local build → don't short-circuit
|
|
770
|
+
// (let dev hot-rebuild).
|
|
771
|
+
const marker = path.join(outdir, ".prebuilt");
|
|
772
|
+
if (fs.existsSync(marker)) return { manifestPath, outdir };
|
|
773
|
+
try {
|
|
774
|
+
const m = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
775
|
+
if (
|
|
776
|
+
typeof m.public_prefix === "string" &&
|
|
777
|
+
/^https?:\/\//i.test(m.public_prefix)
|
|
778
|
+
) {
|
|
779
|
+
return { manifestPath, outdir };
|
|
780
|
+
}
|
|
781
|
+
} catch {
|
|
782
|
+
/* unreadable/partial manifest → fall through and rebuild */
|
|
783
|
+
}
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
|
|
745
787
|
/**
|
|
746
788
|
* Run the bundler in-process and return the manifest path + outdir.
|
|
747
789
|
* Used from `handleBundleClient` (protocol RPC path from Rust) AND
|
|
@@ -750,6 +792,18 @@ let _inflightBuild: Promise<BuildOutput> | null = null;
|
|
|
750
792
|
export async function buildClientBundle(
|
|
751
793
|
appDirRel: string = "app",
|
|
752
794
|
): Promise<BuildOutput> {
|
|
795
|
+
// Prebuilt bundle (Pylon Cloud): the builder already produced a complete,
|
|
796
|
+
// content-hashed client-build with `public_prefix` baked to the CDN, shipped
|
|
797
|
+
// it in the artifact, and published the hashed assets to the CDN. Reuse it
|
|
798
|
+
// verbatim instead of rebuilding on the app machine — rebuilding would (a)
|
|
799
|
+
// burn cold-start CPU on every machine on every boot and (b) risk emitting a
|
|
800
|
+
// manifest whose hashes don't match the assets already on the CDN. The
|
|
801
|
+
// `.prebuilt` marker is written LAST by the builder, so a torn/partial copy
|
|
802
|
+
// is never mistaken for complete. Dev never writes the marker, so local
|
|
803
|
+
// hot-rebuild is unaffected.
|
|
804
|
+
const prebuilt = await _prebuiltBundle();
|
|
805
|
+
if (prebuilt) return prebuilt;
|
|
806
|
+
|
|
753
807
|
if (_inflightBuild) return _inflightBuild;
|
|
754
808
|
_inflightBuild = (async () => {
|
|
755
809
|
try {
|
|
@@ -1212,3 +1266,28 @@ function makeBuildId(): string {
|
|
|
1212
1266
|
_buildCounter += 1;
|
|
1213
1267
|
return `${process.pid.toString(36)}-${Date.now().toString(36)}-${_buildCounter}`;
|
|
1214
1268
|
}
|
|
1269
|
+
|
|
1270
|
+
/**
|
|
1271
|
+
* Base URL/path prepended to every SSR-emitted asset reference (the entry
|
|
1272
|
+
* `<script type="module">`, `<link rel="stylesheet">`, and `modulepreload`s)
|
|
1273
|
+
* and to client-side chunk loads — baked into the bundle manifest as
|
|
1274
|
+
* `public_prefix`.
|
|
1275
|
+
*
|
|
1276
|
+
* Defaults to `/_pylon/build/`, which the Rust host serves from the build's
|
|
1277
|
+
* local `outdir`. That keeps dev and any deploy that doesn't opt in unchanged.
|
|
1278
|
+
*
|
|
1279
|
+
* Set `PYLON_PUBLIC_PREFIX` to an ABSOLUTE base (a CDN / object-storage URL for
|
|
1280
|
+
* this build's content-hashed assets, e.g. `https://assets.pyln.dev/<build>/`)
|
|
1281
|
+
* to serve assets off the app machines entirely. This is what makes hashed
|
|
1282
|
+
* assets survive an artifact prune and resolve IDENTICALLY across every Fly
|
|
1283
|
+
* machine: the entry filename SSR emits becomes an absolute URL that always
|
|
1284
|
+
* exists, instead of a machine-local path a prune/rollover can 404 (the bug
|
|
1285
|
+
* where a stale machine emitted `client-entry-…-<oldhash>.js` after that file
|
|
1286
|
+
* was pruned, breaking hydration). A trailing slash is enforced so
|
|
1287
|
+
* `${prefix}${file}` always joins correctly.
|
|
1288
|
+
*/
|
|
1289
|
+
function resolvePublicPrefix(): string {
|
|
1290
|
+
const raw = (process.env.PYLON_PUBLIC_PREFIX ?? "").trim();
|
|
1291
|
+
if (!raw) return "/_pylon/build/";
|
|
1292
|
+
return raw.endsWith("/") ? raw : `${raw}/`;
|
|
1293
|
+
}
|
package/src/ssr-runtime.ts
CHANGED
|
@@ -1018,7 +1018,12 @@ export function buildHydrationTail(args: {
|
|
|
1018
1018
|
.replace(/\u2029/g, "\\u2029");
|
|
1019
1019
|
let tail = `<script id="__PYLON_DATA__" type="application/json">${json}</script>`;
|
|
1020
1020
|
if (args.manifestRoute) {
|
|
1021
|
-
|
|
1021
|
+
// A cross-origin CDN module (`public_prefix` is an absolute http(s) URL) is
|
|
1022
|
+
// fetched in CORS mode; `crossorigin` makes the matching modulepreload
|
|
1023
|
+
// reusable (no double fetch) and surfaces load errors. No-op for the
|
|
1024
|
+
// same-origin `/_pylon/build/` default.
|
|
1025
|
+
const co = /^https?:\/\//i.test(args.publicPrefix) ? " crossorigin" : "";
|
|
1026
|
+
tail += `<script type="module"${co} src="${args.publicPrefix}${args.manifestRoute.file}"></script>`;
|
|
1022
1027
|
} else {
|
|
1023
1028
|
tail += `<script>console.warn(${JSON.stringify(`[pylon ssr] hydration disabled: ${args.manifestErr}`)})</script>`;
|
|
1024
1029
|
}
|
|
@@ -1117,11 +1122,12 @@ async function renderBoundaryToClient(
|
|
|
1117
1122
|
}
|
|
1118
1123
|
}
|
|
1119
1124
|
if (manifestRoute) {
|
|
1125
|
+
const co = /^https?:\/\//i.test(publicPrefix) ? " crossorigin" : "";
|
|
1120
1126
|
for (const css of manifestRoute.css) {
|
|
1121
1127
|
headBlob += `<link rel="stylesheet" href="${publicPrefix}${css}">`;
|
|
1122
1128
|
}
|
|
1123
1129
|
for (const chunk of manifestRoute.imports) {
|
|
1124
|
-
headBlob += `<link rel="modulepreload" href="${publicPrefix}${chunk}">`;
|
|
1130
|
+
headBlob += `<link rel="modulepreload"${co} href="${publicPrefix}${chunk}">`;
|
|
1125
1131
|
}
|
|
1126
1132
|
} else {
|
|
1127
1133
|
// No per-boundary entry → fall back to the global stylesheet union so the
|
|
@@ -1884,11 +1890,12 @@ export async function handleRenderRoute(
|
|
|
1884
1890
|
// (it needs the inline __PYLON_DATA__ to have been parsed first).
|
|
1885
1891
|
let headBlob = "";
|
|
1886
1892
|
if (preloadManifestRoute) {
|
|
1893
|
+
const co = /^https?:\/\//i.test(preloadPublicPrefix) ? " crossorigin" : "";
|
|
1887
1894
|
for (const css of preloadManifestRoute.css) {
|
|
1888
1895
|
headBlob += `<link rel="stylesheet" href="${preloadPublicPrefix}${css}">`;
|
|
1889
1896
|
}
|
|
1890
1897
|
for (const chunk of preloadManifestRoute.imports) {
|
|
1891
|
-
headBlob += `<link rel="modulepreload" href="${preloadPublicPrefix}${chunk}">`;
|
|
1898
|
+
headBlob += `<link rel="modulepreload"${co} href="${preloadPublicPrefix}${chunk}">`;
|
|
1892
1899
|
}
|
|
1893
1900
|
} else {
|
|
1894
1901
|
// No per-route client entry. This is the unmatched-URL not-found
|