dekit-cli 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -26
- package/dist/editor/assets/{index-HLKs1tz-.css → index-DKRQEtBb.css} +1 -1
- package/dist/editor/assets/index-E_PyZ4Xf.js +57 -0
- package/dist/editor/index.html +2 -2
- package/docs/usage.md +196 -7
- package/editor/src/App.tsx +130 -6
- package/editor/src/styles/editor.css +67 -0
- package/editor/src/types.ts +14 -0
- package/package.json +2 -2
- package/src/cli/init.test.ts +22 -0
- package/src/cli/init.ts +12 -1
- package/src/cli/screenshot.ts +21 -7
- package/src/editor-server.ts +8 -0
- package/src/parser.test.ts +164 -0
- package/src/parser.ts +74 -3
- package/src/types.ts +16 -0
- package/src/vite-plugin-dekit.test.ts +85 -1
- package/src/vite-plugin-dekit.ts +42 -2
- package/templates/projects/mobile/dekit.yaml +1 -0
- package/dist/editor/assets/index-D16Bm-Rw.js +0 -57
package/src/editor-server.ts
CHANGED
|
@@ -20,10 +20,18 @@ export async function startEditorServer(
|
|
|
20
20
|
for (const [name, def] of Object.entries(config.pages)) {
|
|
21
21
|
pagePaths[name] = join(config.baseDir, def.template);
|
|
22
22
|
}
|
|
23
|
+
const pageProperties: Record<string, Record<string, { type: string; default: unknown }>> = {};
|
|
24
|
+
for (const [name, def] of Object.entries(config.pages)) {
|
|
25
|
+
if (def.properties) {
|
|
26
|
+
pageProperties[name] = def.properties;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
23
29
|
const payload = {
|
|
24
30
|
pages: Object.keys(config.pages),
|
|
25
31
|
pagePaths,
|
|
32
|
+
pageProperties,
|
|
26
33
|
components: Object.keys(config.components),
|
|
34
|
+
device: config.device ?? null,
|
|
27
35
|
designServerUrl: `http://localhost:${designPort}`,
|
|
28
36
|
};
|
|
29
37
|
res.setHeader("Content-Type", "application/json");
|
package/src/parser.test.ts
CHANGED
|
@@ -55,6 +55,170 @@ pages:
|
|
|
55
55
|
).rejects.toThrow(/not found|does not exist/i);
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
test("parses device as preset name string", async () => {
|
|
59
|
+
const yaml = `
|
|
60
|
+
version: 1.0
|
|
61
|
+
device: "iPhone 16"
|
|
62
|
+
pages: {}
|
|
63
|
+
`;
|
|
64
|
+
const config = await parseDesignConfig("/fake/dekit.yaml", yaml);
|
|
65
|
+
expect(config.device).toEqual({
|
|
66
|
+
width: 393,
|
|
67
|
+
height: 852,
|
|
68
|
+
dpr: 3,
|
|
69
|
+
name: "iPhone 16",
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("parses device as custom dimensions object", async () => {
|
|
74
|
+
const yaml = `
|
|
75
|
+
version: 1.0
|
|
76
|
+
device:
|
|
77
|
+
width: 400
|
|
78
|
+
height: 800
|
|
79
|
+
dpr: 2
|
|
80
|
+
pages: {}
|
|
81
|
+
`;
|
|
82
|
+
const config = await parseDesignConfig("/fake/dekit.yaml", yaml);
|
|
83
|
+
expect(config.device).toEqual({
|
|
84
|
+
width: 400,
|
|
85
|
+
height: 800,
|
|
86
|
+
dpr: 2,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("device defaults dpr to 1 when omitted", async () => {
|
|
91
|
+
const yaml = `
|
|
92
|
+
version: 1.0
|
|
93
|
+
device:
|
|
94
|
+
width: 400
|
|
95
|
+
height: 800
|
|
96
|
+
pages: {}
|
|
97
|
+
`;
|
|
98
|
+
const config = await parseDesignConfig("/fake/dekit.yaml", yaml);
|
|
99
|
+
expect(config.device!.dpr).toBe(1);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("device is undefined when not specified", async () => {
|
|
103
|
+
const yaml = `
|
|
104
|
+
version: 1.0
|
|
105
|
+
pages: {}
|
|
106
|
+
`;
|
|
107
|
+
const config = await parseDesignConfig("/fake/dekit.yaml", yaml);
|
|
108
|
+
expect(config.device).toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("rejects unknown device preset name", async () => {
|
|
112
|
+
const yaml = `
|
|
113
|
+
version: 1.0
|
|
114
|
+
device: "Nokia 3310"
|
|
115
|
+
pages: {}
|
|
116
|
+
`;
|
|
117
|
+
await expect(
|
|
118
|
+
parseDesignConfig("/fake/dekit.yaml", yaml)
|
|
119
|
+
).rejects.toThrow(/Unknown device/i);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("rejects device object without width/height", async () => {
|
|
123
|
+
const yaml = `
|
|
124
|
+
version: 1.0
|
|
125
|
+
device:
|
|
126
|
+
dpr: 2
|
|
127
|
+
pages: {}
|
|
128
|
+
`;
|
|
129
|
+
await expect(
|
|
130
|
+
parseDesignConfig("/fake/dekit.yaml", yaml)
|
|
131
|
+
).rejects.toThrow(/width.*height/i);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("parses page properties", async () => {
|
|
135
|
+
const yaml = `
|
|
136
|
+
version: 1.0
|
|
137
|
+
pages:
|
|
138
|
+
home:
|
|
139
|
+
template: "pages/home/home.html"
|
|
140
|
+
style: "pages/home/home.css"
|
|
141
|
+
properties:
|
|
142
|
+
showBanner:
|
|
143
|
+
type: boolean
|
|
144
|
+
default: true
|
|
145
|
+
itemCount:
|
|
146
|
+
type: number
|
|
147
|
+
default: 3
|
|
148
|
+
title:
|
|
149
|
+
type: string
|
|
150
|
+
default: "Hello"
|
|
151
|
+
`;
|
|
152
|
+
const config = await parseDesignConfig("/fake/dekit.yaml", yaml);
|
|
153
|
+
expect(config.pages["home"].properties).toEqual({
|
|
154
|
+
showBanner: { type: "boolean", default: true },
|
|
155
|
+
itemCount: { type: "number", default: 3 },
|
|
156
|
+
title: { type: "string", default: "Hello" },
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("page without properties has undefined properties", async () => {
|
|
161
|
+
const yaml = `
|
|
162
|
+
version: 1.0
|
|
163
|
+
pages:
|
|
164
|
+
home:
|
|
165
|
+
template: "pages/home/home.html"
|
|
166
|
+
style: "pages/home/home.css"
|
|
167
|
+
`;
|
|
168
|
+
const config = await parseDesignConfig("/fake/dekit.yaml", yaml);
|
|
169
|
+
expect(config.pages["home"].properties).toBeUndefined();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("rejects invalid property type", async () => {
|
|
173
|
+
const yaml = `
|
|
174
|
+
version: 1.0
|
|
175
|
+
pages:
|
|
176
|
+
home:
|
|
177
|
+
template: "pages/home/home.html"
|
|
178
|
+
style: "pages/home/home.css"
|
|
179
|
+
properties:
|
|
180
|
+
color:
|
|
181
|
+
type: color
|
|
182
|
+
default: red
|
|
183
|
+
`;
|
|
184
|
+
await expect(
|
|
185
|
+
parseDesignConfig("/fake/dekit.yaml", yaml)
|
|
186
|
+
).rejects.toThrow(/Invalid type.*color/i);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("rejects property with mismatched default type", async () => {
|
|
190
|
+
const yaml = `
|
|
191
|
+
version: 1.0
|
|
192
|
+
pages:
|
|
193
|
+
home:
|
|
194
|
+
template: "pages/home/home.html"
|
|
195
|
+
style: "pages/home/home.css"
|
|
196
|
+
properties:
|
|
197
|
+
count:
|
|
198
|
+
type: number
|
|
199
|
+
default: "not a number"
|
|
200
|
+
`;
|
|
201
|
+
await expect(
|
|
202
|
+
parseDesignConfig("/fake/dekit.yaml", yaml)
|
|
203
|
+
).rejects.toThrow(/must be a number/i);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("rejects property missing default", async () => {
|
|
207
|
+
const yaml = `
|
|
208
|
+
version: 1.0
|
|
209
|
+
pages:
|
|
210
|
+
home:
|
|
211
|
+
template: "pages/home/home.html"
|
|
212
|
+
style: "pages/home/home.css"
|
|
213
|
+
properties:
|
|
214
|
+
flag:
|
|
215
|
+
type: boolean
|
|
216
|
+
`;
|
|
217
|
+
await expect(
|
|
218
|
+
parseDesignConfig("/fake/dekit.yaml", yaml)
|
|
219
|
+
).rejects.toThrow(/Missing default/i);
|
|
220
|
+
});
|
|
221
|
+
|
|
58
222
|
test("rejects missing version field", async () => {
|
|
59
223
|
const yaml = `
|
|
60
224
|
global-style: "global.css"
|
package/src/parser.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import yaml from "js-yaml";
|
|
5
|
-
import type { DesignConfig, ComponentDef, PageDef } from "./types.js";
|
|
5
|
+
import type { DesignConfig, DeviceConfig, ComponentDef, PageDef, PropertyDef, PropertyType } from "./types.js";
|
|
6
|
+
import { DEVICE_PRESETS } from "./devices.js";
|
|
6
7
|
|
|
7
8
|
export async function parseDesignConfig(
|
|
8
9
|
configPath: string,
|
|
@@ -24,6 +25,8 @@ export async function parseDesignConfig(
|
|
|
24
25
|
// Validate file paths if the base directory actually exists on disk
|
|
25
26
|
const shouldValidateFiles = existsSync(baseDir);
|
|
26
27
|
|
|
28
|
+
const device = parseDevice(doc.device);
|
|
29
|
+
|
|
27
30
|
const globalStyle = (doc["global-style"] as string) ?? "";
|
|
28
31
|
if (globalStyle && shouldValidateFiles) {
|
|
29
32
|
const globalStylePath = join(baseDir, globalStyle);
|
|
@@ -51,24 +54,92 @@ export async function parseDesignConfig(
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
const rawPages =
|
|
54
|
-
(doc.pages as Record<string, { template: string; style: string }>) ?? {};
|
|
57
|
+
(doc.pages as Record<string, { template: string; style: string; properties?: Record<string, unknown> }>) ?? {};
|
|
55
58
|
const pages: Record<string, PageDef> = {};
|
|
56
59
|
for (const [key, val] of Object.entries(rawPages)) {
|
|
57
60
|
if (shouldValidateFiles) {
|
|
58
61
|
validateFilePaths(baseDir, key, val.template, val.style);
|
|
59
62
|
}
|
|
60
|
-
|
|
63
|
+
const properties = val.properties ? parseProperties(key, val.properties) : undefined;
|
|
64
|
+
pages[key] = { template: val.template, style: val.style, properties };
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
return {
|
|
64
68
|
version: doc.version as number,
|
|
65
69
|
globalStyle,
|
|
70
|
+
device,
|
|
66
71
|
components,
|
|
67
72
|
pages,
|
|
68
73
|
baseDir,
|
|
69
74
|
};
|
|
70
75
|
}
|
|
71
76
|
|
|
77
|
+
function parseDevice(raw: unknown): DeviceConfig | undefined {
|
|
78
|
+
if (raw == null) return undefined;
|
|
79
|
+
|
|
80
|
+
if (typeof raw === "string") {
|
|
81
|
+
const normalizedInput = raw.toLowerCase().replace(/\s+/g, "-");
|
|
82
|
+
const preset = DEVICE_PRESETS.find(
|
|
83
|
+
(d) => d.name.toLowerCase().replace(/\s+/g, "-") === normalizedInput
|
|
84
|
+
);
|
|
85
|
+
if (!preset) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Unknown device: "${raw}". Available: ${DEVICE_PRESETS.map((d) => d.name).join(", ")}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return { width: preset.width, height: preset.height, dpr: preset.dpr, name: preset.name };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (typeof raw === "object") {
|
|
94
|
+
const obj = raw as Record<string, unknown>;
|
|
95
|
+
const width = obj.width;
|
|
96
|
+
const height = obj.height;
|
|
97
|
+
if (typeof width !== "number" || typeof height !== "number") {
|
|
98
|
+
throw new Error("Device config must have numeric 'width' and 'height'");
|
|
99
|
+
}
|
|
100
|
+
const dpr = typeof obj.dpr === "number" ? obj.dpr : 1;
|
|
101
|
+
return { width, height, dpr };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
throw new Error("Invalid device config: expected a device name string or {width, height, dpr} object");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const VALID_PROPERTY_TYPES: PropertyType[] = ["boolean", "number", "string"];
|
|
108
|
+
|
|
109
|
+
function parseProperties(
|
|
110
|
+
pageKey: string,
|
|
111
|
+
raw: Record<string, unknown>
|
|
112
|
+
): Record<string, PropertyDef> {
|
|
113
|
+
const result: Record<string, PropertyDef> = {};
|
|
114
|
+
for (const [name, def] of Object.entries(raw)) {
|
|
115
|
+
if (!def || typeof def !== "object") {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Invalid property "${name}" on page "${pageKey}": expected {type, default}`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const obj = def as Record<string, unknown>;
|
|
121
|
+
const type = obj.type as string;
|
|
122
|
+
if (!VALID_PROPERTY_TYPES.includes(type as PropertyType)) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`Invalid type "${type}" for property "${name}" on page "${pageKey}". Must be: ${VALID_PROPERTY_TYPES.join(", ")}`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
if (obj.default == null) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Missing default for property "${name}" on page "${pageKey}"`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
const expectedType = type === "boolean" ? "boolean" : type === "number" ? "number" : "string";
|
|
133
|
+
if (typeof obj.default !== expectedType) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`Default value for "${name}" on page "${pageKey}" must be a ${type}`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
result[name] = { type: type as PropertyType, default: obj.default };
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
|
|
72
143
|
function validateFilePaths(
|
|
73
144
|
baseDir: string,
|
|
74
145
|
key: string,
|
package/src/types.ts
CHANGED
|
@@ -3,14 +3,30 @@ export interface ComponentDef {
|
|
|
3
3
|
style: string;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
export type PropertyType = "boolean" | "number" | "string";
|
|
7
|
+
|
|
8
|
+
export interface PropertyDef {
|
|
9
|
+
type: PropertyType;
|
|
10
|
+
default: unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
export interface PageDef {
|
|
7
14
|
template: string;
|
|
8
15
|
style: string;
|
|
16
|
+
properties?: Record<string, PropertyDef>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DeviceConfig {
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
dpr: number;
|
|
23
|
+
name?: string;
|
|
9
24
|
}
|
|
10
25
|
|
|
11
26
|
export interface DesignConfig {
|
|
12
27
|
version: number;
|
|
13
28
|
globalStyle: string;
|
|
29
|
+
device?: DeviceConfig;
|
|
14
30
|
components: Record<string, ComponentDef>;
|
|
15
31
|
pages: Record<string, PageDef>;
|
|
16
32
|
baseDir: string; // absolute path to .dekit/ directory (where dekit.yaml lives)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, test, expect } from "vitest";
|
|
2
|
-
import { assemblePageHtml } from "./vite-plugin-dekit.js";
|
|
2
|
+
import { assemblePageHtml, resolvePageProps } from "./vite-plugin-dekit.js";
|
|
3
3
|
import type { DesignConfig } from "./types.js";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
|
|
@@ -59,4 +59,88 @@ describe("assemblePageHtml", () => {
|
|
|
59
59
|
expect(html).toContain("<slot></slot>");
|
|
60
60
|
expect(html).not.toMatch(/innerHTML\s*=\s*`\s*<template>/);
|
|
61
61
|
});
|
|
62
|
+
|
|
63
|
+
test("injects __DEKIT_PROPS__ when page has properties", async () => {
|
|
64
|
+
const configWithProps: DesignConfig = {
|
|
65
|
+
...mockConfig,
|
|
66
|
+
pages: {
|
|
67
|
+
home: {
|
|
68
|
+
...mockConfig.pages["home"],
|
|
69
|
+
properties: {
|
|
70
|
+
showBanner: { type: "boolean", default: true },
|
|
71
|
+
count: { type: "number", default: 3 },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
const html = await assemblePageHtml(configWithProps, "home");
|
|
77
|
+
expect(html).toContain("window.__DEKIT_PROPS__");
|
|
78
|
+
expect(html).toContain('"showBanner":true');
|
|
79
|
+
expect(html).toContain('"count":3');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("does not inject __DEKIT_PROPS__ when page has no properties", async () => {
|
|
83
|
+
const html = await assemblePageHtml(mockConfig, "home");
|
|
84
|
+
expect(html).not.toContain("__DEKIT_PROPS__");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("props override applies over defaults", async () => {
|
|
88
|
+
const configWithProps: DesignConfig = {
|
|
89
|
+
...mockConfig,
|
|
90
|
+
pages: {
|
|
91
|
+
home: {
|
|
92
|
+
...mockConfig.pages["home"],
|
|
93
|
+
properties: {
|
|
94
|
+
showBanner: { type: "boolean", default: true },
|
|
95
|
+
count: { type: "number", default: 3 },
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
const html = await assemblePageHtml(configWithProps, "home", {
|
|
101
|
+
props: { showBanner: "false", count: "10" },
|
|
102
|
+
});
|
|
103
|
+
expect(html).toContain('"showBanner":false');
|
|
104
|
+
expect(html).toContain('"count":10');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("resolvePageProps", () => {
|
|
109
|
+
test("returns defaults when no overrides", () => {
|
|
110
|
+
const result = resolvePageProps({
|
|
111
|
+
properties: {
|
|
112
|
+
flag: { type: "boolean", default: true },
|
|
113
|
+
num: { type: "number", default: 5 },
|
|
114
|
+
str: { type: "string", default: "hi" },
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
expect(result).toEqual({ flag: true, num: 5, str: "hi" });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("overrides convert types based on schema", () => {
|
|
121
|
+
const result = resolvePageProps(
|
|
122
|
+
{
|
|
123
|
+
properties: {
|
|
124
|
+
flag: { type: "boolean", default: true },
|
|
125
|
+
num: { type: "number", default: 5 },
|
|
126
|
+
str: { type: "string", default: "hi" },
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{ flag: "false", num: "42", str: "world" }
|
|
130
|
+
);
|
|
131
|
+
expect(result).toEqual({ flag: false, num: 42, str: "world" });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("ignores unknown override keys", () => {
|
|
135
|
+
const result = resolvePageProps(
|
|
136
|
+
{ properties: { flag: { type: "boolean", default: true } } },
|
|
137
|
+
{ flag: "false", unknown: "value" }
|
|
138
|
+
);
|
|
139
|
+
expect(result).toEqual({ flag: false });
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("returns empty object when no properties defined", () => {
|
|
143
|
+
expect(resolvePageProps({})).toEqual({});
|
|
144
|
+
expect(resolvePageProps({ properties: undefined })).toEqual({});
|
|
145
|
+
});
|
|
62
146
|
});
|
package/src/vite-plugin-dekit.ts
CHANGED
|
@@ -5,10 +5,38 @@ import { join } from "node:path";
|
|
|
5
5
|
import { generateComponentRegistrationScript } from "./injected/register-components.js";
|
|
6
6
|
import { generateInspectorScript } from "./injected/inspector.js";
|
|
7
7
|
|
|
8
|
+
export function resolvePageProps(
|
|
9
|
+
pageDef: { properties?: Record<string, { type: string; default: unknown }> },
|
|
10
|
+
overrides?: Record<string, string>
|
|
11
|
+
): Record<string, unknown> {
|
|
12
|
+
const props: Record<string, unknown> = {};
|
|
13
|
+
if (!pageDef.properties) return props;
|
|
14
|
+
|
|
15
|
+
for (const [key, def] of Object.entries(pageDef.properties)) {
|
|
16
|
+
props[key] = def.default;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (overrides) {
|
|
20
|
+
for (const [key, raw] of Object.entries(overrides)) {
|
|
21
|
+
const def = pageDef.properties[key];
|
|
22
|
+
if (!def) continue;
|
|
23
|
+
if (def.type === "boolean") {
|
|
24
|
+
props[key] = raw === "true";
|
|
25
|
+
} else if (def.type === "number") {
|
|
26
|
+
props[key] = Number(raw);
|
|
27
|
+
} else {
|
|
28
|
+
props[key] = raw;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return props;
|
|
34
|
+
}
|
|
35
|
+
|
|
8
36
|
export async function assemblePageHtml(
|
|
9
37
|
config: DesignConfig,
|
|
10
38
|
pageName: string,
|
|
11
|
-
options?: { inspector?: boolean }
|
|
39
|
+
options?: { inspector?: boolean; props?: Record<string, string> }
|
|
12
40
|
): Promise<string> {
|
|
13
41
|
const pageDef = config.pages[pageName];
|
|
14
42
|
if (!pageDef) {
|
|
@@ -29,6 +57,11 @@ export async function assemblePageHtml(
|
|
|
29
57
|
? `<script>\n${generateInspectorScript()}\n</script>`
|
|
30
58
|
: "";
|
|
31
59
|
|
|
60
|
+
const resolvedProps = resolvePageProps(pageDef, options?.props);
|
|
61
|
+
const propsBlock = Object.keys(resolvedProps).length > 0
|
|
62
|
+
? `<script>window.__DEKIT_PROPS__ = ${JSON.stringify(resolvedProps)};</script>\n`
|
|
63
|
+
: "";
|
|
64
|
+
|
|
32
65
|
return `<!DOCTYPE html>
|
|
33
66
|
<html>
|
|
34
67
|
<head>
|
|
@@ -39,7 +72,7 @@ export async function assemblePageHtml(
|
|
|
39
72
|
<link rel="stylesheet" href="/@design/${pageDef.style}">
|
|
40
73
|
</head>
|
|
41
74
|
<body>
|
|
42
|
-
${pageTemplate}
|
|
75
|
+
${propsBlock}${pageTemplate}
|
|
43
76
|
<script type="module">
|
|
44
77
|
${componentScript}
|
|
45
78
|
</script>
|
|
@@ -85,9 +118,16 @@ export function dekitPlugin(configRef: { current: DesignConfig }): Plugin {
|
|
|
85
118
|
const pageName = pageMatch[1];
|
|
86
119
|
const urlObj = new URL(url, "http://localhost");
|
|
87
120
|
const noInspector = urlObj.searchParams.has("noinspector");
|
|
121
|
+
const props: Record<string, string> = {};
|
|
122
|
+
for (const [key, value] of urlObj.searchParams.entries()) {
|
|
123
|
+
if (key.startsWith("props.")) {
|
|
124
|
+
props[key.slice(6)] = value;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
88
127
|
try {
|
|
89
128
|
const html = await assemblePageHtml(configRef.current, pageName, {
|
|
90
129
|
inspector: !noInspector,
|
|
130
|
+
props: Object.keys(props).length > 0 ? props : undefined,
|
|
91
131
|
});
|
|
92
132
|
res.setHeader("Content-Type", "text/html");
|
|
93
133
|
res.end(html);
|