@vobs/cli 1.8.5 → 1.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +219 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +219 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/agent-doc.cjs +240 -0
- package/dist/commands/agent-doc.cjs.map +1 -0
- package/dist/commands/agent-doc.d.cts +13 -0
- package/dist/commands/agent-doc.d.ts +13 -0
- package/dist/commands/agent-doc.js +216 -0
- package/dist/commands/agent-doc.js.map +1 -0
- package/dist/index.cjs +219 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +219 -0
- package/dist/index.js.map +1 -1
- package/dist/start.cjs +219 -0
- package/dist/start.cjs.map +1 -1
- package/dist/start.js +219 -0
- package/dist/start.js.map +1 -1
- package/dist/templates/agent-contract.cjs +139 -0
- package/dist/templates/agent-contract.cjs.map +1 -0
- package/dist/templates/agent-contract.d.cts +14 -0
- package/dist/templates/agent-contract.d.ts +14 -0
- package/dist/templates/agent-contract.js +113 -0
- package/dist/templates/agent-contract.js.map +1 -0
- package/dist/templates/agent-contract.md +107 -0
- package/package.json +1 -1
- package/src/cli.ts +17 -1
- package/src/commands/agent-doc.test.ts +151 -0
- package/src/commands/agent-doc.ts +142 -0
- package/src/templates/agent-contract.md +107 -0
- package/src/templates/agent-contract.ts +119 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
var __VOBS_CJS_FILE_URL = require("url").pathToFileURL(__filename).href;
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// packages/cli/src/templates/agent-contract.ts
|
|
22
|
+
var agent_contract_exports = {};
|
|
23
|
+
__export(agent_contract_exports, {
|
|
24
|
+
AGENT_CONTRACT: () => AGENT_CONTRACT
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(agent_contract_exports);
|
|
27
|
+
var AGENT_CONTRACT = `# vobs \u6846\u67B6\u5951\u7EA6\uFF08\u5199\u4EE3\u7801\u524D\u5FC5\u8BFB\uFF09
|
|
28
|
+
|
|
29
|
+
vobs \u662F**\u7F16\u8BD1\u578B / \u7EC6\u7C92\u5EA6 / \u54CD\u5E94\u5F0F**\u6846\u67B6\uFF0C\u4E0E React \u7684\u5FC3\u667A\u6A21\u578B\u6709\u51E0\u5904**\u6839\u672C\u4E0D\u540C**\u3002
|
|
30
|
+
\u4E0B\u9762\u7684\u6BCF\u4E00\u6761\u90FD\u6765\u81EA\u771F\u5B9E\u4E8B\u6545\uFF0C**\u8FDD\u53CD\u65F6\u901A\u5E38\u7F16\u8BD1\u901A\u8FC7\u3001\u8FD0\u884C\u671F\u624D\u9519**\u3002
|
|
31
|
+
|
|
32
|
+
> \u672C\u6587\u6863\u7531 \`vobs agent-doc\` \u4ECE\u6846\u67B6\u7248\u672C\u751F\u6210 \u2014\u2014 \u4E0D\u8981\u624B\u6539\u751F\u6210\u7684\u5757\u3002
|
|
33
|
+
> \u6BCF\u6761\u540E\u9762\u62EC\u53F7\u91CC\u662F**\u8FDD\u53CD\u65F6\u6846\u67B6\u4F1A\u62A5\u7684\u8BCA\u65AD\u7801**\u3002
|
|
34
|
+
|
|
35
|
+
## \u4E00\u3001\u7EC4\u4EF6\u4F53\u53EA\u6267\u884C\u4E00\u6B21\uFF08run-once\uFF09
|
|
36
|
+
|
|
37
|
+
**\u7EC4\u4EF6\u51FD\u6570\u4F53\u53EA\u5728\u521B\u5EFA\u65F6\u8DD1\u4E00\u6B21**\uFF0C\u4E4B\u540E\u53EA\u6709 JSX \u91CC\u8BA2\u9605\u7684\u4F4D\u7F6E\u66F4\u65B0\u3002
|
|
38
|
+
|
|
39
|
+
- \u274C \`const filtered = list.value.filter(...)\` \u2014\u2014 \u4E00\u6B21\u5FEB\u7167\uFF0C\u4E4B\u540E\u6C38\u4E0D\u66F4\u65B0
|
|
40
|
+
- \u2705 \u6D3E\u751F\u653E\u8FDB JSX\uFF1A\`<div>{list.value.filter(...).map(render)}</div>\`
|
|
41
|
+
- \u274C \u7EC4\u4EF6\u4F53\u91CC\u53D6\u5FEB\u7167\u7ED9\u56DE\u8C03\u7528\uFF1A\`const v = name.value; onClick={() => save(v)}\`
|
|
42
|
+
- \u2705 \u56DE\u8C03\u5185\u91CD\u8BFB\uFF1A\`onClick={() => save(name.value)}\`
|
|
43
|
+
- \u52A8\u6001 props \u7528 getter \u5F62\u6001\uFF0C\u4E0D\u8981\u4F20\u6C42\u503C\u540E\u7684\u503C
|
|
44
|
+
|
|
45
|
+
## \u4E8C\u3001\u6761\u4EF6\u6E32\u67D3\uFF1A**\u4E0D\u8981\u5728\u7EC4\u4EF6\u4F53\u91CC \`return\` \u5206\u652F**\uFF08\`VOBS_C107\` / \`VOBS_C104\`\uFF09
|
|
46
|
+
|
|
47
|
+
\u7EC4\u4EF6\u4F53\u91CC\u8BFB\u4FE1\u53F7\u7684 \`return\` \u5728\u6302\u8F7D\u65F6\u56FA\u5316\uFF0C**\u4E09\u79CD\u5199\u6CD5\u574F\u5F97\u4E00\u6A21\u4E00\u6837**\uFF1A
|
|
48
|
+
|
|
49
|
+
\`\`\`tsx
|
|
50
|
+
return cond.value ? <A/> : <B/> // \u274C \u51BB\u7ED3
|
|
51
|
+
if (cond.value) return <A/> // \u274C \u51BB\u7ED3
|
|
52
|
+
return cond.value ? <A/> : null // \u274C \u51BB\u7ED3
|
|
53
|
+
\`\`\`
|
|
54
|
+
|
|
55
|
+
\u9876\u5C42 \`return\` \u5904**\u6CA1\u6709 parent/anchor**\uFF0C\u6240\u4EE5\u4E24\u652F\u90FD\u662F JSX \u4E5F\u4E00\u6837\u4E0D\u4F1A\u91CD\u5206\u652F\u3002
|
|
56
|
+
|
|
57
|
+
| \u573A\u666F | \u7528\u4EC0\u4E48 |
|
|
58
|
+
|---|---|
|
|
59
|
+
| **\u8DEF\u7531\u5206\u652F** | **\`<RouterView/>\`**\uFF08\u58F0\u660E\u5F0F\uFF0C\u5185\u90E8 \`insertDynamic\` + \`resetKey\`\uFF09 |
|
|
60
|
+
| \u4FDD\u7559\u6302\u8F7D\u3001\u53EA\u5207\u663E\u9690 | **\`<Show when={cond}>\`**\uFF08\u5207 \`hidden\` + \`inert\`\uFF0C\u7126\u70B9/\u6EDA\u52A8/\u5185\u90E8\u72B6\u6001\u4E0D\u4E22\uFF09 |
|
|
61
|
+
| \u53EA\u5207\u7C7B\u540D | \`class="base" classList={{ 'is-on': cond.value }}\` |
|
|
62
|
+
| \u666E\u901A\u6761\u4EF6\u6E32\u67D3 | \u653E\u8FDB **JSX \u5B50\u8282\u70B9\u4F4D\u7F6E**\uFF1A\`<div>{cond.value ? <A/> : <B/>}</div>\` |
|
|
63
|
+
|
|
64
|
+
## \u4E09\u3001effect \u91CC\u4E0D\u8981\u5199\u81EA\u5DF1\u8BFB\u8FC7\u7684\u4FE1\u53F7\uFF08\`VOBS_C210\` / \`VOBS_C211\`\uFF09
|
|
65
|
+
|
|
66
|
+
**\u4F9D\u8D56\u662F\u81EA\u52A8\u6536\u96C6\u7684**\uFF1Aeffect \u8FD0\u884C\u671F\u95F4\u8BFB\u5230\u7684**\u4EFB\u4F55**\u4FE1\u53F7\u90FD\u4F1A\u53D8\u6210\u4F9D\u8D56\u3002
|
|
67
|
+
|
|
68
|
+
- \u274C \`effect(() => { count.value++ })\` \u2014\u2014 \u8BFB+\u5199\u540C\u4E00\u4E2A\u4FE1\u53F7 = \u81EA\u8BA2\u9605\u5FAA\u73AF
|
|
69
|
+
- \u274C \`effect(() => { if (session.value) void sync() })\` \u2014\u2014 **\u300C\u53EA\u8C03\u4E86\u4E2A\u51FD\u6570\u300D\u4E0D\u7B49\u4E8E\u6CA1\u4F9D\u8D56**\uFF1A
|
|
70
|
+
\u88AB\u8C03\u51FD\u6570\u5728**\u9996\u4E2A \`await\` \u4E4B\u524D**\u7684\u4EE3\u7801\u662F**\u540C\u6B65\u6267\u884C**\u7684\uFF0C\u5B83\u8BFB\u7684\u4FE1\u53F7\u7B97\u5728 effect \u5934\u4E0A
|
|
71
|
+
- \u2705 \u53EA\u60F3\u58F0\u660E\u4F9D\u8D56\uFF1A**\`effect(on(deps, () => { \u2026 }))\`** \u2014\u2014 \u56DE\u8C03\u5728 untrack \u4F5C\u7528\u57DF\u91CC\u8DD1\uFF0C
|
|
72
|
+
\u5B83\u8C03\u7528\u7684\u51FD\u6570\u78B0\u4EC0\u4E48\u4FE1\u53F7\u90FD\u4E0D\u4F1A\u53CD\u5411\u8BA2\u9605
|
|
73
|
+
- \u2705 \u6D3E\u751F\u503C\u7528 \`memo\`\uFF0C\u4E0D\u8981"\u8BFB A \u5199 B"
|
|
74
|
+
- \u515C\u5E95\u624D\u662F \`untrack(() => { X.value = next })\`
|
|
75
|
+
|
|
76
|
+
**\u522B\u5728 effect \u91CC\u8C03 async \u51FD\u6570**\uFF08\`VOBS_C106\`\uFF09\uFF1A\`effect\` \u4E0D\u7B49\u5B83\uFF0C
|
|
77
|
+
\u4E14\u9996\u4E2A \`await\` \u4E4B\u524D\u7684\u90E8\u5206\u662F\u540C\u6B65\u7684\u3002\u5F02\u6B65\u53D6\u6570\u7528 **\`@vobs/resource\`**\u3002
|
|
78
|
+
|
|
79
|
+
## \u56DB\u3001\u5BA2\u6237\u7AEF\u526F\u4F5C\u7528\u4E0E SSR
|
|
80
|
+
|
|
81
|
+
- \u5B9A\u65F6\u5668 / \u76D1\u542C / \`matchMedia\` / \`localStorage\` \u2192 **\`onMount\` \u542F\u52A8\u3001\`onDestroy\` \u6E05\u7406**
|
|
82
|
+
\uFF08**\u4E0D\u8981\u624B\u5199 \`typeof window\` \u5B88\u536B**\uFF09
|
|
83
|
+
- **\u6E32\u67D3\u8F93\u51FA\u672C\u8EAB**\u4F9D\u8D56\u6D4F\u89C8\u5668\uFF08\u7A97\u53E3\u5C3A\u5BF8 / \`localStorage\` \u56DE\u586B / \`Date.now\` / \u968F\u673A\u503C\uFF09
|
|
84
|
+
\u2192 **\`<ClientOnly fallback={\u2026}>\`**\uFF08\u9996\u8F6E\u4E24\u4FA7\u90FD\u6E32\u67D3 fallback\uFF0C\u6C34\u5408\u5BF9\u5F97\u4E0A\uFF09
|
|
85
|
+
- \u6A21\u5757\u9876\u5C42**\u7981\u6B62** JSX\uFF08\`VOBS_C105\`\uFF09\uFF1Aimport \u6C42\u503C\u65E9\u4E8E\u6E32\u67D3\u5668\u5B89\u88C5
|
|
86
|
+
|
|
87
|
+
## \u4E94\u3001\u5217\u8868\u4E0E\u6570\u636E
|
|
88
|
+
|
|
89
|
+
- **\u6570\u7EC4\u66F4\u65B0\u5FC5\u987B\u6362\u5F15\u7528**\uFF1A\`list.value = [...list.value, item]\`\uFF1B
|
|
90
|
+
\u539F\u5730 \`push\` / \u6539\u5B57\u6BB5**\u4E0D\u89E6\u53D1\u66F4\u65B0**
|
|
91
|
+
- JSX **\u5B50\u8282\u70B9\u4F4D\u7F6E**\u53EA\u653E\u56DB\u79CD\u5F62\u6001\uFF1A\u7EC4\u4EF6\u6807\u7B7E / \u5143\u7D20 / \u4E24\u5206\u652F\u4E09\u5143 / \`.map()\`
|
|
92
|
+
- \u6570\u636E\u7ED3\u6784\u91CC**\u53EA\u5B58\u7EAF\u63CF\u8FF0**\uFF08\u5B57\u7B26\u4E32/\u6837\u5F0F/\u7ED3\u6784\u5B57\u6BB5\uFF09\uFF0C**\u8282\u70B9\u5BF9\u8C61\u522B\u8FDB\u6570\u636E\u5E38\u91CF**
|
|
93
|
+
\uFF08SSG \u5E8F\u5217\u5316\u4F1A\u70B8\uFF0C\u4EA7\u7269\u51FA\u73B0 \`[object Xxx]\` \u65F6\u6846\u67B6\u4F1A\u76F4\u63A5\u62A5\u9519\uFF09
|
|
94
|
+
|
|
95
|
+
## \u516D\u3001\u8F93\u5165
|
|
96
|
+
|
|
97
|
+
- **\u6570\u5B57\u8F93\u5165\u8D70 \`parseNumber\`**\uFF08\`<Field type="number">\` \u5DF2\u5185\u7F6E\uFF09\uFF1A
|
|
98
|
+
\u7A7A\u4E32 / \u975E\u6CD5 / \u8D85\u754C**\u4E0D\u63D0\u4EA4**\uFF0C\u4FDD\u6301\u539F\u503C
|
|
99
|
+
\u2014\u2014 \`Number('') === 0\` \u4F1A\u628A\u8F93\u5165\u6E05\u6210 0 \u5E76\u6CBF\u8054\u52A8\u94FE\u8DEF\u6E05\u96F6\u5144\u5F1F\u7EF4\u5EA6
|
|
100
|
+
- \`<select>\` \u7684 value \u76F4\u63A5\u7ED1\uFF0C**\u4E0D\u8981\u5199 ref \u515C\u5E95**\uFF081.5.1+ \u5DF2\u4FEE\u65F6\u5E8F\uFF09
|
|
101
|
+
|
|
102
|
+
## \u4E03\u3001\u56FE\u6807\u4E0E SVG
|
|
103
|
+
|
|
104
|
+
- **SVG \u76F4\u63A5\u5199 JSX**\uFF08\`<svg><path/></svg>\`\uFF0C1.7.4+ \u6309 namespace \u521B\u5EFA\u4E0E\u6C34\u5408\uFF09
|
|
105
|
+
- \u56FE\u6807\u8981**\u767B\u8BB0\u8FDB\u767D\u540D\u5355**\uFF1B\u67E5\u8868 miss \u65F6 \`@vobs/icon-core\` \u4F1A\u8B66\u544A\u70B9\u540D\uFF08\u4F46\u4ECD\u5E94\u767B\u8BB0\uFF09
|
|
106
|
+
|
|
107
|
+
## \u516B\u3001\u63D0\u4EA4\u524D\u81EA\u6D4B
|
|
108
|
+
|
|
109
|
+
\`\`\`bash
|
|
110
|
+
pnpm run check:source # = vobs check\uFF0C\u5168\u4ED3\u4E00\u6B21\u5217\u51FA\u5168\u90E8\u8BCA\u65AD
|
|
111
|
+
pnpm run check:runtime # \u771F\u5B9E\u6D4F\u89C8\u5668\u9010\u8DEF\u7531\u8DD1\u62A4\u680F\uFF08\u9700 Chrome\uFF09
|
|
112
|
+
pnpm run check:runtime:interact # \u518D\u70B9\u6240\u6709\u6309\u94AE\u3001\u89E6\u53D1\u6240\u6709\u8F93\u5165
|
|
113
|
+
\`\`\`
|
|
114
|
+
|
|
115
|
+
\`vite build\` **\u4F1A**\u6253\u5370\u7F16\u8BD1\u671F\u8B66\u544A\uFF08\`C104\`/\`C105\`/\`C106\`/\`C107\`\uFF09\uFF0C\u4F46\u53EA\u8986\u76D6\u5B83\u7F16\u8BD1\u5230\u7684\u6587\u4EF6\uFF1B
|
|
116
|
+
\`check:source\` \u624D\u662F\u5168\u4ED3\u5165\u53E3\u3002
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## \u4E00\u53E5\u8BDD\u901F\u8BB0
|
|
121
|
+
|
|
122
|
+
| \u4E3B\u9898 | \u4E00\u53E5\u8BDD |
|
|
123
|
+
|---|---|
|
|
124
|
+
| \u7EC4\u4EF6\u4F53 | \u53EA\u8DD1\u4E00\u6B21\uFF0C\u6D3E\u751F\u8FDB JSX |
|
|
125
|
+
| \u56DE\u8C03 | \u89E6\u53D1\u65F6\u91CD\u8BFB \`.value\` |
|
|
126
|
+
| \u6761\u4EF6\u6E32\u67D3 | \u8DEF\u7531\u7528 \`RouterView\`\uFF0C\u663E\u9690\u7528 \`Show\`\uFF0C\u522B\u5728\u7EC4\u4EF6\u4F53 return \u5206\u652F |
|
|
127
|
+
| effect | \u522B\u5199\u81EA\u5DF1\u8BFB\u7684\u4FE1\u53F7\uFF0C\u7528 \`on(deps, fn)\` |
|
|
128
|
+
| async | \u522B\u585E\u8FDB effect\uFF0C\u7528 \`@vobs/resource\` |
|
|
129
|
+
| \u5BA2\u6237\u7AEF | \`onMount\`/\`onDestroy\`/\`ClientOnly\`\uFF0C\u522B\u624B\u5199 \`typeof window\` |
|
|
130
|
+
| \u5217\u8868 | \u6362\u5F15\u7528 |
|
|
131
|
+
| \u6570\u5B57 | \`parseNumber\` |
|
|
132
|
+
| SVG | \u76F4\u63A5\u5199 |
|
|
133
|
+
| \u81EA\u6D4B | \`check:source\` + \`check:runtime\` |
|
|
134
|
+
`;
|
|
135
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
136
|
+
0 && (module.exports = {
|
|
137
|
+
AGENT_CONTRACT
|
|
138
|
+
});
|
|
139
|
+
//# sourceMappingURL=agent-contract.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/templates/agent-contract.ts"],"sourcesContent":["/**\n * vobs 框架契约正文 —— `vobs agent-doc` 的输出来源。\n *\n * 为什么是 TS 模块而不是 .md 文件:读文件要靠 `import.meta.url` 解析相对路径,\n * 而**全量测试时 vitest 会转换模块**,`import.meta.url` 指向虚拟路径,\n * `new URL('../../src/templates/…')` 就找不到文件(实测:单独跑 8 条全过、\n * 全量跑 8 条全红)。内联成字符串**免疫打包与转换**,也省掉发布时拷模板。\n *\n * 内容由 `packages/cli/src/templates/agent-contract.md` 生成(保留 .md 作可读源);\n * 改契约请改 .md 后重新生成,或直接改这里的字符串。\n */\nexport const AGENT_CONTRACT = `# vobs 框架契约(写代码前必读)\n\nvobs 是**编译型 / 细粒度 / 响应式**框架,与 React 的心智模型有几处**根本不同**。\n下面的每一条都来自真实事故,**违反时通常编译通过、运行期才错**。\n\n> 本文档由 \\`vobs agent-doc\\` 从框架版本生成 —— 不要手改生成的块。\n> 每条后面括号里是**违反时框架会报的诊断码**。\n\n## 一、组件体只执行一次(run-once)\n\n**组件函数体只在创建时跑一次**,之后只有 JSX 里订阅的位置更新。\n\n- ❌ \\`const filtered = list.value.filter(...)\\` —— 一次快照,之后永不更新\n- ✅ 派生放进 JSX:\\`<div>{list.value.filter(...).map(render)}</div>\\`\n- ❌ 组件体里取快照给回调用:\\`const v = name.value; onClick={() => save(v)}\\`\n- ✅ 回调内重读:\\`onClick={() => save(name.value)}\\`\n- 动态 props 用 getter 形态,不要传求值后的值\n\n## 二、条件渲染:**不要在组件体里 \\`return\\` 分支**(\\`VOBS_C107\\` / \\`VOBS_C104\\`)\n\n组件体里读信号的 \\`return\\` 在挂载时固化,**三种写法坏得一模一样**:\n\n\\`\\`\\`tsx\nreturn cond.value ? <A/> : <B/> // ❌ 冻结\nif (cond.value) return <A/> // ❌ 冻结\nreturn cond.value ? <A/> : null // ❌ 冻结\n\\`\\`\\`\n\n顶层 \\`return\\` 处**没有 parent/anchor**,所以两支都是 JSX 也一样不会重分支。\n\n| 场景 | 用什么 |\n|---|---|\n| **路由分支** | **\\`<RouterView/>\\`**(声明式,内部 \\`insertDynamic\\` + \\`resetKey\\`) |\n| 保留挂载、只切显隐 | **\\`<Show when={cond}>\\`**(切 \\`hidden\\` + \\`inert\\`,焦点/滚动/内部状态不丢) |\n| 只切类名 | \\`class=\"base\" classList={{ 'is-on': cond.value }}\\` |\n| 普通条件渲染 | 放进 **JSX 子节点位置**:\\`<div>{cond.value ? <A/> : <B/>}</div>\\` |\n\n## 三、effect 里不要写自己读过的信号(\\`VOBS_C210\\` / \\`VOBS_C211\\`)\n\n**依赖是自动收集的**:effect 运行期间读到的**任何**信号都会变成依赖。\n\n- ❌ \\`effect(() => { count.value++ })\\` —— 读+写同一个信号 = 自订阅循环\n- ❌ \\`effect(() => { if (session.value) void sync() })\\` —— **「只调了个函数」不等于没依赖**:\n 被调函数在**首个 \\`await\\` 之前**的代码是**同步执行**的,它读的信号算在 effect 头上\n- ✅ 只想声明依赖:**\\`effect(on(deps, () => { … }))\\`** —— 回调在 untrack 作用域里跑,\n 它调用的函数碰什么信号都不会反向订阅\n- ✅ 派生值用 \\`memo\\`,不要\"读 A 写 B\"\n- 兜底才是 \\`untrack(() => { X.value = next })\\`\n\n**别在 effect 里调 async 函数**(\\`VOBS_C106\\`):\\`effect\\` 不等它,\n且首个 \\`await\\` 之前的部分是同步的。异步取数用 **\\`@vobs/resource\\`**。\n\n## 四、客户端副作用与 SSR\n\n- 定时器 / 监听 / \\`matchMedia\\` / \\`localStorage\\` → **\\`onMount\\` 启动、\\`onDestroy\\` 清理**\n (**不要手写 \\`typeof window\\` 守卫**)\n- **渲染输出本身**依赖浏览器(窗口尺寸 / \\`localStorage\\` 回填 / \\`Date.now\\` / 随机值)\n → **\\`<ClientOnly fallback={…}>\\`**(首轮两侧都渲染 fallback,水合对得上)\n- 模块顶层**禁止** JSX(\\`VOBS_C105\\`):import 求值早于渲染器安装\n\n## 五、列表与数据\n\n- **数组更新必须换引用**:\\`list.value = [...list.value, item]\\`;\n 原地 \\`push\\` / 改字段**不触发更新**\n- JSX **子节点位置**只放四种形态:组件标签 / 元素 / 两分支三元 / \\`.map()\\`\n- 数据结构里**只存纯描述**(字符串/样式/结构字段),**节点对象别进数据常量**\n (SSG 序列化会炸,产物出现 \\`[object Xxx]\\` 时框架会直接报错)\n\n## 六、输入\n\n- **数字输入走 \\`parseNumber\\`**(\\`<Field type=\"number\">\\` 已内置):\n 空串 / 非法 / 超界**不提交**,保持原值\n —— \\`Number('') === 0\\` 会把输入清成 0 并沿联动链路清零兄弟维度\n- \\`<select>\\` 的 value 直接绑,**不要写 ref 兜底**(1.5.1+ 已修时序)\n\n## 七、图标与 SVG\n\n- **SVG 直接写 JSX**(\\`<svg><path/></svg>\\`,1.7.4+ 按 namespace 创建与水合)\n- 图标要**登记进白名单**;查表 miss 时 \\`@vobs/icon-core\\` 会警告点名(但仍应登记)\n\n## 八、提交前自测\n\n\\`\\`\\`bash\npnpm run check:source # = vobs check,全仓一次列出全部诊断\npnpm run check:runtime # 真实浏览器逐路由跑护栏(需 Chrome)\npnpm run check:runtime:interact # 再点所有按钮、触发所有输入\n\\`\\`\\`\n\n\\`vite build\\` **会**打印编译期警告(\\`C104\\`/\\`C105\\`/\\`C106\\`/\\`C107\\`),但只覆盖它编译到的文件;\n\\`check:source\\` 才是全仓入口。\n\n---\n\n## 一句话速记\n\n| 主题 | 一句话 |\n|---|---|\n| 组件体 | 只跑一次,派生进 JSX |\n| 回调 | 触发时重读 \\`.value\\` |\n| 条件渲染 | 路由用 \\`RouterView\\`,显隐用 \\`Show\\`,别在组件体 return 分支 |\n| effect | 别写自己读的信号,用 \\`on(deps, fn)\\` |\n| async | 别塞进 effect,用 \\`@vobs/resource\\` |\n| 客户端 | \\`onMount\\`/\\`onDestroy\\`/\\`ClientOnly\\`,别手写 \\`typeof window\\` |\n| 列表 | 换引用 |\n| 数字 | \\`parseNumber\\` |\n| SVG | 直接写 |\n| 自测 | \\`check:source\\` + \\`check:runtime\\` |\n`\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAWO,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vobs 框架契约正文 —— `vobs agent-doc` 的输出来源。
|
|
3
|
+
*
|
|
4
|
+
* 为什么是 TS 模块而不是 .md 文件:读文件要靠 `import.meta.url` 解析相对路径,
|
|
5
|
+
* 而**全量测试时 vitest 会转换模块**,`import.meta.url` 指向虚拟路径,
|
|
6
|
+
* `new URL('../../src/templates/…')` 就找不到文件(实测:单独跑 8 条全过、
|
|
7
|
+
* 全量跑 8 条全红)。内联成字符串**免疫打包与转换**,也省掉发布时拷模板。
|
|
8
|
+
*
|
|
9
|
+
* 内容由 `packages/cli/src/templates/agent-contract.md` 生成(保留 .md 作可读源);
|
|
10
|
+
* 改契约请改 .md 后重新生成,或直接改这里的字符串。
|
|
11
|
+
*/
|
|
12
|
+
declare const AGENT_CONTRACT = "# vobs \u6846\u67B6\u5951\u7EA6\uFF08\u5199\u4EE3\u7801\u524D\u5FC5\u8BFB\uFF09\n\nvobs \u662F**\u7F16\u8BD1\u578B / \u7EC6\u7C92\u5EA6 / \u54CD\u5E94\u5F0F**\u6846\u67B6\uFF0C\u4E0E React \u7684\u5FC3\u667A\u6A21\u578B\u6709\u51E0\u5904**\u6839\u672C\u4E0D\u540C**\u3002\n\u4E0B\u9762\u7684\u6BCF\u4E00\u6761\u90FD\u6765\u81EA\u771F\u5B9E\u4E8B\u6545\uFF0C**\u8FDD\u53CD\u65F6\u901A\u5E38\u7F16\u8BD1\u901A\u8FC7\u3001\u8FD0\u884C\u671F\u624D\u9519**\u3002\n\n> \u672C\u6587\u6863\u7531 `vobs agent-doc` \u4ECE\u6846\u67B6\u7248\u672C\u751F\u6210 \u2014\u2014 \u4E0D\u8981\u624B\u6539\u751F\u6210\u7684\u5757\u3002\n> \u6BCF\u6761\u540E\u9762\u62EC\u53F7\u91CC\u662F**\u8FDD\u53CD\u65F6\u6846\u67B6\u4F1A\u62A5\u7684\u8BCA\u65AD\u7801**\u3002\n\n## \u4E00\u3001\u7EC4\u4EF6\u4F53\u53EA\u6267\u884C\u4E00\u6B21\uFF08run-once\uFF09\n\n**\u7EC4\u4EF6\u51FD\u6570\u4F53\u53EA\u5728\u521B\u5EFA\u65F6\u8DD1\u4E00\u6B21**\uFF0C\u4E4B\u540E\u53EA\u6709 JSX \u91CC\u8BA2\u9605\u7684\u4F4D\u7F6E\u66F4\u65B0\u3002\n\n- \u274C `const filtered = list.value.filter(...)` \u2014\u2014 \u4E00\u6B21\u5FEB\u7167\uFF0C\u4E4B\u540E\u6C38\u4E0D\u66F4\u65B0\n- \u2705 \u6D3E\u751F\u653E\u8FDB JSX\uFF1A`<div>{list.value.filter(...).map(render)}</div>`\n- \u274C \u7EC4\u4EF6\u4F53\u91CC\u53D6\u5FEB\u7167\u7ED9\u56DE\u8C03\u7528\uFF1A`const v = name.value; onClick={() => save(v)}`\n- \u2705 \u56DE\u8C03\u5185\u91CD\u8BFB\uFF1A`onClick={() => save(name.value)}`\n- \u52A8\u6001 props \u7528 getter \u5F62\u6001\uFF0C\u4E0D\u8981\u4F20\u6C42\u503C\u540E\u7684\u503C\n\n## \u4E8C\u3001\u6761\u4EF6\u6E32\u67D3\uFF1A**\u4E0D\u8981\u5728\u7EC4\u4EF6\u4F53\u91CC `return` \u5206\u652F**\uFF08`VOBS_C107` / `VOBS_C104`\uFF09\n\n\u7EC4\u4EF6\u4F53\u91CC\u8BFB\u4FE1\u53F7\u7684 `return` \u5728\u6302\u8F7D\u65F6\u56FA\u5316\uFF0C**\u4E09\u79CD\u5199\u6CD5\u574F\u5F97\u4E00\u6A21\u4E00\u6837**\uFF1A\n\n```tsx\nreturn cond.value ? <A/> : <B/> // \u274C \u51BB\u7ED3\nif (cond.value) return <A/> // \u274C \u51BB\u7ED3\nreturn cond.value ? <A/> : null // \u274C \u51BB\u7ED3\n```\n\n\u9876\u5C42 `return` \u5904**\u6CA1\u6709 parent/anchor**\uFF0C\u6240\u4EE5\u4E24\u652F\u90FD\u662F JSX \u4E5F\u4E00\u6837\u4E0D\u4F1A\u91CD\u5206\u652F\u3002\n\n| \u573A\u666F | \u7528\u4EC0\u4E48 |\n|---|---|\n| **\u8DEF\u7531\u5206\u652F** | **`<RouterView/>`**\uFF08\u58F0\u660E\u5F0F\uFF0C\u5185\u90E8 `insertDynamic` + `resetKey`\uFF09 |\n| \u4FDD\u7559\u6302\u8F7D\u3001\u53EA\u5207\u663E\u9690 | **`<Show when={cond}>`**\uFF08\u5207 `hidden` + `inert`\uFF0C\u7126\u70B9/\u6EDA\u52A8/\u5185\u90E8\u72B6\u6001\u4E0D\u4E22\uFF09 |\n| \u53EA\u5207\u7C7B\u540D | `class=\"base\" classList={{ 'is-on': cond.value }}` |\n| \u666E\u901A\u6761\u4EF6\u6E32\u67D3 | \u653E\u8FDB **JSX \u5B50\u8282\u70B9\u4F4D\u7F6E**\uFF1A`<div>{cond.value ? <A/> : <B/>}</div>` |\n\n## \u4E09\u3001effect \u91CC\u4E0D\u8981\u5199\u81EA\u5DF1\u8BFB\u8FC7\u7684\u4FE1\u53F7\uFF08`VOBS_C210` / `VOBS_C211`\uFF09\n\n**\u4F9D\u8D56\u662F\u81EA\u52A8\u6536\u96C6\u7684**\uFF1Aeffect \u8FD0\u884C\u671F\u95F4\u8BFB\u5230\u7684**\u4EFB\u4F55**\u4FE1\u53F7\u90FD\u4F1A\u53D8\u6210\u4F9D\u8D56\u3002\n\n- \u274C `effect(() => { count.value++ })` \u2014\u2014 \u8BFB+\u5199\u540C\u4E00\u4E2A\u4FE1\u53F7 = \u81EA\u8BA2\u9605\u5FAA\u73AF\n- \u274C `effect(() => { if (session.value) void sync() })` \u2014\u2014 **\u300C\u53EA\u8C03\u4E86\u4E2A\u51FD\u6570\u300D\u4E0D\u7B49\u4E8E\u6CA1\u4F9D\u8D56**\uFF1A\n \u88AB\u8C03\u51FD\u6570\u5728**\u9996\u4E2A `await` \u4E4B\u524D**\u7684\u4EE3\u7801\u662F**\u540C\u6B65\u6267\u884C**\u7684\uFF0C\u5B83\u8BFB\u7684\u4FE1\u53F7\u7B97\u5728 effect \u5934\u4E0A\n- \u2705 \u53EA\u60F3\u58F0\u660E\u4F9D\u8D56\uFF1A**`effect(on(deps, () => { \u2026 }))`** \u2014\u2014 \u56DE\u8C03\u5728 untrack \u4F5C\u7528\u57DF\u91CC\u8DD1\uFF0C\n \u5B83\u8C03\u7528\u7684\u51FD\u6570\u78B0\u4EC0\u4E48\u4FE1\u53F7\u90FD\u4E0D\u4F1A\u53CD\u5411\u8BA2\u9605\n- \u2705 \u6D3E\u751F\u503C\u7528 `memo`\uFF0C\u4E0D\u8981\"\u8BFB A \u5199 B\"\n- \u515C\u5E95\u624D\u662F `untrack(() => { X.value = next })`\n\n**\u522B\u5728 effect \u91CC\u8C03 async \u51FD\u6570**\uFF08`VOBS_C106`\uFF09\uFF1A`effect` \u4E0D\u7B49\u5B83\uFF0C\n\u4E14\u9996\u4E2A `await` \u4E4B\u524D\u7684\u90E8\u5206\u662F\u540C\u6B65\u7684\u3002\u5F02\u6B65\u53D6\u6570\u7528 **`@vobs/resource`**\u3002\n\n## \u56DB\u3001\u5BA2\u6237\u7AEF\u526F\u4F5C\u7528\u4E0E SSR\n\n- \u5B9A\u65F6\u5668 / \u76D1\u542C / `matchMedia` / `localStorage` \u2192 **`onMount` \u542F\u52A8\u3001`onDestroy` \u6E05\u7406**\n \uFF08**\u4E0D\u8981\u624B\u5199 `typeof window` \u5B88\u536B**\uFF09\n- **\u6E32\u67D3\u8F93\u51FA\u672C\u8EAB**\u4F9D\u8D56\u6D4F\u89C8\u5668\uFF08\u7A97\u53E3\u5C3A\u5BF8 / `localStorage` \u56DE\u586B / `Date.now` / \u968F\u673A\u503C\uFF09\n \u2192 **`<ClientOnly fallback={\u2026}>`**\uFF08\u9996\u8F6E\u4E24\u4FA7\u90FD\u6E32\u67D3 fallback\uFF0C\u6C34\u5408\u5BF9\u5F97\u4E0A\uFF09\n- \u6A21\u5757\u9876\u5C42**\u7981\u6B62** JSX\uFF08`VOBS_C105`\uFF09\uFF1Aimport \u6C42\u503C\u65E9\u4E8E\u6E32\u67D3\u5668\u5B89\u88C5\n\n## \u4E94\u3001\u5217\u8868\u4E0E\u6570\u636E\n\n- **\u6570\u7EC4\u66F4\u65B0\u5FC5\u987B\u6362\u5F15\u7528**\uFF1A`list.value = [...list.value, item]`\uFF1B\n \u539F\u5730 `push` / \u6539\u5B57\u6BB5**\u4E0D\u89E6\u53D1\u66F4\u65B0**\n- JSX **\u5B50\u8282\u70B9\u4F4D\u7F6E**\u53EA\u653E\u56DB\u79CD\u5F62\u6001\uFF1A\u7EC4\u4EF6\u6807\u7B7E / \u5143\u7D20 / \u4E24\u5206\u652F\u4E09\u5143 / `.map()`\n- \u6570\u636E\u7ED3\u6784\u91CC**\u53EA\u5B58\u7EAF\u63CF\u8FF0**\uFF08\u5B57\u7B26\u4E32/\u6837\u5F0F/\u7ED3\u6784\u5B57\u6BB5\uFF09\uFF0C**\u8282\u70B9\u5BF9\u8C61\u522B\u8FDB\u6570\u636E\u5E38\u91CF**\n \uFF08SSG \u5E8F\u5217\u5316\u4F1A\u70B8\uFF0C\u4EA7\u7269\u51FA\u73B0 `[object Xxx]` \u65F6\u6846\u67B6\u4F1A\u76F4\u63A5\u62A5\u9519\uFF09\n\n## \u516D\u3001\u8F93\u5165\n\n- **\u6570\u5B57\u8F93\u5165\u8D70 `parseNumber`**\uFF08`<Field type=\"number\">` \u5DF2\u5185\u7F6E\uFF09\uFF1A\n \u7A7A\u4E32 / \u975E\u6CD5 / \u8D85\u754C**\u4E0D\u63D0\u4EA4**\uFF0C\u4FDD\u6301\u539F\u503C\n \u2014\u2014 `Number('') === 0` \u4F1A\u628A\u8F93\u5165\u6E05\u6210 0 \u5E76\u6CBF\u8054\u52A8\u94FE\u8DEF\u6E05\u96F6\u5144\u5F1F\u7EF4\u5EA6\n- `<select>` \u7684 value \u76F4\u63A5\u7ED1\uFF0C**\u4E0D\u8981\u5199 ref \u515C\u5E95**\uFF081.5.1+ \u5DF2\u4FEE\u65F6\u5E8F\uFF09\n\n## \u4E03\u3001\u56FE\u6807\u4E0E SVG\n\n- **SVG \u76F4\u63A5\u5199 JSX**\uFF08`<svg><path/></svg>`\uFF0C1.7.4+ \u6309 namespace \u521B\u5EFA\u4E0E\u6C34\u5408\uFF09\n- \u56FE\u6807\u8981**\u767B\u8BB0\u8FDB\u767D\u540D\u5355**\uFF1B\u67E5\u8868 miss \u65F6 `@vobs/icon-core` \u4F1A\u8B66\u544A\u70B9\u540D\uFF08\u4F46\u4ECD\u5E94\u767B\u8BB0\uFF09\n\n## \u516B\u3001\u63D0\u4EA4\u524D\u81EA\u6D4B\n\n```bash\npnpm run check:source # = vobs check\uFF0C\u5168\u4ED3\u4E00\u6B21\u5217\u51FA\u5168\u90E8\u8BCA\u65AD\npnpm run check:runtime # \u771F\u5B9E\u6D4F\u89C8\u5668\u9010\u8DEF\u7531\u8DD1\u62A4\u680F\uFF08\u9700 Chrome\uFF09\npnpm run check:runtime:interact # \u518D\u70B9\u6240\u6709\u6309\u94AE\u3001\u89E6\u53D1\u6240\u6709\u8F93\u5165\n```\n\n`vite build` **\u4F1A**\u6253\u5370\u7F16\u8BD1\u671F\u8B66\u544A\uFF08`C104`/`C105`/`C106`/`C107`\uFF09\uFF0C\u4F46\u53EA\u8986\u76D6\u5B83\u7F16\u8BD1\u5230\u7684\u6587\u4EF6\uFF1B\n`check:source` \u624D\u662F\u5168\u4ED3\u5165\u53E3\u3002\n\n---\n\n## \u4E00\u53E5\u8BDD\u901F\u8BB0\n\n| \u4E3B\u9898 | \u4E00\u53E5\u8BDD |\n|---|---|\n| \u7EC4\u4EF6\u4F53 | \u53EA\u8DD1\u4E00\u6B21\uFF0C\u6D3E\u751F\u8FDB JSX |\n| \u56DE\u8C03 | \u89E6\u53D1\u65F6\u91CD\u8BFB `.value` |\n| \u6761\u4EF6\u6E32\u67D3 | \u8DEF\u7531\u7528 `RouterView`\uFF0C\u663E\u9690\u7528 `Show`\uFF0C\u522B\u5728\u7EC4\u4EF6\u4F53 return \u5206\u652F |\n| effect | \u522B\u5199\u81EA\u5DF1\u8BFB\u7684\u4FE1\u53F7\uFF0C\u7528 `on(deps, fn)` |\n| async | \u522B\u585E\u8FDB effect\uFF0C\u7528 `@vobs/resource` |\n| \u5BA2\u6237\u7AEF | `onMount`/`onDestroy`/`ClientOnly`\uFF0C\u522B\u624B\u5199 `typeof window` |\n| \u5217\u8868 | \u6362\u5F15\u7528 |\n| \u6570\u5B57 | `parseNumber` |\n| SVG | \u76F4\u63A5\u5199 |\n| \u81EA\u6D4B | `check:source` + `check:runtime` |\n";
|
|
13
|
+
|
|
14
|
+
export { AGENT_CONTRACT };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vobs 框架契约正文 —— `vobs agent-doc` 的输出来源。
|
|
3
|
+
*
|
|
4
|
+
* 为什么是 TS 模块而不是 .md 文件:读文件要靠 `import.meta.url` 解析相对路径,
|
|
5
|
+
* 而**全量测试时 vitest 会转换模块**,`import.meta.url` 指向虚拟路径,
|
|
6
|
+
* `new URL('../../src/templates/…')` 就找不到文件(实测:单独跑 8 条全过、
|
|
7
|
+
* 全量跑 8 条全红)。内联成字符串**免疫打包与转换**,也省掉发布时拷模板。
|
|
8
|
+
*
|
|
9
|
+
* 内容由 `packages/cli/src/templates/agent-contract.md` 生成(保留 .md 作可读源);
|
|
10
|
+
* 改契约请改 .md 后重新生成,或直接改这里的字符串。
|
|
11
|
+
*/
|
|
12
|
+
declare const AGENT_CONTRACT = "# vobs \u6846\u67B6\u5951\u7EA6\uFF08\u5199\u4EE3\u7801\u524D\u5FC5\u8BFB\uFF09\n\nvobs \u662F**\u7F16\u8BD1\u578B / \u7EC6\u7C92\u5EA6 / \u54CD\u5E94\u5F0F**\u6846\u67B6\uFF0C\u4E0E React \u7684\u5FC3\u667A\u6A21\u578B\u6709\u51E0\u5904**\u6839\u672C\u4E0D\u540C**\u3002\n\u4E0B\u9762\u7684\u6BCF\u4E00\u6761\u90FD\u6765\u81EA\u771F\u5B9E\u4E8B\u6545\uFF0C**\u8FDD\u53CD\u65F6\u901A\u5E38\u7F16\u8BD1\u901A\u8FC7\u3001\u8FD0\u884C\u671F\u624D\u9519**\u3002\n\n> \u672C\u6587\u6863\u7531 `vobs agent-doc` \u4ECE\u6846\u67B6\u7248\u672C\u751F\u6210 \u2014\u2014 \u4E0D\u8981\u624B\u6539\u751F\u6210\u7684\u5757\u3002\n> \u6BCF\u6761\u540E\u9762\u62EC\u53F7\u91CC\u662F**\u8FDD\u53CD\u65F6\u6846\u67B6\u4F1A\u62A5\u7684\u8BCA\u65AD\u7801**\u3002\n\n## \u4E00\u3001\u7EC4\u4EF6\u4F53\u53EA\u6267\u884C\u4E00\u6B21\uFF08run-once\uFF09\n\n**\u7EC4\u4EF6\u51FD\u6570\u4F53\u53EA\u5728\u521B\u5EFA\u65F6\u8DD1\u4E00\u6B21**\uFF0C\u4E4B\u540E\u53EA\u6709 JSX \u91CC\u8BA2\u9605\u7684\u4F4D\u7F6E\u66F4\u65B0\u3002\n\n- \u274C `const filtered = list.value.filter(...)` \u2014\u2014 \u4E00\u6B21\u5FEB\u7167\uFF0C\u4E4B\u540E\u6C38\u4E0D\u66F4\u65B0\n- \u2705 \u6D3E\u751F\u653E\u8FDB JSX\uFF1A`<div>{list.value.filter(...).map(render)}</div>`\n- \u274C \u7EC4\u4EF6\u4F53\u91CC\u53D6\u5FEB\u7167\u7ED9\u56DE\u8C03\u7528\uFF1A`const v = name.value; onClick={() => save(v)}`\n- \u2705 \u56DE\u8C03\u5185\u91CD\u8BFB\uFF1A`onClick={() => save(name.value)}`\n- \u52A8\u6001 props \u7528 getter \u5F62\u6001\uFF0C\u4E0D\u8981\u4F20\u6C42\u503C\u540E\u7684\u503C\n\n## \u4E8C\u3001\u6761\u4EF6\u6E32\u67D3\uFF1A**\u4E0D\u8981\u5728\u7EC4\u4EF6\u4F53\u91CC `return` \u5206\u652F**\uFF08`VOBS_C107` / `VOBS_C104`\uFF09\n\n\u7EC4\u4EF6\u4F53\u91CC\u8BFB\u4FE1\u53F7\u7684 `return` \u5728\u6302\u8F7D\u65F6\u56FA\u5316\uFF0C**\u4E09\u79CD\u5199\u6CD5\u574F\u5F97\u4E00\u6A21\u4E00\u6837**\uFF1A\n\n```tsx\nreturn cond.value ? <A/> : <B/> // \u274C \u51BB\u7ED3\nif (cond.value) return <A/> // \u274C \u51BB\u7ED3\nreturn cond.value ? <A/> : null // \u274C \u51BB\u7ED3\n```\n\n\u9876\u5C42 `return` \u5904**\u6CA1\u6709 parent/anchor**\uFF0C\u6240\u4EE5\u4E24\u652F\u90FD\u662F JSX \u4E5F\u4E00\u6837\u4E0D\u4F1A\u91CD\u5206\u652F\u3002\n\n| \u573A\u666F | \u7528\u4EC0\u4E48 |\n|---|---|\n| **\u8DEF\u7531\u5206\u652F** | **`<RouterView/>`**\uFF08\u58F0\u660E\u5F0F\uFF0C\u5185\u90E8 `insertDynamic` + `resetKey`\uFF09 |\n| \u4FDD\u7559\u6302\u8F7D\u3001\u53EA\u5207\u663E\u9690 | **`<Show when={cond}>`**\uFF08\u5207 `hidden` + `inert`\uFF0C\u7126\u70B9/\u6EDA\u52A8/\u5185\u90E8\u72B6\u6001\u4E0D\u4E22\uFF09 |\n| \u53EA\u5207\u7C7B\u540D | `class=\"base\" classList={{ 'is-on': cond.value }}` |\n| \u666E\u901A\u6761\u4EF6\u6E32\u67D3 | \u653E\u8FDB **JSX \u5B50\u8282\u70B9\u4F4D\u7F6E**\uFF1A`<div>{cond.value ? <A/> : <B/>}</div>` |\n\n## \u4E09\u3001effect \u91CC\u4E0D\u8981\u5199\u81EA\u5DF1\u8BFB\u8FC7\u7684\u4FE1\u53F7\uFF08`VOBS_C210` / `VOBS_C211`\uFF09\n\n**\u4F9D\u8D56\u662F\u81EA\u52A8\u6536\u96C6\u7684**\uFF1Aeffect \u8FD0\u884C\u671F\u95F4\u8BFB\u5230\u7684**\u4EFB\u4F55**\u4FE1\u53F7\u90FD\u4F1A\u53D8\u6210\u4F9D\u8D56\u3002\n\n- \u274C `effect(() => { count.value++ })` \u2014\u2014 \u8BFB+\u5199\u540C\u4E00\u4E2A\u4FE1\u53F7 = \u81EA\u8BA2\u9605\u5FAA\u73AF\n- \u274C `effect(() => { if (session.value) void sync() })` \u2014\u2014 **\u300C\u53EA\u8C03\u4E86\u4E2A\u51FD\u6570\u300D\u4E0D\u7B49\u4E8E\u6CA1\u4F9D\u8D56**\uFF1A\n \u88AB\u8C03\u51FD\u6570\u5728**\u9996\u4E2A `await` \u4E4B\u524D**\u7684\u4EE3\u7801\u662F**\u540C\u6B65\u6267\u884C**\u7684\uFF0C\u5B83\u8BFB\u7684\u4FE1\u53F7\u7B97\u5728 effect \u5934\u4E0A\n- \u2705 \u53EA\u60F3\u58F0\u660E\u4F9D\u8D56\uFF1A**`effect(on(deps, () => { \u2026 }))`** \u2014\u2014 \u56DE\u8C03\u5728 untrack \u4F5C\u7528\u57DF\u91CC\u8DD1\uFF0C\n \u5B83\u8C03\u7528\u7684\u51FD\u6570\u78B0\u4EC0\u4E48\u4FE1\u53F7\u90FD\u4E0D\u4F1A\u53CD\u5411\u8BA2\u9605\n- \u2705 \u6D3E\u751F\u503C\u7528 `memo`\uFF0C\u4E0D\u8981\"\u8BFB A \u5199 B\"\n- \u515C\u5E95\u624D\u662F `untrack(() => { X.value = next })`\n\n**\u522B\u5728 effect \u91CC\u8C03 async \u51FD\u6570**\uFF08`VOBS_C106`\uFF09\uFF1A`effect` \u4E0D\u7B49\u5B83\uFF0C\n\u4E14\u9996\u4E2A `await` \u4E4B\u524D\u7684\u90E8\u5206\u662F\u540C\u6B65\u7684\u3002\u5F02\u6B65\u53D6\u6570\u7528 **`@vobs/resource`**\u3002\n\n## \u56DB\u3001\u5BA2\u6237\u7AEF\u526F\u4F5C\u7528\u4E0E SSR\n\n- \u5B9A\u65F6\u5668 / \u76D1\u542C / `matchMedia` / `localStorage` \u2192 **`onMount` \u542F\u52A8\u3001`onDestroy` \u6E05\u7406**\n \uFF08**\u4E0D\u8981\u624B\u5199 `typeof window` \u5B88\u536B**\uFF09\n- **\u6E32\u67D3\u8F93\u51FA\u672C\u8EAB**\u4F9D\u8D56\u6D4F\u89C8\u5668\uFF08\u7A97\u53E3\u5C3A\u5BF8 / `localStorage` \u56DE\u586B / `Date.now` / \u968F\u673A\u503C\uFF09\n \u2192 **`<ClientOnly fallback={\u2026}>`**\uFF08\u9996\u8F6E\u4E24\u4FA7\u90FD\u6E32\u67D3 fallback\uFF0C\u6C34\u5408\u5BF9\u5F97\u4E0A\uFF09\n- \u6A21\u5757\u9876\u5C42**\u7981\u6B62** JSX\uFF08`VOBS_C105`\uFF09\uFF1Aimport \u6C42\u503C\u65E9\u4E8E\u6E32\u67D3\u5668\u5B89\u88C5\n\n## \u4E94\u3001\u5217\u8868\u4E0E\u6570\u636E\n\n- **\u6570\u7EC4\u66F4\u65B0\u5FC5\u987B\u6362\u5F15\u7528**\uFF1A`list.value = [...list.value, item]`\uFF1B\n \u539F\u5730 `push` / \u6539\u5B57\u6BB5**\u4E0D\u89E6\u53D1\u66F4\u65B0**\n- JSX **\u5B50\u8282\u70B9\u4F4D\u7F6E**\u53EA\u653E\u56DB\u79CD\u5F62\u6001\uFF1A\u7EC4\u4EF6\u6807\u7B7E / \u5143\u7D20 / \u4E24\u5206\u652F\u4E09\u5143 / `.map()`\n- \u6570\u636E\u7ED3\u6784\u91CC**\u53EA\u5B58\u7EAF\u63CF\u8FF0**\uFF08\u5B57\u7B26\u4E32/\u6837\u5F0F/\u7ED3\u6784\u5B57\u6BB5\uFF09\uFF0C**\u8282\u70B9\u5BF9\u8C61\u522B\u8FDB\u6570\u636E\u5E38\u91CF**\n \uFF08SSG \u5E8F\u5217\u5316\u4F1A\u70B8\uFF0C\u4EA7\u7269\u51FA\u73B0 `[object Xxx]` \u65F6\u6846\u67B6\u4F1A\u76F4\u63A5\u62A5\u9519\uFF09\n\n## \u516D\u3001\u8F93\u5165\n\n- **\u6570\u5B57\u8F93\u5165\u8D70 `parseNumber`**\uFF08`<Field type=\"number\">` \u5DF2\u5185\u7F6E\uFF09\uFF1A\n \u7A7A\u4E32 / \u975E\u6CD5 / \u8D85\u754C**\u4E0D\u63D0\u4EA4**\uFF0C\u4FDD\u6301\u539F\u503C\n \u2014\u2014 `Number('') === 0` \u4F1A\u628A\u8F93\u5165\u6E05\u6210 0 \u5E76\u6CBF\u8054\u52A8\u94FE\u8DEF\u6E05\u96F6\u5144\u5F1F\u7EF4\u5EA6\n- `<select>` \u7684 value \u76F4\u63A5\u7ED1\uFF0C**\u4E0D\u8981\u5199 ref \u515C\u5E95**\uFF081.5.1+ \u5DF2\u4FEE\u65F6\u5E8F\uFF09\n\n## \u4E03\u3001\u56FE\u6807\u4E0E SVG\n\n- **SVG \u76F4\u63A5\u5199 JSX**\uFF08`<svg><path/></svg>`\uFF0C1.7.4+ \u6309 namespace \u521B\u5EFA\u4E0E\u6C34\u5408\uFF09\n- \u56FE\u6807\u8981**\u767B\u8BB0\u8FDB\u767D\u540D\u5355**\uFF1B\u67E5\u8868 miss \u65F6 `@vobs/icon-core` \u4F1A\u8B66\u544A\u70B9\u540D\uFF08\u4F46\u4ECD\u5E94\u767B\u8BB0\uFF09\n\n## \u516B\u3001\u63D0\u4EA4\u524D\u81EA\u6D4B\n\n```bash\npnpm run check:source # = vobs check\uFF0C\u5168\u4ED3\u4E00\u6B21\u5217\u51FA\u5168\u90E8\u8BCA\u65AD\npnpm run check:runtime # \u771F\u5B9E\u6D4F\u89C8\u5668\u9010\u8DEF\u7531\u8DD1\u62A4\u680F\uFF08\u9700 Chrome\uFF09\npnpm run check:runtime:interact # \u518D\u70B9\u6240\u6709\u6309\u94AE\u3001\u89E6\u53D1\u6240\u6709\u8F93\u5165\n```\n\n`vite build` **\u4F1A**\u6253\u5370\u7F16\u8BD1\u671F\u8B66\u544A\uFF08`C104`/`C105`/`C106`/`C107`\uFF09\uFF0C\u4F46\u53EA\u8986\u76D6\u5B83\u7F16\u8BD1\u5230\u7684\u6587\u4EF6\uFF1B\n`check:source` \u624D\u662F\u5168\u4ED3\u5165\u53E3\u3002\n\n---\n\n## \u4E00\u53E5\u8BDD\u901F\u8BB0\n\n| \u4E3B\u9898 | \u4E00\u53E5\u8BDD |\n|---|---|\n| \u7EC4\u4EF6\u4F53 | \u53EA\u8DD1\u4E00\u6B21\uFF0C\u6D3E\u751F\u8FDB JSX |\n| \u56DE\u8C03 | \u89E6\u53D1\u65F6\u91CD\u8BFB `.value` |\n| \u6761\u4EF6\u6E32\u67D3 | \u8DEF\u7531\u7528 `RouterView`\uFF0C\u663E\u9690\u7528 `Show`\uFF0C\u522B\u5728\u7EC4\u4EF6\u4F53 return \u5206\u652F |\n| effect | \u522B\u5199\u81EA\u5DF1\u8BFB\u7684\u4FE1\u53F7\uFF0C\u7528 `on(deps, fn)` |\n| async | \u522B\u585E\u8FDB effect\uFF0C\u7528 `@vobs/resource` |\n| \u5BA2\u6237\u7AEF | `onMount`/`onDestroy`/`ClientOnly`\uFF0C\u522B\u624B\u5199 `typeof window` |\n| \u5217\u8868 | \u6362\u5F15\u7528 |\n| \u6570\u5B57 | `parseNumber` |\n| SVG | \u76F4\u63A5\u5199 |\n| \u81EA\u6D4B | `check:source` + `check:runtime` |\n";
|
|
13
|
+
|
|
14
|
+
export { AGENT_CONTRACT };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// packages/cli/src/templates/agent-contract.ts
|
|
2
|
+
var AGENT_CONTRACT = `# vobs \u6846\u67B6\u5951\u7EA6\uFF08\u5199\u4EE3\u7801\u524D\u5FC5\u8BFB\uFF09
|
|
3
|
+
|
|
4
|
+
vobs \u662F**\u7F16\u8BD1\u578B / \u7EC6\u7C92\u5EA6 / \u54CD\u5E94\u5F0F**\u6846\u67B6\uFF0C\u4E0E React \u7684\u5FC3\u667A\u6A21\u578B\u6709\u51E0\u5904**\u6839\u672C\u4E0D\u540C**\u3002
|
|
5
|
+
\u4E0B\u9762\u7684\u6BCF\u4E00\u6761\u90FD\u6765\u81EA\u771F\u5B9E\u4E8B\u6545\uFF0C**\u8FDD\u53CD\u65F6\u901A\u5E38\u7F16\u8BD1\u901A\u8FC7\u3001\u8FD0\u884C\u671F\u624D\u9519**\u3002
|
|
6
|
+
|
|
7
|
+
> \u672C\u6587\u6863\u7531 \`vobs agent-doc\` \u4ECE\u6846\u67B6\u7248\u672C\u751F\u6210 \u2014\u2014 \u4E0D\u8981\u624B\u6539\u751F\u6210\u7684\u5757\u3002
|
|
8
|
+
> \u6BCF\u6761\u540E\u9762\u62EC\u53F7\u91CC\u662F**\u8FDD\u53CD\u65F6\u6846\u67B6\u4F1A\u62A5\u7684\u8BCA\u65AD\u7801**\u3002
|
|
9
|
+
|
|
10
|
+
## \u4E00\u3001\u7EC4\u4EF6\u4F53\u53EA\u6267\u884C\u4E00\u6B21\uFF08run-once\uFF09
|
|
11
|
+
|
|
12
|
+
**\u7EC4\u4EF6\u51FD\u6570\u4F53\u53EA\u5728\u521B\u5EFA\u65F6\u8DD1\u4E00\u6B21**\uFF0C\u4E4B\u540E\u53EA\u6709 JSX \u91CC\u8BA2\u9605\u7684\u4F4D\u7F6E\u66F4\u65B0\u3002
|
|
13
|
+
|
|
14
|
+
- \u274C \`const filtered = list.value.filter(...)\` \u2014\u2014 \u4E00\u6B21\u5FEB\u7167\uFF0C\u4E4B\u540E\u6C38\u4E0D\u66F4\u65B0
|
|
15
|
+
- \u2705 \u6D3E\u751F\u653E\u8FDB JSX\uFF1A\`<div>{list.value.filter(...).map(render)}</div>\`
|
|
16
|
+
- \u274C \u7EC4\u4EF6\u4F53\u91CC\u53D6\u5FEB\u7167\u7ED9\u56DE\u8C03\u7528\uFF1A\`const v = name.value; onClick={() => save(v)}\`
|
|
17
|
+
- \u2705 \u56DE\u8C03\u5185\u91CD\u8BFB\uFF1A\`onClick={() => save(name.value)}\`
|
|
18
|
+
- \u52A8\u6001 props \u7528 getter \u5F62\u6001\uFF0C\u4E0D\u8981\u4F20\u6C42\u503C\u540E\u7684\u503C
|
|
19
|
+
|
|
20
|
+
## \u4E8C\u3001\u6761\u4EF6\u6E32\u67D3\uFF1A**\u4E0D\u8981\u5728\u7EC4\u4EF6\u4F53\u91CC \`return\` \u5206\u652F**\uFF08\`VOBS_C107\` / \`VOBS_C104\`\uFF09
|
|
21
|
+
|
|
22
|
+
\u7EC4\u4EF6\u4F53\u91CC\u8BFB\u4FE1\u53F7\u7684 \`return\` \u5728\u6302\u8F7D\u65F6\u56FA\u5316\uFF0C**\u4E09\u79CD\u5199\u6CD5\u574F\u5F97\u4E00\u6A21\u4E00\u6837**\uFF1A
|
|
23
|
+
|
|
24
|
+
\`\`\`tsx
|
|
25
|
+
return cond.value ? <A/> : <B/> // \u274C \u51BB\u7ED3
|
|
26
|
+
if (cond.value) return <A/> // \u274C \u51BB\u7ED3
|
|
27
|
+
return cond.value ? <A/> : null // \u274C \u51BB\u7ED3
|
|
28
|
+
\`\`\`
|
|
29
|
+
|
|
30
|
+
\u9876\u5C42 \`return\` \u5904**\u6CA1\u6709 parent/anchor**\uFF0C\u6240\u4EE5\u4E24\u652F\u90FD\u662F JSX \u4E5F\u4E00\u6837\u4E0D\u4F1A\u91CD\u5206\u652F\u3002
|
|
31
|
+
|
|
32
|
+
| \u573A\u666F | \u7528\u4EC0\u4E48 |
|
|
33
|
+
|---|---|
|
|
34
|
+
| **\u8DEF\u7531\u5206\u652F** | **\`<RouterView/>\`**\uFF08\u58F0\u660E\u5F0F\uFF0C\u5185\u90E8 \`insertDynamic\` + \`resetKey\`\uFF09 |
|
|
35
|
+
| \u4FDD\u7559\u6302\u8F7D\u3001\u53EA\u5207\u663E\u9690 | **\`<Show when={cond}>\`**\uFF08\u5207 \`hidden\` + \`inert\`\uFF0C\u7126\u70B9/\u6EDA\u52A8/\u5185\u90E8\u72B6\u6001\u4E0D\u4E22\uFF09 |
|
|
36
|
+
| \u53EA\u5207\u7C7B\u540D | \`class="base" classList={{ 'is-on': cond.value }}\` |
|
|
37
|
+
| \u666E\u901A\u6761\u4EF6\u6E32\u67D3 | \u653E\u8FDB **JSX \u5B50\u8282\u70B9\u4F4D\u7F6E**\uFF1A\`<div>{cond.value ? <A/> : <B/>}</div>\` |
|
|
38
|
+
|
|
39
|
+
## \u4E09\u3001effect \u91CC\u4E0D\u8981\u5199\u81EA\u5DF1\u8BFB\u8FC7\u7684\u4FE1\u53F7\uFF08\`VOBS_C210\` / \`VOBS_C211\`\uFF09
|
|
40
|
+
|
|
41
|
+
**\u4F9D\u8D56\u662F\u81EA\u52A8\u6536\u96C6\u7684**\uFF1Aeffect \u8FD0\u884C\u671F\u95F4\u8BFB\u5230\u7684**\u4EFB\u4F55**\u4FE1\u53F7\u90FD\u4F1A\u53D8\u6210\u4F9D\u8D56\u3002
|
|
42
|
+
|
|
43
|
+
- \u274C \`effect(() => { count.value++ })\` \u2014\u2014 \u8BFB+\u5199\u540C\u4E00\u4E2A\u4FE1\u53F7 = \u81EA\u8BA2\u9605\u5FAA\u73AF
|
|
44
|
+
- \u274C \`effect(() => { if (session.value) void sync() })\` \u2014\u2014 **\u300C\u53EA\u8C03\u4E86\u4E2A\u51FD\u6570\u300D\u4E0D\u7B49\u4E8E\u6CA1\u4F9D\u8D56**\uFF1A
|
|
45
|
+
\u88AB\u8C03\u51FD\u6570\u5728**\u9996\u4E2A \`await\` \u4E4B\u524D**\u7684\u4EE3\u7801\u662F**\u540C\u6B65\u6267\u884C**\u7684\uFF0C\u5B83\u8BFB\u7684\u4FE1\u53F7\u7B97\u5728 effect \u5934\u4E0A
|
|
46
|
+
- \u2705 \u53EA\u60F3\u58F0\u660E\u4F9D\u8D56\uFF1A**\`effect(on(deps, () => { \u2026 }))\`** \u2014\u2014 \u56DE\u8C03\u5728 untrack \u4F5C\u7528\u57DF\u91CC\u8DD1\uFF0C
|
|
47
|
+
\u5B83\u8C03\u7528\u7684\u51FD\u6570\u78B0\u4EC0\u4E48\u4FE1\u53F7\u90FD\u4E0D\u4F1A\u53CD\u5411\u8BA2\u9605
|
|
48
|
+
- \u2705 \u6D3E\u751F\u503C\u7528 \`memo\`\uFF0C\u4E0D\u8981"\u8BFB A \u5199 B"
|
|
49
|
+
- \u515C\u5E95\u624D\u662F \`untrack(() => { X.value = next })\`
|
|
50
|
+
|
|
51
|
+
**\u522B\u5728 effect \u91CC\u8C03 async \u51FD\u6570**\uFF08\`VOBS_C106\`\uFF09\uFF1A\`effect\` \u4E0D\u7B49\u5B83\uFF0C
|
|
52
|
+
\u4E14\u9996\u4E2A \`await\` \u4E4B\u524D\u7684\u90E8\u5206\u662F\u540C\u6B65\u7684\u3002\u5F02\u6B65\u53D6\u6570\u7528 **\`@vobs/resource\`**\u3002
|
|
53
|
+
|
|
54
|
+
## \u56DB\u3001\u5BA2\u6237\u7AEF\u526F\u4F5C\u7528\u4E0E SSR
|
|
55
|
+
|
|
56
|
+
- \u5B9A\u65F6\u5668 / \u76D1\u542C / \`matchMedia\` / \`localStorage\` \u2192 **\`onMount\` \u542F\u52A8\u3001\`onDestroy\` \u6E05\u7406**
|
|
57
|
+
\uFF08**\u4E0D\u8981\u624B\u5199 \`typeof window\` \u5B88\u536B**\uFF09
|
|
58
|
+
- **\u6E32\u67D3\u8F93\u51FA\u672C\u8EAB**\u4F9D\u8D56\u6D4F\u89C8\u5668\uFF08\u7A97\u53E3\u5C3A\u5BF8 / \`localStorage\` \u56DE\u586B / \`Date.now\` / \u968F\u673A\u503C\uFF09
|
|
59
|
+
\u2192 **\`<ClientOnly fallback={\u2026}>\`**\uFF08\u9996\u8F6E\u4E24\u4FA7\u90FD\u6E32\u67D3 fallback\uFF0C\u6C34\u5408\u5BF9\u5F97\u4E0A\uFF09
|
|
60
|
+
- \u6A21\u5757\u9876\u5C42**\u7981\u6B62** JSX\uFF08\`VOBS_C105\`\uFF09\uFF1Aimport \u6C42\u503C\u65E9\u4E8E\u6E32\u67D3\u5668\u5B89\u88C5
|
|
61
|
+
|
|
62
|
+
## \u4E94\u3001\u5217\u8868\u4E0E\u6570\u636E
|
|
63
|
+
|
|
64
|
+
- **\u6570\u7EC4\u66F4\u65B0\u5FC5\u987B\u6362\u5F15\u7528**\uFF1A\`list.value = [...list.value, item]\`\uFF1B
|
|
65
|
+
\u539F\u5730 \`push\` / \u6539\u5B57\u6BB5**\u4E0D\u89E6\u53D1\u66F4\u65B0**
|
|
66
|
+
- JSX **\u5B50\u8282\u70B9\u4F4D\u7F6E**\u53EA\u653E\u56DB\u79CD\u5F62\u6001\uFF1A\u7EC4\u4EF6\u6807\u7B7E / \u5143\u7D20 / \u4E24\u5206\u652F\u4E09\u5143 / \`.map()\`
|
|
67
|
+
- \u6570\u636E\u7ED3\u6784\u91CC**\u53EA\u5B58\u7EAF\u63CF\u8FF0**\uFF08\u5B57\u7B26\u4E32/\u6837\u5F0F/\u7ED3\u6784\u5B57\u6BB5\uFF09\uFF0C**\u8282\u70B9\u5BF9\u8C61\u522B\u8FDB\u6570\u636E\u5E38\u91CF**
|
|
68
|
+
\uFF08SSG \u5E8F\u5217\u5316\u4F1A\u70B8\uFF0C\u4EA7\u7269\u51FA\u73B0 \`[object Xxx]\` \u65F6\u6846\u67B6\u4F1A\u76F4\u63A5\u62A5\u9519\uFF09
|
|
69
|
+
|
|
70
|
+
## \u516D\u3001\u8F93\u5165
|
|
71
|
+
|
|
72
|
+
- **\u6570\u5B57\u8F93\u5165\u8D70 \`parseNumber\`**\uFF08\`<Field type="number">\` \u5DF2\u5185\u7F6E\uFF09\uFF1A
|
|
73
|
+
\u7A7A\u4E32 / \u975E\u6CD5 / \u8D85\u754C**\u4E0D\u63D0\u4EA4**\uFF0C\u4FDD\u6301\u539F\u503C
|
|
74
|
+
\u2014\u2014 \`Number('') === 0\` \u4F1A\u628A\u8F93\u5165\u6E05\u6210 0 \u5E76\u6CBF\u8054\u52A8\u94FE\u8DEF\u6E05\u96F6\u5144\u5F1F\u7EF4\u5EA6
|
|
75
|
+
- \`<select>\` \u7684 value \u76F4\u63A5\u7ED1\uFF0C**\u4E0D\u8981\u5199 ref \u515C\u5E95**\uFF081.5.1+ \u5DF2\u4FEE\u65F6\u5E8F\uFF09
|
|
76
|
+
|
|
77
|
+
## \u4E03\u3001\u56FE\u6807\u4E0E SVG
|
|
78
|
+
|
|
79
|
+
- **SVG \u76F4\u63A5\u5199 JSX**\uFF08\`<svg><path/></svg>\`\uFF0C1.7.4+ \u6309 namespace \u521B\u5EFA\u4E0E\u6C34\u5408\uFF09
|
|
80
|
+
- \u56FE\u6807\u8981**\u767B\u8BB0\u8FDB\u767D\u540D\u5355**\uFF1B\u67E5\u8868 miss \u65F6 \`@vobs/icon-core\` \u4F1A\u8B66\u544A\u70B9\u540D\uFF08\u4F46\u4ECD\u5E94\u767B\u8BB0\uFF09
|
|
81
|
+
|
|
82
|
+
## \u516B\u3001\u63D0\u4EA4\u524D\u81EA\u6D4B
|
|
83
|
+
|
|
84
|
+
\`\`\`bash
|
|
85
|
+
pnpm run check:source # = vobs check\uFF0C\u5168\u4ED3\u4E00\u6B21\u5217\u51FA\u5168\u90E8\u8BCA\u65AD
|
|
86
|
+
pnpm run check:runtime # \u771F\u5B9E\u6D4F\u89C8\u5668\u9010\u8DEF\u7531\u8DD1\u62A4\u680F\uFF08\u9700 Chrome\uFF09
|
|
87
|
+
pnpm run check:runtime:interact # \u518D\u70B9\u6240\u6709\u6309\u94AE\u3001\u89E6\u53D1\u6240\u6709\u8F93\u5165
|
|
88
|
+
\`\`\`
|
|
89
|
+
|
|
90
|
+
\`vite build\` **\u4F1A**\u6253\u5370\u7F16\u8BD1\u671F\u8B66\u544A\uFF08\`C104\`/\`C105\`/\`C106\`/\`C107\`\uFF09\uFF0C\u4F46\u53EA\u8986\u76D6\u5B83\u7F16\u8BD1\u5230\u7684\u6587\u4EF6\uFF1B
|
|
91
|
+
\`check:source\` \u624D\u662F\u5168\u4ED3\u5165\u53E3\u3002
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## \u4E00\u53E5\u8BDD\u901F\u8BB0
|
|
96
|
+
|
|
97
|
+
| \u4E3B\u9898 | \u4E00\u53E5\u8BDD |
|
|
98
|
+
|---|---|
|
|
99
|
+
| \u7EC4\u4EF6\u4F53 | \u53EA\u8DD1\u4E00\u6B21\uFF0C\u6D3E\u751F\u8FDB JSX |
|
|
100
|
+
| \u56DE\u8C03 | \u89E6\u53D1\u65F6\u91CD\u8BFB \`.value\` |
|
|
101
|
+
| \u6761\u4EF6\u6E32\u67D3 | \u8DEF\u7531\u7528 \`RouterView\`\uFF0C\u663E\u9690\u7528 \`Show\`\uFF0C\u522B\u5728\u7EC4\u4EF6\u4F53 return \u5206\u652F |
|
|
102
|
+
| effect | \u522B\u5199\u81EA\u5DF1\u8BFB\u7684\u4FE1\u53F7\uFF0C\u7528 \`on(deps, fn)\` |
|
|
103
|
+
| async | \u522B\u585E\u8FDB effect\uFF0C\u7528 \`@vobs/resource\` |
|
|
104
|
+
| \u5BA2\u6237\u7AEF | \`onMount\`/\`onDestroy\`/\`ClientOnly\`\uFF0C\u522B\u624B\u5199 \`typeof window\` |
|
|
105
|
+
| \u5217\u8868 | \u6362\u5F15\u7528 |
|
|
106
|
+
| \u6570\u5B57 | \`parseNumber\` |
|
|
107
|
+
| SVG | \u76F4\u63A5\u5199 |
|
|
108
|
+
| \u81EA\u6D4B | \`check:source\` + \`check:runtime\` |
|
|
109
|
+
`;
|
|
110
|
+
export {
|
|
111
|
+
AGENT_CONTRACT
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=agent-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/templates/agent-contract.ts"],"sourcesContent":["/**\n * vobs 框架契约正文 —— `vobs agent-doc` 的输出来源。\n *\n * 为什么是 TS 模块而不是 .md 文件:读文件要靠 `import.meta.url` 解析相对路径,\n * 而**全量测试时 vitest 会转换模块**,`import.meta.url` 指向虚拟路径,\n * `new URL('../../src/templates/…')` 就找不到文件(实测:单独跑 8 条全过、\n * 全量跑 8 条全红)。内联成字符串**免疫打包与转换**,也省掉发布时拷模板。\n *\n * 内容由 `packages/cli/src/templates/agent-contract.md` 生成(保留 .md 作可读源);\n * 改契约请改 .md 后重新生成,或直接改这里的字符串。\n */\nexport const AGENT_CONTRACT = `# vobs 框架契约(写代码前必读)\n\nvobs 是**编译型 / 细粒度 / 响应式**框架,与 React 的心智模型有几处**根本不同**。\n下面的每一条都来自真实事故,**违反时通常编译通过、运行期才错**。\n\n> 本文档由 \\`vobs agent-doc\\` 从框架版本生成 —— 不要手改生成的块。\n> 每条后面括号里是**违反时框架会报的诊断码**。\n\n## 一、组件体只执行一次(run-once)\n\n**组件函数体只在创建时跑一次**,之后只有 JSX 里订阅的位置更新。\n\n- ❌ \\`const filtered = list.value.filter(...)\\` —— 一次快照,之后永不更新\n- ✅ 派生放进 JSX:\\`<div>{list.value.filter(...).map(render)}</div>\\`\n- ❌ 组件体里取快照给回调用:\\`const v = name.value; onClick={() => save(v)}\\`\n- ✅ 回调内重读:\\`onClick={() => save(name.value)}\\`\n- 动态 props 用 getter 形态,不要传求值后的值\n\n## 二、条件渲染:**不要在组件体里 \\`return\\` 分支**(\\`VOBS_C107\\` / \\`VOBS_C104\\`)\n\n组件体里读信号的 \\`return\\` 在挂载时固化,**三种写法坏得一模一样**:\n\n\\`\\`\\`tsx\nreturn cond.value ? <A/> : <B/> // ❌ 冻结\nif (cond.value) return <A/> // ❌ 冻结\nreturn cond.value ? <A/> : null // ❌ 冻结\n\\`\\`\\`\n\n顶层 \\`return\\` 处**没有 parent/anchor**,所以两支都是 JSX 也一样不会重分支。\n\n| 场景 | 用什么 |\n|---|---|\n| **路由分支** | **\\`<RouterView/>\\`**(声明式,内部 \\`insertDynamic\\` + \\`resetKey\\`) |\n| 保留挂载、只切显隐 | **\\`<Show when={cond}>\\`**(切 \\`hidden\\` + \\`inert\\`,焦点/滚动/内部状态不丢) |\n| 只切类名 | \\`class=\"base\" classList={{ 'is-on': cond.value }}\\` |\n| 普通条件渲染 | 放进 **JSX 子节点位置**:\\`<div>{cond.value ? <A/> : <B/>}</div>\\` |\n\n## 三、effect 里不要写自己读过的信号(\\`VOBS_C210\\` / \\`VOBS_C211\\`)\n\n**依赖是自动收集的**:effect 运行期间读到的**任何**信号都会变成依赖。\n\n- ❌ \\`effect(() => { count.value++ })\\` —— 读+写同一个信号 = 自订阅循环\n- ❌ \\`effect(() => { if (session.value) void sync() })\\` —— **「只调了个函数」不等于没依赖**:\n 被调函数在**首个 \\`await\\` 之前**的代码是**同步执行**的,它读的信号算在 effect 头上\n- ✅ 只想声明依赖:**\\`effect(on(deps, () => { … }))\\`** —— 回调在 untrack 作用域里跑,\n 它调用的函数碰什么信号都不会反向订阅\n- ✅ 派生值用 \\`memo\\`,不要\"读 A 写 B\"\n- 兜底才是 \\`untrack(() => { X.value = next })\\`\n\n**别在 effect 里调 async 函数**(\\`VOBS_C106\\`):\\`effect\\` 不等它,\n且首个 \\`await\\` 之前的部分是同步的。异步取数用 **\\`@vobs/resource\\`**。\n\n## 四、客户端副作用与 SSR\n\n- 定时器 / 监听 / \\`matchMedia\\` / \\`localStorage\\` → **\\`onMount\\` 启动、\\`onDestroy\\` 清理**\n (**不要手写 \\`typeof window\\` 守卫**)\n- **渲染输出本身**依赖浏览器(窗口尺寸 / \\`localStorage\\` 回填 / \\`Date.now\\` / 随机值)\n → **\\`<ClientOnly fallback={…}>\\`**(首轮两侧都渲染 fallback,水合对得上)\n- 模块顶层**禁止** JSX(\\`VOBS_C105\\`):import 求值早于渲染器安装\n\n## 五、列表与数据\n\n- **数组更新必须换引用**:\\`list.value = [...list.value, item]\\`;\n 原地 \\`push\\` / 改字段**不触发更新**\n- JSX **子节点位置**只放四种形态:组件标签 / 元素 / 两分支三元 / \\`.map()\\`\n- 数据结构里**只存纯描述**(字符串/样式/结构字段),**节点对象别进数据常量**\n (SSG 序列化会炸,产物出现 \\`[object Xxx]\\` 时框架会直接报错)\n\n## 六、输入\n\n- **数字输入走 \\`parseNumber\\`**(\\`<Field type=\"number\">\\` 已内置):\n 空串 / 非法 / 超界**不提交**,保持原值\n —— \\`Number('') === 0\\` 会把输入清成 0 并沿联动链路清零兄弟维度\n- \\`<select>\\` 的 value 直接绑,**不要写 ref 兜底**(1.5.1+ 已修时序)\n\n## 七、图标与 SVG\n\n- **SVG 直接写 JSX**(\\`<svg><path/></svg>\\`,1.7.4+ 按 namespace 创建与水合)\n- 图标要**登记进白名单**;查表 miss 时 \\`@vobs/icon-core\\` 会警告点名(但仍应登记)\n\n## 八、提交前自测\n\n\\`\\`\\`bash\npnpm run check:source # = vobs check,全仓一次列出全部诊断\npnpm run check:runtime # 真实浏览器逐路由跑护栏(需 Chrome)\npnpm run check:runtime:interact # 再点所有按钮、触发所有输入\n\\`\\`\\`\n\n\\`vite build\\` **会**打印编译期警告(\\`C104\\`/\\`C105\\`/\\`C106\\`/\\`C107\\`),但只覆盖它编译到的文件;\n\\`check:source\\` 才是全仓入口。\n\n---\n\n## 一句话速记\n\n| 主题 | 一句话 |\n|---|---|\n| 组件体 | 只跑一次,派生进 JSX |\n| 回调 | 触发时重读 \\`.value\\` |\n| 条件渲染 | 路由用 \\`RouterView\\`,显隐用 \\`Show\\`,别在组件体 return 分支 |\n| effect | 别写自己读的信号,用 \\`on(deps, fn)\\` |\n| async | 别塞进 effect,用 \\`@vobs/resource\\` |\n| 客户端 | \\`onMount\\`/\\`onDestroy\\`/\\`ClientOnly\\`,别手写 \\`typeof window\\` |\n| 列表 | 换引用 |\n| 数字 | \\`parseNumber\\` |\n| SVG | 直接写 |\n| 自测 | \\`check:source\\` + \\`check:runtime\\` |\n`\n"],"mappings":";AAWO,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# vobs 框架契约(写代码前必读)
|
|
2
|
+
|
|
3
|
+
vobs 是**编译型 / 细粒度 / 响应式**框架,与 React 的心智模型有几处**根本不同**。
|
|
4
|
+
下面的每一条都来自真实事故,**违反时通常编译通过、运行期才错**。
|
|
5
|
+
|
|
6
|
+
> 本文档由 `vobs agent-doc` 从框架版本生成 —— 不要手改生成的块。
|
|
7
|
+
> 每条后面括号里是**违反时框架会报的诊断码**。
|
|
8
|
+
|
|
9
|
+
## 一、组件体只执行一次(run-once)
|
|
10
|
+
|
|
11
|
+
**组件函数体只在创建时跑一次**,之后只有 JSX 里订阅的位置更新。
|
|
12
|
+
|
|
13
|
+
- ❌ `const filtered = list.value.filter(...)` —— 一次快照,之后永不更新
|
|
14
|
+
- ✅ 派生放进 JSX:`<div>{list.value.filter(...).map(render)}</div>`
|
|
15
|
+
- ❌ 组件体里取快照给回调用:`const v = name.value; onClick={() => save(v)}`
|
|
16
|
+
- ✅ 回调内重读:`onClick={() => save(name.value)}`
|
|
17
|
+
- 动态 props 用 getter 形态,不要传求值后的值
|
|
18
|
+
|
|
19
|
+
## 二、条件渲染:**不要在组件体里 `return` 分支**(`VOBS_C107` / `VOBS_C104`)
|
|
20
|
+
|
|
21
|
+
组件体里读信号的 `return` 在挂载时固化,**三种写法坏得一模一样**:
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
return cond.value ? <A/> : <B/> // ❌ 冻结
|
|
25
|
+
if (cond.value) return <A/> // ❌ 冻结
|
|
26
|
+
return cond.value ? <A/> : null // ❌ 冻结
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
顶层 `return` 处**没有 parent/anchor**,所以两支都是 JSX 也一样不会重分支。
|
|
30
|
+
|
|
31
|
+
| 场景 | 用什么 |
|
|
32
|
+
|---|---|
|
|
33
|
+
| **路由分支** | **`<RouterView/>`**(声明式,内部 `insertDynamic` + `resetKey`) |
|
|
34
|
+
| 保留挂载、只切显隐 | **`<Show when={cond}>`**(切 `hidden` + `inert`,焦点/滚动/内部状态不丢) |
|
|
35
|
+
| 只切类名 | `class="base" classList={{ 'is-on': cond.value }}` |
|
|
36
|
+
| 普通条件渲染 | 放进 **JSX 子节点位置**:`<div>{cond.value ? <A/> : <B/>}</div>` |
|
|
37
|
+
|
|
38
|
+
## 三、effect 里不要写自己读过的信号(`VOBS_C210` / `VOBS_C211`)
|
|
39
|
+
|
|
40
|
+
**依赖是自动收集的**:effect 运行期间读到的**任何**信号都会变成依赖。
|
|
41
|
+
|
|
42
|
+
- ❌ `effect(() => { count.value++ })` —— 读+写同一个信号 = 自订阅循环
|
|
43
|
+
- ❌ `effect(() => { if (session.value) void sync() })` —— **「只调了个函数」不等于没依赖**:
|
|
44
|
+
被调函数在**首个 `await` 之前**的代码是**同步执行**的,它读的信号算在 effect 头上
|
|
45
|
+
- ✅ 只想声明依赖:**`effect(on(deps, () => { … }))`** —— 回调在 untrack 作用域里跑,
|
|
46
|
+
它调用的函数碰什么信号都不会反向订阅
|
|
47
|
+
- ✅ 派生值用 `memo`,不要"读 A 写 B"
|
|
48
|
+
- 兜底才是 `untrack(() => { X.value = next })`
|
|
49
|
+
|
|
50
|
+
**别在 effect 里调 async 函数**(`VOBS_C106`):`effect` 不等它,
|
|
51
|
+
且首个 `await` 之前的部分是同步的。异步取数用 **`@vobs/resource`**。
|
|
52
|
+
|
|
53
|
+
## 四、客户端副作用与 SSR
|
|
54
|
+
|
|
55
|
+
- 定时器 / 监听 / `matchMedia` / `localStorage` → **`onMount` 启动、`onDestroy` 清理**
|
|
56
|
+
(**不要手写 `typeof window` 守卫**)
|
|
57
|
+
- **渲染输出本身**依赖浏览器(窗口尺寸 / `localStorage` 回填 / `Date.now` / 随机值)
|
|
58
|
+
→ **`<ClientOnly fallback={…}>`**(首轮两侧都渲染 fallback,水合对得上)
|
|
59
|
+
- 模块顶层**禁止** JSX(`VOBS_C105`):import 求值早于渲染器安装
|
|
60
|
+
|
|
61
|
+
## 五、列表与数据
|
|
62
|
+
|
|
63
|
+
- **数组更新必须换引用**:`list.value = [...list.value, item]`;
|
|
64
|
+
原地 `push` / 改字段**不触发更新**
|
|
65
|
+
- JSX **子节点位置**只放四种形态:组件标签 / 元素 / 两分支三元 / `.map()`
|
|
66
|
+
- 数据结构里**只存纯描述**(字符串/样式/结构字段),**节点对象别进数据常量**
|
|
67
|
+
(SSG 序列化会炸,产物出现 `[object Xxx]` 时框架会直接报错)
|
|
68
|
+
|
|
69
|
+
## 六、输入
|
|
70
|
+
|
|
71
|
+
- **数字输入走 `parseNumber`**(`<Field type="number">` 已内置):
|
|
72
|
+
空串 / 非法 / 超界**不提交**,保持原值
|
|
73
|
+
—— `Number('') === 0` 会把输入清成 0 并沿联动链路清零兄弟维度
|
|
74
|
+
- `<select>` 的 value 直接绑,**不要写 ref 兜底**(1.5.1+ 已修时序)
|
|
75
|
+
|
|
76
|
+
## 七、图标与 SVG
|
|
77
|
+
|
|
78
|
+
- **SVG 直接写 JSX**(`<svg><path/></svg>`,1.7.4+ 按 namespace 创建与水合)
|
|
79
|
+
- 图标要**登记进白名单**;查表 miss 时 `@vobs/icon-core` 会警告点名(但仍应登记)
|
|
80
|
+
|
|
81
|
+
## 八、提交前自测
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pnpm run check:source # = vobs check,全仓一次列出全部诊断
|
|
85
|
+
pnpm run check:runtime # 真实浏览器逐路由跑护栏(需 Chrome)
|
|
86
|
+
pnpm run check:runtime:interact # 再点所有按钮、触发所有输入
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`vite build` **会**打印编译期警告(`C104`/`C105`/`C106`/`C107`),但只覆盖它编译到的文件;
|
|
90
|
+
`check:source` 才是全仓入口。
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 一句话速记
|
|
95
|
+
|
|
96
|
+
| 主题 | 一句话 |
|
|
97
|
+
|---|---|
|
|
98
|
+
| 组件体 | 只跑一次,派生进 JSX |
|
|
99
|
+
| 回调 | 触发时重读 `.value` |
|
|
100
|
+
| 条件渲染 | 路由用 `RouterView`,显隐用 `Show`,别在组件体 return 分支 |
|
|
101
|
+
| effect | 别写自己读的信号,用 `on(deps, fn)` |
|
|
102
|
+
| async | 别塞进 effect,用 `@vobs/resource` |
|
|
103
|
+
| 客户端 | `onMount`/`onDestroy`/`ClientOnly`,别手写 `typeof window` |
|
|
104
|
+
| 列表 | 换引用 |
|
|
105
|
+
| 数字 | `parseNumber` |
|
|
106
|
+
| SVG | 直接写 |
|
|
107
|
+
| 自测 | `check:source` + `check:runtime` |
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { generateCommand } from './commands/generate.js'
|
|
|
6
6
|
import { addCommand } from './commands/add.js'
|
|
7
7
|
import { dshCommand } from './commands/dsh.js'
|
|
8
8
|
import { checkCommand } from './commands/check.js'
|
|
9
|
+
import { agentDocCommand } from './commands/agent-doc.js'
|
|
9
10
|
import { showBanner, showLogo, showDevBanner } from './banner.js'
|
|
10
11
|
import { cliVersion } from './version.js'
|
|
11
12
|
|
|
@@ -108,6 +109,21 @@ export function createCLI(): ReturnType<typeof cac> {
|
|
|
108
109
|
})
|
|
109
110
|
})
|
|
110
111
|
|
|
112
|
+
cli
|
|
113
|
+
.command('agent-doc', '生成/校验给 LLM 读的框架契约(AGENTS.md + CLAUDE.md)')
|
|
114
|
+
.option('--dir <dir>', '目标目录', { default: process.cwd() })
|
|
115
|
+
.option('--write', '写入文件(默认打到 stdout)')
|
|
116
|
+
.option('--check', '校验项目里那份是否与当前框架版本一致(可进 CI)')
|
|
117
|
+
.option('--body', '只输出契约正文(不含标记块外说明)')
|
|
118
|
+
.action(async (options: Record<string, unknown>) => {
|
|
119
|
+
await agentDocCommand({
|
|
120
|
+
dir: options.dir as string | undefined,
|
|
121
|
+
write: options.write === true,
|
|
122
|
+
check: options.check === true,
|
|
123
|
+
body: options.body === true
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
111
127
|
cli
|
|
112
128
|
.command('dsh [action] [target]', 'DSH 插件工具链(init / dev / build / check / install)')
|
|
113
129
|
.option('--dir <dir>', 'init:目标目录')
|
|
@@ -160,5 +176,5 @@ export function createCLI(): ReturnType<typeof cac> {
|
|
|
160
176
|
* 用这份清单把"跑某个命令"与"顶层用法错误"分开。
|
|
161
177
|
*/
|
|
162
178
|
export const KNOWN_SUBCOMMANDS: readonly string[] = [
|
|
163
|
-
'init', 'create', 'dev', 'build', 'generate', 'g', 'add', 'check', 'dsh', 'logo'
|
|
179
|
+
'init', 'create', 'dev', 'build', 'generate', 'g', 'add', 'check', 'dsh', 'agent-doc', 'logo'
|
|
164
180
|
]
|