@monimesl/dot-compiler 0.1.0-alpha.9 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +19 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +225 -214
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
3
3
|
import { dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { copyFile, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
5
|
-
import { ScriptParseError, parseDotModule, parseExpression, parseLogicModule, parseStateModule, unboundReferences } from "@monimesl/dot-script-parser";
|
|
6
|
-
import {
|
|
5
|
+
import { ScriptParseError, globalMemberUses, parseDotModule, parseExpression, parseLogicModule, parseStateModule, unboundReferences } from "@monimesl/dot-script-parser";
|
|
6
|
+
import { DEFAULT_DASH_API_VERSION, DOT_JSON_PERMISSIONS, REACT_HOST_MODULES, SourceError, builtinComponents, builtinRegistry, componentRegistry, eventName, isDashApiVersion, isDotJsonPermission, isLocalNetworkHost, isNetworkHost, isSemanticVersion, isWordsOr, otherThanWords, reactHostUrl } from "@monimesl/dot-core";
|
|
7
7
|
import { zipSync } from "fflate";
|
|
8
8
|
import { rolldown } from "rolldown";
|
|
9
9
|
import { esmExternalRequirePlugin } from "rolldown/plugins";
|
|
@@ -18,73 +18,43 @@ var CompileWarning = class extends SourceError {
|
|
|
18
18
|
name = "CompileWarning";
|
|
19
19
|
};
|
|
20
20
|
//#endregion
|
|
21
|
-
//#region src/
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
"electricity",
|
|
28
|
-
"entertainment",
|
|
29
|
-
"events",
|
|
30
|
-
"finance",
|
|
31
|
-
"food_and_drink",
|
|
32
|
-
"gambling",
|
|
33
|
-
"games",
|
|
34
|
-
"government",
|
|
35
|
-
"health",
|
|
36
|
-
"insurance",
|
|
37
|
-
"internet_service_provider",
|
|
38
|
-
"investments",
|
|
39
|
-
"jobs",
|
|
40
|
-
"lifestyle",
|
|
41
|
-
"loans",
|
|
42
|
-
"micro_credit",
|
|
43
|
-
"microfinance",
|
|
44
|
-
"news",
|
|
45
|
-
"other",
|
|
46
|
-
"productivity",
|
|
47
|
-
"real_estate",
|
|
48
|
-
"religion",
|
|
49
|
-
"remittances",
|
|
50
|
-
"savings",
|
|
51
|
-
"shopping",
|
|
52
|
-
"social",
|
|
53
|
-
"sports",
|
|
54
|
-
"telecom",
|
|
55
|
-
"television",
|
|
56
|
-
"transport",
|
|
57
|
-
"travel",
|
|
58
|
-
"utilities",
|
|
59
|
-
"water"
|
|
60
|
-
];
|
|
61
|
-
function isCategory(value) {
|
|
62
|
-
return CATEGORIES.includes(value);
|
|
21
|
+
//#region src/utils.ts
|
|
22
|
+
function quotedList(values) {
|
|
23
|
+
return values.map((value) => `"${value}"`).join(", ");
|
|
24
|
+
}
|
|
25
|
+
function relativeSlashPath(from, to) {
|
|
26
|
+
return relative(from, to).split(sep).join("/");
|
|
63
27
|
}
|
|
64
28
|
//#endregion
|
|
65
29
|
//#region src/project/dot-json.ts
|
|
66
30
|
const DOT_JSON_FILE = "dot.json";
|
|
67
31
|
const DOT_JSON_FIELDS = [
|
|
68
|
-
"$schema",
|
|
69
32
|
"id",
|
|
33
|
+
"$schema",
|
|
34
|
+
"package",
|
|
70
35
|
"name",
|
|
71
36
|
"version",
|
|
72
|
-
"description",
|
|
73
|
-
"categories",
|
|
74
|
-
"icon",
|
|
75
37
|
"permissions",
|
|
76
38
|
"network",
|
|
77
39
|
"dash"
|
|
78
40
|
];
|
|
79
41
|
const REQUIRED_DOT_JSON_FIELDS = [
|
|
80
|
-
"
|
|
42
|
+
"package",
|
|
81
43
|
"name",
|
|
82
44
|
"version"
|
|
83
45
|
];
|
|
84
|
-
const DASH_FIELDS = ["minVersion"];
|
|
85
46
|
const NETWORK_FIELDS = ["hosts"];
|
|
86
|
-
const
|
|
87
|
-
const
|
|
47
|
+
const DASH_FIELDS = ["minApiVersion"];
|
|
48
|
+
const LISTING_FIELDS = [
|
|
49
|
+
"description",
|
|
50
|
+
"categories",
|
|
51
|
+
"icon"
|
|
52
|
+
];
|
|
53
|
+
const PACKAGE_WORD = "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?";
|
|
54
|
+
const DOT_PACKAGE_PATTERN = new RegExp(`^(?=[a-z])${PACKAGE_WORD}(?:\\.${PACKAGE_WORD})+$`);
|
|
55
|
+
function isDotPackage(name) {
|
|
56
|
+
return name.length <= 128 && DOT_PACKAGE_PATTERN.test(name);
|
|
57
|
+
}
|
|
88
58
|
async function readDotJson(file) {
|
|
89
59
|
let dotJson;
|
|
90
60
|
try {
|
|
@@ -92,46 +62,42 @@ async function readDotJson(file) {
|
|
|
92
62
|
} catch (error) {
|
|
93
63
|
throw new CompileError(`This isn't valid JSON: ${error.message}`, file);
|
|
94
64
|
}
|
|
95
|
-
if (!isObject(dotJson)) throw new CompileError("dot.json must be an object, such as { \"
|
|
65
|
+
if (!isObject(dotJson)) throw new CompileError("dot.json must be an object, such as { \"package\": \"com.mysalone.thing\", \"name\": \"My Salone Thing\", \"version\": \"1.0.0\" }", file);
|
|
96
66
|
if ("pages" in dotJson) throw new CompileError("List pages in dot.ts, not here: defineDot({ pages: [...] })", file);
|
|
67
|
+
for (const field of LISTING_FIELDS) if (field in dotJson) throw new CompileError(`Remove "${field}". A dot's description, categories and icon are set where it's published.`, file);
|
|
97
68
|
rejectUnknownFields(dotJson, DOT_JSON_FIELDS, file);
|
|
69
|
+
if (dotJson.package === void 0 && typeof dotJson.id === "string" && DOT_PACKAGE_PATTERN.test(dotJson.id)) throw new CompileError(`"${dotJson.id}" is a package name. Rename "id" to "package".`, file);
|
|
98
70
|
for (const field of REQUIRED_DOT_JSON_FIELDS) if (dotJson[field] === void 0) throw new CompileError(`"${field}" is required`, file);
|
|
99
|
-
const { $schema, id, name, version,
|
|
71
|
+
const { $schema, package: packageName, id, name, version, permissions, network, dash } = dotJson;
|
|
100
72
|
if ($schema !== void 0 && typeof $schema !== "string") throw new CompileError("\"$schema\" must be a string", file);
|
|
101
|
-
if (typeof
|
|
102
|
-
if (
|
|
73
|
+
if (typeof packageName !== "string" || !DOT_PACKAGE_PATTERN.test(packageName)) throw new CompileError("\"package\" must be a reverse domain name. Use lowercase words joined by dots, such as \"com.mysalone.thing\".", file);
|
|
74
|
+
if (packageName.length > 128) throw new CompileError(`"package" must be at most 128 characters`, file);
|
|
75
|
+
if (id !== void 0 && !isNonEmptyString(id)) throw new CompileError("\"id\" must be the ID Monime gave the dot, as text", file);
|
|
103
76
|
if (!isNonEmptyString(name)) throw new CompileError("\"name\" must be text, and not empty", file);
|
|
104
77
|
if (!isSemanticVersion(version)) throw new CompileError("\"version\" must be a semantic version, such as \"1.0.0\"", file);
|
|
105
|
-
if (description !== void 0 && !isNonEmptyString(description)) throw new CompileError("\"description\" must be text, and not empty", file);
|
|
106
|
-
if (icon !== void 0 && !(isNonEmptyString(icon) && icon.endsWith(".png"))) throw new CompileError("\"icon\" must be the path to a PNG file, such as \"icon.png\"", file);
|
|
107
78
|
return {
|
|
108
79
|
id,
|
|
80
|
+
package: packageName,
|
|
109
81
|
name,
|
|
110
82
|
version,
|
|
111
|
-
description,
|
|
112
|
-
categories: readCategories(categories, file),
|
|
113
|
-
icon,
|
|
114
83
|
permissions: readPermissions(permissions, file),
|
|
115
84
|
network: readNetworkAccess(network, file),
|
|
116
|
-
dash:
|
|
85
|
+
dash: readDashNeeds(dash, file)
|
|
117
86
|
};
|
|
118
87
|
}
|
|
119
|
-
function
|
|
120
|
-
if (
|
|
121
|
-
if (!
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
listed.push(category);
|
|
127
|
-
}
|
|
128
|
-
return listed;
|
|
88
|
+
function readDashNeeds(dash, file) {
|
|
89
|
+
if (dash === void 0) return;
|
|
90
|
+
if (!isObject(dash)) throw new CompileError("\"dash\" must be an object, such as { \"minApiVersion\": \"1.0\" }", file);
|
|
91
|
+
rejectUnknownFields(dash, DASH_FIELDS, file, "dash");
|
|
92
|
+
const { minApiVersion = DEFAULT_DASH_API_VERSION } = dash;
|
|
93
|
+
if (!isDashApiVersion(minApiVersion)) throw new CompileError("\"dash.minApiVersion\" must be a Dash API version: a major and a minor, such as \"1.0\"", file);
|
|
94
|
+
return { minApiVersion };
|
|
129
95
|
}
|
|
130
96
|
function readPermissions(permissions, file) {
|
|
131
97
|
if (permissions === void 0) return;
|
|
132
|
-
if (!isObject(permissions)) throw new CompileError("\"permissions\" must be an object, such as { \"
|
|
98
|
+
if (!isObject(permissions)) throw new CompileError("\"permissions\" must be an object, such as { \"camera\": \"Scan tickets at the door\" }", file);
|
|
133
99
|
for (const [permission, reason] of Object.entries(permissions)) {
|
|
134
|
-
if (!
|
|
100
|
+
if (!isDotJsonPermission(permission)) throw new CompileError(`Unknown permission "${permission}". The permissions are ${quotedList(DOT_JSON_PERMISSIONS)}`, file);
|
|
135
101
|
if (!isNonEmptyString(reason)) throw new CompileError(`"permissions.${permission}" must say why the dot needs it, such as "Show your name on receipts"`, file);
|
|
136
102
|
}
|
|
137
103
|
return permissions;
|
|
@@ -150,14 +116,6 @@ function readNetworkAccess(network, file) {
|
|
|
150
116
|
}
|
|
151
117
|
return { hosts: listed };
|
|
152
118
|
}
|
|
153
|
-
function readDashRequirements(dash, file) {
|
|
154
|
-
if (dash === void 0) return;
|
|
155
|
-
if (!isObject(dash)) throw new CompileError("\"dash\" must be an object, such as { \"minVersion\": \"2.4.0\" }", file);
|
|
156
|
-
rejectUnknownFields(dash, DASH_FIELDS, file, "dash");
|
|
157
|
-
const { minVersion } = dash;
|
|
158
|
-
if (minVersion !== void 0 && !isSemanticVersion(minVersion)) throw new CompileError("\"dash.minVersion\" must be a semantic version, such as \"2.4.0\"", file);
|
|
159
|
-
return { minVersion };
|
|
160
|
-
}
|
|
161
119
|
function rejectUnknownFields(object, allowed, file, parent) {
|
|
162
120
|
for (const field of Object.keys(object)) {
|
|
163
121
|
if (allowed.includes(field)) continue;
|
|
@@ -165,9 +123,6 @@ function rejectUnknownFields(object, allowed, file, parent) {
|
|
|
165
123
|
throw new CompileError(`Unknown field "${parent}.${field}". The fields allowed in "${parent}" are ${quotedList(allowed)}`, file);
|
|
166
124
|
}
|
|
167
125
|
}
|
|
168
|
-
function quotedList(values) {
|
|
169
|
-
return values.map((value) => `"${value}"`).join(", ");
|
|
170
|
-
}
|
|
171
126
|
function isObject(value) {
|
|
172
127
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
173
128
|
}
|
|
@@ -207,7 +162,7 @@ async function listStatics(root) {
|
|
|
207
162
|
if (!entry.isFile() || entry.name.startsWith(".")) continue;
|
|
208
163
|
const file = join(entry.parentPath, entry.name);
|
|
209
164
|
await checkImage(file, extname(entry.name).toLowerCase());
|
|
210
|
-
statics.push(`${STATICS_DIR}/${
|
|
165
|
+
statics.push(`${STATICS_DIR}/${relativeSlashPath(staticsDir, file)}`);
|
|
211
166
|
}
|
|
212
167
|
return statics.sort();
|
|
213
168
|
}
|
|
@@ -235,50 +190,51 @@ async function loadProject(root) {
|
|
|
235
190
|
const dotJsonFile = join(root, DOT_JSON_FILE);
|
|
236
191
|
if (!existsSync(dotJsonFile)) throw new CompileError(`There is no ${DOT_JSON_FILE} in this folder`, root);
|
|
237
192
|
const dotJson = await readDotJson(dotJsonFile);
|
|
238
|
-
if (dotJson.icon !== void 0 && !existsSync(join(root, dotJson.icon))) throw new CompileError(`The icon "${dotJson.icon}" is missing`, dotJsonFile);
|
|
239
193
|
const dotFile = join(root, DOT_FILE);
|
|
240
194
|
if (!existsSync(dotFile)) throw new CompileError(`There is no ${DOT_FILE}. Add one: export default defineDot({ pages: [...] })`, root);
|
|
241
195
|
const { pages } = parseDotModule(await readFile(dotFile, "utf8"), dotFile);
|
|
242
|
-
const
|
|
243
|
-
const
|
|
244
|
-
for (const page of
|
|
245
|
-
const
|
|
246
|
-
if (
|
|
196
|
+
const pageSources = await Promise.all(pages.map((path) => loadPage(root, path, dotFile)));
|
|
197
|
+
const pagesByLowercaseId = /* @__PURE__ */ new Map();
|
|
198
|
+
for (const page of pageSources) {
|
|
199
|
+
const earlierPage = pagesByLowercaseId.get(page.id.toLowerCase());
|
|
200
|
+
if (earlierPage) {
|
|
247
201
|
let file = dotFile;
|
|
248
202
|
if (page.id !== page.path) file = page.controller;
|
|
249
|
-
else if (
|
|
250
|
-
throw new CompileError(`Page id "${page.id}" (${page.path}) is already used by ${
|
|
203
|
+
else if (earlierPage.id !== earlierPage.path) file = earlierPage.controller;
|
|
204
|
+
throw new CompileError(`Page id "${page.id}" (${page.path}) is already used by ${earlierPage.path}`, file);
|
|
251
205
|
}
|
|
252
|
-
|
|
206
|
+
pagesByLowercaseId.set(page.id.toLowerCase(), page);
|
|
253
207
|
}
|
|
254
208
|
return {
|
|
255
209
|
root,
|
|
256
210
|
dotJson,
|
|
257
211
|
dot: dotFile,
|
|
258
|
-
pages:
|
|
212
|
+
pages: pageSources,
|
|
259
213
|
statics: await listStatics(root)
|
|
260
214
|
};
|
|
261
215
|
}
|
|
262
216
|
async function loadPage(root, path, dotFile) {
|
|
263
|
-
const
|
|
217
|
+
const pageDir = join(root, PAGES_DIR, path);
|
|
264
218
|
const files = {
|
|
265
|
-
view: join(
|
|
266
|
-
state: join(
|
|
267
|
-
controller: join(
|
|
219
|
+
view: join(pageDir, PAGE_FILES.view),
|
|
220
|
+
state: join(pageDir, PAGE_FILES.state),
|
|
221
|
+
controller: join(pageDir, PAGE_FILES.controller)
|
|
268
222
|
};
|
|
269
223
|
const missing = Object.values(files).filter((file) => !existsSync(file));
|
|
270
224
|
if (missing.length > 0) throw new CompileError(`Page "${path}" is missing ${missing.join(", ")}`, dotFile);
|
|
271
|
-
const
|
|
225
|
+
const controller = await readFile(files.controller, "utf8");
|
|
226
|
+
const { id, share } = parseLogicModule(controller, files.controller);
|
|
272
227
|
return {
|
|
273
228
|
path,
|
|
274
229
|
id: id ?? path,
|
|
230
|
+
share,
|
|
275
231
|
...files
|
|
276
232
|
};
|
|
277
233
|
}
|
|
278
234
|
//#endregion
|
|
279
235
|
//#region src/entries.ts
|
|
280
236
|
const RUNTIME_PACKAGE = "@monimesl/dot-runtime";
|
|
281
|
-
const
|
|
237
|
+
const RUNTIME_MODULES = {
|
|
282
238
|
ui: `${RUNTIME_PACKAGE}/ui`,
|
|
283
239
|
logic: `${RUNTIME_PACKAGE}/logic`,
|
|
284
240
|
renderer: `${RUNTIME_PACKAGE}/react`
|
|
@@ -292,13 +248,18 @@ const viewModuleId = (id) => `dot:view/${id}`;
|
|
|
292
248
|
function literal(value) {
|
|
293
249
|
return JSON.stringify(value);
|
|
294
250
|
}
|
|
295
|
-
function pageTable(project,
|
|
296
|
-
return `{${project.pages.map((page, index) => `\n ${literal(page.id)}: ${
|
|
251
|
+
function pageTable(project, importName) {
|
|
252
|
+
return `{${project.pages.map((page, index) => `\n ${literal(page.id)}: ${importName}${index},`).join("")}\n }`;
|
|
297
253
|
}
|
|
298
254
|
function uiEntry(project, components) {
|
|
299
|
-
const
|
|
255
|
+
const rendererModule = literal(RUNTIME_MODULES.renderer);
|
|
256
|
+
let renderer;
|
|
257
|
+
if (components) {
|
|
258
|
+
const componentList = components.join(", ");
|
|
259
|
+
renderer = [`import { createReactRenderer, ${componentList} } from ${rendererModule};`, `const createRenderer = () => createReactRenderer({ components: { ${componentList} } });`];
|
|
260
|
+
} else renderer = [`import createRenderer from ${rendererModule};`];
|
|
300
261
|
return [
|
|
301
|
-
`import { runWebview } from ${literal(
|
|
262
|
+
`import { runWebview } from ${literal(RUNTIME_MODULES.ui)};`,
|
|
302
263
|
...renderer,
|
|
303
264
|
...project.pages.map((page, index) => `import view${index} from ${literal(viewModuleId(page.id))};`),
|
|
304
265
|
"",
|
|
@@ -309,11 +270,14 @@ function uiEntry(project, components) {
|
|
|
309
270
|
""
|
|
310
271
|
].join("\n");
|
|
311
272
|
}
|
|
312
|
-
function logicEntry(project, { strict }) {
|
|
273
|
+
function logicEntry(project, { strict, dashMinApiVersion }) {
|
|
313
274
|
const options = [" dot,", ` pages: ${pageTable(project, "page")},`];
|
|
314
|
-
if (strict)
|
|
275
|
+
if (strict) {
|
|
276
|
+
options.push(" strict: true,");
|
|
277
|
+
options.push(` dashMinApiVersion: ${literal(dashMinApiVersion)},`);
|
|
278
|
+
}
|
|
315
279
|
return [
|
|
316
|
-
`import { runService } from ${literal(
|
|
280
|
+
`import { runService } from ${literal(RUNTIME_MODULES.logic)};`,
|
|
317
281
|
`import dot from ${literal(project.dot)};`,
|
|
318
282
|
...project.pages.map((page, index) => `import page${index} from ${literal(page.controller)};`),
|
|
319
283
|
"",
|
|
@@ -329,7 +293,7 @@ function envModule(env) {
|
|
|
329
293
|
//#endregion
|
|
330
294
|
//#region src/globals.ts
|
|
331
295
|
const DOT_GLOBALS = {
|
|
332
|
-
|
|
296
|
+
[RUNTIME_PACKAGE]: [
|
|
333
297
|
"definePage",
|
|
334
298
|
"defineDot",
|
|
335
299
|
"createEvent",
|
|
@@ -337,17 +301,18 @@ const DOT_GLOBALS = {
|
|
|
337
301
|
],
|
|
338
302
|
[ENV_MODULE]: ["env"]
|
|
339
303
|
};
|
|
340
|
-
const
|
|
304
|
+
const SCRIPT_FILE = /\.[cm]?[jt]sx?$/;
|
|
305
|
+
const ANSWERS_AT_ONCE = /* @__PURE__ */ new Set(["utils", "supports"]);
|
|
341
306
|
function dotGlobals(root) {
|
|
342
307
|
const realRoot = realpathSync(root);
|
|
343
|
-
const isDotModule = (id) => id.startsWith(realRoot + sep) && !id.includes(`${sep}node_modules${sep}`) &&
|
|
308
|
+
const isDotModule = (id) => id.startsWith(realRoot + sep) && !id.includes(`${sep}node_modules${sep}`) && SCRIPT_FILE.test(id);
|
|
344
309
|
const isPageModule = (id) => id.startsWith(join(realRoot, PAGES_DIR) + sep);
|
|
345
310
|
return {
|
|
346
311
|
name: "dot:globals",
|
|
347
312
|
transform(code, id) {
|
|
348
313
|
if (!isDotModule(id)) return null;
|
|
349
314
|
const used = new Set(unboundReferences(code, Object.values(DOT_GLOBALS).flat(), id));
|
|
350
|
-
if (isPageModule(id) && used.has("dash")) this.error(`${relative(realRoot, id)} uses the global \`dash\`. In a page, use \`this.dash\` in actions and hooks, so its calls stop when the page closes.`);
|
|
315
|
+
if (isPageModule(id) && used.has("dash") && makesDashCalls(code, id)) this.error(`${relative(realRoot, id)} uses the global \`dash\`. In a page, use \`this.dash\` in actions and hooks, so its calls stop when the page closes. Only dash.utils and dash.supports, which answer at once, work anywhere.`);
|
|
351
316
|
const imports = [];
|
|
352
317
|
for (const [source, names] of Object.entries(DOT_GLOBALS)) {
|
|
353
318
|
const imported = names.filter((name) => used.has(name));
|
|
@@ -361,9 +326,12 @@ function dotGlobals(root) {
|
|
|
361
326
|
}
|
|
362
327
|
};
|
|
363
328
|
}
|
|
329
|
+
function makesDashCalls(code, id) {
|
|
330
|
+
for (const member of globalMemberUses(code, "dash", id)) if (!ANSWERS_AT_ONCE.has(member)) return true;
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
364
333
|
//#endregion
|
|
365
334
|
//#region src/build/bundle.ts
|
|
366
|
-
/** Bundles one entry with rolldown, replacing `outDir`. Returns the written files. */
|
|
367
335
|
async function bundle(options) {
|
|
368
336
|
await rm(options.outDir, {
|
|
369
337
|
recursive: true,
|
|
@@ -396,14 +364,13 @@ async function bundle(options) {
|
|
|
396
364
|
entryFileNames: options.fileName,
|
|
397
365
|
sourcemap: options.mode === "development",
|
|
398
366
|
sourcemapExcludeSources: options.mode === "production",
|
|
399
|
-
minify: options.
|
|
367
|
+
minify: options.mode === "production"
|
|
400
368
|
});
|
|
401
369
|
return output.map((chunk) => join(options.outDir, chunk.fileName));
|
|
402
370
|
} finally {
|
|
403
371
|
await bundler.close();
|
|
404
372
|
}
|
|
405
373
|
}
|
|
406
|
-
/** Serves generated modules and resolves their imports as if they lived in the project root. */
|
|
407
374
|
function generatedModules(root, modules) {
|
|
408
375
|
const importer = join(root, DOT_JSON_FILE);
|
|
409
376
|
return {
|
|
@@ -443,7 +410,7 @@ function requirePackage(fromDir, name, installedIn) {
|
|
|
443
410
|
try {
|
|
444
411
|
packageFile = require.resolve(`${name}/package.json`);
|
|
445
412
|
} catch {
|
|
446
|
-
throw new CompileError(`${name}
|
|
413
|
+
throw new CompileError(`${name} isn't installed in ${installedIn}. Install the project's dependencies, then try again`, fromDir);
|
|
447
414
|
}
|
|
448
415
|
const { version } = JSON.parse(readFileSync(packageFile, "utf8"));
|
|
449
416
|
return {
|
|
@@ -463,8 +430,12 @@ function printExpr(expr, scope) {
|
|
|
463
430
|
switch (expr.type) {
|
|
464
431
|
case "Literal": return JSON.stringify(expr.value);
|
|
465
432
|
case "Identifier": return scope.get(expr.name) ?? `d.${expr.name}`;
|
|
466
|
-
case "Member":
|
|
467
|
-
|
|
433
|
+
case "Member":
|
|
434
|
+
if (expr.property.type === "Literal" && typeof expr.property.value === "string" && isIdentifier(expr.property.value)) return `${print(expr.object)}?.${expr.property.value}`;
|
|
435
|
+
return `${print(expr.object)}?.[${print(expr.property)}]`;
|
|
436
|
+
case "Unary":
|
|
437
|
+
if (expr.operator === "typeof") return `(typeof ${print(expr.argument)})`;
|
|
438
|
+
return `(${expr.operator}${print(expr.argument)})`;
|
|
468
439
|
case "Binary": return `(${print(expr.left)} ${expr.operator} ${print(expr.right)})`;
|
|
469
440
|
case "Conditional": return `(${print(expr.test)} ? ${print(expr.consequent)} : ${print(expr.alternate)})`;
|
|
470
441
|
}
|
|
@@ -529,15 +500,17 @@ function compileLoop(compiler, node, parent, scope) {
|
|
|
529
500
|
checkVariableName("item", item);
|
|
530
501
|
checkVariableName("index", index);
|
|
531
502
|
if (item === index) compiler.fail("<For> `item` and `index` need different names", node.loc);
|
|
532
|
-
const
|
|
533
|
-
const
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
503
|
+
const itemVariable = "$" + item;
|
|
504
|
+
const indexVariable = "$" + index;
|
|
505
|
+
const loopScope = new Map(scope);
|
|
506
|
+
loopScope.set(item, itemVariable);
|
|
507
|
+
loopScope.set(index, indexVariable);
|
|
508
|
+
const eachArguments = [list, `(${itemVariable}, ${indexVariable}) => [${compiler.compile(node.children, parent, loopScope).join(", ")}]`];
|
|
509
|
+
if (key === void 0) compiler.warn("<For> has no `key`, so items are matched by position. If items can move, add key=\"<property>\" or key=\"*this\". To keep matching by position, add key=\"*index\".", node.loc);
|
|
510
|
+
else if (key === "*index") {} else if (key === "*this") eachArguments.push(`(${itemVariable}) => ${itemVariable}`);
|
|
511
|
+
else if (isIdentifier(key)) eachArguments.push(`(${itemVariable}) => ${itemVariable}?.${key}`);
|
|
539
512
|
else compiler.fail(`<For> \`key\` must be a property name, *this or *index, got "${key}"`, node.loc);
|
|
540
|
-
return `each(${
|
|
513
|
+
return `each(${eachArguments.join(", ")})`;
|
|
541
514
|
}
|
|
542
515
|
//#endregion
|
|
543
516
|
//#region src/view/compiler.ts
|
|
@@ -550,6 +523,7 @@ const ROOT_DEFAULTS = {
|
|
|
550
523
|
const SEGMENT_BINDING_REGEXP = /{{([\s\S]*?)}}/g;
|
|
551
524
|
const DATA_PREFIX = "data-";
|
|
552
525
|
const DATA_ATTRIBUTE = /^data-[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
526
|
+
const THEME_COLOR_NAME = /^[a-z][a-zA-Z0-9]*$/;
|
|
553
527
|
var ViewCompiler = class {
|
|
554
528
|
#context;
|
|
555
529
|
#components;
|
|
@@ -559,63 +533,72 @@ var ViewCompiler = class {
|
|
|
559
533
|
for (const name of Object.keys(CONTROL_FLOW_TAGS)) if (name in this.#components) this.fail(`<${name}> is reserved for control flow`);
|
|
560
534
|
}
|
|
561
535
|
compile(nodes, parent, scope) {
|
|
562
|
-
const
|
|
563
|
-
for (let
|
|
564
|
-
const node = nodes[
|
|
536
|
+
const compiled = [];
|
|
537
|
+
for (let index = 0; index < nodes.length; index++) {
|
|
538
|
+
const node = nodes[index];
|
|
565
539
|
if (node.type === "Text") {
|
|
566
|
-
|
|
540
|
+
compiled.push(...this.#compileText(node.content, parent, scope, node.loc));
|
|
567
541
|
continue;
|
|
568
542
|
}
|
|
569
543
|
switch (node.tag) {
|
|
570
544
|
case "If": {
|
|
571
|
-
const branches = ifChain(node, nodes.slice(
|
|
572
|
-
|
|
573
|
-
|
|
545
|
+
const branches = ifChain(node, nodes.slice(index + 1));
|
|
546
|
+
compiled.push(compileConditional(this, branches, parent, scope));
|
|
547
|
+
index += branches.length - 1;
|
|
574
548
|
break;
|
|
575
549
|
}
|
|
576
550
|
case "ElseIf":
|
|
577
551
|
case "Else": return this.fail(`<${node.tag}> must come right after <If> or <ElseIf>`, node.loc);
|
|
578
552
|
case "For":
|
|
579
|
-
|
|
553
|
+
compiled.push(compileLoop(this, node, parent, scope));
|
|
580
554
|
break;
|
|
581
|
-
default:
|
|
555
|
+
default: compiled.push(`h(${this.#compileElementArgs(node, parent, scope).join(", ")})`);
|
|
582
556
|
}
|
|
583
557
|
}
|
|
584
|
-
return
|
|
558
|
+
return compiled;
|
|
585
559
|
}
|
|
586
560
|
#compileText(content, parent, scope, loc) {
|
|
587
561
|
if (parent?.children !== "text") {
|
|
588
|
-
|
|
589
|
-
this.fail(`'${content}' must be inside ${
|
|
562
|
+
if (content.trim() === "") return [];
|
|
563
|
+
this.fail(`'${content}' must be inside ${this.#textWrapperNames()}`, loc);
|
|
590
564
|
}
|
|
591
565
|
return this.#compileAsSegments(content, scope, loc).map(printSegment);
|
|
592
566
|
}
|
|
567
|
+
#textWrapperNames() {
|
|
568
|
+
const textWrappers = Object.values(this.#components).filter((component) => {
|
|
569
|
+
return component.inline === true && component.children === "text";
|
|
570
|
+
});
|
|
571
|
+
const textFirst = [...textWrappers.filter((component) => component.name === "Text"), ...textWrappers.filter((component) => component.name !== "Text")];
|
|
572
|
+
if (textFirst.length === 0) return "an inline component";
|
|
573
|
+
return textFirst.map((component) => `<${component.name}>`).join(" or ");
|
|
574
|
+
}
|
|
593
575
|
compileRoot(nodes) {
|
|
594
576
|
if (!this.#components["View"]) this.fail(`The components must include <${ROOT_TAG}>, which is the root of every page`);
|
|
595
577
|
const [root, outside] = nodes;
|
|
596
578
|
if (root?.type !== "Element" || root.tag !== "View") return this.fail(`A page must have one <${ROOT_TAG}> around everything else`, root?.loc);
|
|
597
|
-
if (outside) {
|
|
598
|
-
const name = outside.type === "Text" ? `'${outside.content}'` : `<${outside.tag}>`;
|
|
599
|
-
this.fail(`${name} must be inside the page's <${ROOT_TAG}>`, outside.loc);
|
|
600
|
-
}
|
|
579
|
+
if (outside) this.fail(`${describeNode(outside)} must be inside the page's <${ROOT_TAG}>`, outside.loc);
|
|
601
580
|
return this.#compileElementArgs(root, null, EMPTY_SCOPE, ROOT_DEFAULTS);
|
|
602
581
|
}
|
|
603
582
|
#compileElementArgs(node, parent, scope, defaults = {}) {
|
|
604
583
|
const component = this.#components[node.tag];
|
|
605
|
-
if (!component) return this.fail(`Unknown component <${node.tag}
|
|
584
|
+
if (!component) return this.fail(`Unknown component <${node.tag}>`, node.loc);
|
|
606
585
|
this.#context.onComponent?.(component.name);
|
|
607
586
|
if (parent && !canHaveChildren(parent, component)) this.fail(`<${node.tag}> can't go inside <${parent.name}>`, node.loc);
|
|
608
|
-
const attributes = Object.entries(node.attributes);
|
|
609
587
|
for (const { prop, ignores, because } of component.overrides ?? []) if (prop in node.attributes && ignores in node.attributes) this.warn(`<${component.name}> ignores \`${ignores}\` with \`${prop}\`: ${because}`, node.loc);
|
|
610
|
-
const
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
];
|
|
588
|
+
const dataAttributes = [];
|
|
589
|
+
const propAttributes = [];
|
|
590
|
+
for (const [name, value] of Object.entries(node.attributes)) if (name.startsWith("data-")) dataAttributes.push([name, value]);
|
|
591
|
+
else propAttributes.push([name, value]);
|
|
592
|
+
const data = this.#compileData(dataAttributes, scope, node.loc);
|
|
593
|
+
const props = [];
|
|
594
|
+
for (const [name, value] of propAttributes) props.push(this.#compileProp(component, name, value, scope, node.loc));
|
|
595
|
+
for (const [name, handler] of Object.entries(node.events)) props.push(this.#compileEvent(component, name, handler, data, node.loc));
|
|
596
|
+
for (const [name, value] of Object.entries(defaults)) if (!(name in node.attributes)) props.push(`${name}: ${JSON.stringify(value)}`);
|
|
597
|
+
let propsCode = "null";
|
|
598
|
+
if (props.length > 0) propsCode = `{ ${props.join(", ")} }`;
|
|
616
599
|
return [
|
|
617
600
|
JSON.stringify(component.name),
|
|
618
|
-
|
|
601
|
+
propsCode,
|
|
619
602
|
...this.compile(node.children, component, scope)
|
|
620
603
|
];
|
|
621
604
|
}
|
|
@@ -633,12 +616,12 @@ var ViewCompiler = class {
|
|
|
633
616
|
const kind = typeof type === "string" ? `a ${type}` : "an enum";
|
|
634
617
|
this.fail(`\`${name}\` is ${kind}, so set it with one {{ }} binding`, loc);
|
|
635
618
|
}
|
|
636
|
-
return `${name}: ${segments
|
|
619
|
+
return `${name}: ${concatenateSegments(segments)}`;
|
|
637
620
|
}
|
|
638
621
|
#compileData(attributes, scope, loc) {
|
|
639
622
|
if (attributes.length === 0) return null;
|
|
640
623
|
return `{ ${attributes.map(([name, value]) => {
|
|
641
|
-
if (!DATA_ATTRIBUTE.test(name)) this.fail(`\`${name}\` must
|
|
624
|
+
if (!DATA_ATTRIBUTE.test(name)) this.fail(`\`${name}\` must be lowercase words joined by -, such as data-account-id`, loc);
|
|
642
625
|
return `${dataKey(name)}: ${this.#compileDataValue(value, scope, loc)}`;
|
|
643
626
|
}).join(", ")} }`;
|
|
644
627
|
}
|
|
@@ -647,19 +630,21 @@ var ViewCompiler = class {
|
|
|
647
630
|
const bindingCode = singleBindingCode(segments);
|
|
648
631
|
if (bindingCode !== null) return bindingCode;
|
|
649
632
|
if (segments.length === 0) return "\"\"";
|
|
650
|
-
return segments
|
|
633
|
+
return concatenateSegments(segments);
|
|
651
634
|
}
|
|
652
635
|
#checkStaticsReference(value, loc) {
|
|
653
636
|
const { statics } = this.#context;
|
|
654
|
-
if (statics && value.startsWith(`statics/`) && !statics.includes(value)) this.fail(`"${value}"
|
|
637
|
+
if (statics && value.startsWith(`statics/`) && !statics.includes(value)) this.fail(`"${value}" isn't in ${STATICS_DIR}/`, loc);
|
|
655
638
|
}
|
|
656
639
|
#compileEvent(component, attribute, handler, data, loc) {
|
|
657
640
|
const event = eventName(attribute);
|
|
658
641
|
if (!component.events.includes(event)) this.fail(`<${component.name}> has no \`${attribute}\` event`, loc);
|
|
659
642
|
if (!isIdentifier(handler)) this.fail(`\`${attribute}\` must name a mutation or action in controller.ts, got "${handler}"`, loc);
|
|
660
643
|
const { handlers } = this.#context;
|
|
661
|
-
if (handlers && !handlers.includes(handler)) this.fail(`\`${handler}\`
|
|
662
|
-
|
|
644
|
+
if (handlers && !handlers.includes(handler)) this.fail(`\`${handler}\` isn't a mutation or action in controller.ts`, loc);
|
|
645
|
+
let onArguments = JSON.stringify(handler);
|
|
646
|
+
if (data) onArguments += `, ${data}`;
|
|
647
|
+
return `${attribute}: on(${onArguments})`;
|
|
663
648
|
}
|
|
664
649
|
checkControlFlowElementAttributes(node) {
|
|
665
650
|
const { attributes: allowed, required } = CONTROL_FLOW_TAGS[node.tag];
|
|
@@ -674,9 +659,10 @@ var ViewCompiler = class {
|
|
|
674
659
|
return bindingCode;
|
|
675
660
|
}
|
|
676
661
|
#compileLiteral(type, value, loc) {
|
|
662
|
+
if (isWordsOr(type)) return this.#compileWordOr(type, value, loc);
|
|
677
663
|
if (typeof type !== "string") {
|
|
678
664
|
if (type.includes(value)) return JSON.stringify(value);
|
|
679
|
-
return this.fail(`Expected one of ${type
|
|
665
|
+
return this.fail(`Expected one of ${quotedList(type)}, got "${value}"`, loc);
|
|
680
666
|
}
|
|
681
667
|
switch (type) {
|
|
682
668
|
case "boolean":
|
|
@@ -684,28 +670,34 @@ var ViewCompiler = class {
|
|
|
684
670
|
if (value === "false") return "false";
|
|
685
671
|
return this.fail(`Expected true or false, got "${value}"`, loc);
|
|
686
672
|
case "number":
|
|
687
|
-
if (
|
|
673
|
+
if (isNumberText(value)) return printNumber(value);
|
|
688
674
|
return this.fail(`Expected a number, got "${value}"`, loc);
|
|
689
675
|
case "string":
|
|
690
676
|
case "any": return JSON.stringify(value);
|
|
691
677
|
}
|
|
692
678
|
}
|
|
679
|
+
#compileWordOr(type, value, loc) {
|
|
680
|
+
if (type.words.includes(value)) return JSON.stringify(value);
|
|
681
|
+
if (type.or === "number" && isNumberText(value)) return printNumber(value);
|
|
682
|
+
if (type.or === "themeColor" && THEME_COLOR_NAME.test(value)) return JSON.stringify(value);
|
|
683
|
+
return this.fail(`Expected one of ${quotedList(type.words)}, or ${otherThanWords(type)}, got "${value}"`, loc);
|
|
684
|
+
}
|
|
693
685
|
#compileAsSegments(input, scope, loc) {
|
|
694
686
|
const segments = [];
|
|
695
|
-
let
|
|
687
|
+
let staticStart = 0;
|
|
696
688
|
for (const match of input.matchAll(SEGMENT_BINDING_REGEXP)) {
|
|
697
|
-
if (match.index >
|
|
689
|
+
if (match.index > staticStart) segments.push({
|
|
698
690
|
kind: "static",
|
|
699
|
-
value: input.slice(
|
|
691
|
+
value: input.slice(staticStart, match.index)
|
|
700
692
|
});
|
|
701
|
-
const
|
|
693
|
+
const expression = this.#parseBindingExpression(match[1], scope, loc);
|
|
702
694
|
segments.push({
|
|
703
695
|
kind: "binding",
|
|
704
|
-
code: printExpr(
|
|
696
|
+
code: printExpr(expression, scope)
|
|
705
697
|
});
|
|
706
|
-
|
|
698
|
+
staticStart = match.index + match[0].length;
|
|
707
699
|
}
|
|
708
|
-
const rest = input.slice(
|
|
700
|
+
const rest = input.slice(staticStart);
|
|
709
701
|
if (rest.includes("{{")) this.fail("A binding is missing its closing `}}`", loc);
|
|
710
702
|
if (rest) segments.push({
|
|
711
703
|
kind: "static",
|
|
@@ -714,22 +706,22 @@ var ViewCompiler = class {
|
|
|
714
706
|
return segments;
|
|
715
707
|
}
|
|
716
708
|
#parseBindingExpression(source, scope, loc) {
|
|
717
|
-
let
|
|
709
|
+
let expression;
|
|
718
710
|
try {
|
|
719
|
-
|
|
711
|
+
expression = parseExpression(source);
|
|
720
712
|
} catch (error) {
|
|
721
713
|
if (!(error instanceof ScriptParseError)) throw error;
|
|
722
714
|
return this.fail(`Invalid binding \`{{${source}}}\`: ${error.reason}`, loc);
|
|
723
715
|
}
|
|
724
716
|
const { stateKeys } = this.#context;
|
|
725
717
|
if (stateKeys) {
|
|
726
|
-
const undeclared = stateReads(
|
|
718
|
+
const undeclared = stateReads(expression, scope).find((name) => !stateKeys.includes(name));
|
|
727
719
|
if (undeclared) {
|
|
728
720
|
const declared = stateKeys.join(", ") || "none";
|
|
729
721
|
this.fail(`\`${undeclared}\` isn't in state.ts. It has: ${declared}`, loc);
|
|
730
722
|
}
|
|
731
723
|
}
|
|
732
|
-
return
|
|
724
|
+
return expression;
|
|
733
725
|
}
|
|
734
726
|
warn(message, loc) {
|
|
735
727
|
this.#context.onWarning?.(new CompileWarning(message, this.#context.file, loc));
|
|
@@ -741,8 +733,9 @@ var ViewCompiler = class {
|
|
|
741
733
|
function ifChain(ifNode, nextSiblings) {
|
|
742
734
|
const branches = [ifNode];
|
|
743
735
|
for (const sibling of nextSiblings) {
|
|
744
|
-
if (
|
|
736
|
+
if (!continuesIf(sibling)) break;
|
|
745
737
|
branches.push(sibling);
|
|
738
|
+
if (sibling.tag === "Else") break;
|
|
746
739
|
}
|
|
747
740
|
return branches;
|
|
748
741
|
}
|
|
@@ -755,11 +748,21 @@ function singleBindingCode(segments) {
|
|
|
755
748
|
return null;
|
|
756
749
|
}
|
|
757
750
|
function dataKey(attribute) {
|
|
758
|
-
const
|
|
759
|
-
|
|
751
|
+
const words = attribute.slice(5).split("-");
|
|
752
|
+
let key = words[0] ?? "";
|
|
753
|
+
for (const word of words.slice(1)) key += word.charAt(0).toUpperCase() + word.slice(1);
|
|
754
|
+
return key;
|
|
755
|
+
}
|
|
756
|
+
function describeNode(node) {
|
|
757
|
+
if (node.type === "Text") return `'${node.content}'`;
|
|
758
|
+
return `<${node.tag}>`;
|
|
760
759
|
}
|
|
761
760
|
function printSegment(segment) {
|
|
762
|
-
|
|
761
|
+
if (segment.kind === "static") return JSON.stringify(segment.value);
|
|
762
|
+
return `text(${segment.code})`;
|
|
763
|
+
}
|
|
764
|
+
function concatenateSegments(segments) {
|
|
765
|
+
return segments.map(printSegment).join(" + ");
|
|
763
766
|
}
|
|
764
767
|
function canHaveChildren(parent, child) {
|
|
765
768
|
switch (parent.children) {
|
|
@@ -768,17 +771,23 @@ function canHaveChildren(parent, child) {
|
|
|
768
771
|
case "elements": return true;
|
|
769
772
|
}
|
|
770
773
|
}
|
|
774
|
+
function isNumberText(value) {
|
|
775
|
+
return value.trim() !== "" && Number.isFinite(Number(value));
|
|
776
|
+
}
|
|
777
|
+
function printNumber(value) {
|
|
778
|
+
return String(Number(value));
|
|
779
|
+
}
|
|
771
780
|
//#endregion
|
|
772
781
|
//#region src/view/compile.ts
|
|
773
782
|
function compileView(source, context) {
|
|
774
783
|
const nodes = parseDmlToIR(source, {
|
|
775
784
|
locations: true,
|
|
776
785
|
file: context.file,
|
|
777
|
-
whitespace: "
|
|
786
|
+
whitespace: "inline"
|
|
778
787
|
});
|
|
779
788
|
return [
|
|
780
789
|
"export default function render(d, { h, on, text, each }) {",
|
|
781
|
-
` return h(${new ViewCompiler(context).compileRoot(nodes).map((
|
|
790
|
+
` return h(${new ViewCompiler(context).compileRoot(nodes).map((argument) => `\n ${argument},`).join("")}\n );`,
|
|
782
791
|
"}",
|
|
783
792
|
""
|
|
784
793
|
].join("\n");
|
|
@@ -838,13 +847,13 @@ function addVariables(env, variables, file) {
|
|
|
838
847
|
//#endregion
|
|
839
848
|
//#region src/build/build.ts
|
|
840
849
|
const MANIFEST_FILE = "manifest.json";
|
|
841
|
-
const ICON_FILE = "icon.png";
|
|
842
850
|
const STYLES_FILE = "dot.css";
|
|
843
851
|
const ARCHIVE_DATE = new Date(1980, 0, 1);
|
|
844
852
|
/** Compiles and bundles the dot into `outDir` for hosts, and refreshes the types in `.dot/`. */
|
|
845
853
|
async function build(options) {
|
|
846
854
|
const project = await loadProject(options.root);
|
|
847
855
|
const mode = options.mode ?? "production";
|
|
856
|
+
if (mode === "production") refuseLocalNetworkHosts(project);
|
|
848
857
|
const env = await loadPublicEnv(project.root, mode);
|
|
849
858
|
const runtime = installedRuntime(project.root);
|
|
850
859
|
await writePageTypes(project);
|
|
@@ -861,20 +870,16 @@ async function build(options) {
|
|
|
861
870
|
fileName: "dot.js",
|
|
862
871
|
format,
|
|
863
872
|
mode,
|
|
864
|
-
minify: mode === "production",
|
|
865
873
|
conditions: options.conditions ?? [],
|
|
866
874
|
external
|
|
867
875
|
});
|
|
868
|
-
const files = [
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
];
|
|
876
|
-
const icon = await copyIcon(project, outDir);
|
|
877
|
-
if (icon) files.push(icon);
|
|
876
|
+
const files = [];
|
|
877
|
+
files.push(...await bundleLayer(ENTRY.ui, uiDir, "esm", Object.keys(REACT_HOST_MODULES)));
|
|
878
|
+
files.push(...await bundleLayer(ENTRY.logic, join(outDir, "logic"), "iife", []));
|
|
879
|
+
files.push(await copyRendererStyles(runtime, uiDir));
|
|
880
|
+
files.push(await writeIndexHtml(project, uiDir, runtime.react.version));
|
|
881
|
+
files.push(...await copyStatics(project, uiDir));
|
|
882
|
+
files.push(await writeManifest(project, outDir, runtime));
|
|
878
883
|
if (mode === "production") files.push(await writeArchive(project, outDir, files));
|
|
879
884
|
return {
|
|
880
885
|
outDir,
|
|
@@ -882,6 +887,9 @@ async function build(options) {
|
|
|
882
887
|
warnings
|
|
883
888
|
};
|
|
884
889
|
}
|
|
890
|
+
function refuseLocalNetworkHosts(project) {
|
|
891
|
+
for (const host of project.dotJson.network?.hosts ?? []) if (isLocalNetworkHost(host)) throw new CompileError(`"${host}" in network.hosts only works while developing, as Dash refuses it for published dots. Use your server's public name in dot.json`, join(project.root, DOT_JSON_FILE));
|
|
892
|
+
}
|
|
885
893
|
async function copyRendererStyles(runtime, uiDir) {
|
|
886
894
|
const file = join(uiDir, STYLES_FILE);
|
|
887
895
|
await mkdir(uiDir, { recursive: true });
|
|
@@ -891,25 +899,30 @@ async function copyRendererStyles(runtime, uiDir) {
|
|
|
891
899
|
async function writeArchive(project, outDir, files) {
|
|
892
900
|
const entries = {};
|
|
893
901
|
for (const file of files) if (!file.endsWith(".map")) {
|
|
894
|
-
const name =
|
|
902
|
+
const name = relativeSlashPath(outDir, file);
|
|
895
903
|
entries[name] = [await readFile(file), { mtime: ARCHIVE_DATE }];
|
|
896
904
|
}
|
|
897
|
-
const {
|
|
898
|
-
for (const name of await readdir(outDir)) if (name.startsWith(`${
|
|
899
|
-
const file = join(outDir, `${
|
|
905
|
+
const { package: packageName, version } = project.dotJson;
|
|
906
|
+
for (const name of await readdir(outDir)) if (name.startsWith(`${packageName}-`) && name.endsWith(".zip")) await rm(join(outDir, name));
|
|
907
|
+
const file = join(outDir, `${packageName}-${version}.zip`);
|
|
900
908
|
await writeFile(file, zipSync(entries, { level: 9 }));
|
|
901
909
|
return file;
|
|
902
910
|
}
|
|
903
911
|
async function compileModules(project, env, components, mode, warnings) {
|
|
904
912
|
const modules = /* @__PURE__ */ new Map();
|
|
905
|
-
const
|
|
913
|
+
const usedComponents = /* @__PURE__ */ new Set();
|
|
906
914
|
for (const page of project.pages) {
|
|
907
|
-
const view = await compilePage(page, components, project.statics, warnings,
|
|
915
|
+
const view = await compilePage(page, components, project.statics, warnings, usedComponents);
|
|
908
916
|
modules.set(viewModuleId(page.id), view);
|
|
909
917
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
modules.set(ENTRY.
|
|
918
|
+
let rendererComponents;
|
|
919
|
+
if (mode === "production") rendererComponents = [...usedComponents].filter((name) => name in builtinComponents).sort();
|
|
920
|
+
modules.set(ENTRY.ui, uiEntry(project, rendererComponents));
|
|
921
|
+
const logicOptions = {
|
|
922
|
+
strict: mode === "development",
|
|
923
|
+
dashMinApiVersion: dashMinApiVersion(project)
|
|
924
|
+
};
|
|
925
|
+
modules.set(ENTRY.logic, logicEntry(project, logicOptions));
|
|
913
926
|
modules.set(ENV_MODULE, envModule(env));
|
|
914
927
|
return modules;
|
|
915
928
|
}
|
|
@@ -935,32 +948,30 @@ async function writeManifest(project, outDir, runtime) {
|
|
|
935
948
|
await writeFile(file, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
936
949
|
return file;
|
|
937
950
|
}
|
|
938
|
-
async function copyIcon(project, outDir) {
|
|
939
|
-
if (project.dotJson.icon === void 0) return null;
|
|
940
|
-
const file = join(outDir, ICON_FILE);
|
|
941
|
-
await copyFile(join(project.root, project.dotJson.icon), file);
|
|
942
|
-
return file;
|
|
943
|
-
}
|
|
944
951
|
function dotManifest(project, runtime) {
|
|
945
|
-
const { id,
|
|
952
|
+
const { id, package: packageName, name, version, permissions, network } = project.dotJson;
|
|
946
953
|
const pages = project.pages.map((page) => page.id);
|
|
954
|
+
const share = {};
|
|
955
|
+
for (const page of project.pages) if (page.share) share[page.id] = page.share;
|
|
947
956
|
return {
|
|
948
957
|
id,
|
|
958
|
+
package: packageName,
|
|
949
959
|
name,
|
|
950
960
|
version,
|
|
951
961
|
runtime: runtime.version,
|
|
952
962
|
react: runtime.react.version,
|
|
953
|
-
description,
|
|
954
|
-
categories,
|
|
955
|
-
icon: project.dotJson.icon === void 0 ? void 0 : ICON_FILE,
|
|
956
963
|
permissions,
|
|
957
964
|
network,
|
|
958
|
-
dash,
|
|
965
|
+
dash: { minApiVersion: dashMinApiVersion(project) },
|
|
959
966
|
entry: pages[0],
|
|
960
|
-
pages
|
|
967
|
+
pages,
|
|
968
|
+
share
|
|
961
969
|
};
|
|
962
970
|
}
|
|
963
|
-
|
|
971
|
+
function dashMinApiVersion(project) {
|
|
972
|
+
return project.dotJson.dash?.minApiVersion ?? DEFAULT_DASH_API_VERSION;
|
|
973
|
+
}
|
|
974
|
+
async function compilePage(page, components, statics, warnings, usedComponents) {
|
|
964
975
|
const [view, state, controller] = await Promise.all([
|
|
965
976
|
readFile(page.view, "utf8"),
|
|
966
977
|
readFile(page.state, "utf8"),
|
|
@@ -971,7 +982,7 @@ async function compilePage(page, components, statics, warnings, used) {
|
|
|
971
982
|
components,
|
|
972
983
|
statics,
|
|
973
984
|
onWarning: (warning) => warnings.push(warning),
|
|
974
|
-
onComponent: (name) =>
|
|
985
|
+
onComponent: (name) => usedComponents.add(name)
|
|
975
986
|
};
|
|
976
987
|
const { keys } = parseStateModule(state, page.state);
|
|
977
988
|
if (keys) context.stateKeys = keys;
|
|
@@ -989,7 +1000,7 @@ function indexHtml(title, reactVersion) {
|
|
|
989
1000
|
<meta charset="UTF-8" />
|
|
990
1001
|
<meta
|
|
991
1002
|
name="viewport"
|
|
992
|
-
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
|
1003
|
+
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
|
993
1004
|
/>
|
|
994
1005
|
<title>${escaped}</title>
|
|
995
1006
|
<link rel="stylesheet" href="./${STYLES_FILE}" />
|
|
@@ -1008,6 +1019,6 @@ function indexHtml(title, reactVersion) {
|
|
|
1008
1019
|
`;
|
|
1009
1020
|
}
|
|
1010
1021
|
//#endregion
|
|
1011
|
-
export { CONTROL_FLOW_TAGS, CompileError, CompileWarning, DATA_PREFIX, PAGES_DIR, ROOT_TAG, STATICS_DIR, build, canHaveChildren, compileView, dataKey, installedRuntime, listStatics, loadProject };
|
|
1022
|
+
export { CONTROL_FLOW_TAGS, CompileError, CompileWarning, DATA_PREFIX, DOT_FILE, DOT_JSON_FILE, PAGES_DIR, ROOT_TAG, STATICS_DIR, build, canHaveChildren, compileView, dataKey, installedRuntime, isDotPackage, listStatics, loadProject };
|
|
1012
1023
|
|
|
1013
1024
|
//# sourceMappingURL=index.js.map
|