@finesoft/create-app 0.1.0
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/index.mjs +129 -0
- package/package.json +26 -0
- package/templates/react/env.d.ts +1 -0
- package/templates/react/index.html +13 -0
- package/templates/react/package.json +21 -0
- package/templates/react/src/App.tsx +20 -0
- package/templates/react/src/actions.ts +14 -0
- package/templates/react/src/bootstrap.ts +36 -0
- package/templates/react/src/components/Layout.tsx +63 -0
- package/templates/react/src/components/Loading.tsx +3 -0
- package/templates/react/src/components/Navigation.tsx +51 -0
- package/templates/react/src/components/PageRenderer.tsx +41 -0
- package/templates/react/src/components/ProductCard.tsx +35 -0
- package/templates/react/src/lib/controllers/about.ts +18 -0
- package/templates/react/src/lib/controllers/error.ts +11 -0
- package/templates/react/src/lib/controllers/home.ts +53 -0
- package/templates/react/src/lib/controllers/product-detail.ts +52 -0
- package/templates/react/src/lib/controllers/search.ts +58 -0
- package/templates/react/src/lib/guards/auth.ts +17 -0
- package/templates/react/src/lib/guards/seo.ts +15 -0
- package/templates/react/src/lib/mappers/product.ts +27 -0
- package/templates/react/src/lib/models/product.ts +48 -0
- package/templates/react/src/lib/services/api-client.ts +28 -0
- package/templates/react/src/main.tsx +49 -0
- package/templates/react/src/pages/About.tsx +16 -0
- package/templates/react/src/pages/Home.tsx +34 -0
- package/templates/react/src/pages/NotFound.tsx +25 -0
- package/templates/react/src/pages/ProductDetail.tsx +34 -0
- package/templates/react/src/pages/Search.tsx +23 -0
- package/templates/react/src/ssr.tsx +23 -0
- package/templates/react/tsconfig.json +17 -0
- package/templates/react/vite.config.ts +20 -0
- package/templates/react-minimal/env.d.ts +1 -0
- package/templates/react-minimal/index.html +13 -0
- package/templates/react-minimal/package.json +21 -0
- package/templates/react-minimal/src/App.tsx +22 -0
- package/templates/react-minimal/src/bootstrap.ts +6 -0
- package/templates/react-minimal/src/lib/controllers/home.ts +14 -0
- package/templates/react-minimal/src/main.tsx +44 -0
- package/templates/react-minimal/src/ssr.tsx +25 -0
- package/templates/react-minimal/tsconfig.json +17 -0
- package/templates/react-minimal/vite.config.ts +12 -0
- package/templates/svelte/env.d.ts +9 -0
- package/templates/svelte/index.html +13 -0
- package/templates/svelte/package.json +18 -0
- package/templates/svelte/src/App.svelte +85 -0
- package/templates/svelte/src/actions.ts +14 -0
- package/templates/svelte/src/bootstrap.ts +36 -0
- package/templates/svelte/src/components/Layout.svelte +19 -0
- package/templates/svelte/src/components/Loading.svelte +1 -0
- package/templates/svelte/src/components/Navigation.svelte +75 -0
- package/templates/svelte/src/components/PageRenderer.svelte +22 -0
- package/templates/svelte/src/components/ProductCard.svelte +37 -0
- package/templates/svelte/src/lib/controllers/about.ts +18 -0
- package/templates/svelte/src/lib/controllers/error.ts +11 -0
- package/templates/svelte/src/lib/controllers/home.ts +53 -0
- package/templates/svelte/src/lib/controllers/product-detail.ts +52 -0
- package/templates/svelte/src/lib/controllers/search.ts +58 -0
- package/templates/svelte/src/lib/framework-svelte.ts +34 -0
- package/templates/svelte/src/lib/guards/auth.ts +17 -0
- package/templates/svelte/src/lib/guards/seo.ts +15 -0
- package/templates/svelte/src/lib/mappers/product.ts +27 -0
- package/templates/svelte/src/lib/models/product.ts +47 -0
- package/templates/svelte/src/lib/render.ts +15 -0
- package/templates/svelte/src/lib/services/api-client.ts +28 -0
- package/templates/svelte/src/main.ts +27 -0
- package/templates/svelte/src/pages/About.svelte +10 -0
- package/templates/svelte/src/pages/Home.svelte +34 -0
- package/templates/svelte/src/pages/NotFound.svelte +23 -0
- package/templates/svelte/src/pages/ProductDetail.svelte +32 -0
- package/templates/svelte/src/pages/Search.svelte +29 -0
- package/templates/svelte/src/ssr.ts +12 -0
- package/templates/svelte/tsconfig.json +16 -0
- package/templates/svelte/vite.config.ts +21 -0
- package/templates/svelte-minimal/env.d.ts +9 -0
- package/templates/svelte-minimal/index.html +13 -0
- package/templates/svelte-minimal/package.json +18 -0
- package/templates/svelte-minimal/src/App.svelte +63 -0
- package/templates/svelte-minimal/src/bootstrap.ts +6 -0
- package/templates/svelte-minimal/src/lib/controllers/home.ts +15 -0
- package/templates/svelte-minimal/src/lib/framework-svelte.ts +34 -0
- package/templates/svelte-minimal/src/lib/render.ts +12 -0
- package/templates/svelte-minimal/src/main.ts +22 -0
- package/templates/svelte-minimal/src/pages/Home.svelte +10 -0
- package/templates/svelte-minimal/src/ssr.ts +18 -0
- package/templates/svelte-minimal/tsconfig.json +16 -0
- package/templates/svelte-minimal/vite.config.ts +12 -0
- package/templates/vue/env.d.ts +7 -0
- package/templates/vue/index.html +13 -0
- package/templates/vue/package.json +18 -0
- package/templates/vue/src/App.vue +29 -0
- package/templates/vue/src/actions.ts +14 -0
- package/templates/vue/src/bootstrap.ts +42 -0
- package/templates/vue/src/components/Layout.vue +18 -0
- package/templates/vue/src/components/Loading.vue +3 -0
- package/templates/vue/src/components/Navigation.vue +58 -0
- package/templates/vue/src/components/PageRenderer.vue +26 -0
- package/templates/vue/src/components/ProductCard.vue +39 -0
- package/templates/vue/src/lib/controllers/about.ts +18 -0
- package/templates/vue/src/lib/controllers/error.ts +11 -0
- package/templates/vue/src/lib/controllers/home.ts +53 -0
- package/templates/vue/src/lib/controllers/product-detail.ts +52 -0
- package/templates/vue/src/lib/controllers/search.ts +58 -0
- package/templates/vue/src/lib/guards/auth.ts +17 -0
- package/templates/vue/src/lib/guards/seo.ts +15 -0
- package/templates/vue/src/lib/mappers/product.ts +27 -0
- package/templates/vue/src/lib/models/product.ts +47 -0
- package/templates/vue/src/lib/services/api-client.ts +31 -0
- package/templates/vue/src/main.ts +46 -0
- package/templates/vue/src/pages/About.vue +16 -0
- package/templates/vue/src/pages/Home.vue +38 -0
- package/templates/vue/src/pages/NotFound.vue +22 -0
- package/templates/vue/src/pages/ProductDetail.vue +34 -0
- package/templates/vue/src/pages/Search.vue +33 -0
- package/templates/vue/src/ssr.ts +24 -0
- package/templates/vue/tsconfig.json +17 -0
- package/templates/vue/vite.config.ts +20 -0
- package/templates/vue-minimal/env.d.ts +7 -0
- package/templates/vue-minimal/index.html +13 -0
- package/templates/vue-minimal/package.json +18 -0
- package/templates/vue-minimal/src/App.vue +21 -0
- package/templates/vue-minimal/src/bootstrap.ts +6 -0
- package/templates/vue-minimal/src/lib/controllers/home.ts +15 -0
- package/templates/vue-minimal/src/main.ts +41 -0
- package/templates/vue-minimal/src/pages/Home.vue +12 -0
- package/templates/vue-minimal/src/ssr.ts +28 -0
- package/templates/vue-minimal/tsconfig.json +17 -0
- package/templates/vue-minimal/vite.config.ts +12 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type BeforeLoadGuard, next, redirect } from "@finesoft/front";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Auth guard — redirects unauthenticated users to login.
|
|
5
|
+
*
|
|
6
|
+
* Demonstrates beforeLoad middleware with `redirect()` and `next()`.
|
|
7
|
+
* In a real app, check a session cookie or token via `ctx.getCookie()`.
|
|
8
|
+
*/
|
|
9
|
+
export const authGuard: BeforeLoadGuard = (ctx) => {
|
|
10
|
+
const token = ctx.getCookie("auth_token");
|
|
11
|
+
|
|
12
|
+
if (!token) {
|
|
13
|
+
return redirect(`/login?from=${encodeURIComponent(ctx.url)}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return next();
|
|
17
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type AfterLoadGuard, next, rewrite } from "@finesoft/front";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SEO guard — normalizes URLs after data load.
|
|
5
|
+
*
|
|
6
|
+
* Demonstrates afterLoad middleware with `rewrite()`.
|
|
7
|
+
* Strips trailing slashes for canonical URL consistency.
|
|
8
|
+
*/
|
|
9
|
+
export const seoGuard: AfterLoadGuard = (ctx) => {
|
|
10
|
+
if (ctx.path !== "/" && ctx.path.endsWith("/")) {
|
|
11
|
+
return rewrite(ctx.path.slice(0, -1));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return next();
|
|
15
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { makeFlowAction, mapEach, type Mapper } from "@finesoft/front";
|
|
2
|
+
import type { ProductItem } from "../models/product";
|
|
3
|
+
|
|
4
|
+
/** Raw API response shape */
|
|
5
|
+
interface ApiProduct {
|
|
6
|
+
id: number;
|
|
7
|
+
title: string;
|
|
8
|
+
price: number;
|
|
9
|
+
thumbnail?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Transform raw API product → domain ProductItem */
|
|
13
|
+
const toProductItem: Mapper<ApiProduct, ProductItem> = (raw) => ({
|
|
14
|
+
id: String(raw.id),
|
|
15
|
+
itemType: "product",
|
|
16
|
+
name: raw.title,
|
|
17
|
+
price: raw.price,
|
|
18
|
+
imageUrl: raw.thumbnail ?? "/img/placeholder.svg",
|
|
19
|
+
clickAction: makeFlowAction(`/products/${raw.id}`),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Composed mapper pipeline: ApiProduct[] → ProductItem[]
|
|
24
|
+
*
|
|
25
|
+
* Demonstrates pipe() + mapEach() for data transformation.
|
|
26
|
+
*/
|
|
27
|
+
export const mapProducts: Mapper<ApiProduct[], ProductItem[]> = mapEach(toProductItem);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { BaseItem, BasePage, BaseShelf } from "@finesoft/front";
|
|
2
|
+
|
|
3
|
+
export interface ProductItem extends BaseItem {
|
|
4
|
+
itemType: "product";
|
|
5
|
+
name: string;
|
|
6
|
+
price: number;
|
|
7
|
+
imageUrl: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ProductShelf extends BaseShelf {
|
|
11
|
+
shelfType: "products";
|
|
12
|
+
items: ProductItem[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface HomePage extends BasePage {
|
|
16
|
+
pageType: "home";
|
|
17
|
+
shelves: ProductShelf[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ProductPage extends BasePage {
|
|
21
|
+
pageType: "product";
|
|
22
|
+
product: {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
price: number;
|
|
26
|
+
description: string;
|
|
27
|
+
imageUrl: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SearchPage extends BasePage {
|
|
32
|
+
pageType: "search";
|
|
33
|
+
query: string;
|
|
34
|
+
results: ProductItem[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AboutPage extends BasePage {
|
|
38
|
+
pageType: "about";
|
|
39
|
+
content: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ErrorPage extends BasePage {
|
|
43
|
+
pageType: "error";
|
|
44
|
+
status: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 所有页面类型联合 */
|
|
48
|
+
export type AppPage = HomePage | ProductPage | SearchPage | AboutPage | ErrorPage;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { HttpClient, type HttpClientConfig } from "@finesoft/front";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Demo API client — extends HttpClient for typed endpoint access.
|
|
5
|
+
*
|
|
6
|
+
* Showcases HttpClient subclass pattern with base URL and custom fetch.
|
|
7
|
+
*/
|
|
8
|
+
export class ApiClient extends HttpClient {
|
|
9
|
+
constructor(config?: Partial<HttpClientConfig>) {
|
|
10
|
+
super({
|
|
11
|
+
baseUrl: config?.baseUrl ?? "/api",
|
|
12
|
+
defaultHeaders: { "Content-Type": "application/json", ...config?.defaultHeaders },
|
|
13
|
+
fetch: config?.fetch,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async getProducts(): Promise<unknown[]> {
|
|
18
|
+
return this.get<unknown[]>("/products");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async getProduct(id: string): Promise<unknown> {
|
|
22
|
+
return this.get<unknown>(`/products/${id}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async searchProducts(query: string): Promise<unknown[]> {
|
|
26
|
+
return this.get<unknown[]>("/products", { q: query });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { startBrowserApp, type Action, type BasePage, type Framework } from "@finesoft/front";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import App from "./App";
|
|
4
|
+
import { bootstrap } from "./bootstrap";
|
|
5
|
+
import type { AppPage } from "./lib/models/product";
|
|
6
|
+
|
|
7
|
+
void startBrowserApp({
|
|
8
|
+
bootstrap,
|
|
9
|
+
mountId: "app",
|
|
10
|
+
mount: (target: HTMLElement, { framework }: { framework: Framework; locale: string }) => {
|
|
11
|
+
let root: ReturnType<typeof createRoot> | null = null;
|
|
12
|
+
let currentPage: AppPage | null = null;
|
|
13
|
+
|
|
14
|
+
function render(page: AppPage | null, loading: boolean) {
|
|
15
|
+
const handleAction = (action: Action) => {
|
|
16
|
+
void framework.perform(action);
|
|
17
|
+
};
|
|
18
|
+
if (!root) root = createRoot(target);
|
|
19
|
+
root.render(<App page={page} loading={loading} onAction={handleAction} />);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return ({
|
|
23
|
+
page,
|
|
24
|
+
isFirstPage,
|
|
25
|
+
}: {
|
|
26
|
+
page: Promise<BasePage> | BasePage;
|
|
27
|
+
isFirstPage?: boolean;
|
|
28
|
+
}) => {
|
|
29
|
+
if (page instanceof Promise) {
|
|
30
|
+
if (!isFirstPage) render(currentPage, true);
|
|
31
|
+
void page.then((p) => {
|
|
32
|
+
currentPage = p as AppPage;
|
|
33
|
+
render(currentPage, false);
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
currentPage = page as AppPage;
|
|
37
|
+
render(currentPage, false);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
callbacks: {
|
|
42
|
+
onNavigate(pathname) {
|
|
43
|
+
console.log("[navigate]", pathname);
|
|
44
|
+
},
|
|
45
|
+
onModal(page) {
|
|
46
|
+
console.log("[modal]", page.title);
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Action } from "@finesoft/front";
|
|
2
|
+
import type { AboutPage } from "../lib/models/product";
|
|
3
|
+
|
|
4
|
+
interface AboutProps {
|
|
5
|
+
page: AboutPage;
|
|
6
|
+
onAction?: (action: Action) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function About({ page }: AboutProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div>
|
|
12
|
+
<h1>{page.title}</h1>
|
|
13
|
+
<p>{page.content}</p>
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Action } from "@finesoft/front";
|
|
2
|
+
import ProductCard from "../components/ProductCard";
|
|
3
|
+
import type { HomePage } from "../lib/models/product";
|
|
4
|
+
|
|
5
|
+
interface HomeProps {
|
|
6
|
+
page: HomePage;
|
|
7
|
+
onAction?: (action: Action) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function Home({ page, onAction }: HomeProps) {
|
|
11
|
+
return (
|
|
12
|
+
<div>
|
|
13
|
+
<h1>{page.title}</h1>
|
|
14
|
+
<p>{page.description}</p>
|
|
15
|
+
{page.shelves.map((shelf) => (
|
|
16
|
+
<section key={shelf.id}>
|
|
17
|
+
<h2>{shelf.title}</h2>
|
|
18
|
+
<div
|
|
19
|
+
style={{
|
|
20
|
+
display: "flex",
|
|
21
|
+
gap: "1rem",
|
|
22
|
+
flexWrap: shelf.isHorizontal ? "nowrap" : "wrap",
|
|
23
|
+
overflowX: shelf.isHorizontal ? "auto" : undefined,
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
{shelf.items.map((item) => (
|
|
27
|
+
<ProductCard key={item.id} item={item} onAction={onAction} />
|
|
28
|
+
))}
|
|
29
|
+
</div>
|
|
30
|
+
</section>
|
|
31
|
+
))}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Action } from "@finesoft/front";
|
|
2
|
+
import { makeFlowAction } from "@finesoft/front";
|
|
3
|
+
import type { ErrorPage } from "../lib/models/product";
|
|
4
|
+
|
|
5
|
+
interface NotFoundProps {
|
|
6
|
+
page: ErrorPage;
|
|
7
|
+
onAction?: (action: Action) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function NotFound({ page, onAction }: NotFoundProps) {
|
|
11
|
+
const handleHome = (e: React.MouseEvent) => {
|
|
12
|
+
e.preventDefault();
|
|
13
|
+
onAction?.(makeFlowAction("/"));
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div>
|
|
18
|
+
<h1>{page.title}</h1>
|
|
19
|
+
<p>{page.description}</p>
|
|
20
|
+
<a href="/" onClick={handleHome}>
|
|
21
|
+
← Go Home
|
|
22
|
+
</a>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Action } from "@finesoft/front";
|
|
2
|
+
import { makeFlowAction } from "@finesoft/front";
|
|
3
|
+
import type { ProductPage } from "../lib/models/product";
|
|
4
|
+
|
|
5
|
+
interface ProductDetailProps {
|
|
6
|
+
page: ProductPage;
|
|
7
|
+
onAction?: (action: Action) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function ProductDetail({ page, onAction }: ProductDetailProps) {
|
|
11
|
+
const handleBack = (e: React.MouseEvent) => {
|
|
12
|
+
e.preventDefault();
|
|
13
|
+
onAction?.(makeFlowAction("/"));
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div>
|
|
18
|
+
<a href="/" onClick={handleBack}>
|
|
19
|
+
← Back
|
|
20
|
+
</a>
|
|
21
|
+
<h1>{page.product.name}</h1>
|
|
22
|
+
<p
|
|
23
|
+
style={{
|
|
24
|
+
fontSize: "1.5rem",
|
|
25
|
+
color: "#007bff",
|
|
26
|
+
fontWeight: "bold",
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
${page.product.price.toFixed(2)}
|
|
30
|
+
</p>
|
|
31
|
+
<p>{page.product.description}</p>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Action } from "@finesoft/front";
|
|
2
|
+
import ProductCard from "../components/ProductCard";
|
|
3
|
+
import type { SearchPage } from "../lib/models/product";
|
|
4
|
+
|
|
5
|
+
interface SearchProps {
|
|
6
|
+
page: SearchPage;
|
|
7
|
+
onAction?: (action: Action) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function Search({ page, onAction }: SearchProps) {
|
|
11
|
+
return (
|
|
12
|
+
<div>
|
|
13
|
+
<h1>{page.title}</h1>
|
|
14
|
+
<p>{page.description}</p>
|
|
15
|
+
<div style={{ display: "flex", gap: "1rem", flexWrap: "wrap" }}>
|
|
16
|
+
{page.results.map((item) => (
|
|
17
|
+
<ProductCard key={item.id} item={item} onAction={onAction} />
|
|
18
|
+
))}
|
|
19
|
+
</div>
|
|
20
|
+
{page.results.length === 0 && <p>No products found.</p>}
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createSSRRender, serializeServerData } from "@finesoft/front";
|
|
2
|
+
import { renderToString } from "react-dom/server";
|
|
3
|
+
import App from "./App";
|
|
4
|
+
import { bootstrap } from "./bootstrap";
|
|
5
|
+
import { getErrorPage } from "./lib/controllers/error";
|
|
6
|
+
import type { AppPage } from "./lib/models/product";
|
|
7
|
+
|
|
8
|
+
export { serializeServerData };
|
|
9
|
+
|
|
10
|
+
export const render = createSSRRender({
|
|
11
|
+
bootstrap,
|
|
12
|
+
getErrorPage,
|
|
13
|
+
renderApp(page, _locale) {
|
|
14
|
+
const html = renderToString(<App page={page as AppPage} />);
|
|
15
|
+
return {
|
|
16
|
+
html,
|
|
17
|
+
head: `<title>${page.title}</title><meta name="description" content="${
|
|
18
|
+
page.description ?? ""
|
|
19
|
+
}">`,
|
|
20
|
+
css: "",
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"strict": true,
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"paths": {
|
|
13
|
+
"@/*": ["./src/*"]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*", "env.d.ts"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { finesoftFrontViteConfig } from "@finesoft/front";
|
|
2
|
+
import react from "@vitejs/plugin-react";
|
|
3
|
+
import { defineConfig } from "vite-plus";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [
|
|
7
|
+
react(),
|
|
8
|
+
finesoftFrontViteConfig({
|
|
9
|
+
ssr: { entry: "src/ssr.tsx" },
|
|
10
|
+
locales: ["en", "zh"],
|
|
11
|
+
defaultLocale: "en",
|
|
12
|
+
proxies: [
|
|
13
|
+
{
|
|
14
|
+
prefix: "/api",
|
|
15
|
+
target: "https://jsonplaceholder.typicode.com",
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
}),
|
|
19
|
+
],
|
|
20
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="<!--ssr-lang-->">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<!--ssr-head-->
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"><!--ssr-body--></div>
|
|
10
|
+
<!--ssr-data-->
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "finesoft-react-minimal",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vp dev",
|
|
7
|
+
"build": "vp build",
|
|
8
|
+
"preview": "vp preview"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@finesoft/front": "workspace:*",
|
|
12
|
+
"react": "^19.0.0",
|
|
13
|
+
"react-dom": "^19.0.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/react": "^19.0.0",
|
|
17
|
+
"@types/react-dom": "^19.0.0",
|
|
18
|
+
"@vitejs/plugin-react": "^4.0.0",
|
|
19
|
+
"vite-plus": "catalog:"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Action, BasePage } from "@finesoft/front";
|
|
2
|
+
|
|
3
|
+
interface AppProps {
|
|
4
|
+
page?: BasePage | null;
|
|
5
|
+
loading?: boolean;
|
|
6
|
+
onAction?: (action: Action) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function App({ page, loading = false }: AppProps) {
|
|
10
|
+
if (loading)
|
|
11
|
+
return (
|
|
12
|
+
<main style={{ padding: "2rem", textAlign: "center", color: "#999" }}>Loading…</main>
|
|
13
|
+
);
|
|
14
|
+
if (!page) return null;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<main style={{ padding: "1rem" }}>
|
|
18
|
+
<h1>{page.title}</h1>
|
|
19
|
+
<p>{page.description}</p>
|
|
20
|
+
</main>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Framework, defineRoutes } from "@finesoft/front";
|
|
2
|
+
import { HomeController } from "./lib/controllers/home";
|
|
3
|
+
|
|
4
|
+
export function bootstrap(framework: Framework): void {
|
|
5
|
+
defineRoutes(framework, [{ path: "/", intentId: "home", controller: new HomeController() }]);
|
|
6
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseController, type BasePage } from "@finesoft/front";
|
|
2
|
+
|
|
3
|
+
export class HomeController extends BaseController<Record<string, string>, BasePage> {
|
|
4
|
+
readonly intentId = "home";
|
|
5
|
+
|
|
6
|
+
execute(): BasePage {
|
|
7
|
+
return {
|
|
8
|
+
id: "home",
|
|
9
|
+
pageType: "home",
|
|
10
|
+
title: "Home",
|
|
11
|
+
description: "Welcome to Finesoft Front",
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { startBrowserApp, type Action, type BasePage, type Framework } from "@finesoft/front";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import App from "./App";
|
|
4
|
+
import { bootstrap } from "./bootstrap";
|
|
5
|
+
|
|
6
|
+
void startBrowserApp({
|
|
7
|
+
bootstrap,
|
|
8
|
+
mountId: "app",
|
|
9
|
+
mount: (target: HTMLElement, { framework }: { framework: Framework; locale: string }) => {
|
|
10
|
+
let root: ReturnType<typeof createRoot> | null = null;
|
|
11
|
+
let currentPage: BasePage | null = null;
|
|
12
|
+
|
|
13
|
+
function render(page: BasePage | null, loading: boolean) {
|
|
14
|
+
const handleAction = (action: Action) => {
|
|
15
|
+
void framework.perform(action);
|
|
16
|
+
};
|
|
17
|
+
if (!root) root = createRoot(target);
|
|
18
|
+
root.render(<App page={page} loading={loading} onAction={handleAction} />);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return ({
|
|
22
|
+
page,
|
|
23
|
+
isFirstPage,
|
|
24
|
+
}: {
|
|
25
|
+
page: Promise<BasePage> | BasePage;
|
|
26
|
+
isFirstPage?: boolean;
|
|
27
|
+
}) => {
|
|
28
|
+
if (page instanceof Promise) {
|
|
29
|
+
if (!isFirstPage) render(currentPage, true);
|
|
30
|
+
void page.then((p) => {
|
|
31
|
+
currentPage = p;
|
|
32
|
+
render(currentPage, false);
|
|
33
|
+
});
|
|
34
|
+
} else {
|
|
35
|
+
currentPage = page;
|
|
36
|
+
render(currentPage, false);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
callbacks: {
|
|
41
|
+
onNavigate() {},
|
|
42
|
+
onModal() {},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createSSRRender, serializeServerData } from "@finesoft/front";
|
|
2
|
+
import { renderToString } from "react-dom/server";
|
|
3
|
+
import App from "./App";
|
|
4
|
+
import { bootstrap } from "./bootstrap";
|
|
5
|
+
|
|
6
|
+
export const render = createSSRRender({
|
|
7
|
+
bootstrap,
|
|
8
|
+
getErrorPage(status, message) {
|
|
9
|
+
return {
|
|
10
|
+
id: "error",
|
|
11
|
+
pageType: "error",
|
|
12
|
+
title: `Error ${status}`,
|
|
13
|
+
description: message,
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
renderApp(page, _locale) {
|
|
17
|
+
return {
|
|
18
|
+
html: renderToString(<App page={page} />),
|
|
19
|
+
head: `<title>${page.title}</title>`,
|
|
20
|
+
css: "",
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export { serializeServerData };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"strict": true,
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"paths": {
|
|
13
|
+
"@/*": ["./src/*"]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*", "env.d.ts"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { finesoftFrontViteConfig } from "@finesoft/front";
|
|
2
|
+
import react from "@vitejs/plugin-react";
|
|
3
|
+
import { defineConfig } from "vite-plus";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [
|
|
7
|
+
react(),
|
|
8
|
+
finesoftFrontViteConfig({
|
|
9
|
+
ssr: { entry: "src/ssr.tsx" },
|
|
10
|
+
}),
|
|
11
|
+
],
|
|
12
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="<!--ssr-lang-->">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<!--ssr-head-->
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"><!--ssr-body--></div>
|
|
10
|
+
<!--ssr-data-->
|
|
11
|
+
<script type="module" src="/src/main.ts"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "finesoft-svelte",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vp dev",
|
|
7
|
+
"build": "vp build",
|
|
8
|
+
"preview": "vp preview"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@finesoft/front": "workspace:*",
|
|
12
|
+
"svelte": "^5.0.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
|
16
|
+
"vite-plus": "catalog:"
|
|
17
|
+
}
|
|
18
|
+
}
|