@pylonsync/functions 0.3.277 → 0.3.279
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 +58 -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.279",
|
|
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,27 @@ 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
|
+
const marker = path.join(outdir, ".prebuilt");
|
|
760
|
+
if (fs.existsSync(marker) && fs.existsSync(manifestPath)) {
|
|
761
|
+
return { manifestPath, outdir };
|
|
762
|
+
}
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
|
|
745
766
|
/**
|
|
746
767
|
* Run the bundler in-process and return the manifest path + outdir.
|
|
747
768
|
* Used from `handleBundleClient` (protocol RPC path from Rust) AND
|
|
@@ -750,6 +771,18 @@ let _inflightBuild: Promise<BuildOutput> | null = null;
|
|
|
750
771
|
export async function buildClientBundle(
|
|
751
772
|
appDirRel: string = "app",
|
|
752
773
|
): Promise<BuildOutput> {
|
|
774
|
+
// Prebuilt bundle (Pylon Cloud): the builder already produced a complete,
|
|
775
|
+
// content-hashed client-build with `public_prefix` baked to the CDN, shipped
|
|
776
|
+
// it in the artifact, and published the hashed assets to the CDN. Reuse it
|
|
777
|
+
// verbatim instead of rebuilding on the app machine — rebuilding would (a)
|
|
778
|
+
// burn cold-start CPU on every machine on every boot and (b) risk emitting a
|
|
779
|
+
// manifest whose hashes don't match the assets already on the CDN. The
|
|
780
|
+
// `.prebuilt` marker is written LAST by the builder, so a torn/partial copy
|
|
781
|
+
// is never mistaken for complete. Dev never writes the marker, so local
|
|
782
|
+
// hot-rebuild is unaffected.
|
|
783
|
+
const prebuilt = await _prebuiltBundle();
|
|
784
|
+
if (prebuilt) return prebuilt;
|
|
785
|
+
|
|
753
786
|
if (_inflightBuild) return _inflightBuild;
|
|
754
787
|
_inflightBuild = (async () => {
|
|
755
788
|
try {
|
|
@@ -1212,3 +1245,28 @@ function makeBuildId(): string {
|
|
|
1212
1245
|
_buildCounter += 1;
|
|
1213
1246
|
return `${process.pid.toString(36)}-${Date.now().toString(36)}-${_buildCounter}`;
|
|
1214
1247
|
}
|
|
1248
|
+
|
|
1249
|
+
/**
|
|
1250
|
+
* Base URL/path prepended to every SSR-emitted asset reference (the entry
|
|
1251
|
+
* `<script type="module">`, `<link rel="stylesheet">`, and `modulepreload`s)
|
|
1252
|
+
* and to client-side chunk loads — baked into the bundle manifest as
|
|
1253
|
+
* `public_prefix`.
|
|
1254
|
+
*
|
|
1255
|
+
* Defaults to `/_pylon/build/`, which the Rust host serves from the build's
|
|
1256
|
+
* local `outdir`. That keeps dev and any deploy that doesn't opt in unchanged.
|
|
1257
|
+
*
|
|
1258
|
+
* Set `PYLON_PUBLIC_PREFIX` to an ABSOLUTE base (a CDN / object-storage URL for
|
|
1259
|
+
* this build's content-hashed assets, e.g. `https://assets.pyln.dev/<build>/`)
|
|
1260
|
+
* to serve assets off the app machines entirely. This is what makes hashed
|
|
1261
|
+
* assets survive an artifact prune and resolve IDENTICALLY across every Fly
|
|
1262
|
+
* machine: the entry filename SSR emits becomes an absolute URL that always
|
|
1263
|
+
* exists, instead of a machine-local path a prune/rollover can 404 (the bug
|
|
1264
|
+
* where a stale machine emitted `client-entry-…-<oldhash>.js` after that file
|
|
1265
|
+
* was pruned, breaking hydration). A trailing slash is enforced so
|
|
1266
|
+
* `${prefix}${file}` always joins correctly.
|
|
1267
|
+
*/
|
|
1268
|
+
function resolvePublicPrefix(): string {
|
|
1269
|
+
const raw = (process.env.PYLON_PUBLIC_PREFIX ?? "").trim();
|
|
1270
|
+
if (!raw) return "/_pylon/build/";
|
|
1271
|
+
return raw.endsWith("/") ? raw : `${raw}/`;
|
|
1272
|
+
}
|
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
|