@pylonsync/functions 0.11.4 → 0.11.6
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.
|
@@ -24,6 +24,14 @@ interface BundleClientMessage {
|
|
|
24
24
|
* import into a loud build failure that names the offending importer.
|
|
25
25
|
*/
|
|
26
26
|
export declare function assertNotServerOnly(specifier: string, importer: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* A module file → its project-relative, extension-less component path
|
|
29
|
+
* (`<cwd>/app/blog/page.tsx` → `app/blog/page`). Always "/"-separated, on
|
|
30
|
+
* Windows too: the result is a manifest key, the input to the SSR boundary
|
|
31
|
+
* walk, and is spliced into generated `import` specifiers, where a backslash
|
|
32
|
+
* is a string escape (`"../app\page"` resolves `../apppage`).
|
|
33
|
+
*/
|
|
34
|
+
export declare function componentPathFor(path: any, cwd: string, file: string): string;
|
|
27
35
|
/**
|
|
28
36
|
* Every route-level `loading` module under the app dir, as project-relative
|
|
29
37
|
* component paths (`app/dashboard/loading`), sorted for a stable build.
|
|
@@ -108,6 +116,7 @@ export declare function buildClientBundle(appDirRel?: string): Promise<BuildOutp
|
|
|
108
116
|
* project to need Tailwind installed.
|
|
109
117
|
*/
|
|
110
118
|
export declare function buildTailwind(fs: any, path: any, cwd: string, outdir: string, appDirRel: string): Promise<string | null>;
|
|
119
|
+
export declare function _doBuildInner(fs: any, path: any, cwd: string, appDirRel: string): Promise<BuildOutput>;
|
|
111
120
|
/**
|
|
112
121
|
* Return the bundle manifest. If a fresh manifest exists on disk,
|
|
113
122
|
* use it (caching parse output across requests). Otherwise build
|
package/package.json
CHANGED
|
@@ -25,6 +25,8 @@ import {
|
|
|
25
25
|
buildClientBundle,
|
|
26
26
|
buildTailwind,
|
|
27
27
|
assertNotServerOnly,
|
|
28
|
+
componentPathFor,
|
|
29
|
+
_doBuildInner,
|
|
28
30
|
discoverLoadingModules,
|
|
29
31
|
generateLoadingRegistry,
|
|
30
32
|
type PylonBundleManifest,
|
|
@@ -234,6 +236,55 @@ describe("ssr-client-bundler (Phase 1.5e)", () => {
|
|
|
234
236
|
}
|
|
235
237
|
});
|
|
236
238
|
|
|
239
|
+
test("Windows separators: the build succeeds and every manifest path uses /", async () => {
|
|
240
|
+
// node:path on Windows returns "\"-separated relative paths. They used to
|
|
241
|
+
// reach the generated entries as `import Page from "../app\page"`, where
|
|
242
|
+
// `\p` is a string escape, so Bun.build failed with "Bundle failed" and
|
|
243
|
+
// every page shipped without hydration (#15).
|
|
244
|
+
tempDir = makeFixture(
|
|
245
|
+
{
|
|
246
|
+
"page.tsx": PAGE_BODY("Home"),
|
|
247
|
+
"blog/page.tsx": PAGE_BODY("Blog"),
|
|
248
|
+
"not-found.tsx": PAGE_BODY("Missing"),
|
|
249
|
+
"loading.tsx": LOADING_BODY,
|
|
250
|
+
},
|
|
251
|
+
{ "layout.tsx": LAYOUT_BODY },
|
|
252
|
+
);
|
|
253
|
+
const winPath = {
|
|
254
|
+
...path,
|
|
255
|
+
relative: (from: string, to: string) =>
|
|
256
|
+
path.relative(from, to).replace(/\//g, "\\"),
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const { manifestPath } = await _doBuildInner(fs, winPath, tempDir, "app");
|
|
260
|
+
const manifest = JSON.parse(
|
|
261
|
+
fs.readFileSync(manifestPath, "utf8"),
|
|
262
|
+
) as PylonBundleManifest;
|
|
263
|
+
|
|
264
|
+
expect(Object.keys(manifest.routes).sort()).toEqual([
|
|
265
|
+
"app/blog/page",
|
|
266
|
+
"app/not-found",
|
|
267
|
+
"app/page",
|
|
268
|
+
]);
|
|
269
|
+
expect(manifest.outdir).toBe(".pylon/client-build");
|
|
270
|
+
for (const route of Object.values(manifest.routes)) {
|
|
271
|
+
expect(route.file).not.toContain("\\");
|
|
272
|
+
for (const imp of route.imports) expect(imp).not.toContain("\\");
|
|
273
|
+
}
|
|
274
|
+
const entry = fs.readFileSync(
|
|
275
|
+
path.join(tempDir, ".pylon", "client-entry-app__blog__page.tsx"),
|
|
276
|
+
"utf8",
|
|
277
|
+
);
|
|
278
|
+
expect(entry).toContain('import Page from "../app/blog/page";');
|
|
279
|
+
expect(entry).toContain('import L0 from "../app/layout";');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("componentPathFor returns a /-separated path for win32 input", () => {
|
|
283
|
+
expect(
|
|
284
|
+
componentPathFor(path.win32, "C:\\proj", "C:\\proj\\app\\(shop)\\cart\\page.tsx"),
|
|
285
|
+
).toBe("app/(shop)/cart/page");
|
|
286
|
+
});
|
|
287
|
+
|
|
237
288
|
test("adding a route grows the manifest by one and stays small per-entry", async () => {
|
|
238
289
|
tempDir = makeFixture(
|
|
239
290
|
{
|
|
@@ -141,6 +141,20 @@ export function assertNotServerOnly(specifier: string, importer: string): void {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
/**
|
|
145
|
+
* A module file → its project-relative, extension-less component path
|
|
146
|
+
* (`<cwd>/app/blog/page.tsx` → `app/blog/page`). Always "/"-separated, on
|
|
147
|
+
* Windows too: the result is a manifest key, the input to the SSR boundary
|
|
148
|
+
* walk, and is spliced into generated `import` specifiers, where a backslash
|
|
149
|
+
* is a string escape (`"../app\page"` resolves `../apppage`).
|
|
150
|
+
*/
|
|
151
|
+
export function componentPathFor(path: any, cwd: string, file: string): string {
|
|
152
|
+
return path
|
|
153
|
+
.relative(cwd, file)
|
|
154
|
+
.replace(/\.(tsx?|jsx?)$/, "")
|
|
155
|
+
.replace(/\\/g, "/");
|
|
156
|
+
}
|
|
157
|
+
|
|
144
158
|
/**
|
|
145
159
|
* Synchronously walk the route dir (`<appDirRel>` under cwd, e.g.
|
|
146
160
|
* `app` or `web/app`) and return one entry per discovered page, each
|
|
@@ -182,7 +196,7 @@ function discoverRoutes(
|
|
|
182
196
|
.map((n: string) => path.join(dir, n))
|
|
183
197
|
.find((p: string) => fs.existsSync(p));
|
|
184
198
|
const nextLayouts = layoutHere
|
|
185
|
-
? [...layouts, path
|
|
199
|
+
? [...layouts, componentPathFor(path, cwd, layoutHere)]
|
|
186
200
|
: layouts;
|
|
187
201
|
const pageHere = ["page.tsx", "page.ts", "page.jsx", "page.js"]
|
|
188
202
|
.map((n: string) => path.join(dir, n))
|
|
@@ -190,7 +204,7 @@ function discoverRoutes(
|
|
|
190
204
|
if (pageHere) {
|
|
191
205
|
pages.push({
|
|
192
206
|
segments: [...segments],
|
|
193
|
-
component: path
|
|
207
|
+
component: componentPathFor(path, cwd, pageHere),
|
|
194
208
|
layouts: nextLayouts,
|
|
195
209
|
// e.g. [] → "/", ["p","[slug]"] → "/p/[slug]".
|
|
196
210
|
pattern: "/" + segments.join("/"),
|
|
@@ -207,7 +221,7 @@ function discoverRoutes(
|
|
|
207
221
|
if (bHere) {
|
|
208
222
|
pages.push({
|
|
209
223
|
segments: [...segments],
|
|
210
|
-
component: path
|
|
224
|
+
component: componentPathFor(path, cwd, bHere),
|
|
211
225
|
layouts: nextLayouts,
|
|
212
226
|
});
|
|
213
227
|
}
|
|
@@ -255,9 +269,7 @@ export function discoverLoadingModules(
|
|
|
255
269
|
.map((n: string) => path.join(dir, n))
|
|
256
270
|
.find((p: string) => fs.existsSync(p));
|
|
257
271
|
if (here) {
|
|
258
|
-
found.push(
|
|
259
|
-
path.relative(cwd, here).replace(/\.(tsx?|jsx?)$/, "").replace(/\\/g, "/"),
|
|
260
|
-
);
|
|
272
|
+
found.push(componentPathFor(path, cwd, here));
|
|
261
273
|
}
|
|
262
274
|
for (const e of entries) {
|
|
263
275
|
if (!e.isDirectory()) continue;
|
|
@@ -1381,7 +1393,9 @@ export async function buildTailwind(
|
|
|
1381
1393
|
return stylesName;
|
|
1382
1394
|
}
|
|
1383
1395
|
|
|
1384
|
-
|
|
1396
|
+
// Exported for the Windows-separator regression test (ssr-client-bundler.test.ts),
|
|
1397
|
+
// which injects a `path` whose `relative` returns "\"-separated paths.
|
|
1398
|
+
export async function _doBuildInner(
|
|
1385
1399
|
fs: any,
|
|
1386
1400
|
path: any,
|
|
1387
1401
|
cwd: string,
|
|
@@ -1557,7 +1571,10 @@ async function _doBuildInner(
|
|
|
1557
1571
|
// - chunks (kind === "chunk") — looked up by basename when
|
|
1558
1572
|
// scanning entry files for static `import "./chunks/..."`
|
|
1559
1573
|
// specifiers.
|
|
1560
|
-
|
|
1574
|
+
// Manifest paths are URL paths under `public_prefix` ("/"-separated). The
|
|
1575
|
+
// runtime's /_pylon/build/ handler rejects any request with a backslash.
|
|
1576
|
+
const toUrlPath = (p: string) => p.replace(/\\/g, "/");
|
|
1577
|
+
const outdirRel = toUrlPath(path.relative(cwd, outdir));
|
|
1561
1578
|
const entriesByStem = new Map<
|
|
1562
1579
|
string,
|
|
1563
1580
|
{ absPath: string; relPath: string }
|
|
@@ -1568,7 +1585,7 @@ async function _doBuildInner(
|
|
|
1568
1585
|
>();
|
|
1569
1586
|
for (const o of result.outputs) {
|
|
1570
1587
|
const absPath: string = o.path;
|
|
1571
|
-
const relPath = path.relative(outdir, absPath);
|
|
1588
|
+
const relPath = toUrlPath(path.relative(outdir, absPath));
|
|
1572
1589
|
const base = path.basename(absPath);
|
|
1573
1590
|
// Strip `-<hash>.js` to recover the entry source's stem
|
|
1574
1591
|
// (e.g. `client-entry-app__hello__page`). The hash is
|