@teyik0/furin 0.1.0-alpha.4 → 0.1.0-alpha.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.
- package/dist/build/client.d.ts +12 -5
- package/dist/build/compile-entry.d.ts +1 -0
- package/dist/build/entry-template.d.ts +1 -0
- package/dist/build/index.js +109 -33
- package/dist/build/server-routes-entry.d.ts +1 -0
- package/dist/build/types.d.ts +1 -0
- package/dist/cli/index.js +104 -29
- package/dist/client.d.ts +30 -3
- package/dist/furin.d.ts +2 -200
- package/dist/furin.js +485 -223
- package/dist/internal.d.ts +1 -0
- package/dist/link.d.ts +103 -5
- package/dist/link.js +202 -19
- package/dist/render/cache.d.ts +89 -1
- package/dist/render/index.d.ts +3 -2
- package/dist/render/shell.d.ts +4 -1
- package/dist/render/template.d.ts +2 -0
- package/dist/router.d.ts +4 -2
- package/dist/router.js +302 -150
- package/package.json +3 -3
- package/src/adapter/bun.ts +74 -3
- package/src/build/client.ts +19 -9
- package/src/build/compile-entry.ts +3 -1
- package/src/build/entry-template.ts +10 -2
- package/src/build/index.ts +1 -5
- package/src/build/scan-server.ts +2 -2
- package/src/build/server-routes-entry.ts +3 -1
- package/src/build/shared.ts +1 -0
- package/src/build/types.ts +1 -0
- package/src/client.ts +51 -8
- package/src/furin.ts +99 -22
- package/src/internal.ts +1 -0
- package/src/link.tsx +365 -20
- package/src/render/cache.ts +233 -1
- package/src/render/index.ts +231 -124
- package/src/render/loaders.ts +9 -4
- package/src/render/shell.ts +29 -5
- package/src/render/template.ts +17 -1
- package/src/router.ts +183 -71
package/dist/build/client.d.ts
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import type { ResolvedRoute } from "../router";
|
|
2
2
|
import type { BuildClientOptions } from "./types";
|
|
3
|
+
export interface BuildClientResult {
|
|
4
|
+
/** Public path of the JS entry chunk, e.g. `/_client/chunk-abc.js` */
|
|
5
|
+
entryChunk: string;
|
|
6
|
+
/** Public paths of all CSS chunks, e.g. `["/_client/chunk-abc.css"]` */
|
|
7
|
+
cssChunks: string[];
|
|
8
|
+
}
|
|
3
9
|
/**
|
|
4
10
|
* Builds the production client bundle via Bun.build() using _hydrate.tsx as
|
|
5
11
|
* the JS entrypoint (NOT an HTML entrypoint). Bun produces:
|
|
6
12
|
* <outDir>/client/chunk-*.js — code-split bundles
|
|
7
13
|
* <outDir>/client/chunk-*.css — extracted CSS (if imported)
|
|
8
14
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Bun bug where the output index.html references a
|
|
12
|
-
* actual entry chunk, preventing React from mounting
|
|
15
|
+
* Returns the chunk paths so the caller can compute a `buildId` and write
|
|
16
|
+
* `index.html` with the correct meta tag. Using an HTML entrypoint with
|
|
17
|
+
* code-splitting causes a Bun bug where the output index.html references a
|
|
18
|
+
* leaf chunk instead of the actual entry chunk, preventing React from mounting
|
|
19
|
+
* in production.
|
|
13
20
|
*
|
|
14
21
|
* The output index.html is NOT served to browsers directly. The server reads
|
|
15
22
|
* it as an SSR template, injects the pre-rendered React HTML into
|
|
16
23
|
* <!--ssr-outlet-->, and sends the complete page.
|
|
17
24
|
*/
|
|
18
|
-
export declare function buildClient(routes: ResolvedRoute[], { outDir, rootLayout, plugins }: BuildClientOptions): Promise<
|
|
25
|
+
export declare function buildClient(routes: ResolvedRoute[], { outDir, rootLayout, plugins }: BuildClientOptions): Promise<BuildClientResult>;
|
package/dist/build/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// src/build/index.ts
|
|
3
|
-
import { existsSync as existsSync8, writeFileSync as
|
|
3
|
+
import { existsSync as existsSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
4
4
|
import { join as join9, relative as relative3, resolve as resolve4 } from "path";
|
|
5
5
|
|
|
6
6
|
// src/adapter/bun.ts
|
|
7
|
-
import { existsSync as existsSync6, rmSync as rmSync2 } from "fs";
|
|
7
|
+
import { existsSync as existsSync6, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
8
8
|
import { join as join7, resolve as resolve3 } from "path";
|
|
9
9
|
|
|
10
10
|
// src/build/client.ts
|
|
@@ -1368,13 +1368,22 @@ function transformForClient(code, filename) {
|
|
|
1368
1368
|
};
|
|
1369
1369
|
}
|
|
1370
1370
|
|
|
1371
|
+
// src/build/hydrate.ts
|
|
1372
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1373
|
+
import { join as join2 } from "path";
|
|
1374
|
+
|
|
1371
1375
|
// src/render/shell.ts
|
|
1376
|
+
function escapeHtml(str) {
|
|
1377
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1378
|
+
}
|
|
1379
|
+
var THEME_INIT_SCRIPT = `<script>try{var __t=localStorage.getItem("furin-theme");` + `document.documentElement.classList.add(__t==="light"?"light":"dark")}` + `catch(e){document.documentElement.classList.add("dark")}</script>`;
|
|
1372
1380
|
function generateIndexHtml() {
|
|
1373
1381
|
return `<!DOCTYPE html>
|
|
1374
1382
|
<html lang="en">
|
|
1375
1383
|
<head>
|
|
1376
1384
|
<meta charset="UTF-8">
|
|
1377
1385
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1386
|
+
${THEME_INIT_SCRIPT}
|
|
1378
1387
|
<!--ssr-head-->
|
|
1379
1388
|
</head>
|
|
1380
1389
|
<body>
|
|
@@ -1384,16 +1393,19 @@ function generateIndexHtml() {
|
|
|
1384
1393
|
</html>
|
|
1385
1394
|
`;
|
|
1386
1395
|
}
|
|
1387
|
-
function generateProdIndexHtml(entryChunk, cssChunks) {
|
|
1396
|
+
function generateProdIndexHtml(entryChunk, cssChunks, buildId) {
|
|
1388
1397
|
const cssLinks = cssChunks.map((c) => ` <link rel="stylesheet" crossorigin href="${c}">`).join(`
|
|
1389
1398
|
`);
|
|
1390
|
-
const scriptTag =
|
|
1399
|
+
const scriptTag = `<script type="module" crossorigin src="${entryChunk}"></script>`;
|
|
1400
|
+
const buildIdMeta = buildId ? ` <meta name="furin-build-id" content="${escapeHtml(buildId)}">
|
|
1401
|
+
` : "";
|
|
1391
1402
|
return `<!DOCTYPE html>
|
|
1392
1403
|
<html lang="en">
|
|
1393
1404
|
<head>
|
|
1394
1405
|
<meta charset="UTF-8">
|
|
1395
1406
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1396
|
-
${
|
|
1407
|
+
${THEME_INIT_SCRIPT}
|
|
1408
|
+
${buildIdMeta}${cssLinks ? `${cssLinks}
|
|
1397
1409
|
` : ""} <!--ssr-head-->
|
|
1398
1410
|
</head>
|
|
1399
1411
|
<body>
|
|
@@ -1404,10 +1416,6 @@ ${cssLinks ? `${cssLinks}
|
|
|
1404
1416
|
`;
|
|
1405
1417
|
}
|
|
1406
1418
|
|
|
1407
|
-
// src/build/hydrate.ts
|
|
1408
|
-
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1409
|
-
import { join as join2 } from "path";
|
|
1410
|
-
|
|
1411
1419
|
// src/build/route-types.ts
|
|
1412
1420
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
1413
1421
|
import { join } from "path";
|
|
@@ -1607,6 +1615,7 @@ function toBuildRouteManifestEntry(route, rootDir) {
|
|
|
1607
1615
|
function buildTargetManifest(rootDir, buildRoot, target, serverEntry) {
|
|
1608
1616
|
const targetDir = join3(buildRoot, target);
|
|
1609
1617
|
return {
|
|
1618
|
+
buildId: "",
|
|
1610
1619
|
generatedAt: new Date().toISOString(),
|
|
1611
1620
|
targetDir: toPosixPath(relative(rootDir, targetDir)),
|
|
1612
1621
|
clientDir: toPosixPath(relative(rootDir, join3(targetDir, "client"))),
|
|
@@ -1683,10 +1692,13 @@ ${transformed}`;
|
|
|
1683
1692
|
}
|
|
1684
1693
|
const entryOutput = result.outputs.find((o) => o.kind === "entry-point");
|
|
1685
1694
|
const cssOutputs = result.outputs.filter((o) => o.path.endsWith(".css") && !o.path.endsWith(".css.map"));
|
|
1686
|
-
|
|
1695
|
+
if (!entryOutput) {
|
|
1696
|
+
throw new Error("[furin] client build did not emit entry chunk");
|
|
1697
|
+
}
|
|
1698
|
+
const entryChunk = `/_client/${basename(entryOutput.path)}`;
|
|
1687
1699
|
const cssChunks = cssOutputs.map((o) => `/_client/${basename(o.path)}`);
|
|
1688
|
-
writeFileSync3(join4(clientDir, "index.html"), generateProdIndexHtml(entryChunk, cssChunks));
|
|
1689
1700
|
console.log("[furin] Production client build complete");
|
|
1701
|
+
return { entryChunk, cssChunks };
|
|
1690
1702
|
}
|
|
1691
1703
|
|
|
1692
1704
|
// src/build/compile-entry.ts
|
|
@@ -1698,7 +1710,14 @@ import { resolve as resolve2 } from "path";
|
|
|
1698
1710
|
var INTERNAL_MODULE_PATH = resolve2(import.meta.dir, "../internal.ts").replace(/\\/g, "/");
|
|
1699
1711
|
var RUNTIME_ENV_MODULE_PATH = resolve2(import.meta.dir, "../runtime-env.ts").replace(/\\/g, "/");
|
|
1700
1712
|
function buildEntrySource(options) {
|
|
1701
|
-
const { headerComment, rootPath, routes, serverEntry
|
|
1713
|
+
const { buildId, headerComment, rootPath, routes, serverEntry } = options;
|
|
1714
|
+
let { extraImports, extraContext } = options;
|
|
1715
|
+
if (extraImports === undefined) {
|
|
1716
|
+
extraImports = [];
|
|
1717
|
+
}
|
|
1718
|
+
if (extraContext === undefined) {
|
|
1719
|
+
extraContext = [];
|
|
1720
|
+
}
|
|
1702
1721
|
const allModulePaths = [rootPath, ...routes.map((r) => r.path)];
|
|
1703
1722
|
const moduleImports = [];
|
|
1704
1723
|
const moduleEntries = [];
|
|
@@ -1721,6 +1740,7 @@ function buildEntrySource(options) {
|
|
|
1721
1740
|
'process.env.NODE_ENV = "production";',
|
|
1722
1741
|
"",
|
|
1723
1742
|
"__setCompileContext({",
|
|
1743
|
+
` buildId: ${JSON.stringify(buildId ?? "")},`,
|
|
1724
1744
|
` rootPath: ${JSON.stringify(rootPath.replace(/\\/g, "/"))},`,
|
|
1725
1745
|
" modules: {",
|
|
1726
1746
|
...moduleEntries,
|
|
@@ -1740,7 +1760,7 @@ function buildEntrySource(options) {
|
|
|
1740
1760
|
|
|
1741
1761
|
// src/build/compile-entry.ts
|
|
1742
1762
|
function generateCompileEntry(options) {
|
|
1743
|
-
const { outDir, rootPath, routes, serverEntry, embed, publicDir } = options;
|
|
1763
|
+
const { buildId, outDir, rootPath, routes, serverEntry, embed, publicDir } = options;
|
|
1744
1764
|
ensureDir(outDir);
|
|
1745
1765
|
const assetImports = [];
|
|
1746
1766
|
let embeddedBlock = [];
|
|
@@ -1788,6 +1808,7 @@ function generateCompileEntry(options) {
|
|
|
1788
1808
|
];
|
|
1789
1809
|
}
|
|
1790
1810
|
const source = buildEntrySource({
|
|
1811
|
+
buildId,
|
|
1791
1812
|
headerComment: "// Auto-generated by furin compile \u2014 do not edit",
|
|
1792
1813
|
rootPath,
|
|
1793
1814
|
routes,
|
|
@@ -1804,9 +1825,10 @@ function generateCompileEntry(options) {
|
|
|
1804
1825
|
import { writeFileSync as writeFileSync5 } from "fs";
|
|
1805
1826
|
import { join as join6 } from "path";
|
|
1806
1827
|
function generateServerRoutesEntry(options) {
|
|
1807
|
-
const { outDir, rootPath, routes, serverEntry } = options;
|
|
1828
|
+
const { buildId, outDir, rootPath, routes, serverEntry } = options;
|
|
1808
1829
|
ensureDir(outDir);
|
|
1809
1830
|
const source = buildEntrySource({
|
|
1831
|
+
buildId,
|
|
1810
1832
|
headerComment: "// Auto-generated by furin build \u2014 do not edit",
|
|
1811
1833
|
rootPath,
|
|
1812
1834
|
routes,
|
|
@@ -1818,6 +1840,33 @@ function generateServerRoutesEntry(options) {
|
|
|
1818
1840
|
}
|
|
1819
1841
|
|
|
1820
1842
|
// src/adapter/bun.ts
|
|
1843
|
+
var BUILD_ID_INPUT_PATHS = [
|
|
1844
|
+
resolve3(import.meta.dir, "../build/compile-entry.ts"),
|
|
1845
|
+
resolve3(import.meta.dir, "../build/entry-template.ts"),
|
|
1846
|
+
resolve3(import.meta.dir, "../build/server-routes-entry.ts"),
|
|
1847
|
+
resolve3(import.meta.dir, "../render/index.ts"),
|
|
1848
|
+
resolve3(import.meta.dir, "../render/shell.ts"),
|
|
1849
|
+
resolve3(import.meta.dir, "../router.ts")
|
|
1850
|
+
];
|
|
1851
|
+
async function createBuildFingerprint(entryChunk, cssChunks, routes, rootPath, serverEntry) {
|
|
1852
|
+
const fingerprintPaths = new Set([rootPath, ...routes.map((route) => route.path)]);
|
|
1853
|
+
if (serverEntry) {
|
|
1854
|
+
fingerprintPaths.add(serverEntry);
|
|
1855
|
+
}
|
|
1856
|
+
for (const path of BUILD_ID_INPUT_PATHS) {
|
|
1857
|
+
if (!existsSync6(path)) {
|
|
1858
|
+
console.warn(`[furin] Warning: build fingerprint input "${toPosixPath(path)}" is missing \u2014 ` + "the generated build ID may not reflect all framework changes.");
|
|
1859
|
+
}
|
|
1860
|
+
fingerprintPaths.add(path);
|
|
1861
|
+
}
|
|
1862
|
+
const fileParts = await Promise.all([...fingerprintPaths].sort().map(async (path) => {
|
|
1863
|
+
const content = existsSync6(path) ? await Bun.file(path).text() : "";
|
|
1864
|
+
return `${toPosixPath(path)}:${content}`;
|
|
1865
|
+
}));
|
|
1866
|
+
const routeParts = routes.map((route) => JSON.stringify({ mode: route.mode, path: toPosixPath(route.path), pattern: route.pattern })).sort();
|
|
1867
|
+
return [entryChunk, ...[...cssChunks].sort(), ...routeParts, ...fileParts].join(`
|
|
1868
|
+
`);
|
|
1869
|
+
}
|
|
1821
1870
|
async function buildBunTarget(routes, rootDir, buildRoot, rootPath, serverEntry, options) {
|
|
1822
1871
|
if (options.compile && !serverEntry) {
|
|
1823
1872
|
throw new Error(`[furin] \`compile: "${options.compile}"\` requires a server entry point. ` + "Create src/server.ts or set `serverEntry` in your furin.config.ts.");
|
|
@@ -1827,11 +1876,16 @@ async function buildBunTarget(routes, rootDir, buildRoot, rootPath, serverEntry,
|
|
|
1827
1876
|
const targetDir = resolve3(rootDir, targetManifest.targetDir);
|
|
1828
1877
|
rmSync2(targetDir, { force: true, recursive: true });
|
|
1829
1878
|
ensureDir(targetDir);
|
|
1830
|
-
await buildClient(routes, {
|
|
1879
|
+
const { entryChunk, cssChunks } = await buildClient(routes, {
|
|
1831
1880
|
outDir: targetDir,
|
|
1832
1881
|
rootLayout: rootPath,
|
|
1833
1882
|
plugins: options.plugins
|
|
1834
1883
|
});
|
|
1884
|
+
const buildFingerprint = await createBuildFingerprint(entryChunk, cssChunks, routes, rootPath, serverEntry);
|
|
1885
|
+
const buildId = Bun.hash(buildFingerprint).toString(16).slice(0, 12);
|
|
1886
|
+
targetManifest.buildId = buildId;
|
|
1887
|
+
const clientDir = join7(targetDir, "client");
|
|
1888
|
+
writeFileSync6(join7(clientDir, "index.html"), generateProdIndexHtml(entryChunk, cssChunks, buildId));
|
|
1835
1889
|
const routeManifest = routes.map((r) => ({ pattern: r.pattern, path: r.path, mode: r.mode }));
|
|
1836
1890
|
const publicDir = existsSync6(join7(rootDir, "public")) ? join7(rootDir, "public") : undefined;
|
|
1837
1891
|
const targetPublicDir = publicDir ? join7(targetDir, "public") : undefined;
|
|
@@ -1839,14 +1893,15 @@ async function buildBunTarget(routes, rootDir, buildRoot, rootPath, serverEntry,
|
|
|
1839
1893
|
copyDirRecursive(publicDir, targetPublicDir);
|
|
1840
1894
|
}
|
|
1841
1895
|
if (options.compile && serverEntry) {
|
|
1842
|
-
const
|
|
1896
|
+
const clientDir2 = join7(targetDir, "client");
|
|
1843
1897
|
const outfile = join7(targetDir, "server");
|
|
1844
1898
|
const entryPath = generateCompileEntry({
|
|
1899
|
+
buildId,
|
|
1845
1900
|
rootPath,
|
|
1846
1901
|
routes: routeManifest,
|
|
1847
1902
|
serverEntry,
|
|
1848
1903
|
outDir: targetDir,
|
|
1849
|
-
embed: options.compile === "embed" ? { clientDir } : undefined,
|
|
1904
|
+
embed: options.compile === "embed" ? { clientDir: clientDir2 } : undefined,
|
|
1850
1905
|
publicDir
|
|
1851
1906
|
});
|
|
1852
1907
|
await Bun.build({
|
|
@@ -1859,12 +1914,13 @@ async function buildBunTarget(routes, rootDir, buildRoot, rootPath, serverEntry,
|
|
|
1859
1914
|
console.log(`[furin] Server binary: ${outfile}`);
|
|
1860
1915
|
targetManifest.serverPath = toPosixPath(join7(targetManifest.targetDir, "server"));
|
|
1861
1916
|
if (options.compile === "embed") {
|
|
1862
|
-
rmSync2(
|
|
1917
|
+
rmSync2(clientDir2, { force: true, recursive: true });
|
|
1863
1918
|
targetManifest.clientDir = null;
|
|
1864
1919
|
targetManifest.templatePath = null;
|
|
1865
1920
|
}
|
|
1866
1921
|
} else if (serverEntry) {
|
|
1867
1922
|
const entryPath = generateServerRoutesEntry({
|
|
1923
|
+
buildId,
|
|
1868
1924
|
rootPath,
|
|
1869
1925
|
routes: routeManifest,
|
|
1870
1926
|
serverEntry,
|
|
@@ -1885,8 +1941,7 @@ async function buildBunTarget(routes, rootDir, buildRoot, rootPath, serverEntry,
|
|
|
1885
1941
|
"_compile-entry.ts",
|
|
1886
1942
|
"_compile-entry.js.map",
|
|
1887
1943
|
"server.ts",
|
|
1888
|
-
"_hydrate.tsx"
|
|
1889
|
-
"index.html"
|
|
1944
|
+
"_hydrate.tsx"
|
|
1890
1945
|
]) {
|
|
1891
1946
|
rmSync2(join7(targetDir, file), { force: true });
|
|
1892
1947
|
}
|
|
@@ -1924,14 +1979,20 @@ function getCompileContext() {
|
|
|
1924
1979
|
import { renderToReadableStream } from "react-dom/server";
|
|
1925
1980
|
|
|
1926
1981
|
// src/render/cache.ts
|
|
1982
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
1927
1983
|
var isrCache = new Map;
|
|
1928
1984
|
var ssgCache = new Map;
|
|
1985
|
+
var _requestInvalidationScope = new AsyncLocalStorage;
|
|
1986
|
+
var _globalPendingInvalidations = new Set;
|
|
1929
1987
|
|
|
1930
1988
|
// src/render/element.tsx
|
|
1931
1989
|
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
1932
1990
|
// src/runtime-env.ts
|
|
1933
1991
|
var IS_DEV = true;
|
|
1934
1992
|
|
|
1993
|
+
// src/render/index.ts
|
|
1994
|
+
var pendingRevalidations = new Set;
|
|
1995
|
+
|
|
1935
1996
|
// src/utils.ts
|
|
1936
1997
|
function isFurinPage(value) {
|
|
1937
1998
|
return typeof value === "object" && value !== null && "__type" in value && value.__type === "FURIN_PAGE";
|
|
@@ -2007,13 +2068,7 @@ async function scanPageFiles(pagesDir, root) {
|
|
|
2007
2068
|
continue;
|
|
2008
2069
|
}
|
|
2009
2070
|
if (IS_DEV) {
|
|
2010
|
-
routes.push(
|
|
2011
|
-
pattern: filePathToPattern(relativePath),
|
|
2012
|
-
path: absolutePath,
|
|
2013
|
-
mode: "ssr",
|
|
2014
|
-
page: undefined,
|
|
2015
|
-
routeChain: []
|
|
2016
|
-
});
|
|
2071
|
+
routes.push(await buildDevRoute(absolutePath, relativePath, root));
|
|
2017
2072
|
continue;
|
|
2018
2073
|
}
|
|
2019
2074
|
const ctx = getCompileContext();
|
|
@@ -2034,6 +2089,30 @@ async function scanPageFiles(pagesDir, root) {
|
|
|
2034
2089
|
}
|
|
2035
2090
|
return routes;
|
|
2036
2091
|
}
|
|
2092
|
+
async function buildDevRoute(absolutePath, relativePath, root) {
|
|
2093
|
+
let page;
|
|
2094
|
+
let routeChain = [];
|
|
2095
|
+
try {
|
|
2096
|
+
const pageMod = await import(`${absolutePath}?furin-server&t=${Date.now()}`);
|
|
2097
|
+
if (isFurinPage(pageMod.default)) {
|
|
2098
|
+
page = pageMod.default;
|
|
2099
|
+
routeChain = collectRouteChainFromRoute(page._route);
|
|
2100
|
+
validateRouteChain(routeChain, root.route, relativePath);
|
|
2101
|
+
}
|
|
2102
|
+
} catch {}
|
|
2103
|
+
const devStubPage = {
|
|
2104
|
+
__type: "FURIN_PAGE",
|
|
2105
|
+
_route: { __type: "FURIN_ROUTE" },
|
|
2106
|
+
component: () => null
|
|
2107
|
+
};
|
|
2108
|
+
return {
|
|
2109
|
+
pattern: filePathToPattern(relativePath),
|
|
2110
|
+
path: absolutePath,
|
|
2111
|
+
mode: page ? resolveMode(page, routeChain) : "ssr",
|
|
2112
|
+
page: page ?? devStubPage,
|
|
2113
|
+
routeChain
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2037
2116
|
async function collectPageFilePaths(dir) {
|
|
2038
2117
|
const files = [];
|
|
2039
2118
|
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
@@ -2103,8 +2182,8 @@ function walkNode(node, out) {
|
|
|
2103
2182
|
if (node.type === "CallExpression") {
|
|
2104
2183
|
const callee = node.callee;
|
|
2105
2184
|
const args = node.arguments;
|
|
2106
|
-
const
|
|
2107
|
-
if (
|
|
2185
|
+
const isFurinCall = callee?.type === "Identifier" && callee.name === "furin";
|
|
2186
|
+
if (isFurinCall && Array.isArray(args) && args.length > 0) {
|
|
2108
2187
|
const firstArg = args[0];
|
|
2109
2188
|
if (firstArg?.type === "ObjectExpression") {
|
|
2110
2189
|
const pagesDir = extractStringProperty(firstArg, "pagesDir");
|
|
@@ -2184,9 +2263,6 @@ async function buildApp(options) {
|
|
|
2184
2263
|
return target;
|
|
2185
2264
|
});
|
|
2186
2265
|
const { root, routes } = await scanPages(pagesDir);
|
|
2187
|
-
if (!root) {
|
|
2188
|
-
throw new Error("[furin] No root layout found. Create a root.tsx in your pages directory with a layout component.");
|
|
2189
|
-
}
|
|
2190
2266
|
ensureDir(buildRoot);
|
|
2191
2267
|
const manifest = {
|
|
2192
2268
|
version: 1,
|
|
@@ -2211,7 +2287,7 @@ async function buildApp(options) {
|
|
|
2211
2287
|
throw new Error(`[furin] Unsupported build target "${target}"`);
|
|
2212
2288
|
}
|
|
2213
2289
|
}
|
|
2214
|
-
|
|
2290
|
+
writeFileSync7(join9(buildRoot, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
2215
2291
|
`);
|
|
2216
2292
|
return {
|
|
2217
2293
|
manifest,
|