@lingfan-engine/create 0.3.3 → 0.3.5
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/bin/create.mjs +7 -3
- package/package.json +1 -1
- package/src/cli/answers.ts +275 -0
- package/src/cli/cli.ts +93 -194
- package/src/cli/index.ts +29 -0
- package/src/cli/install.ts +60 -0
- package/src/cli/prompts.ts +230 -31
- package/src/cli/stage.ts +113 -0
- package/src/generate/index.ts +7 -2
- package/src/generate/project.ts +99 -34
- package/src/overlay/index.ts +1 -0
- package/src/overlay/merge.ts +27 -1
- package/src/registry/index.ts +4 -0
- package/src/registry/template-meta.ts +73 -0
- package/src/shared/guards.ts +14 -0
- package/src/shared/index.ts +10 -1
- package/src/shared/package-manager.ts +87 -0
- package/src/shared/texts.ts +45 -17
- package/template/README.md +59 -32
- package/template/__PROJECT__/gitignore +2 -0
- package/template/__PROJECT__/package.json +2 -1
- package/template/__PROJECT__/package.patch.json +5 -0
- package/template/__PROJECT__/src/host/index.ts +1 -0
- package/template/__PROJECT__/src/host/native-ports.ts +56 -0
- package/template/__PROJECT__/src/main.ts +5 -2
- package/template/overlays/README.md +6 -0
- package/template/overlays/react/src/main.tsx +3 -0
- package/template/overlays/story/Resources/Stories/{title/title_main.story → manual/manual_intro.story} +15 -10
- package/template/overlays/story/package.patch.json +1 -17
- package/template/overlays/tauri/README.md +6 -5
- package/template/overlays/tauri/package.patch.json +1 -4
- package/template/overlays/vue/src/main.ts +3 -0
- package/template/template-meta.json +16 -12
- package/template/overlays/tauri/src/main.ts +0 -101
package/template/README.md
CHANGED
|
@@ -41,49 +41,75 @@ npm create @lingfan-engine my-story
|
|
|
41
41
|
pnpm create @lingfan-engine my-story
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
壳可选「浏览器」「小程序(UniApp)」与「桌面(Tauri 2)」。全部取缺省值(原生 + TypeScript 源 + 浏览器)可以跳过问答:
|
|
44
|
+
生成器会问三件事:工程名、描述、形态。工程名写在命令行里就跳过这一问;描述可以留空,
|
|
45
|
+
填了会同时写进 `package.json` 的 `description` 与 `Resources/project.json` 的 `description`。
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
形态是一次多选,按编号回答,空格或逗号分隔:
|
|
48
|
+
|
|
49
|
+
| 编号 | 形态 | 选中后得到什么 |
|
|
50
|
+
| ---- | ---------------- | ---------------------------------------------------------------- |
|
|
51
|
+
| 1 | Vue 3 | 页面结构换成单文件组件,装配与宿主不变 |
|
|
52
|
+
| 2 | React | 页面结构换成 JSX 组件,装配与宿主不变 |
|
|
53
|
+
| 3 | 桌面(Tauri 2) | 在同一份工程上再加一个桌面壳:存档落到应用数据目录,页面结构不变 |
|
|
54
|
+
| 4 | 小程序(UniApp) | 页面层换成 UniApp Vue 3 单文件组件,宿主装配走小程序端口 |
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
Vue 3 与 React 互斥(页面结构只能有一个所有者);小程序自带 Vue 页面层,与 Vue 3、React、
|
|
57
|
+
桌面壳都互斥;桌面壳与 Vue 3 或 React 组合都可以。
|
|
58
|
+
回车全跳过即骨架原生形态:框架无关 + 浏览器。
|
|
59
|
+
|
|
60
|
+
生成后按提示进目录、装依赖、跑起来。生成器不指定包管理器——它按启动自己的那个(npm / pnpm / yarn / bun)给出命令,用哪个都行。骨架自带编译好的故事产物,开箱即跑;
|
|
53
61
|
改过 `src/Stories.src/` 之后才需要跑一次 `pnpm stories:build`(或在开发时挂 `pnpm stories:watch`)。
|
|
62
|
+
生成器默认不装依赖,想一步到位加 `--install`。
|
|
54
63
|
|
|
55
|
-
|
|
64
|
+
脚本里不想应答问答时,用参数跳过:
|
|
56
65
|
|
|
57
66
|
```
|
|
58
|
-
npm create @lingfan-engine my-story -- --framework vue --shell
|
|
59
|
-
npm create @lingfan-engine my-story --
|
|
60
|
-
npm create @lingfan-engine my-story -- --shell miniprogram
|
|
61
|
-
npm create @lingfan-engine my-story -- --framework vue --shell tauri
|
|
62
|
-
npm create @lingfan-engine my-story -- --sources story
|
|
67
|
+
npm create @lingfan-engine my-story --description "试菜单的小工程" --framework vue --shell tauri
|
|
68
|
+
npm create @lingfan-engine my-story --yes
|
|
63
69
|
```
|
|
64
70
|
|
|
71
|
+
`--framework` 取 `none` / `vue` / `react`,`--shell` 取 `browser` / `tauri` / `miniprogram`。
|
|
72
|
+
`--sources` 取 `ts` / `story`:`story` 额外附一份手写 `.story` 示例,不改变骨架里 TS 创作链的去留。
|
|
73
|
+
`--template 框架[-壳[-故事源]]` 是等价写法,例如 `--template vue-tauri`;形态选择以问答为准,这个参数留给既有脚本。
|
|
74
|
+
(npm 会先解析自己的同名开关,用 npm 时要在选项前加 `--`,如 `npm create @lingfan-engine my-story -- --yes`。)
|
|
75
|
+
目标目录非空时生成器会问怎么处置(保留现有文件 / 清空 / 取消),`--overwrite` 是「清空」的非交互写法。
|
|
76
|
+
|
|
65
77
|
**框架层只换页面结构与入口,`src/host/**`(装配、订阅、帧循环、输入映射)一行不改,
|
|
66
78
|
样式表仍是同一份 `src/styles/stage.css`**,所以换框架不影响游玩界面的外观口径,
|
|
67
79
|
也不影响引擎与适配器——它们不认识任何 UI 框架。选 Vue 3 是 `index.html` 换成挂载点、
|
|
68
80
|
加 `src/App.vue` 与 Vue 版组合根;选 React 是同一套做法换成 `src/App.tsx` 与 React 版组合根,
|
|
69
81
|
外加 `tsconfig.json` 的 `jsx: react-jsx`。
|
|
70
82
|
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
**壳层分两种**:桌面壳只补一层桌面外壳,页面结构与宿主原样保留,所以能叠在 Vue 3 或 React 之上;
|
|
84
|
+
小程序壳换掉整套页面与宿主,自带 Vue 页面层,因此不与其他框架层叠加。
|
|
85
|
+
|
|
86
|
+
**故事怎么写与形态那一问无关**:三种创作形态落在同一棵 `Resources/Stories/` 树上,构建期一起被扫描,
|
|
87
|
+
可以混装在一个工程里,谁也不排斥谁——
|
|
88
|
+
|
|
89
|
+
| 创作形态 | 写在哪 | 改完怎么生效 |
|
|
90
|
+
| ------------- | ------------------------------ | --------------------------------------------------- |
|
|
91
|
+
| TS 源 | `src/Stories.src/**/*.ts` | 跑 `pnpm stories:build`(开发时挂 `stories:watch`) |
|
|
92
|
+
| 手写 `.story` | `Resources/Stories/**/*.story` | 直接改文本,保存即生效 |
|
|
93
|
+
| 手写 `.json` | `Resources/Stories/**/*.json` | 直接改 JSON,保存即生效 |
|
|
94
|
+
|
|
95
|
+
**TS 源**那一条链骨架自带:改 `src/Stories.src/**/*.ts`,跑 `pnpm stories:build` 编译成
|
|
96
|
+
`Resources/Stories/**/*.json`;类型检查走独立的 `tsconfig.stories.json`(故事源有 ambient 全局,
|
|
97
|
+
不该和页面 DOM 类型面混在一个工程里)。骨架自带三条脚本(`stories:build` / `stories:watch` /
|
|
98
|
+
`stories-typecheck`)与开箱可跑的产物,`pnpm dev` 不必先构建。
|
|
99
|
+
|
|
100
|
+
**手写示例**由 `--sources story` 额外交付:`overlays/story/` 只往工程里加一份
|
|
101
|
+
`Resources/Stories/manual/manual_intro.story`(列 id 全在 `manual_` 命名空间,与骨架自带的两列不撞名),
|
|
102
|
+
不删任何骨架文件,TS 创作链原样保留。想改就改那份 `.story`,想回 TS 就往 `src/Stories.src/` 加列,两边互不影响。
|
|
73
103
|
|
|
74
|
-
|
|
75
|
-
改 `src/Stories.src/**/*.ts`,跑 `pnpm stories:build` 编译成 `Resources/Stories/**/*.json`。产物目录由构建
|
|
76
|
-
**全量管理**——源里没有的列构建会当陈旧列删掉,所以别在产物目录里手改;类型检查走独立的
|
|
77
|
-
`tsconfig.stories.json`(故事源有 ambient 全局,不该和页面 DOM 类型面混在一个工程里)。骨架自带三条脚本
|
|
78
|
-
(`stories:build` / `stories:watch` / `stories-typecheck`)与开箱可跑的产物,`pnpm dev` 不必先构建。
|
|
104
|
+
三条注意:
|
|
79
105
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
106
|
+
- 列 `id` 全工程唯一:不同文件里的列只要 id 不撞就能共存,撞了构建会报出冲突的两个来源。
|
|
107
|
+
- 产物目录 `Resources/Stories/**` 里由构建写出的 `.json` 由构建自己维护:它只清理上一轮写过、这一轮不再产出的文件,
|
|
108
|
+
手写的 `.story` 与 `.json` 它不碰。改 TS 源比改它生成的 `.json` 可靠。
|
|
109
|
+
- `type=ui` 的列不进历史、不进存档、不建检查点——它是界面,不是剧情。
|
|
83
110
|
|
|
84
|
-
|
|
111
|
+
**小程序壳换的是一整套页面与宿主**:小程序形态的页面结构由 `src/components/**` 承接,宿主装配在
|
|
85
112
|
`src/mp/**`(`uni.*` 只在 `src/mp/platform.ts` 出现一处),内容不进包体、走工程接口按基址取。
|
|
86
|
-
因此它是壳而不是框架,与 `--framework` 不叠加:小程序已自带 Vue 页面层,框架取缺省值即可。
|
|
87
113
|
它的 `vite` 与 `vue` 是一组钉住的版本(UniApp 插件对这些版本有硬约束),由
|
|
88
114
|
`package.patch.json` 的 `create.pinned` 保住,不会被改写成「跟最新」。
|
|
89
115
|
|
|
@@ -98,8 +124,8 @@ npm create @lingfan-engine my-story -- --sources story
|
|
|
98
124
|
模板依赖三个引擎包:`@lingfan-engine/core`(核心)、`@lingfan-engine/adapters`(预设适配器)、`@lingfan-engine/ui`(参考展示层)。
|
|
99
125
|
它们以 **TS 源码出口**(`exports: "./src/index.ts"`)发布——消费方由 Vite/TS 直接编译,**无中间构建步骤**,
|
|
100
126
|
`pnpm install` 直接可用。骨架还声明两个创作期依赖(`@lingfan-engine/stories-build` 与
|
|
101
|
-
`@lingfan-engine/editor
|
|
102
|
-
`
|
|
127
|
+
`@lingfan-engine/editor`)——TS 创作链归骨架,任何形态的生成结果都带着它;
|
|
128
|
+
`--sources story` 只是额外多一份手写示例,这两条依赖照旧保留。
|
|
103
129
|
|
|
104
130
|
## 工程结构(单资源根)
|
|
105
131
|
|
|
@@ -127,7 +153,7 @@ __PROJECT__/
|
|
|
127
153
|
├── src/styles/stage.css # 页面与层样式(换框架不动这份:叠加层共用同一张表)
|
|
128
154
|
└── Resources/ # 故事工程根(自包含、可整体搬运)
|
|
129
155
|
├── project.json # 工程清单:id/name/entry/lang/formatVersion
|
|
130
|
-
├── Stories/ #
|
|
156
|
+
├── Stories/ # 故事(构建写的 .json 与手写的 .story/.json 可混存)
|
|
131
157
|
├── Audio/ Images/ Video/
|
|
132
158
|
├── Lang/{lang}/ # I18N overlay
|
|
133
159
|
└── Saves/ # 运行期产物(不入 git)
|
|
@@ -179,11 +205,12 @@ pwsh -File pack-template.ps1 -SourceDir ./__PROJECT__ -OutputFile ./__PROJECT__.
|
|
|
179
205
|
|
|
180
206
|
## 叠加层与占位符
|
|
181
207
|
|
|
182
|
-
`template-meta.json` 的 `frameworks` / `sources` / `shells`
|
|
208
|
+
`template-meta.json` 的 `frameworks` / `sources` / `shells` 声明模板有哪些层,每项的 `id` 对应
|
|
183
209
|
`overlays/<id>/` 目录:同路径文件**覆盖**骨架,根目录的 `*.patch.json` **深合并**进 `package.json`,
|
|
184
|
-
其中 `create.remove`
|
|
185
|
-
|
|
186
|
-
|
|
210
|
+
其中 `create.remove` 列出本层要删掉的骨架路径(换形态时清残留)。`frameworks` / `shells` 决定生成器会问哪些选项;
|
|
211
|
+
`sources` 不参与形态问答——TS 源、手写 `.story`、手写 `.json` 在同一棵树上共存,它只表示要不要额外附一份手写示例。
|
|
212
|
+
缺省项(`none` / `ts` / `browser`)没有目录,表示「这一层什么都不加」,不是错误——缺省故事源 `ts` 正是如此:
|
|
213
|
+
整条创作链在骨架里,没有叠加层可加。`story` 有目录,但只为多放一份示例文件,不删骨架的任何东西。合并顺序固定为「骨架 → 框架 → 故事源 → 壳」:
|
|
187
214
|
故事源要晚于框架层才能把各框架入口的故事表指向生成物;壳放最后,因为它对工程形态有最终发言权。
|
|
188
215
|
|
|
189
216
|
骨架里的占位符有两种大小写形态:`__PROJECT__`(清单 id/name、页面标题、目录名)与
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"@lingfan-engine/core": "^0.3.2",
|
|
18
18
|
"@lingfan-engine/shell": "^0.3.2",
|
|
19
19
|
"@lingfan-engine/stories-build": "^0.3.2",
|
|
20
|
-
"@lingfan-engine/ui": "^0.3.2"
|
|
20
|
+
"@lingfan-engine/ui": "^0.3.2",
|
|
21
|
+
"@tauri-apps/api": "^2"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"@lingfan-engine/editor": "^0.3.2",
|
|
@@ -39,6 +39,7 @@ export {
|
|
|
39
39
|
type NativeUiTable,
|
|
40
40
|
} from "./mount/index";
|
|
41
41
|
export { wireEngine, type HostWiringOptions } from "./engine-wiring";
|
|
42
|
+
export { createNativePorts, isTauriHosted } from "./native-ports";
|
|
42
43
|
export { startHostFrameLoop, type HostFrameLoopOptions } from "./frame-loop";
|
|
43
44
|
export { wireInput, type HostInputOptions } from "./input-map";
|
|
44
45
|
export {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 原生宿主端口:让同一份工程既能跑在浏览器里,也能被桌面壳加载。
|
|
3
|
+
*
|
|
4
|
+
* 为什么判定放在这里而不是壳层:`tauri` 叠加层只补 `src-tauri/` 与清单补丁,
|
|
5
|
+
* 页面结构与组合根永远归属框架层。若壳层自己带一份组合根,`vue`/`react` 形态的
|
|
6
|
+
* 页面节点就不再与那份组合根同源。所以「是否需要原生端口」由运行时事实判定,
|
|
7
|
+
* 各形态的组合根都调同一个函数。
|
|
8
|
+
*
|
|
9
|
+
* 为什么用运行时判定:`tauri dev` 时页面可能被外部浏览器打开,构建期开关会判错。
|
|
10
|
+
* `__TAURI_INTERNALS__` 只由真实 WebView 注入,是此刻唯一可靠的事实。
|
|
11
|
+
*
|
|
12
|
+
* 为什么模块名写在这里:打包器按字面量说明符静态解析(所以本工程必须声明
|
|
13
|
+
* `@tauri-apps/api`),而适配器包不出现该模块名,浏览器与小程序形态才能在
|
|
14
|
+
* 没有它时也装得上。非壳形态直接返回 `undefined`,动态导入一次都不会发生。
|
|
15
|
+
*/
|
|
16
|
+
import {
|
|
17
|
+
createTauriSavePort,
|
|
18
|
+
type TauriInvoke,
|
|
19
|
+
} from "@lingfan-engine/adapters";
|
|
20
|
+
import type { BootPortOverrides } from "./boot";
|
|
21
|
+
|
|
22
|
+
/** 当前页面是否由桌面壳加载(只有真实 WebView 会注入 `__TAURI_INTERNALS__`) */
|
|
23
|
+
export function isTauriHosted(): boolean {
|
|
24
|
+
return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 惰性 invoke:首次真正调用存档命令时才装载 `@tauri-apps/api/core`。
|
|
29
|
+
*
|
|
30
|
+
* 不在启动路径上 await 是刻意的——存档端口是同步构造的,而装载要等模块解析;
|
|
31
|
+
* 把等待推迟到第一次读写,启动时序与浏览器形态完全一致。装载失败会原样抛给
|
|
32
|
+
* 调用方,由存档流程的既有错误处理呈现,不会变成静默失败。
|
|
33
|
+
*/
|
|
34
|
+
function createLazyInvoke(): TauriInvoke {
|
|
35
|
+
let loading: Promise<TauriInvoke> | undefined;
|
|
36
|
+
return async (command, args) => {
|
|
37
|
+
loading ??= import("@tauri-apps/api/core").then((module) => {
|
|
38
|
+
const loaded: TauriInvoke = (name, payload) =>
|
|
39
|
+
module.invoke(name, payload);
|
|
40
|
+
return loaded;
|
|
41
|
+
});
|
|
42
|
+
const invoke = await loading;
|
|
43
|
+
return invoke(command, args);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 按运行时事实给出端口覆盖;非桌面壳形态返回 `undefined`。
|
|
49
|
+
*
|
|
50
|
+
* 现在只换存档(`localStorage` 落在 WebView 数据目录,清缓存、换账户、重装都会
|
|
51
|
+
* 带走存档)。其余端口沿用 `bootHost` 的 Web 缺省实现,随需要再加。
|
|
52
|
+
*/
|
|
53
|
+
export function createNativePorts(): BootPortOverrides | undefined {
|
|
54
|
+
if (!isTauriHosted()) return undefined;
|
|
55
|
+
return { saves: createTauriSavePort(createLazyInvoke()) };
|
|
56
|
+
}
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 本宿主**不依赖任何 UI 框架**——这是「引擎框架无关」的活证明:渲染直接操作 DOM,
|
|
5
5
|
* 换成 Vue / React / 其他只改本文件与 index.html,引擎与适配器零改动。
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* 页面结构与这里的节点解析始终成对:桌面壳只补 `src-tauri/`,不自带组合根,
|
|
7
|
+
* 故 `-f` 选什么框架都不影响「`must` 与 index.html 同源」这条前提。
|
|
8
8
|
*
|
|
9
9
|
* 本文件只做三件事,实现都在 `host/` 里:
|
|
10
10
|
* 1. 解析页面节点(`must` 调用点集中在此,与 index.html 的 id 互锁一眼可见);
|
|
@@ -23,6 +23,7 @@ import { MANIFEST_FILE } from "@lingfan-engine/core";
|
|
|
23
23
|
import { DEFAULT_TEXT_CPS, DEFAULT_TOAST_MS } from "@lingfan-engine/ui";
|
|
24
24
|
import {
|
|
25
25
|
bootHost,
|
|
26
|
+
createNativePorts,
|
|
26
27
|
must,
|
|
27
28
|
optional,
|
|
28
29
|
reportBootFailure,
|
|
@@ -69,6 +70,8 @@ void bootHost({
|
|
|
69
70
|
stories: STORIES,
|
|
70
71
|
defaultCps: DEFAULT_TEXT_CPS,
|
|
71
72
|
toastDurationMs: DEFAULT_TOAST_MS,
|
|
73
|
+
// 桌面壳里换成原生存档端口;浏览器里返回 undefined,端口走 Web 缺省实现
|
|
74
|
+
ports: createNativePorts(),
|
|
72
75
|
// 偏好面板槽位:页面给了就装(`optional` 已消化「这层不给」的情况)
|
|
73
76
|
prefsContainer: dom.prefs,
|
|
74
77
|
// 历史面板槽位:给了就有「历史」按钮与回溯(点一条即回到那个检查点)
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
每个子目录是一层可选的工程内容,目录名就是 `template-meta.json` 里 `frameworks[]` / `sources[]` / `shells[]` 的 `id`。
|
|
4
4
|
生成器按「骨架 → 框架层 → 故事源层 → 壳层」的顺序合并,同路径文件由后一层覆盖前一层。
|
|
5
5
|
故事源层的两个 id 不对称:缺省 `ts` 没有目录(链在骨架里),可选 `story` 有目录(要摘掉那条链)。
|
|
6
|
+
有些层不能共存,这一层与谁相斥写在 `template-meta.json` 的 `excludes` 里;生成器据此推导合法形态,
|
|
7
|
+
不维护白名单。
|
|
6
8
|
|
|
7
9
|
## 缺省项没有目录
|
|
8
10
|
|
|
@@ -96,6 +98,10 @@
|
|
|
96
98
|
1. 在 `template-meta.json` 的 `frameworks`、`sources` 或 `shells` 里加一项 `{ id, label, hint }`。
|
|
97
99
|
2. 建同名目录,放入该层独有的文件与 `package.patch.json`。
|
|
98
100
|
3. 生成器不改:它对目录是缺席即跳过,不需要为新 `id` 写分支。
|
|
101
|
+
4. 这一层与别的层不能共存时,用 `excludes` 声明:写上对方层的 id,可以跨维度(框架排斥壳、
|
|
102
|
+
壳排斥框架都行)。只写单边就生效,但建议两边都写,读的人一眼能看清这对互斥。
|
|
103
|
+
尤其当这一层也要写 `src/main.*` 或 `src/App.*` 时——页面结构只能有一个所有者,
|
|
104
|
+
必须靠 `excludes` 明确谁赢,不能让合并顺序替你做这个决定。
|
|
99
105
|
|
|
100
106
|
一层只加自己真正需要的东西。能被骨架覆盖的差异(比如只换一个入口文件的 import)优先留在骨架里,
|
|
101
107
|
叠加层越多,同一处逻辑就有越多份副本要跟着改。
|
|
@@ -15,6 +15,7 @@ import { MANIFEST_FILE } from "@lingfan-engine/core";
|
|
|
15
15
|
import { DEFAULT_TEXT_CPS, DEFAULT_TOAST_MS } from "@lingfan-engine/ui";
|
|
16
16
|
import {
|
|
17
17
|
bootHost,
|
|
18
|
+
createNativePorts,
|
|
18
19
|
must,
|
|
19
20
|
optional,
|
|
20
21
|
reportBootFailure,
|
|
@@ -92,6 +93,8 @@ if (dom !== undefined) {
|
|
|
92
93
|
stories: STORIES,
|
|
93
94
|
defaultCps: DEFAULT_TEXT_CPS,
|
|
94
95
|
toastDurationMs: DEFAULT_TOAST_MS,
|
|
96
|
+
// 桌面壳里换成原生存档端口;浏览器里返回 undefined,端口走 Web 缺省实现
|
|
97
|
+
ports: createNativePorts(),
|
|
95
98
|
historyContainer: dom.history,
|
|
96
99
|
prefsContainer: dom.prefs,
|
|
97
100
|
// UI 挂载落法由 React 接管:`#mounts` 的子节点归 `UiHost` 渲染,
|
|
@@ -1,29 +1,34 @@
|
|
|
1
|
-
// __PROJECT__
|
|
1
|
+
// __PROJECT__ 的手写故事示例(文本形态 = JSON v1 的投影)
|
|
2
2
|
// 资源路径相对项目资源根 Resources/:如 bgm "Audio/main.mp3"
|
|
3
3
|
// 一个文件里可以写多列(label / scene);拆文件直接放进 Resources/Stories/ 下,构建期扫描自动收录。
|
|
4
|
+
//
|
|
5
|
+
// 这份示例与骨架自带的 `Stories/title/*.json` 并存:TS 源、手写 .story、手写 .json
|
|
6
|
+
// 是同一棵树上的三种文件形态,可以混装。列 id 全在 `manual_` 命名空间里,
|
|
7
|
+
// 因此不会与骨架已有的 `title_main` / `look_around` 撞名(列 id 全工程唯一)。
|
|
8
|
+
// 想从这里接着写,改本文件即可;要切回 TS 源,往 src/Stories.src 里加列即可,两边互不影响。
|
|
4
9
|
|
|
5
10
|
// 标题界面:`type=ui` 的列不进历史、不进存档、不建检查点——它是界面,不是剧情。
|
|
6
11
|
// 引擎对这类列只管一件事:按 op 渲染(say 出一句、menu 出一组选项),所以下面几条开箱即跑,
|
|
7
12
|
// 视图上就是标题界面本身。想换成自己的视觉有两条路:常驻舞台的组件放 src/ui/ 并用 mount 控显隐;
|
|
8
13
|
// 整屏的标题页/设置页属宿主应用,落 src/pages/ 一类宿主目录、由宿主自己切进切出。
|
|
9
|
-
scene
|
|
10
|
-
say "
|
|
11
|
-
say "
|
|
14
|
+
scene manual_intro type=ui
|
|
15
|
+
say "这一段来自 Resources/Stories/manual/manual_intro.story。"
|
|
16
|
+
say "文本故事、手写 JSON、TS 源可以同时在 Resources/Stories/ 下混装;列 id 全工程唯一即可。"
|
|
12
17
|
menu "接下来去哪里?"
|
|
13
|
-
"
|
|
14
|
-
"看舞台元素" ->
|
|
18
|
+
"看对话与输入" -> manual_talk
|
|
19
|
+
"看舞台元素" -> manual_stage
|
|
15
20
|
|
|
16
21
|
// 游戏正文第一列:进入这里才开始有历史与存档。
|
|
17
|
-
scene
|
|
22
|
+
scene manual_talk
|
|
18
23
|
input "先问一句:你叫什么名字?" store="player.name"
|
|
19
24
|
say "你好,{player.name}!"
|
|
20
25
|
say "这一列是 game 列:从这里开始,每一步都可回溯、可存档。"
|
|
21
26
|
menu "接下来去哪里?"
|
|
22
|
-
"看舞台元素" ->
|
|
23
|
-
"
|
|
27
|
+
"看舞台元素" -> manual_stage
|
|
28
|
+
"回这一列开头" -> manual_talk
|
|
24
29
|
|
|
25
30
|
// scene 列 = 声明式空间层:进入本列时整体装载,切换列时清空
|
|
26
|
-
scene
|
|
31
|
+
scene manual_stage
|
|
27
32
|
panel x=5% y=10% width=90% zindex=10
|
|
28
33
|
text "scene 列 = 声明式空间层:这些方块是元素,不是对话"
|
|
29
34
|
text "panel 是容器,子元素用更深缩进写"
|
|
@@ -1,17 +1 @@
|
|
|
1
|
-
{
|
|
2
|
-
"create": {
|
|
3
|
-
"remove": [
|
|
4
|
-
"Resources/Stories",
|
|
5
|
-
"src/Stories.src",
|
|
6
|
-
"src/gen/font.gen.d.ts",
|
|
7
|
-
"src/gen/script.gen.d.ts",
|
|
8
|
-
"src/gen/state.gen.d.ts",
|
|
9
|
-
"src/gen/stories.gen.d.ts",
|
|
10
|
-
"tsconfig.stories.json"
|
|
11
|
-
],
|
|
12
|
-
"unset": {
|
|
13
|
-
"scripts": ["stories:build", "stories:watch", "stories-typecheck"],
|
|
14
|
-
"devDependencies": ["@lingfan-engine/editor"]
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
1
|
+
{}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# 桌面壳层(Tauri 2)
|
|
2
2
|
|
|
3
3
|
这一层是**壳**,不是框架层:页面结构沿用骨架那一份,`index.html` 与 `vite.config.ts` 原样保留。
|
|
4
|
-
加进来的只有工程根下的 `src-tauri
|
|
5
|
-
|
|
4
|
+
加进来的只有工程根下的 `src-tauri/` 和一份清单补丁——**不带组合根**:入口与页面结构
|
|
5
|
+
永远归属框架层,否则 `-f vue` / `-f react` 时壳层的节点解析会与框架层渲染的页面错配。
|
|
6
|
+
桥的装配在骨架 `src/host/native-ports.ts`,由各形态的组合根统一调用。
|
|
6
7
|
|
|
7
8
|
## 内容
|
|
8
9
|
|
|
9
10
|
- `src-tauri/Cargo.toml`、`build.rs`、`tauri.conf.json`、`capabilities/default.json`
|
|
10
11
|
- `src-tauri/src/`:`main.rs`(转发到库入口)、`lib.rs`(装配与命令注册)、`save.rs`、`preferences.rs`
|
|
11
|
-
- `
|
|
12
|
-
- `package.patch.json`:补 `@tauri-apps/api`(运行时)与 `@tauri-apps/cli`(命令行),加 `tauri` 脚本
|
|
12
|
+
- `package.patch.json`:补 `@tauri-apps/cli`(命令行)与 `tauri` 脚本
|
|
13
13
|
|
|
14
14
|
窗口 1280×720,标题取工程名;`frontendDist` 指向 `../dist`,开发期 `devUrl` 指向
|
|
15
15
|
`http://localhost:1420`(与骨架 `vite.config.ts` 的 `server.port` 一致),所以 `beforeDevCommand`
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
|
|
46
46
|
资源加密、媒体 HTTP 服务、zip 索引、工程写回、开发期 WS——这些在参考宿主里有实现,
|
|
47
47
|
本层不带。要加密或媒体服务的工程自己加:能力以端口 + 适配器成对出现,
|
|
48
|
-
|
|
48
|
+
壳层只负责把端口接到原生侧(插口是 `bootHost` 的 `ports` 选项,
|
|
49
|
+
现有的原生存档端口见骨架 `src/host/native-ports.ts`)。
|
|
49
50
|
|
|
50
51
|
## 怎么用
|
|
51
52
|
|
|
@@ -2,13 +2,10 @@
|
|
|
2
2
|
"scripts": {
|
|
3
3
|
"tauri": "tauri"
|
|
4
4
|
},
|
|
5
|
-
"dependencies": {
|
|
6
|
-
"@tauri-apps/api": "^2"
|
|
7
|
-
},
|
|
8
5
|
"devDependencies": {
|
|
9
6
|
"@tauri-apps/cli": "^2"
|
|
10
7
|
},
|
|
11
8
|
"create": {
|
|
12
|
-
"pinned": ["@tauri-apps/cli"
|
|
9
|
+
"pinned": ["@tauri-apps/cli"]
|
|
13
10
|
}
|
|
14
11
|
}
|
|
@@ -13,6 +13,7 @@ import { MANIFEST_FILE } from "@lingfan-engine/core";
|
|
|
13
13
|
import { DEFAULT_TEXT_CPS, DEFAULT_TOAST_MS } from "@lingfan-engine/ui";
|
|
14
14
|
import {
|
|
15
15
|
bootHost,
|
|
16
|
+
createNativePorts,
|
|
16
17
|
must,
|
|
17
18
|
optional,
|
|
18
19
|
reportBootFailure,
|
|
@@ -76,6 +77,8 @@ if (dom !== undefined) {
|
|
|
76
77
|
stories: STORIES,
|
|
77
78
|
defaultCps: DEFAULT_TEXT_CPS,
|
|
78
79
|
toastDurationMs: DEFAULT_TOAST_MS,
|
|
80
|
+
// 桌面壳里换成原生存档端口;浏览器里返回 undefined,端口走 Web 缺省实现
|
|
81
|
+
ports: createNativePorts(),
|
|
79
82
|
prefsContainer: dom.prefs,
|
|
80
83
|
// 历史面板槽位:给了就有「历史」按钮与回溯
|
|
81
84
|
historyContainer: dom.history,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.12.0",
|
|
3
3
|
"minEngineVersion": "0.3.2",
|
|
4
4
|
"placeholder": "__PROJECT__",
|
|
5
5
|
"frameworks": [
|
|
@@ -11,12 +11,14 @@
|
|
|
11
11
|
{
|
|
12
12
|
"id": "vue",
|
|
13
13
|
"label": "Vue 3",
|
|
14
|
-
"hint": "页面结构换成单文件组件,装配与宿主不变"
|
|
14
|
+
"hint": "页面结构换成单文件组件,装配与宿主不变",
|
|
15
|
+
"excludes": ["react", "miniprogram"]
|
|
15
16
|
},
|
|
16
17
|
{
|
|
17
18
|
"id": "react",
|
|
18
19
|
"label": "React",
|
|
19
|
-
"hint": "页面结构换成 JSX 组件,装配与宿主不变"
|
|
20
|
+
"hint": "页面结构换成 JSX 组件,装配与宿主不变",
|
|
21
|
+
"excludes": ["vue", "miniprogram"]
|
|
20
22
|
}
|
|
21
23
|
],
|
|
22
24
|
"shells": [
|
|
@@ -25,15 +27,17 @@
|
|
|
25
27
|
"label": "浏览器",
|
|
26
28
|
"hint": "只生成 Web 端工程,用 vite 起本地服务"
|
|
27
29
|
},
|
|
28
|
-
{
|
|
29
|
-
"id": "miniprogram",
|
|
30
|
-
"label": "小程序(UniApp)",
|
|
31
|
-
"hint": "页面层换成 UniApp Vue 3 单文件组件,宿主装配走小程序端口"
|
|
32
|
-
},
|
|
33
30
|
{
|
|
34
31
|
"id": "tauri",
|
|
35
32
|
"label": "桌面(Tauri 2)",
|
|
36
|
-
"hint": "
|
|
33
|
+
"hint": "给同一份工程再加一个桌面壳:存档落到应用数据目录,页面结构不变",
|
|
34
|
+
"excludes": ["miniprogram"]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "miniprogram",
|
|
38
|
+
"label": "小程序(UniApp)",
|
|
39
|
+
"hint": "页面层换成 UniApp Vue 3 单文件组件,宿主装配走小程序端口",
|
|
40
|
+
"excludes": ["vue", "react", "tauri"]
|
|
37
41
|
}
|
|
38
42
|
],
|
|
39
43
|
"sources": [
|
|
@@ -44,9 +48,9 @@
|
|
|
44
48
|
},
|
|
45
49
|
{
|
|
46
50
|
"id": "story",
|
|
47
|
-
"label": "
|
|
48
|
-
"hint": "
|
|
51
|
+
"label": "附带手写故事示例",
|
|
52
|
+
"hint": "多放一份 .story 示例,与 TS 源共存,不摘任何东西"
|
|
49
53
|
}
|
|
50
54
|
],
|
|
51
|
-
"_comment": "模板版本唯一来源,独立于引擎版本:仅当 template/v1 内容真正变化时才手动 bump。placeholder 是目录/文件内的工程名占位符,生成时整体替换为工程名(对齐 _LingFanEngineTemplateTitle_ 占位符语义)。minEngineVersion 表示此模板要求的最低引擎版本——生成期会拿它跟 registry 上的实际版本比对,不达标即失败(离线查不到版本时跳过判定)。frameworks / shells
|
|
55
|
+
"_comment": "模板版本唯一来源,独立于引擎版本:仅当 template/v1 内容真正变化时才手动 bump。placeholder 是目录/文件内的工程名占位符,生成时整体替换为工程名(对齐 _LingFanEngineTemplateTitle_ 占位符语义)。minEngineVersion 表示此模板要求的最低引擎版本——生成期会拿它跟 registry 上的实际版本比对,不达标即失败(离线查不到版本时跳过判定)。frameworks / shells 决定生成器会问什么、能叠哪些层,各自首项为缺省值;每一项的 id 对应 template/v1/overlays/<id> 目录(缺省层可以没有目录——缺省的 none 与 browser 就是这样,不需要叠加层)。sources 不参与形态问答:TS 源、手写 .story、手写 .json 是同一棵 Resources/Stories/ 树上的三种文件形态,可以共存于一个工程,所以 story 层的目录只额外附一份手写示例、不摘任何骨架文件;缺省故事源 ts 同样没有目录。合并顺序为「骨架 → 框架 → 故事源 → 壳」,同路径文件由后一层覆盖前一层。excludes 声明该层与哪些层不能共存,元素是层 id、可以跨维度引用,生成器据此推导合法形态;只写单边即可生效,但成对声明能让读的人一眼看清——所以每一对互斥都在两边都写了。页面结构只能有一个所有者:两层都要写 src/main.* 或 src/App.* 时,必须在其中一方 excludes 里声明互斥,不得靠合并顺序决定谁活下来。"
|
|
52
56
|
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 组合根(桌面壳形态):与骨架只差两处——先装宿主桥,再把存档端口换成 Rust 实现。
|
|
3
|
-
*
|
|
4
|
-
* 为什么先装桥:`@lingfan-engine/adapters` 是给外部工程消费的源码包,包内不写
|
|
5
|
-
* `@tauri-apps/api` 的模块名(写了会让纯 Web 形态在构建期解析失败,写成变量说明符又会在
|
|
6
|
-
* 产物里残留裸模块名、WebView 执行时抛错)。模块名留在本文件这一层——本工程有这个依赖,
|
|
7
|
-
* 且只在壳里跑到这里;`installTauriBridge` 把两个函数交给适配器,之后各端口工厂的
|
|
8
|
-
* 缺省实现才可用。
|
|
9
|
-
*
|
|
10
|
-
* 为什么换存档:WebView 的 `localStorage` 落在系统给这个应用分配的 WebView 数据目录里,
|
|
11
|
-
* 清缓存、换系统账户、重装都会带走存档;桌面工程的存档应当是可指路径上的文件。
|
|
12
|
-
* 换法是把端口交给 `bootHost` 的 `ports.saves`,装配顺序与其余接线一行不改。
|
|
13
|
-
*
|
|
14
|
-
* 工程文件与资源**不换**:骨架的 `vite.config.ts` 把 `Resources/` 当静态根,
|
|
15
|
-
* 打包时这些文件被复制进 `dist/`,所以打包后 `/project.json`、`/Stories/**`、
|
|
16
|
-
* `/Audio/x.mp3` 仍按同一套路径取得到——桌面壳不需要为此多造一组 Rust 命令。
|
|
17
|
-
*
|
|
18
|
-
* 页面结构沿用骨架的 `index.html`(本层不带自己的),故节点解析与骨架同源。
|
|
19
|
-
*/
|
|
20
|
-
import { MANIFEST_FILE } from "@lingfan-engine/core";
|
|
21
|
-
import { DEFAULT_TEXT_CPS, DEFAULT_TOAST_MS } from "@lingfan-engine/ui";
|
|
22
|
-
import {
|
|
23
|
-
createTauriSavePort,
|
|
24
|
-
installTauriBridge,
|
|
25
|
-
} from "@lingfan-engine/adapters";
|
|
26
|
-
import {
|
|
27
|
-
bootHost,
|
|
28
|
-
must,
|
|
29
|
-
optional,
|
|
30
|
-
reportBootFailure,
|
|
31
|
-
type StageDom,
|
|
32
|
-
} from "./host";
|
|
33
|
-
import { STORIES } from "./stories";
|
|
34
|
-
import { uis } from "./mounts";
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* 工程清单与故事文件:**都在应用资源根 `Resources/` 内**。
|
|
38
|
-
* 打包后它们位于 `dist/` 顶层,路径与开发期一致。
|
|
39
|
-
* 清单名取引擎契约里的 `MANIFEST_FILE`(组装、写回、定位资源根共用同一个声明);
|
|
40
|
-
* 故事表在 `./stories`(唯一出口,各形态共用),内容由构建期扫描资源根得出。
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
|
-
/** 页面节点句柄表:每个 id 都必须存在于 index.html(缺一个 `must` 当场抛) */
|
|
44
|
-
const dom: StageDom = {
|
|
45
|
-
root: must("#stage-root"),
|
|
46
|
-
stage: must("#stage"),
|
|
47
|
-
dialogue: must("#dialogue"),
|
|
48
|
-
speaker: must("#speaker"),
|
|
49
|
-
text: must("#text"),
|
|
50
|
-
hint: must("#hint"),
|
|
51
|
-
nvl: must("#nvl"),
|
|
52
|
-
choices: must("#choices"),
|
|
53
|
-
notifications: must("#notifications"),
|
|
54
|
-
toolbar: must("#toolbar"),
|
|
55
|
-
slot: must<HTMLSelectElement>("#slot"),
|
|
56
|
-
save: must<HTMLButtonElement>("#save"),
|
|
57
|
-
load: must<HTMLButtonElement>("#load"),
|
|
58
|
-
transition: must("#transition"),
|
|
59
|
-
error: must("#error"),
|
|
60
|
-
// 历史面板槽位(可选):骨架 index.html 里有,缺了也不影响启动
|
|
61
|
-
history: optional("#history"),
|
|
62
|
-
// 偏好面板槽位(可选):骨架 index.html 里有,缺了也不影响启动
|
|
63
|
-
prefs: optional("#prefs"),
|
|
64
|
-
// UI 挂载层槽位(可选):本层不给就 null,`mount` 命令只改状态、没有组件出现
|
|
65
|
-
mounts: optional("#mounts"),
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* 装配宿主桥并启动宿主。
|
|
70
|
-
*
|
|
71
|
-
* 静态说明符(不带 `@vite-ignore`)是刻意的:打包器据此把这两个模块打进产物,
|
|
72
|
-
* 运行期不再需要解析裸模块名。
|
|
73
|
-
*/
|
|
74
|
-
async function boot(): Promise<void> {
|
|
75
|
-
const [{ invoke }, { listen }] = await Promise.all([
|
|
76
|
-
import("@tauri-apps/api/core"),
|
|
77
|
-
import("@tauri-apps/api/event"),
|
|
78
|
-
]);
|
|
79
|
-
installTauriBridge({ invoke, listen });
|
|
80
|
-
|
|
81
|
-
await bootHost({
|
|
82
|
-
dom,
|
|
83
|
-
manifestFile: MANIFEST_FILE,
|
|
84
|
-
stories: STORIES,
|
|
85
|
-
defaultCps: DEFAULT_TEXT_CPS,
|
|
86
|
-
toastDurationMs: DEFAULT_TOAST_MS,
|
|
87
|
-
historyContainer: dom.history,
|
|
88
|
-
prefsContainer: dom.prefs,
|
|
89
|
-
// UI 挂载槽位:壳层用骨架的原生渲染器(本层不引框架,`#mounts` 的子节点由宿主写)
|
|
90
|
-
mountsContainer: dom.mounts,
|
|
91
|
-
mountsTable: uis,
|
|
92
|
-
ports: {
|
|
93
|
-
// 存档落到应用数据目录的 saves/ 下,一槽一文件(加密与回档保护在 Rust 侧扩展)
|
|
94
|
-
saves: createTauriSavePort(),
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
boot().catch((error: unknown) => {
|
|
100
|
-
reportBootFailure(dom, error);
|
|
101
|
-
});
|