@funstack/static 0.0.2-alpha.0 → 0.0.3
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/README.md +59 -0
- package/dist/bin/skill-installer.d.mts +1 -0
- package/dist/bin/skill-installer.mjs +12 -0
- package/dist/bin/skill-installer.mjs.map +1 -0
- package/dist/build/buildApp.mjs +4 -3
- package/dist/build/buildApp.mjs.map +1 -1
- package/dist/build/rscProcessor.mjs +9 -3
- package/dist/build/rscProcessor.mjs.map +1 -1
- package/dist/client/entry.d.mts +1 -0
- package/dist/client/entry.mjs +17 -9
- package/dist/client/entry.mjs.map +1 -1
- package/dist/client/globals.mjs.map +1 -1
- package/dist/docs/FAQ.md +5 -0
- package/dist/docs/GettingStarted.md +180 -0
- package/dist/docs/api/Defer.md +110 -0
- package/dist/docs/api/FunstackStatic.md +184 -0
- package/dist/docs/index.md +20 -0
- package/dist/docs/learn/HowItWorks.md +109 -0
- package/dist/docs/learn/OptimizingPayloads.md +105 -0
- package/dist/docs/learn/RSC.md +179 -0
- package/dist/docs/learn/SSR.md +104 -0
- package/dist/entries/client.d.mts +1 -1
- package/dist/entries/rsc-client.d.mts +2 -2
- package/dist/entries/rsc-client.mjs +2 -2
- package/dist/entries/server.d.mts +2 -2
- package/dist/plugin/index.d.mts +17 -1
- package/dist/plugin/index.d.mts.map +1 -1
- package/dist/plugin/index.mjs +10 -1
- package/dist/plugin/index.mjs.map +1 -1
- package/dist/rsc/defer.d.mts +18 -3
- package/dist/rsc/defer.d.mts.map +1 -1
- package/dist/rsc/defer.mjs +34 -14
- package/dist/rsc/defer.mjs.map +1 -1
- package/dist/rsc/entry.d.mts.map +1 -1
- package/dist/rsc/entry.mjs +85 -20
- package/dist/rsc/entry.mjs.map +1 -1
- package/dist/rsc-client/clientWrapper.d.mts +3 -3
- package/dist/rsc-client/clientWrapper.d.mts.map +1 -1
- package/dist/rsc-client/clientWrapper.mjs +2 -2
- package/dist/rsc-client/clientWrapper.mjs.map +1 -1
- package/dist/rsc-client/entry.d.mts +1 -1
- package/dist/rsc-client/entry.mjs +1 -1
- package/dist/ssr/entry.d.mts +2 -0
- package/dist/ssr/entry.d.mts.map +1 -1
- package/dist/ssr/entry.mjs +6 -2
- package/dist/ssr/entry.mjs.map +1 -1
- package/package.json +26 -11
- package/skills/funstack-static-knowledge/SKILL.md +44 -0
|
@@ -3,10 +3,10 @@ import React from "react";
|
|
|
3
3
|
|
|
4
4
|
//#region src/rsc-client/clientWrapper.d.ts
|
|
5
5
|
declare const RegistryContext: React.Context<DeferRegistry | undefined>;
|
|
6
|
-
interface
|
|
6
|
+
interface DeferredComponentProps {
|
|
7
7
|
moduleID: string;
|
|
8
8
|
}
|
|
9
|
-
declare const
|
|
9
|
+
declare const DeferredComponent: React.FC<DeferredComponentProps>;
|
|
10
10
|
//#endregion
|
|
11
|
-
export {
|
|
11
|
+
export { DeferredComponent, RegistryContext };
|
|
12
12
|
//# sourceMappingURL=clientWrapper.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clientWrapper.d.mts","names":[],"sources":["../../src/rsc-client/clientWrapper.tsx"],"
|
|
1
|
+
{"version":3,"file":"clientWrapper.d.mts","names":[],"sources":["../../src/rsc-client/clientWrapper.tsx"],"mappings":";;;;cAUa,eAAA,EAAe,KAAA,CAAA,OAAA,CAAA,aAAA;AAAA,UAIlB,sBAAA;EACR,QAAA;AAAA;AAAA,cAGW,iBAAA,EAAmB,KAAA,CAAM,EAAA,CAAG,sBAAA"}
|
|
@@ -5,7 +5,7 @@ import React, { createContext, use } from "react";
|
|
|
5
5
|
|
|
6
6
|
//#region src/rsc-client/clientWrapper.tsx
|
|
7
7
|
const RegistryContext = createContext(void 0);
|
|
8
|
-
const
|
|
8
|
+
const DeferredComponent = ({ moduleID }) => {
|
|
9
9
|
const registry = use(RegistryContext);
|
|
10
10
|
const modulePath = getModulePathFor(moduleID);
|
|
11
11
|
if (registry) {
|
|
@@ -40,5 +40,5 @@ function getClientRSCStream(modulePath) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
//#endregion
|
|
43
|
-
export {
|
|
43
|
+
export { DeferredComponent, RegistryContext };
|
|
44
44
|
//# sourceMappingURL=clientWrapper.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clientWrapper.mjs","names":[],"sources":["../../src/rsc-client/clientWrapper.tsx"],"sourcesContent":["import React from \"react\";\nimport {\n createFromFetch,\n createFromReadableStream,\n} from \"@vitejs/plugin-rsc/browser\";\nimport { getModulePathFor } from \"../rsc/rscModule\";\nimport { createContext, use } from \"react\";\nimport type { LoadedDeferEntry, DeferRegistry } from \"../rsc/defer\";\nimport { withBasePath } from \"../util/basePath\";\n\nexport const RegistryContext = createContext<DeferRegistry | undefined>(\n undefined,\n);\n\ninterface
|
|
1
|
+
{"version":3,"file":"clientWrapper.mjs","names":[],"sources":["../../src/rsc-client/clientWrapper.tsx"],"sourcesContent":["import React from \"react\";\nimport {\n createFromFetch,\n createFromReadableStream,\n} from \"@vitejs/plugin-rsc/browser\";\nimport { getModulePathFor } from \"../rsc/rscModule\";\nimport { createContext, use } from \"react\";\nimport type { LoadedDeferEntry, DeferRegistry } from \"../rsc/defer\";\nimport { withBasePath } from \"../util/basePath\";\n\nexport const RegistryContext = createContext<DeferRegistry | undefined>(\n undefined,\n);\n\ninterface DeferredComponentProps {\n moduleID: string;\n}\n\nexport const DeferredComponent: React.FC<DeferredComponentProps> = ({\n moduleID,\n}) => {\n const registry = use(RegistryContext);\n const modulePath = getModulePathFor(moduleID);\n if (registry) {\n const entry = registry.load(moduleID);\n if (!entry) {\n throw new Error(`Module entry not found for ID '${moduleID}'`);\n }\n return getRSCStreamFromRegistry(entry);\n }\n const stream = getClientRSCStream(withBasePath(modulePath));\n return use(stream);\n};\n\nconst moduleToStreamMap = new Map<string, Promise<React.ReactNode>>();\n\nasync function getRSCStreamFromRegistry(\n entry: LoadedDeferEntry,\n): Promise<React.ReactNode> {\n switch (entry.state.state) {\n case \"streaming\": {\n return createFromReadableStream<React.ReactNode>(entry.state.stream);\n }\n case \"ready\": {\n const { readable, writable } = new TransformStream<\n Uint8Array,\n Uint8Array\n >();\n const writer = writable.getWriter();\n const encoder = new TextEncoder();\n await writer.write(encoder.encode(entry.state.data));\n await writer.close();\n return createFromReadableStream<React.ReactNode>(readable);\n }\n case \"error\": {\n return Promise.reject(entry.state.error);\n }\n }\n}\n\nfunction getClientRSCStream(modulePath: string) {\n let stream = moduleToStreamMap.get(modulePath);\n if (!stream) {\n stream = createFromFetch<React.ReactNode>(fetch(modulePath));\n moduleToStreamMap.set(modulePath, stream);\n }\n return stream;\n}\n"],"mappings":";;;;;;AAUA,MAAa,kBAAkB,cAC7B,OACD;AAMD,MAAa,qBAAuD,EAClE,eACI;CACJ,MAAM,WAAW,IAAI,gBAAgB;CACrC,MAAM,aAAa,iBAAiB,SAAS;AAC7C,KAAI,UAAU;EACZ,MAAM,QAAQ,SAAS,KAAK,SAAS;AACrC,MAAI,CAAC,MACH,OAAM,IAAI,MAAM,kCAAkC,SAAS,GAAG;AAEhE,SAAO,yBAAyB,MAAM;;AAGxC,QAAO,IADQ,mBAAmB,aAAa,WAAW,CAAC,CACzC;;AAGpB,MAAM,oCAAoB,IAAI,KAAuC;AAErE,eAAe,yBACb,OAC0B;AAC1B,SAAQ,MAAM,MAAM,OAApB;EACE,KAAK,YACH,QAAO,yBAA0C,MAAM,MAAM,OAAO;EAEtE,KAAK,SAAS;GACZ,MAAM,EAAE,UAAU,aAAa,IAAI,iBAGhC;GACH,MAAM,SAAS,SAAS,WAAW;GACnC,MAAM,UAAU,IAAI,aAAa;AACjC,SAAM,OAAO,MAAM,QAAQ,OAAO,MAAM,MAAM,KAAK,CAAC;AACpD,SAAM,OAAO,OAAO;AACpB,UAAO,yBAA0C,SAAS;;EAE5D,KAAK,QACH,QAAO,QAAQ,OAAO,MAAM,MAAM,MAAM;;;AAK9C,SAAS,mBAAmB,YAAoB;CAC9C,IAAI,SAAS,kBAAkB,IAAI,WAAW;AAC9C,KAAI,CAAC,QAAQ;AACX,WAAS,gBAAiC,MAAM,WAAW,CAAC;AAC5D,oBAAkB,IAAI,YAAY,OAAO;;AAE3C,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DeferredComponent, RegistryContext } from "./clientWrapper.mjs";
|
package/dist/ssr/entry.d.mts
CHANGED
|
@@ -4,8 +4,10 @@ import { DeferRegistry } from "../rsc/defer.mjs";
|
|
|
4
4
|
declare function renderHTML(rscStream: ReadableStream<Uint8Array>, options: {
|
|
5
5
|
appEntryMarker: string;
|
|
6
6
|
build: boolean;
|
|
7
|
+
ssr?: boolean;
|
|
7
8
|
nonce?: string;
|
|
8
9
|
deferRegistry?: DeferRegistry;
|
|
10
|
+
clientRscStream?: ReadableStream<Uint8Array>;
|
|
9
11
|
}): Promise<{
|
|
10
12
|
stream: ReadableStream<Uint8Array>;
|
|
11
13
|
status?: number;
|
package/dist/ssr/entry.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.d.mts","names":[],"sources":["../../src/ssr/entry.tsx"],"
|
|
1
|
+
{"version":3,"file":"entry.d.mts","names":[],"sources":["../../src/ssr/entry.tsx"],"mappings":";;;iBAWsB,UAAA,CACpB,SAAA,EAAW,cAAA,CAAe,UAAA,GAC1B,OAAA;EACE,cAAA;EACA,KAAA;EACA,GAAA;EACA,KAAA;EACA,aAAA,GAAgB,aAAA;EAChB,eAAA,GAAkB,cAAA,CAAe,UAAA;AAAA,IAElC,OAAA;EAAU,MAAA,EAAQ,cAAA,CAAe,UAAA;EAAa,MAAA;AAAA"}
|
package/dist/ssr/entry.mjs
CHANGED
|
@@ -24,7 +24,8 @@ async function renderHTML(rscStream, options) {
|
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
let bootstrapScriptContent = "";
|
|
27
|
-
if (options.build) bootstrapScriptContent += `globalThis.${appClientManifestVar}={
|
|
27
|
+
if (options.build) if (options.ssr) bootstrapScriptContent += `globalThis.${appClientManifestVar}={stream:"${rscPayloadPlaceholder}"};\n`;
|
|
28
|
+
else bootstrapScriptContent += `globalThis.${appClientManifestVar}={marker:"${options.appEntryMarker}",stream:"${rscPayloadPlaceholder}"};\n`;
|
|
28
29
|
bootstrapScriptContent += await import.meta.viteRsc.loadBootstrapScriptContent("index");
|
|
29
30
|
let htmlStream;
|
|
30
31
|
let status;
|
|
@@ -41,7 +42,10 @@ async function renderHTML(rscStream, options) {
|
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
44
|
let responseStream = htmlStream;
|
|
44
|
-
if (!options.build)
|
|
45
|
+
if (!options.build) {
|
|
46
|
+
const streamToInject = options.clientRscStream ?? rscStream2;
|
|
47
|
+
responseStream = responseStream.pipeThrough(injectRSCPayload(streamToInject, { nonce: options?.nonce }));
|
|
48
|
+
}
|
|
45
49
|
return {
|
|
46
50
|
stream: responseStream,
|
|
47
51
|
status
|
package/dist/ssr/entry.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.mjs","names":[],"sources":["../../src/ssr/entry.tsx"],"sourcesContent":["import { createFromReadableStream } from \"@vitejs/plugin-rsc/ssr\";\nimport { use } from \"react\";\nimport { renderToReadableStream } from \"react-dom/server.edge\";\nimport { injectRSCPayload } from \"rsc-html-stream/server\";\nimport type { RscPayload } from \"../rsc/entry\";\nimport { appClientManifestVar } from \"../client/globals\";\nimport { rscPayloadPlaceholder } from \"../build/rscPath\";\nimport { preload } from \"react-dom\";\nimport type { DeferRegistry } from \"../rsc/defer\";\nimport { RegistryContext } from \"#rsc-client\";\n\nexport async function renderHTML(\n rscStream: ReadableStream<Uint8Array>,\n options: {\n appEntryMarker: string;\n build: boolean;\n nonce?: string;\n deferRegistry?: DeferRegistry;\n },\n): Promise<{ stream: ReadableStream<Uint8Array>; status?: number }> {\n const [rscStream1, rscStream2] = rscStream.tee();\n\n let payload: Promise<RscPayload> | undefined;\n function SsrRoot() {\n // Tip: calling `createFromReadableStream` inside a component\n // makes `preinit`/`preload` work properly.\n payload ??= createFromReadableStream<RscPayload>(rscStream1);\n if (options.build) {\n preload(rscPayloadPlaceholder, {\n crossOrigin: \"anonymous\",\n as: \"fetch\",\n });\n }\n return (\n <RegistryContext value={options.deferRegistry}>\n {use(payload).root}\n </RegistryContext>\n );\n }\n\n let bootstrapScriptContent: string = \"\";\n if (options.build) {\n bootstrapScriptContent += `globalThis.${appClientManifestVar}={marker:\"${options.appEntryMarker}\",stream:\"${rscPayloadPlaceholder}\"};\\n`;\n }\n bootstrapScriptContent +=\n await import.meta.viteRsc.loadBootstrapScriptContent(\"index\");\n\n let htmlStream: ReadableStream<Uint8Array>;\n let status: number | undefined;\n try {\n htmlStream = await renderToReadableStream(<SsrRoot />, {\n bootstrapScriptContent,\n nonce: options?.nonce,\n });\n } catch (e) {\n // In this case, RSC payload is still sent to client and we let client render from scratch anyway.\n // This triggers the error boundary on client side.\n status = 500;\n htmlStream = await renderToReadableStream(\n <html>\n <body>\n <noscript>Internal Server Error: SSR failed</noscript>\n </body>\n </html>,\n {\n bootstrapScriptContent:\n `globalThis.__NO_HYDRATE=1;` + bootstrapScriptContent,\n nonce: options?.nonce,\n },\n );\n }\n\n let responseStream = htmlStream;\n\n if (!options.build) {\n responseStream = responseStream.pipeThrough(\n injectRSCPayload(
|
|
1
|
+
{"version":3,"file":"entry.mjs","names":[],"sources":["../../src/ssr/entry.tsx"],"sourcesContent":["import { createFromReadableStream } from \"@vitejs/plugin-rsc/ssr\";\nimport { use } from \"react\";\nimport { renderToReadableStream } from \"react-dom/server.edge\";\nimport { injectRSCPayload } from \"rsc-html-stream/server\";\nimport type { RscPayload } from \"../rsc/entry\";\nimport { appClientManifestVar } from \"../client/globals\";\nimport { rscPayloadPlaceholder } from \"../build/rscPath\";\nimport { preload } from \"react-dom\";\nimport type { DeferRegistry } from \"../rsc/defer\";\nimport { RegistryContext } from \"#rsc-client\";\n\nexport async function renderHTML(\n rscStream: ReadableStream<Uint8Array>,\n options: {\n appEntryMarker: string;\n build: boolean;\n ssr?: boolean;\n nonce?: string;\n deferRegistry?: DeferRegistry;\n clientRscStream?: ReadableStream<Uint8Array>;\n },\n): Promise<{ stream: ReadableStream<Uint8Array>; status?: number }> {\n const [rscStream1, rscStream2] = rscStream.tee();\n\n let payload: Promise<RscPayload> | undefined;\n function SsrRoot() {\n // Tip: calling `createFromReadableStream` inside a component\n // makes `preinit`/`preload` work properly.\n payload ??= createFromReadableStream<RscPayload>(rscStream1);\n if (options.build) {\n preload(rscPayloadPlaceholder, {\n crossOrigin: \"anonymous\",\n as: \"fetch\",\n });\n }\n return (\n <RegistryContext value={options.deferRegistry}>\n {use(payload).root}\n </RegistryContext>\n );\n }\n\n let bootstrapScriptContent: string = \"\";\n if (options.build) {\n if (options.ssr) {\n // SSR on: no marker needed, client hydrates full document\n bootstrapScriptContent += `globalThis.${appClientManifestVar}={stream:\"${rscPayloadPlaceholder}\"};\\n`;\n } else {\n // SSR off: marker needed for client to find mount point\n bootstrapScriptContent += `globalThis.${appClientManifestVar}={marker:\"${options.appEntryMarker}\",stream:\"${rscPayloadPlaceholder}\"};\\n`;\n }\n }\n bootstrapScriptContent +=\n await import.meta.viteRsc.loadBootstrapScriptContent(\"index\");\n\n let htmlStream: ReadableStream<Uint8Array>;\n let status: number | undefined;\n try {\n htmlStream = await renderToReadableStream(<SsrRoot />, {\n bootstrapScriptContent,\n nonce: options?.nonce,\n });\n } catch (e) {\n // In this case, RSC payload is still sent to client and we let client render from scratch anyway.\n // This triggers the error boundary on client side.\n status = 500;\n htmlStream = await renderToReadableStream(\n <html>\n <body>\n <noscript>Internal Server Error: SSR failed</noscript>\n </body>\n </html>,\n {\n bootstrapScriptContent:\n `globalThis.__NO_HYDRATE=1;` + bootstrapScriptContent,\n nonce: options?.nonce,\n },\n );\n }\n\n let responseStream = htmlStream;\n\n // Inject RSC payload into HTML for client consumption.\n // In dev: always inject (client reads from inline stream).\n // In build+SSR: skip (HTML already has full content, client hydrates directly).\n // In build+no-SSR: skip (client fetches RSC from separate file).\n if (!options.build) {\n const streamToInject = options.clientRscStream ?? rscStream2;\n responseStream = responseStream.pipeThrough(\n injectRSCPayload(streamToInject, {\n nonce: options?.nonce,\n }),\n );\n }\n\n return { stream: responseStream, status };\n}\n"],"mappings":";;;;;;;;;;;AAWA,eAAsB,WACpB,WACA,SAQkE;CAClE,MAAM,CAAC,YAAY,cAAc,UAAU,KAAK;CAEhD,IAAI;CACJ,SAAS,UAAU;AAGjB,cAAY,yBAAqC,WAAW;AAC5D,MAAI,QAAQ,MACV,SAAQ,uBAAuB;GAC7B,aAAa;GACb,IAAI;GACL,CAAC;AAEJ,SACE,oBAAC;GAAgB,OAAO,QAAQ;aAC7B,IAAI,QAAQ,CAAC;IACE;;CAItB,IAAI,yBAAiC;AACrC,KAAI,QAAQ,MACV,KAAI,QAAQ,IAEV,2BAA0B,cAAc,qBAAqB,YAAY,sBAAsB;KAG/F,2BAA0B,cAAc,qBAAqB,YAAY,QAAQ,eAAe,YAAY,sBAAsB;AAGtI,2BACE,MAAM,OAAO,KAAK,QAAQ,2BAA2B,QAAQ;CAE/D,IAAI;CACJ,IAAI;AACJ,KAAI;AACF,eAAa,MAAM,uBAAuB,oBAAC,YAAU,EAAE;GACrD;GACA,OAAO,SAAS;GACjB,CAAC;UACK,GAAG;AAGV,WAAS;AACT,eAAa,MAAM,uBACjB,oBAAC,oBACC,oBAAC,oBACC,oBAAC,wBAAS,sCAA4C,GACjD,GACF,EACP;GACE,wBACE,+BAA+B;GACjC,OAAO,SAAS;GACjB,CACF;;CAGH,IAAI,iBAAiB;AAMrB,KAAI,CAAC,QAAQ,OAAO;EAClB,MAAM,iBAAiB,QAAQ,mBAAmB;AAClD,mBAAiB,eAAe,YAC9B,iBAAiB,gBAAgB,EAC/B,OAAO,SAAS,OACjB,CAAC,CACH;;AAGH,QAAO;EAAE,QAAQ;EAAgB;EAAQ"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funstack/static",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "FUNSTACK static library",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -8,6 +8,16 @@
|
|
|
8
8
|
"url": "git+https://github.com/uhyo/funstack-static.git",
|
|
9
9
|
"directory": "packages/static"
|
|
10
10
|
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"vite-plugin",
|
|
13
|
+
"react",
|
|
14
|
+
"framework",
|
|
15
|
+
"react-server-components",
|
|
16
|
+
"rsc"
|
|
17
|
+
],
|
|
18
|
+
"bin": {
|
|
19
|
+
"funstack-static-skill-installer": "./dist/bin/skill-installer.mjs"
|
|
20
|
+
},
|
|
11
21
|
"exports": {
|
|
12
22
|
".": {
|
|
13
23
|
"types": "./dist/index.d.mts",
|
|
@@ -26,27 +36,30 @@
|
|
|
26
36
|
}
|
|
27
37
|
},
|
|
28
38
|
"files": [
|
|
29
|
-
"dist"
|
|
39
|
+
"dist",
|
|
40
|
+
"skills"
|
|
30
41
|
],
|
|
31
42
|
"author": "uhyo <uhyo@uhy.ooo>",
|
|
32
43
|
"license": "MIT",
|
|
33
44
|
"devDependencies": {
|
|
34
|
-
"@
|
|
35
|
-
"@types/
|
|
45
|
+
"@playwright/test": "^1.58.1",
|
|
46
|
+
"@types/node": "^25.1.0",
|
|
47
|
+
"@types/react": "^19.2.10",
|
|
36
48
|
"@types/react-dom": "^19.2.3",
|
|
37
|
-
"jsdom": "^27.
|
|
38
|
-
"react": "^19.2.
|
|
39
|
-
"react-dom": "^19.2.
|
|
40
|
-
"tsdown": "^0.
|
|
49
|
+
"jsdom": "^27.4.0",
|
|
50
|
+
"react": "^19.2.4",
|
|
51
|
+
"react-dom": "^19.2.4",
|
|
52
|
+
"tsdown": "^0.20.1",
|
|
41
53
|
"typescript": "^5.9.3",
|
|
42
54
|
"vite": "^7.3.1",
|
|
43
|
-
"vitest": "^4.0.
|
|
55
|
+
"vitest": "^4.0.18"
|
|
44
56
|
},
|
|
45
57
|
"dependencies": {
|
|
46
|
-
"@
|
|
58
|
+
"@funstack/skill-installer": "^1.0.0",
|
|
59
|
+
"@vitejs/plugin-rsc": "^0.5.17",
|
|
47
60
|
"react-error-boundary": "^6.1.0",
|
|
48
61
|
"rsc-html-stream": "^0.0.7",
|
|
49
|
-
"srvx": "^0.10.
|
|
62
|
+
"srvx": "^0.10.1"
|
|
50
63
|
},
|
|
51
64
|
"peerDependencies": {
|
|
52
65
|
"react": "^19.2.3",
|
|
@@ -55,9 +68,11 @@
|
|
|
55
68
|
},
|
|
56
69
|
"scripts": {
|
|
57
70
|
"build": "tsdown",
|
|
71
|
+
"build:docs": "node --experimental-strip-types scripts/generate-ai-docs.ts",
|
|
58
72
|
"dev": "tsdown --watch",
|
|
59
73
|
"test": "vitest",
|
|
60
74
|
"test:run": "vitest run",
|
|
75
|
+
"test:e2e": "playwright test --config e2e/playwright.config.ts",
|
|
61
76
|
"typecheck": "tsc --noEmit"
|
|
62
77
|
}
|
|
63
78
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: funstack-static-knowledge
|
|
3
|
+
description: Use this skill when you need information about `@funstack/static` (the React framework your app is built with). What it is even about, API references, best practices, etc.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# FUNSTACK Static Knowledge
|
|
7
|
+
|
|
8
|
+
**FUNSTACK Static** (`@funstack/static`) is a React framework designed to build SPA application that can be deployed as static files to any static hosting service. Its prominent features is support for React Server Components (RSC) which allows optimizing the performance of the application by rendering parts of the UI at build time.
|
|
9
|
+
|
|
10
|
+
Note that FUNSTACK Static never runs on the server at runtime (except during development). Server Components are rendered at build time into RSC payloads which are then shipped to the client. The client React runtime can then seamlessly render both Client and Server Components into the DOM.
|
|
11
|
+
|
|
12
|
+
## FUNSTACK Static Entrypoint
|
|
13
|
+
|
|
14
|
+
FUNSTACk Static is served as a Vite plugin. See your app's `vite.config.ts` file for the current configuration. A typical configuration looks like this:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { defineConfig } from "vite";
|
|
18
|
+
import react from "@vitejs/plugin-react";
|
|
19
|
+
import { funstackStatic } from "@funstack/static";
|
|
20
|
+
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
plugins: [
|
|
23
|
+
react(),
|
|
24
|
+
funstackStatic({
|
|
25
|
+
root: "./src/Root.tsx",
|
|
26
|
+
app: "./src/App.tsx",
|
|
27
|
+
}),
|
|
28
|
+
],
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Entrypoint.** Here, the `root` option points to the Root component of your application which is responsible for the HTML shell of your application. The `app` option points to the main App component which is the entrypoint for your application's UI.
|
|
33
|
+
|
|
34
|
+
**Server and Client Components.** The entrypoint components (Root and App) are **server components**. FUNSTACK Static follows React's conventions for Server and Client Components; the entrypoint is executed as a Server module. Modules marked with the `"use client"` directive are executed as Client modules. Server modules can import both Server and Client modules, while Client modules can only import other Client modules.
|
|
35
|
+
|
|
36
|
+
**Server Actions.** Note that Server Actions (`"use server"`) are **NOT** supported in FUNSTACK Static, as there is no server runtime deployed.
|
|
37
|
+
|
|
38
|
+
## FUNSTACK Static Docs
|
|
39
|
+
|
|
40
|
+
More detailed documentation about FUNSTACK Static (including API references and best practices) can be found inside `node_modules` at:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
node_modules/@funstack/static/dist/docs/index.md
|
|
44
|
+
```
|