@finesoft/create-app 0.1.34 → 0.1.35
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 +22 -26
- package/package.json +14 -5
- package/templates/react/README.md +35 -0
- package/templates/react/index.html +2 -3
- package/templates/react/package.json +12 -8
- package/templates/react/src/App.tsx +7 -15
- package/templates/react/src/app-definition.ts +35 -0
- package/templates/react/src/components/Layout.tsx +5 -52
- package/templates/react/src/components/Loading.tsx +1 -1
- package/templates/react/src/components/Navigation.tsx +10 -37
- package/templates/react/src/components/ProductCard.tsx +5 -23
- package/templates/react/src/config.ts +2 -0
- package/templates/react/src/lib/controllers/about.ts +18 -13
- package/templates/react/src/lib/controllers/error.ts +11 -7
- package/templates/react/src/lib/controllers/home.ts +36 -41
- package/templates/react/src/lib/controllers/product-detail.ts +71 -40
- package/templates/react/src/lib/controllers/search.ts +33 -54
- package/templates/react/src/lib/data/products.ts +36 -0
- package/templates/react/src/lib/mappers/product.ts +3 -3
- package/templates/react/src/lib/models/product.ts +11 -1
- package/templates/react/src/lib/services/api-client.ts +7 -4
- package/templates/react/src/main.ts +34 -0
- package/templates/react/src/pages/About.tsx +2 -4
- package/templates/react/src/pages/Home.tsx +6 -15
- package/templates/react/src/pages/NotFound.tsx +4 -14
- package/templates/react/src/pages/ProductDetail.tsx +5 -23
- package/templates/react/src/pages/Search.tsx +5 -7
- package/templates/react/src/ssr.ts +11 -0
- package/templates/react/src/styles.css +71 -0
- package/templates/react/src/views.ts +12 -0
- package/templates/react/tsconfig.json +14 -9
- package/templates/react/vite.config.ts +9 -13
- package/templates/react-minimal/README.md +39 -0
- package/templates/react-minimal/index.html +2 -3
- package/templates/react-minimal/package.json +12 -8
- package/templates/react-minimal/src/App.tsx +40 -85
- package/templates/react-minimal/src/app-definition.ts +48 -0
- package/templates/react-minimal/src/config.ts +2 -0
- package/templates/react-minimal/src/lib/controllers/detail.ts +19 -18
- package/templates/react-minimal/src/lib/controllers/error.ts +15 -0
- package/templates/react-minimal/src/lib/controllers/home.ts +19 -24
- package/templates/react-minimal/src/lib/controllers/notes.ts +7 -5
- package/templates/react-minimal/src/lib/locale.ts +16 -0
- package/templates/react-minimal/src/lib/models/page.ts +27 -0
- package/templates/react-minimal/src/lib/navigation.ts +11 -0
- package/templates/react-minimal/src/locales/en-US.json +5 -0
- package/templates/react-minimal/src/locales/zh-Hans.json +5 -0
- package/templates/react-minimal/src/main.ts +43 -0
- package/templates/react-minimal/src/pages/Detail.tsx +20 -0
- package/templates/react-minimal/src/pages/Home.tsx +38 -0
- package/templates/react-minimal/src/pages/NotFound.tsx +11 -0
- package/templates/react-minimal/src/pages/Notes.tsx +21 -0
- package/templates/react-minimal/src/ssr.ts +11 -0
- package/templates/react-minimal/src/styles.css +74 -0
- package/templates/react-minimal/src/views.ts +6 -0
- package/templates/react-minimal/src/vite-env.d.ts +3 -0
- package/templates/react-minimal/tsconfig.json +14 -9
- package/templates/react-minimal/vite.config.ts +9 -7
- package/templates/svelte/README.md +35 -0
- package/templates/svelte/index.html +1 -2
- package/templates/svelte/package.json +9 -5
- package/templates/svelte/src/App.svelte +8 -80
- package/templates/svelte/src/app-definition.ts +35 -0
- package/templates/svelte/src/components/Layout.svelte +2 -2
- package/templates/svelte/src/components/Loading.svelte +1 -1
- package/templates/svelte/src/components/Navigation.svelte +5 -66
- package/templates/svelte/src/components/ProductCard.svelte +3 -24
- package/templates/svelte/src/config.ts +2 -0
- package/templates/svelte/src/lib/controllers/about.ts +18 -13
- package/templates/svelte/src/lib/controllers/error.ts +11 -7
- package/templates/svelte/src/lib/controllers/home.ts +36 -41
- package/templates/svelte/src/lib/controllers/product-detail.ts +71 -40
- package/templates/svelte/src/lib/controllers/search.ts +33 -54
- package/templates/svelte/src/lib/data/products.ts +36 -0
- package/templates/svelte/src/lib/mappers/product.ts +3 -3
- package/templates/svelte/src/lib/models/product.ts +12 -1
- package/templates/svelte/src/lib/services/api-client.ts +7 -4
- package/templates/svelte/src/main.ts +28 -24
- package/templates/svelte/src/pages/About.svelte +2 -2
- package/templates/svelte/src/pages/Home.svelte +3 -15
- package/templates/svelte/src/pages/NotFound.svelte +3 -12
- package/templates/svelte/src/pages/ProductDetail.svelte +4 -21
- package/templates/svelte/src/pages/Search.svelte +2 -10
- package/templates/svelte/src/ssr.ts +10 -9
- package/templates/svelte/src/styles.css +71 -0
- package/templates/svelte/src/views.ts +12 -0
- package/templates/svelte/svelte.config.js +2 -0
- package/templates/svelte/tsconfig.json +13 -8
- package/templates/svelte/vite.config.ts +9 -13
- package/templates/svelte-minimal/README.md +39 -0
- package/templates/svelte-minimal/index.html +1 -2
- package/templates/svelte-minimal/package.json +9 -5
- package/templates/svelte-minimal/src/App.svelte +49 -61
- package/templates/svelte-minimal/src/app-definition.ts +48 -0
- package/templates/svelte-minimal/src/config.ts +2 -0
- package/templates/svelte-minimal/src/lib/controllers/detail.ts +23 -0
- package/templates/svelte-minimal/src/lib/controllers/error.ts +15 -0
- package/templates/svelte-minimal/src/lib/controllers/home.ts +24 -23
- package/templates/svelte-minimal/src/lib/controllers/notes.ts +18 -0
- package/templates/svelte-minimal/src/lib/locale.ts +16 -0
- package/templates/svelte-minimal/src/lib/models/page.ts +27 -0
- package/templates/svelte-minimal/src/lib/navigation.ts +11 -0
- package/templates/svelte-minimal/src/locales/en-US.json +1 -3
- package/templates/svelte-minimal/src/locales/zh-Hans.json +1 -3
- package/templates/svelte-minimal/src/main.ts +37 -19
- package/templates/svelte-minimal/src/pages/Detail.svelte +11 -0
- package/templates/svelte-minimal/src/pages/Home.svelte +17 -21
- package/templates/svelte-minimal/src/pages/NotFound.svelte +10 -0
- package/templates/svelte-minimal/src/pages/Notes.svelte +11 -0
- package/templates/svelte-minimal/src/ssr.ts +9 -17
- package/templates/svelte-minimal/src/styles.css +74 -0
- package/templates/svelte-minimal/src/views.ts +6 -0
- package/templates/svelte-minimal/src/vite-env.d.ts +3 -0
- package/templates/svelte-minimal/svelte.config.js +2 -0
- package/templates/svelte-minimal/tsconfig.json +13 -8
- package/templates/svelte-minimal/vite.config.ts +9 -10
- package/templates/vue/README.md +35 -0
- package/templates/vue/index.html +1 -2
- package/templates/vue/package.json +9 -5
- package/templates/vue/src/App.vue +7 -22
- package/templates/vue/src/app-definition.ts +35 -0
- package/templates/vue/src/components/Layout.vue +4 -6
- package/templates/vue/src/components/Loading.vue +1 -1
- package/templates/vue/src/components/Navigation.vue +10 -43
- package/templates/vue/src/components/ProductCard.vue +4 -28
- package/templates/vue/src/config.ts +2 -0
- package/templates/vue/src/lib/controllers/about.ts +18 -13
- package/templates/vue/src/lib/controllers/error.ts +11 -7
- package/templates/vue/src/lib/controllers/home.ts +36 -41
- package/templates/vue/src/lib/controllers/product-detail.ts +71 -40
- package/templates/vue/src/lib/controllers/search.ts +33 -54
- package/templates/vue/src/lib/data/products.ts +36 -0
- package/templates/vue/src/lib/mappers/product.ts +3 -3
- package/templates/vue/src/lib/models/product.ts +12 -1
- package/templates/vue/src/lib/services/api-client.ts +7 -7
- package/templates/vue/src/main.ts +28 -43
- package/templates/vue/src/pages/About.vue +3 -5
- package/templates/vue/src/pages/Home.vue +5 -23
- package/templates/vue/src/pages/NotFound.vue +4 -13
- package/templates/vue/src/pages/ProductDetail.vue +5 -23
- package/templates/vue/src/pages/Search.vue +4 -18
- package/templates/vue/src/ssr.ts +5 -18
- package/templates/vue/src/styles.css +71 -0
- package/templates/vue/src/views.ts +12 -0
- package/templates/vue/tsconfig.json +14 -9
- package/templates/vue/vite.config.ts +9 -13
- package/templates/vue-minimal/README.md +39 -0
- package/templates/vue-minimal/index.html +1 -2
- package/templates/vue-minimal/package.json +9 -5
- package/templates/vue-minimal/src/App.vue +46 -43
- package/templates/vue-minimal/src/app-definition.ts +48 -0
- package/templates/vue-minimal/src/config.ts +2 -0
- package/templates/vue-minimal/src/lib/controllers/detail.ts +19 -18
- package/templates/vue-minimal/src/lib/controllers/error.ts +15 -0
- package/templates/vue-minimal/src/lib/controllers/home.ts +19 -24
- package/templates/vue-minimal/src/lib/controllers/notes.ts +7 -5
- package/templates/vue-minimal/src/lib/locale.ts +16 -0
- package/templates/vue-minimal/src/lib/models/page.ts +27 -0
- package/templates/vue-minimal/src/lib/navigation.ts +11 -0
- package/templates/vue-minimal/src/locales/en-US.json +5 -0
- package/templates/vue-minimal/src/locales/zh-Hans.json +5 -0
- package/templates/vue-minimal/src/main.ts +35 -70
- package/templates/vue-minimal/src/pages/Detail.vue +20 -0
- package/templates/vue-minimal/src/pages/Home.vue +34 -0
- package/templates/vue-minimal/src/pages/NotFound.vue +13 -0
- package/templates/vue-minimal/src/pages/Notes.vue +22 -0
- package/templates/vue-minimal/src/ssr.ts +7 -46
- package/templates/vue-minimal/src/styles.css +74 -0
- package/templates/vue-minimal/src/views.ts +6 -0
- package/templates/vue-minimal/src/vite-env.d.ts +3 -0
- package/templates/vue-minimal/tsconfig.json +14 -9
- package/templates/vue-minimal/vite.config.ts +9 -7
- package/templates/react/src/actions.ts +0 -14
- package/templates/react/src/bootstrap.ts +0 -36
- package/templates/react/src/components/PageRenderer.tsx +0 -41
- package/templates/react/src/main.tsx +0 -49
- package/templates/react/src/ssr.tsx +0 -23
- package/templates/react-minimal/src/bootstrap.ts +0 -39
- package/templates/react-minimal/src/main.tsx +0 -99
- package/templates/react-minimal/src/ssr.tsx +0 -51
- package/templates/react-minimal/src/views/DetailView.tsx +0 -28
- package/templates/react-minimal/src/views/HomeView.tsx +0 -27
- package/templates/react-minimal/src/views/NotesView.tsx +0 -11
- package/templates/svelte/src/actions.ts +0 -14
- package/templates/svelte/src/bootstrap.ts +0 -36
- package/templates/svelte/src/components/PageRenderer.svelte +0 -22
- package/templates/svelte/src/lib/framework-svelte.ts +0 -34
- package/templates/svelte/src/lib/render.ts +0 -15
- package/templates/svelte-minimal/src/bootstrap.ts +0 -6
- package/templates/svelte-minimal/src/lib/framework-svelte.ts +0 -34
- package/templates/svelte-minimal/src/lib/render.ts +0 -12
- package/templates/vue/src/actions.ts +0 -14
- package/templates/vue/src/bootstrap.ts +0 -42
- package/templates/vue/src/components/PageRenderer.vue +0 -26
- package/templates/vue-minimal/src/bootstrap.ts +0 -39
- package/templates/vue-minimal/src/views/DetailView.vue +0 -29
- package/templates/vue-minimal/src/views/HomeView.vue +0 -26
- package/templates/vue-minimal/src/views/NotesView.vue +0 -12
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { DetailPage } from "../lib/models/page";
|
|
3
|
+
let { page }: { page: DetailPage } = $props();
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<section class="page">
|
|
7
|
+
<h1>{page.title}</h1>
|
|
8
|
+
<p>{page.description}</p>
|
|
9
|
+
<!-- The entry owns this draft; data-restore-root also restores it after reload. -->
|
|
10
|
+
<div class="draft" data-restore-root><label>Detail note<input name="note" placeholder="Kept while this screen is alive; lost when popped" /></label></div>
|
|
11
|
+
</section>
|
|
@@ -1,25 +1,21 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import type { ViewProps } from "@finesoft/front";
|
|
3
|
+
import type { HomePage } from "../lib/models/page";
|
|
4
|
+
import { getHomeLocale } from "../lib/locale";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const framework = getFramework();
|
|
8
|
-
const locale = framework?.getLocale()?.lang ?? "unknown";
|
|
9
|
-
const translator = framework?.getTranslator();
|
|
10
|
-
const localeLabel = translator?.t("home.localeLabel") ?? "Current locale";
|
|
11
|
-
const runtimeBadge =
|
|
12
|
-
translator?.t("home.runtimeBadge") ??
|
|
13
|
-
"Hydration kept the translator alive on the client.";
|
|
14
|
-
const switchHint =
|
|
15
|
-
translator?.t("home.switchHint") ??
|
|
16
|
-
'Change frameworkConfig.locale in src/bootstrap.ts to load another JSON file.';
|
|
6
|
+
let { page, app }: { page: HomePage; app: ViewProps["app"] } = $props();
|
|
7
|
+
const locale = $derived(getHomeLocale(app));
|
|
17
8
|
</script>
|
|
18
9
|
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
10
|
+
<section class="page">
|
|
11
|
+
<h1>{page.title}</h1>
|
|
12
|
+
<p>{page.description}</p>
|
|
13
|
+
<ul class="feed">
|
|
14
|
+
{#each page.items as item (item.id)}<li><button onclick={() => void app.perform({ kind: "flow", url: `/item/${item.id}` })}>{item.title}</button></li>{/each}
|
|
15
|
+
</ul>
|
|
16
|
+
<section class="locale-info" aria-label="Locale">
|
|
17
|
+
<p><strong>{locale.label}:</strong> {locale.lang}</p>
|
|
18
|
+
<p>{locale.badge}</p>
|
|
19
|
+
<p>{locale.hint}</p>
|
|
20
|
+
</section>
|
|
21
|
+
</section>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { NotesPage } from "../lib/models/page";
|
|
3
|
+
let { page }: { page: NotesPage } = $props();
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<section class="page">
|
|
7
|
+
<h1>{page.title}</h1>
|
|
8
|
+
<p>{page.description}</p>
|
|
9
|
+
<!-- The entry owns this draft; data-restore-root also restores it after reload. -->
|
|
10
|
+
<div class="draft" data-restore-root><label>Notes draft<textarea name="notes" rows={6} placeholder="Write a note; switch tabs or reload to restore it"></textarea></label></div>
|
|
11
|
+
</section>
|
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { render as renderSvelte } from "svelte/server";
|
|
2
|
+
import { createSSRRender } from "@finesoft/front";
|
|
3
|
+
import { app } from "./app-definition";
|
|
4
|
+
import App from "./App.svelte";
|
|
4
5
|
|
|
5
6
|
export const render = createSSRRender({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
definition: app,
|
|
8
|
+
render: (app) => {
|
|
9
|
+
const result = renderSvelte(App, { props: { app } });
|
|
10
|
+
return { html: result.body, head: result.head, css: "" };
|
|
9
11
|
},
|
|
10
|
-
getErrorPage(status, message) {
|
|
11
|
-
return {
|
|
12
|
-
id: "error",
|
|
13
|
-
pageType: "error",
|
|
14
|
-
title: `Error ${status}`,
|
|
15
|
-
description: message,
|
|
16
|
-
};
|
|
17
|
-
},
|
|
18
|
-
renderApp,
|
|
19
12
|
});
|
|
20
|
-
|
|
21
|
-
export { serializeServerData };
|
|
13
|
+
export { serializeServerData } from "@finesoft/front";
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: system-ui, sans-serif;
|
|
3
|
+
color: #202124;
|
|
4
|
+
background: #fff;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
body {
|
|
8
|
+
margin: 0;
|
|
9
|
+
}
|
|
10
|
+
button,
|
|
11
|
+
input,
|
|
12
|
+
textarea {
|
|
13
|
+
font: inherit;
|
|
14
|
+
}
|
|
15
|
+
button {
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.app-chrome,
|
|
20
|
+
.page {
|
|
21
|
+
box-sizing: border-box;
|
|
22
|
+
max-width: 34rem;
|
|
23
|
+
margin: 0 auto;
|
|
24
|
+
padding: 1rem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.profile,
|
|
28
|
+
.tabs {
|
|
29
|
+
display: flex;
|
|
30
|
+
gap: 0.5rem;
|
|
31
|
+
align-items: center;
|
|
32
|
+
margin-bottom: 1rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.profile label {
|
|
36
|
+
flex: 1;
|
|
37
|
+
}
|
|
38
|
+
.profile input {
|
|
39
|
+
display: block;
|
|
40
|
+
width: 100%;
|
|
41
|
+
box-sizing: border-box;
|
|
42
|
+
}
|
|
43
|
+
.tabs button[aria-current="true"] {
|
|
44
|
+
font-weight: 700;
|
|
45
|
+
}
|
|
46
|
+
.feed {
|
|
47
|
+
list-style: none;
|
|
48
|
+
padding: 0;
|
|
49
|
+
}
|
|
50
|
+
.feed li + li {
|
|
51
|
+
margin-top: 0.5rem;
|
|
52
|
+
}
|
|
53
|
+
.feed button {
|
|
54
|
+
width: 100%;
|
|
55
|
+
padding: 0.75rem;
|
|
56
|
+
text-align: left;
|
|
57
|
+
}
|
|
58
|
+
.draft label,
|
|
59
|
+
.draft input,
|
|
60
|
+
.draft textarea {
|
|
61
|
+
display: block;
|
|
62
|
+
width: 100%;
|
|
63
|
+
box-sizing: border-box;
|
|
64
|
+
}
|
|
65
|
+
.draft input,
|
|
66
|
+
.draft textarea {
|
|
67
|
+
margin-top: 0.5rem;
|
|
68
|
+
padding: 0.5rem;
|
|
69
|
+
}
|
|
70
|
+
.locale-info {
|
|
71
|
+
margin-top: 2rem;
|
|
72
|
+
color: #555;
|
|
73
|
+
font-size: 0.875rem;
|
|
74
|
+
}
|
|
@@ -4,19 +4,24 @@
|
|
|
4
4
|
"target": "ESNext",
|
|
5
5
|
"module": "ESNext",
|
|
6
6
|
"moduleResolution": "bundler",
|
|
7
|
+
"allowImportingTsExtensions": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"declarationMap": true,
|
|
10
|
+
"sourceMap": true,
|
|
7
11
|
"esModuleInterop": true,
|
|
8
12
|
"forceConsistentCasingInFileNames": true,
|
|
9
13
|
"skipLibCheck": true,
|
|
10
14
|
"noEmit": true,
|
|
15
|
+
"rootDir": ".",
|
|
11
16
|
"paths": {
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"@finesoft/core": ["../../packages/core/src/index.ts"],
|
|
16
|
-
"@finesoft/browser": ["../../packages/browser/src/index.ts"],
|
|
17
|
-
"@finesoft/ssr": ["../../packages/ssr/src/index.ts"],
|
|
18
|
-
"@finesoft/server": ["../../packages/server/src/index.ts"]
|
|
17
|
+
"@finesoft/front": [
|
|
18
|
+
"./.finesoft/front.d.ts"
|
|
19
|
+
]
|
|
19
20
|
}
|
|
20
21
|
},
|
|
21
|
-
"include": [
|
|
22
|
+
"include": [
|
|
23
|
+
"src/**/*",
|
|
24
|
+
"env.d.ts",
|
|
25
|
+
"vite.config.ts"
|
|
26
|
+
]
|
|
22
27
|
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { finesoftFrontViteConfig } from "@finesoft/front";
|
|
2
2
|
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
|
3
|
-
import { defineConfig } from "vite-plus";
|
|
3
|
+
import { defineConfig, lazyPlugins } from "vite-plus";
|
|
4
|
+
|
|
5
|
+
const front = finesoftFrontViteConfig({
|
|
6
|
+
controllerTypes: { root: import.meta.dirname },
|
|
7
|
+
ssr: { entry: "src/ssr.ts" },
|
|
8
|
+
i18n: { messagesDir: "src/locales" },
|
|
9
|
+
});
|
|
4
10
|
|
|
5
11
|
export default defineConfig({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
finesoftFrontViteConfig({
|
|
9
|
-
ssr: { entry: "src/ssr.ts" },
|
|
10
|
-
i18n: {
|
|
11
|
-
messagesDir: "src/locales",
|
|
12
|
-
},
|
|
13
|
-
}),
|
|
14
|
-
],
|
|
12
|
+
lint: { options: { typeAware: true, typeCheck: true } },
|
|
13
|
+
plugins: lazyPlugins(() => [front, svelte()]),
|
|
15
14
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Finesoft Front full application
|
|
2
|
+
|
|
3
|
+
Home, product detail, search and About demonstrate SSR, CSR, URL actions, typed route parameters, DI, data mapping and guards. All three native frameworks have the same example.
|
|
4
|
+
|
|
5
|
+
## Run
|
|
6
|
+
|
|
7
|
+
Node must satisfy `^22.18.0 || >=24.11.0`. Use Vite+ for tooling.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
vp install
|
|
11
|
+
vp run dev
|
|
12
|
+
vp run build
|
|
13
|
+
vp run preview
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Visit `/`, open a product, try `/search?q=Vite`, and navigate to `/about` (CSR). `/admin` demonstrates an authentication guard: without an `auth_token` cookie it redirects to `/login?from=...`. Add your real login page and authentication service when adopting this example.
|
|
17
|
+
|
|
18
|
+
## Server controller example
|
|
19
|
+
|
|
20
|
+
`src/lib/controllers/product-detail.ts` extends `BaseServerController` from `@finesoft/front`. Open `/products/1`, then navigate to another product. The controller reads the incoming `last_product` cookie and explicitly writes a new HttpOnly cookie when the product changes. It also sets `Cache-Control: private, no-store`. This is an application-owned browsing preference; the framework provides request/response tools and does not manage login state or automatically forward credentials.
|
|
21
|
+
|
|
22
|
+
SSR executes the controller directly. Browser navigation invokes it on the server, and hydration reuses the SSR result. Its implementation stays out of the browser bundle; `markPublic` selects the page data sent to the browser. Home, search and About keep ordinary `BaseController` examples. Route types still come from `src/app-definition.ts`; the Vite plugin maintains the generated `Input` / `Failure` types and provides the server context automatically.
|
|
23
|
+
|
|
24
|
+
Keep the configured SSR entry and deploy to a request host such as Node or Worker. Serving only `dist/client` cannot execute this controller. The template runs locally without external API credentials.
|
|
25
|
+
|
|
26
|
+
## Edit
|
|
27
|
+
|
|
28
|
+
- `src/config.ts`: application identity and persistence namespace.
|
|
29
|
+
- `src/app-definition.ts`: pages, routes, guards and navigation.
|
|
30
|
+
- `src/lib/controllers/` and `src/lib/models/`: page loading and typed public data.
|
|
31
|
+
- `src/pages/`, `src/App.*`, `src/styles.css`: native UI and presentation.
|
|
32
|
+
- `src/views.ts`: the view registry shared by browser and server.
|
|
33
|
+
- `src/main.ts` and `src/ssr.ts`: standard browser and SSR startup.
|
|
34
|
+
|
|
35
|
+
All framework APIs import from `@finesoft/front`. Select the native component with `Outlet("react")`, `Outlet("vue")`, or `Outlet("svelte")`, and pass the same renderer to `useSnapshot`. The plugin maintains `.finesoft/front.d.ts` and the exact TypeScript import mapping; install only the chosen renderer. For standalone TypeScript checks, first run `vp exec finesoft-types`. The project is standalone and requires no sibling templates or private runtime packages.
|
package/templates/vue/index.html
CHANGED
|
@@ -8,12 +8,16 @@
|
|
|
8
8
|
"preview": "vp preview"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@finesoft/front": "^0.5.
|
|
12
|
-
"vue": "^3.5.
|
|
11
|
+
"@finesoft/front": "^0.5.2",
|
|
12
|
+
"vue": "^3.5.41"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"@
|
|
16
|
-
"
|
|
17
|
-
"
|
|
15
|
+
"@hono/node-server": "^2.1.0",
|
|
16
|
+
"@types/node": "^22.20.1",
|
|
17
|
+
"@vitejs/plugin-vue": "^6.0.8",
|
|
18
|
+
"hono": "^4.13.1",
|
|
19
|
+
"typescript": "^6.0.3",
|
|
20
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@0.2.8",
|
|
21
|
+
"vite-plus": "0.2.8"
|
|
18
22
|
}
|
|
19
23
|
}
|
|
@@ -1,29 +1,14 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type
|
|
3
|
-
import { computed } from "vue";
|
|
2
|
+
import { Outlet as selectOutlet, useSnapshot, type WebAppView } from "@finesoft/front";
|
|
4
3
|
import Layout from "./components/Layout.vue";
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// state: reactive object for CSR; page: initial prop for SSR
|
|
10
|
-
const {
|
|
11
|
-
state,
|
|
12
|
-
page: ssrPage,
|
|
13
|
-
onAction,
|
|
14
|
-
} = defineProps<{
|
|
15
|
-
state?: { page: AppPage | null; loading: boolean };
|
|
16
|
-
page?: AppPage;
|
|
17
|
-
onAction?: (action: Action) => void;
|
|
18
|
-
}>();
|
|
19
|
-
|
|
20
|
-
const currentPage = computed(() => state?.page ?? ssrPage ?? null);
|
|
21
|
-
const loading = computed(() => state?.loading ?? false);
|
|
4
|
+
import { views } from "./views";
|
|
5
|
+
const Outlet = selectOutlet("vue");
|
|
6
|
+
const { app } = defineProps<{ app: WebAppView }>();
|
|
7
|
+
const snapshot = useSnapshot("vue", app);
|
|
22
8
|
</script>
|
|
23
9
|
|
|
24
10
|
<template>
|
|
25
|
-
<Layout :current-path="
|
|
26
|
-
<
|
|
27
|
-
<PageRenderer v-else-if="currentPage" :page="currentPage" :on-action="onAction" />
|
|
11
|
+
<Layout :current-path="snapshot.entries.find((entry) => entry.visible)?.page.url ?? '/'">
|
|
12
|
+
<Outlet :app="app" :views="views" />
|
|
28
13
|
</Layout>
|
|
29
14
|
</template>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { appId } from "./config";
|
|
2
|
+
import { definePage, defineWebApp, int, str, withDefault } from "@finesoft/front";
|
|
3
|
+
import { AboutController } from "./lib/controllers/about";
|
|
4
|
+
import { HomeController } from "./lib/controllers/home";
|
|
5
|
+
import { ProductDetailController } from "./lib/controllers/product-detail";
|
|
6
|
+
import { SearchController } from "./lib/controllers/search";
|
|
7
|
+
import { authGuard } from "./lib/guards/auth";
|
|
8
|
+
import { seoGuard } from "./lib/guards/seo";
|
|
9
|
+
import { getErrorPage } from "./lib/controllers/error";
|
|
10
|
+
export const homePage = definePage({
|
|
11
|
+
id: "home",
|
|
12
|
+
create: () => new HomeController(),
|
|
13
|
+
routes: ["/", { path: "/admin", beforeLoad: [authGuard] }],
|
|
14
|
+
});
|
|
15
|
+
export const productDetailPage = definePage({
|
|
16
|
+
id: "product-detail",
|
|
17
|
+
routes: [{ path: "/products/:id", params: { id: int() } }],
|
|
18
|
+
create: () => new ProductDetailController(),
|
|
19
|
+
});
|
|
20
|
+
export const searchPage = definePage({
|
|
21
|
+
id: "search",
|
|
22
|
+
create: () => new SearchController(),
|
|
23
|
+
routes: [{ path: "/search", query: { q: withDefault(str(), "") } }],
|
|
24
|
+
});
|
|
25
|
+
export const aboutPage = definePage({
|
|
26
|
+
id: "about",
|
|
27
|
+
create: () => new AboutController(),
|
|
28
|
+
routes: [{ path: "/about", renderMode: "csr" }],
|
|
29
|
+
});
|
|
30
|
+
export const app = defineWebApp({
|
|
31
|
+
id: appId,
|
|
32
|
+
pages: [homePage, productDetailPage, searchPage, aboutPage],
|
|
33
|
+
getErrorPage,
|
|
34
|
+
afterLoad: [seoGuard],
|
|
35
|
+
});
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { Action } from "@finesoft/front";
|
|
3
2
|
import Navigation from "./Navigation.vue";
|
|
4
3
|
|
|
5
|
-
const { currentPath = "/"
|
|
4
|
+
const { currentPath = "/" } = defineProps<{
|
|
6
5
|
currentPath?: string;
|
|
7
|
-
onAction?: (action: Action) => void;
|
|
8
6
|
}>();
|
|
9
7
|
</script>
|
|
10
8
|
|
|
11
9
|
<template>
|
|
12
|
-
<div>
|
|
13
|
-
<Navigation :current-path="currentPath"
|
|
14
|
-
<main
|
|
10
|
+
<div class="app-shell">
|
|
11
|
+
<Navigation :current-path="currentPath" />
|
|
12
|
+
<main class="app-main">
|
|
15
13
|
<slot />
|
|
16
14
|
</main>
|
|
17
15
|
</div>
|
|
@@ -1,58 +1,25 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const { currentPath = "/", onAction } = defineProps<{
|
|
2
|
+
const { currentPath = "/" } = defineProps<{
|
|
5
3
|
currentPath?: string;
|
|
6
|
-
onAction?: (action: Action) => void;
|
|
7
4
|
}>();
|
|
8
|
-
|
|
9
5
|
const links = [
|
|
10
|
-
{ label: "Home",
|
|
11
|
-
{ label: "Search",
|
|
12
|
-
{ label: "About",
|
|
13
|
-
{ label: "GitHub",
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
function handleClick(action: Action) {
|
|
17
|
-
return (e: MouseEvent) => {
|
|
18
|
-
if (onAction) {
|
|
19
|
-
e.preventDefault();
|
|
20
|
-
onAction(action);
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
}
|
|
6
|
+
{ label: "Home", href: "/" },
|
|
7
|
+
{ label: "Search", href: "/search" },
|
|
8
|
+
{ label: "About", href: "/about" },
|
|
9
|
+
{ label: "GitHub", href: "https://github.com/nicepkg/finesoft" },
|
|
10
|
+
] as const;
|
|
24
11
|
</script>
|
|
25
12
|
|
|
26
13
|
<template>
|
|
27
|
-
<nav>
|
|
14
|
+
<nav class="navigation">
|
|
28
15
|
<a
|
|
29
16
|
v-for="link in links"
|
|
30
17
|
:key="link.label"
|
|
31
|
-
:href="link.
|
|
32
|
-
|
|
33
|
-
|
|
18
|
+
:href="link.href"
|
|
19
|
+
class="navigation-link"
|
|
20
|
+
:class="{ active: currentPath === link.href }"
|
|
34
21
|
>
|
|
35
22
|
{{ link.label }}
|
|
36
23
|
</a>
|
|
37
24
|
</nav>
|
|
38
25
|
</template>
|
|
39
|
-
|
|
40
|
-
<style scoped>
|
|
41
|
-
nav {
|
|
42
|
-
display: flex;
|
|
43
|
-
gap: 1rem;
|
|
44
|
-
padding: 1rem;
|
|
45
|
-
border-bottom: 1px solid #eee;
|
|
46
|
-
}
|
|
47
|
-
a {
|
|
48
|
-
text-decoration: none;
|
|
49
|
-
color: #333;
|
|
50
|
-
}
|
|
51
|
-
a:hover {
|
|
52
|
-
color: #007bff;
|
|
53
|
-
}
|
|
54
|
-
a.active {
|
|
55
|
-
color: #007bff;
|
|
56
|
-
font-weight: bold;
|
|
57
|
-
}
|
|
58
|
-
</style>
|
|
@@ -1,39 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { Action } from "@finesoft/front";
|
|
3
2
|
import type { ProductItem } from "../lib/models/product";
|
|
4
3
|
|
|
5
|
-
const { item
|
|
4
|
+
const { item } = defineProps<{
|
|
6
5
|
item: ProductItem;
|
|
7
|
-
onAction?: (action: Action) => void;
|
|
8
6
|
}>();
|
|
9
|
-
|
|
10
|
-
function handleClick(e: MouseEvent) {
|
|
11
|
-
if (onAction && item.clickAction) {
|
|
12
|
-
e.preventDefault();
|
|
13
|
-
onAction(item.clickAction);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
7
|
</script>
|
|
17
8
|
|
|
18
9
|
<template>
|
|
19
|
-
<
|
|
10
|
+
<article class="product-card">
|
|
20
11
|
<h3>{{ item.name }}</h3>
|
|
21
12
|
<p class="price">${{ item.price.toFixed(2) }}</p>
|
|
22
|
-
<a v-if="item.clickAction" :href="item.clickAction.url"
|
|
23
|
-
|
|
24
|
-
</a>
|
|
25
|
-
</div>
|
|
13
|
+
<a v-if="item.clickAction" :href="item.clickAction.url"> View Details → </a>
|
|
14
|
+
</article>
|
|
26
15
|
</template>
|
|
27
|
-
|
|
28
|
-
<style scoped>
|
|
29
|
-
.product-card {
|
|
30
|
-
border: 1px solid #eee;
|
|
31
|
-
border-radius: 8px;
|
|
32
|
-
padding: 1rem;
|
|
33
|
-
min-width: 200px;
|
|
34
|
-
}
|
|
35
|
-
.price {
|
|
36
|
-
color: #007bff;
|
|
37
|
-
font-weight: bold;
|
|
38
|
-
}
|
|
39
|
-
</style>
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
import { BaseController } from "@finesoft/front";
|
|
1
|
+
import { BaseController, type ControllerInput } from "@finesoft/front";
|
|
2
|
+
import { markPublic } from "@finesoft/front";
|
|
2
3
|
import type { AboutPage } from "../models/product";
|
|
3
4
|
|
|
4
|
-
export class AboutController extends BaseController<
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
export class AboutController extends BaseController<
|
|
6
|
+
ControllerInput<Record<string, string>>,
|
|
7
|
+
AboutPage
|
|
8
|
+
> {
|
|
7
9
|
execute(): AboutPage {
|
|
8
|
-
return
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
return markPublic(
|
|
11
|
+
{
|
|
12
|
+
id: "about",
|
|
13
|
+
pageType: "about",
|
|
14
|
+
title: "About",
|
|
15
|
+
description: "About Finesoft Front",
|
|
16
|
+
url: "/about",
|
|
17
|
+
content:
|
|
18
|
+
"Finesoft Front is a full-stack TypeScript framework with router, DI, middleware, SSR, and multi-platform deployment.",
|
|
19
|
+
},
|
|
20
|
+
["content"],
|
|
21
|
+
);
|
|
17
22
|
}
|
|
18
23
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import { markPublic } from "@finesoft/front";
|
|
1
2
|
import type { ErrorPage } from "../models/product";
|
|
2
3
|
|
|
3
4
|
export function getErrorPage(status: number, message: string): ErrorPage {
|
|
4
|
-
return
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
return markPublic(
|
|
6
|
+
{
|
|
7
|
+
id: "error",
|
|
8
|
+
pageType: "error",
|
|
9
|
+
title: `Error ${status}`,
|
|
10
|
+
description: message,
|
|
11
|
+
status,
|
|
12
|
+
},
|
|
13
|
+
["status"],
|
|
14
|
+
);
|
|
11
15
|
}
|