@lightspeed-js/core 0.3.2 → 0.4.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/compile.js +4 -2
- package/dist/index.js +19 -3
- package/dist/ssg.js +71 -25
- package/dist/types/src/parser/template.d.ts +6 -0
- package/dist/types/src/project/component-ids.d.ts +9 -1
- package/dist/types/src/project/components.d.ts +16 -2
- package/dist/types/src/ssg/fragment.d.ts +3 -3
- package/dist/types/src/vite/plugin.d.ts +7 -1
- package/dist/types/test/component-prefix.test.d.ts +1 -0
- package/dist/types/test/eager.test.d.ts +1 -0
- package/dist/vite.js +102 -39
- package/package.json +1 -1
package/dist/compile.js
CHANGED
|
@@ -188,12 +188,14 @@ function emitMountPlaceholders(template, componentRegistry, depth = 0) {
|
|
|
188
188
|
let result = template;
|
|
189
189
|
result = result.replace(/<([A-Z][a-zA-Z0-9]*)((?:\s+[^>]*?)?)\s*\/>/g, (_match, name, attributes) => {
|
|
190
190
|
warnUnknown(name);
|
|
191
|
-
|
|
191
|
+
const entry = componentRegistry?.[name.toLowerCase()];
|
|
192
|
+
return buildPlaceholder(entry?.id ?? name, attributes || "", "", entry?.needs);
|
|
192
193
|
});
|
|
193
194
|
result = result.replace(/<([A-Z][a-zA-Z0-9]*)((?:\s+[^>]*?)?)\s*>([\s\S]*?)<\/\1\s*>/g, (_match, name, attributes, content) => {
|
|
194
195
|
warnUnknown(name);
|
|
195
196
|
const inner = emitMountPlaceholders(content, componentRegistry, depth + 1);
|
|
196
|
-
|
|
197
|
+
const entry = componentRegistry?.[name.toLowerCase()];
|
|
198
|
+
return buildPlaceholder(entry?.id ?? name, attributes || "", inner, entry?.needs);
|
|
197
199
|
});
|
|
198
200
|
return result;
|
|
199
201
|
}
|
package/dist/index.js
CHANGED
|
@@ -1330,7 +1330,11 @@ class Mounter {
|
|
|
1330
1330
|
};
|
|
1331
1331
|
if (adopt) {
|
|
1332
1332
|
const adopted = [];
|
|
1333
|
-
if (
|
|
1333
|
+
if (!parent)
|
|
1334
|
+
context.rootSlots = groupSlotFills(slotNodes);
|
|
1335
|
+
const adoptedAll = this.adoptNodes(Array.from(fragment.childNodes), adopt, context, adopted);
|
|
1336
|
+
delete context.rootSlots;
|
|
1337
|
+
if (adoptedAll) {
|
|
1334
1338
|
for (const marker of adopted) {
|
|
1335
1339
|
await this.mountMarker(marker, instance, context);
|
|
1336
1340
|
}
|
|
@@ -1588,9 +1592,15 @@ class Mounter {
|
|
|
1588
1592
|
d = found.length;
|
|
1589
1593
|
continue;
|
|
1590
1594
|
}
|
|
1595
|
+
const name = found[d].data.slice(SLOT_MARK.length);
|
|
1596
|
+
const region = [];
|
|
1597
|
+
d++;
|
|
1591
1598
|
while (d < found.length && !isSlotEnd(found[d]))
|
|
1592
|
-
d
|
|
1599
|
+
region.push(found[d++]);
|
|
1593
1600
|
d++;
|
|
1601
|
+
if (context.rootSlots && !this.adoptNodes(context.rootSlots.get(name) ?? [], region, context, markers)) {
|
|
1602
|
+
return false;
|
|
1603
|
+
}
|
|
1594
1604
|
continue;
|
|
1595
1605
|
}
|
|
1596
1606
|
const target = found[d++];
|
|
@@ -3031,7 +3041,13 @@ function createLightspeedApp(options) {
|
|
|
3031
3041
|
return new LightspeedRuntime(options);
|
|
3032
3042
|
}
|
|
3033
3043
|
function serializeNodes(nodes) {
|
|
3034
|
-
|
|
3044
|
+
const document2 = nodes[0]?.ownerDocument;
|
|
3045
|
+
if (!document2)
|
|
3046
|
+
return "";
|
|
3047
|
+
const template = document2.createElement("template");
|
|
3048
|
+
for (const node of nodes)
|
|
3049
|
+
template.content.appendChild(node.cloneNode(true));
|
|
3050
|
+
return template.innerHTML;
|
|
3035
3051
|
}
|
|
3036
3052
|
// src/runtime/helpers.ts
|
|
3037
3053
|
function reactive(target) {
|
package/dist/ssg.js
CHANGED
|
@@ -1353,7 +1353,11 @@ class Mounter {
|
|
|
1353
1353
|
};
|
|
1354
1354
|
if (adopt) {
|
|
1355
1355
|
const adopted = [];
|
|
1356
|
-
if (
|
|
1356
|
+
if (!parent)
|
|
1357
|
+
context.rootSlots = groupSlotFills(slotNodes);
|
|
1358
|
+
const adoptedAll = this.adoptNodes(Array.from(fragment.childNodes), adopt, context, adopted);
|
|
1359
|
+
delete context.rootSlots;
|
|
1360
|
+
if (adoptedAll) {
|
|
1357
1361
|
for (const marker of adopted) {
|
|
1358
1362
|
await this.mountMarker(marker, instance, context);
|
|
1359
1363
|
}
|
|
@@ -1611,9 +1615,15 @@ class Mounter {
|
|
|
1611
1615
|
d = found.length;
|
|
1612
1616
|
continue;
|
|
1613
1617
|
}
|
|
1618
|
+
const name = found[d].data.slice(SLOT_MARK.length);
|
|
1619
|
+
const region = [];
|
|
1620
|
+
d++;
|
|
1614
1621
|
while (d < found.length && !isSlotEnd(found[d]))
|
|
1615
|
-
d
|
|
1622
|
+
region.push(found[d++]);
|
|
1616
1623
|
d++;
|
|
1624
|
+
if (context.rootSlots && !this.adoptNodes(context.rootSlots.get(name) ?? [], region, context, markers)) {
|
|
1625
|
+
return false;
|
|
1626
|
+
}
|
|
1617
1627
|
continue;
|
|
1618
1628
|
}
|
|
1619
1629
|
const target = found[d++];
|
|
@@ -3136,7 +3146,13 @@ class LightspeedRuntime {
|
|
|
3136
3146
|
}
|
|
3137
3147
|
}
|
|
3138
3148
|
function serializeNodes(nodes) {
|
|
3139
|
-
|
|
3149
|
+
const document2 = nodes[0]?.ownerDocument;
|
|
3150
|
+
if (!document2)
|
|
3151
|
+
return "";
|
|
3152
|
+
const template = document2.createElement("template");
|
|
3153
|
+
for (const node of nodes)
|
|
3154
|
+
template.content.appendChild(node.cloneNode(true));
|
|
3155
|
+
return template.innerHTML;
|
|
3140
3156
|
}
|
|
3141
3157
|
|
|
3142
3158
|
// src/ssg/render.ts
|
|
@@ -3434,13 +3450,11 @@ function allComments(document2) {
|
|
|
3434
3450
|
return comments;
|
|
3435
3451
|
}
|
|
3436
3452
|
function serialiseBetween(start, end) {
|
|
3437
|
-
|
|
3438
|
-
let current = start.nextSibling;
|
|
3439
|
-
|
|
3440
|
-
html += current.nodeType === 1 ? current.outerHTML : current.textContent ?? "";
|
|
3441
|
-
current = current.nextSibling;
|
|
3453
|
+
const template = start.ownerDocument.createElement("template");
|
|
3454
|
+
for (let current = start.nextSibling;current && current !== end; current = current.nextSibling) {
|
|
3455
|
+
template.content.appendChild(current.cloneNode(true));
|
|
3442
3456
|
}
|
|
3443
|
-
return
|
|
3457
|
+
return template.innerHTML;
|
|
3444
3458
|
}
|
|
3445
3459
|
// src/compile/expressions.ts
|
|
3446
3460
|
function expressionKey(expr, params) {
|
|
@@ -3601,22 +3615,24 @@ function addMarkerBinds(payload, params, add) {
|
|
|
3601
3615
|
}
|
|
3602
3616
|
|
|
3603
3617
|
// src/project/component-ids.ts
|
|
3604
|
-
function componentIdOf(file) {
|
|
3618
|
+
function componentIdOf(file, prefix = "") {
|
|
3605
3619
|
const name = file.split(/[\\/]/).pop() ?? file;
|
|
3606
|
-
return name.replace(/\.lsp$/, "").toLowerCase();
|
|
3620
|
+
return (prefix + name.replace(/\.lsp$/, "")).toLowerCase();
|
|
3607
3621
|
}
|
|
3608
3622
|
function resolveComponentIds(found, declaredOverrides = []) {
|
|
3609
3623
|
const declared = new Set(declaredOverrides.map((id) => id.toLowerCase()));
|
|
3610
3624
|
const files = new Map;
|
|
3611
3625
|
const rootOf = new Map;
|
|
3612
|
-
const
|
|
3626
|
+
const roots = new Map;
|
|
3627
|
+
const result = { files, overrides: [], conflicts: [], unusedOverrides: [], roots };
|
|
3613
3628
|
const used = new Set;
|
|
3614
|
-
for (const { file, root } of found) {
|
|
3615
|
-
const id = componentIdOf(file);
|
|
3629
|
+
for (const { file, root, prefix = "" } of found) {
|
|
3630
|
+
const id = componentIdOf(file, prefix);
|
|
3616
3631
|
const kept = files.get(id);
|
|
3617
3632
|
if (kept === undefined) {
|
|
3618
3633
|
files.set(id, file);
|
|
3619
3634
|
rootOf.set(id, root);
|
|
3635
|
+
roots.set(id, { root, prefix });
|
|
3620
3636
|
continue;
|
|
3621
3637
|
}
|
|
3622
3638
|
if (rootOf.get(id) === root) {
|
|
@@ -3643,7 +3659,7 @@ function describeConflict(conflict, rel) {
|
|
|
3643
3659
|
// src/project/components.ts
|
|
3644
3660
|
import { readFile, readdir } from "fs/promises";
|
|
3645
3661
|
import { createRequire as createRequire2 } from "node:module";
|
|
3646
|
-
import { dirname as dirname2, join, relative as relative2, resolve as resolve2 } from "path";
|
|
3662
|
+
import { dirname as dirname2, join, relative as relative2, resolve as resolve2, sep } from "path";
|
|
3647
3663
|
|
|
3648
3664
|
// src/specifiers.ts
|
|
3649
3665
|
var PACKAGE_NAME = "@lightspeed-js/core";
|
|
@@ -4292,12 +4308,14 @@ function emitMountPlaceholders(template, componentRegistry, depth = 0) {
|
|
|
4292
4308
|
let result = template;
|
|
4293
4309
|
result = result.replace(/<([A-Z][a-zA-Z0-9]*)((?:\s+[^>]*?)?)\s*\/>/g, (_match, name, attributes) => {
|
|
4294
4310
|
warnUnknown(name);
|
|
4295
|
-
|
|
4311
|
+
const entry = componentRegistry?.[name.toLowerCase()];
|
|
4312
|
+
return buildPlaceholder(entry?.id ?? name, attributes || "", "", entry?.needs);
|
|
4296
4313
|
});
|
|
4297
4314
|
result = result.replace(/<([A-Z][a-zA-Z0-9]*)((?:\s+[^>]*?)?)\s*>([\s\S]*?)<\/\1\s*>/g, (_match, name, attributes, content) => {
|
|
4298
4315
|
warnUnknown(name);
|
|
4299
4316
|
const inner = emitMountPlaceholders(content, componentRegistry, depth + 1);
|
|
4300
|
-
|
|
4317
|
+
const entry = componentRegistry?.[name.toLowerCase()];
|
|
4318
|
+
return buildPlaceholder(entry?.id ?? name, attributes || "", inner, entry?.needs);
|
|
4301
4319
|
});
|
|
4302
4320
|
return result;
|
|
4303
4321
|
}
|
|
@@ -4406,18 +4424,43 @@ function createComponentProject(options) {
|
|
|
4406
4424
|
const announced = new Set;
|
|
4407
4425
|
let rebuilding = null;
|
|
4408
4426
|
const projectModules = new Map;
|
|
4409
|
-
function
|
|
4427
|
+
function rootSpecs() {
|
|
4410
4428
|
const root = rootOf();
|
|
4411
|
-
return [
|
|
4429
|
+
return [
|
|
4430
|
+
{ dir: resolve2(root, "src"), prefix: "" },
|
|
4431
|
+
...extraComponentDirs.map((entry) => typeof entry === "string" ? { dir: resolve2(root, entry), prefix: "" } : { dir: resolve2(root, entry.dir), prefix: entry.prefix ?? "" })
|
|
4432
|
+
];
|
|
4433
|
+
}
|
|
4434
|
+
function roots() {
|
|
4435
|
+
return rootSpecs().map((spec) => spec.dir);
|
|
4412
4436
|
}
|
|
4413
4437
|
async function findComponentFiles() {
|
|
4438
|
+
const specs = rootSpecs();
|
|
4414
4439
|
const found = [];
|
|
4415
|
-
for (const
|
|
4416
|
-
|
|
4417
|
-
|
|
4440
|
+
for (const spec of specs) {
|
|
4441
|
+
const nested = new Set(specs.map((s) => s.dir).filter((d) => d !== spec.dir && d.startsWith(spec.dir + sep)));
|
|
4442
|
+
for (const file of await findLspFiles(spec.dir, nested)) {
|
|
4443
|
+
found.push({ file, root: spec.dir, ...spec.prefix ? { prefix: spec.prefix } : {} });
|
|
4444
|
+
}
|
|
4418
4445
|
}
|
|
4419
4446
|
return found;
|
|
4420
4447
|
}
|
|
4448
|
+
let rootOfId = new Map;
|
|
4449
|
+
function registryFor(id) {
|
|
4450
|
+
const all = Object.fromEntries(registry);
|
|
4451
|
+
const home = rootOfId.get(id);
|
|
4452
|
+
if (!home?.prefix)
|
|
4453
|
+
return all;
|
|
4454
|
+
for (const [sibling, where] of rootOfId) {
|
|
4455
|
+
if (where.root !== home.root)
|
|
4456
|
+
continue;
|
|
4457
|
+
const local = sibling.slice(home.prefix.length);
|
|
4458
|
+
const entry = registry.get(sibling);
|
|
4459
|
+
if (local && entry)
|
|
4460
|
+
all[local] = { ...entry, id: sibling };
|
|
4461
|
+
}
|
|
4462
|
+
return all;
|
|
4463
|
+
}
|
|
4421
4464
|
function announce(ids) {
|
|
4422
4465
|
const rel = (file) => relative2(rootOf(), file);
|
|
4423
4466
|
for (const conflict of ids.conflicts) {
|
|
@@ -4443,6 +4486,7 @@ function createComponentProject(options) {
|
|
|
4443
4486
|
const nextFiles = new Map;
|
|
4444
4487
|
try {
|
|
4445
4488
|
const ids = resolveComponentIds(await findComponentFiles(), declaredOverrides);
|
|
4489
|
+
rootOfId = ids.roots;
|
|
4446
4490
|
conflicts = ids.conflicts;
|
|
4447
4491
|
announce(ids);
|
|
4448
4492
|
for (const [componentId, filePath] of ids.files) {
|
|
@@ -4507,9 +4551,9 @@ function createComponentProject(options) {
|
|
|
4507
4551
|
}
|
|
4508
4552
|
async function definitions() {
|
|
4509
4553
|
await rebuild();
|
|
4510
|
-
const registryObj = Object.fromEntries(registry);
|
|
4511
4554
|
const out = [];
|
|
4512
4555
|
for (const [componentId, raw] of registry) {
|
|
4556
|
+
const registryObj = registryFor(componentId);
|
|
4513
4557
|
const scopeId = scopeIdFor(componentId);
|
|
4514
4558
|
const source = raw.logic?.trim() ? await compileLogic2(raw.logic) : "";
|
|
4515
4559
|
const setup = await buildSetup(source, {
|
|
@@ -4545,6 +4589,7 @@ function createComponentProject(options) {
|
|
|
4545
4589
|
return conflicts;
|
|
4546
4590
|
},
|
|
4547
4591
|
roots,
|
|
4592
|
+
registryFor,
|
|
4548
4593
|
rebuild,
|
|
4549
4594
|
definitions,
|
|
4550
4595
|
compileMarkup: (html, id = "index") => transformTemplate(html, id, Object.fromEntries(registry)),
|
|
@@ -4560,14 +4605,15 @@ function withSharedStyles(parsed, watch) {
|
|
|
4560
4605
|
watch(file);
|
|
4561
4606
|
return inlineStyleSources(parsed, nodeStyleReader());
|
|
4562
4607
|
}
|
|
4563
|
-
async function findLspFiles(dir) {
|
|
4608
|
+
async function findLspFiles(dir, skip = new Set) {
|
|
4564
4609
|
const found = [];
|
|
4565
4610
|
try {
|
|
4566
4611
|
const entries = (await readdir(dir, { withFileTypes: true })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
4567
4612
|
for (const entry of entries) {
|
|
4568
4613
|
const fullPath = join(dir, entry.name);
|
|
4569
4614
|
if (entry.isDirectory()) {
|
|
4570
|
-
|
|
4615
|
+
if (!skip.has(fullPath))
|
|
4616
|
+
found.push(...await findLspFiles(fullPath, skip));
|
|
4571
4617
|
} else if (entry.isFile() && entry.name.endsWith(".lsp")) {
|
|
4572
4618
|
found.push(fullPath);
|
|
4573
4619
|
}
|
|
@@ -5,6 +5,12 @@ interface ComponentRegistry {
|
|
|
5
5
|
logic: string;
|
|
6
6
|
/** Cell keys the component itself declared - see `LSPComponent.needs`. */
|
|
7
7
|
needs?: string[];
|
|
8
|
+
/**
|
|
9
|
+
* The id the component registers under, when the tag is a local name for
|
|
10
|
+
* it: inside a prefixed component root, a sibling's unprefixed name
|
|
11
|
+
* (ADR-061). Absent, the tag lowercased is the id.
|
|
12
|
+
*/
|
|
13
|
+
id?: string;
|
|
8
14
|
};
|
|
9
15
|
}
|
|
10
16
|
/**
|
|
@@ -15,6 +15,8 @@ export interface FoundComponent {
|
|
|
15
15
|
file: string;
|
|
16
16
|
/** The component root it was found under. */
|
|
17
17
|
root: string;
|
|
18
|
+
/** The root's prefix (ADR-061): its components register as prefix + name. */
|
|
19
|
+
prefix?: string;
|
|
18
20
|
}
|
|
19
21
|
export type Conflict = {
|
|
20
22
|
kind: "same-root";
|
|
@@ -39,8 +41,14 @@ export interface ComponentIds {
|
|
|
39
41
|
conflicts: Conflict[];
|
|
40
42
|
/** Names in `overrides` that replace nothing - left over after a rename or removal. */
|
|
41
43
|
unusedOverrides: string[];
|
|
44
|
+
/** Component id to the root it came from, and that root's prefix. */
|
|
45
|
+
roots: Map<string, {
|
|
46
|
+
root: string;
|
|
47
|
+
prefix: string;
|
|
48
|
+
}>;
|
|
42
49
|
}
|
|
43
|
-
|
|
50
|
+
/** A component's id: its root's prefix and its filename, lowercased (`Slv` + `Icon.lsp` → `slvicon`). */
|
|
51
|
+
export declare function componentIdOf(file: string, prefix?: string): string;
|
|
44
52
|
/** `found` must be in precedence order: every file of the first root, then the next. */
|
|
45
53
|
export declare function resolveComponentIds(found: FoundComponent[], declaredOverrides?: readonly string[]): ComponentIds;
|
|
46
54
|
/** One message per conflict, each ending in the change that fixes it (ADR-055). */
|
|
@@ -11,11 +11,21 @@ import { parseLSP } from "../parser/index.js";
|
|
|
11
11
|
import type { ComponentDefinition } from "../runtime/components/index.js";
|
|
12
12
|
import { type Conflict } from "./component-ids.js";
|
|
13
13
|
export type Compiler = "esbuild" | "tsc" | "swc";
|
|
14
|
+
/** A component root: a folder, or a folder with a prefix (ADR-061). */
|
|
15
|
+
export type ComponentDir = string | {
|
|
16
|
+
dir: string;
|
|
17
|
+
prefix?: string;
|
|
18
|
+
};
|
|
19
|
+
export declare const componentDirPath: (entry: ComponentDir) => string;
|
|
14
20
|
export interface ComponentProjectOptions {
|
|
15
21
|
/** The project root. A function when it is only known later (Vite's configResolved). */
|
|
16
22
|
root: string | (() => string);
|
|
17
|
-
/**
|
|
18
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Roots beyond the project's own `src`, relative to the root. An entry with
|
|
25
|
+
* a `prefix` registers its components as prefix + name (ADR-061), and may
|
|
26
|
+
* be a folder inside `src`, which then belongs to it rather than to `src`.
|
|
27
|
+
*/
|
|
28
|
+
componentDirs?: ComponentDir[];
|
|
19
29
|
/** Names the app replaces on purpose (ADR-058). */
|
|
20
30
|
overrides?: string[];
|
|
21
31
|
/** For component logic at build time. */
|
|
@@ -39,6 +49,10 @@ export interface ComponentProject {
|
|
|
39
49
|
readonly conflicts: Conflict[];
|
|
40
50
|
/** Every root components come from, in precedence order: `src` first. */
|
|
41
51
|
roots(): string[];
|
|
52
|
+
/** The registry as `id`'s template sees it: with its root's siblings under their local names (ADR-061). */
|
|
53
|
+
registryFor(id: string): Record<string, RegistryEntry & {
|
|
54
|
+
id?: string;
|
|
55
|
+
}>;
|
|
42
56
|
/** Rescan the roots. Concurrent callers share one rescan. */
|
|
43
57
|
rebuild(): Promise<void>;
|
|
44
58
|
/** Compile every component, as the client's modules do, for build-time rendering. */
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
* `app.hydrate(container)` on the element it put the fragment in.
|
|
14
14
|
*/
|
|
15
15
|
import type { TemplateExpression } from "../compile/expressions.js";
|
|
16
|
-
import type { ComponentProject, Compiler } from "../project/components.js";
|
|
16
|
+
import type { ComponentDir, ComponentProject, Compiler } from "../project/components.js";
|
|
17
17
|
export interface FragmentRendererOptions {
|
|
18
18
|
/** The project root: components are found in its `src`, then `componentDirs`. */
|
|
19
19
|
root: string;
|
|
20
|
-
/** Further component roots, relative to the root - as `lightspeedPlugin`'s. */
|
|
21
|
-
componentDirs?:
|
|
20
|
+
/** Further component roots, relative to the root - as `lightspeedPlugin`'s, prefixes included. */
|
|
21
|
+
componentDirs?: ComponentDir[];
|
|
22
22
|
/** Names the app replaces on purpose (ADR-058). */
|
|
23
23
|
overrides?: string[];
|
|
24
24
|
compiler?: Compiler;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
|
+
import { type ComponentDir } from "../project/components.js";
|
|
2
3
|
interface LightspeedPluginOptions {
|
|
3
4
|
typescript?: boolean;
|
|
4
5
|
/**
|
|
@@ -62,8 +63,13 @@ interface LightspeedPluginOptions {
|
|
|
62
63
|
* wherever it was found. The project's own `src` is searched first and wins,
|
|
63
64
|
* so an app can replace a package's component by giving a file the same
|
|
64
65
|
* name - once it says so in `overrides` (ADR-058).
|
|
66
|
+
*
|
|
67
|
+
* `{ dir, prefix }` namespaces a root (ADR-061): its components register as
|
|
68
|
+
* prefix + name (`{ dir: 'node_modules/ui', prefix: 'Ui' }` makes its
|
|
69
|
+
* `Button.lsp` `<UiButton>`), and inside it a sibling's plain name still
|
|
70
|
+
* finds the sibling. A prefixed folder inside `src` belongs to its entry.
|
|
65
71
|
*/
|
|
66
|
-
componentDirs?:
|
|
72
|
+
componentDirs?: ComponentDir[];
|
|
67
73
|
/**
|
|
68
74
|
* Component names the app replaces on purpose: a file in `src` (or an
|
|
69
75
|
* earlier root) with the same name as one in a later `componentDirs` root.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/vite.js
CHANGED
|
@@ -18,22 +18,24 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/project/component-ids.ts
|
|
21
|
-
function componentIdOf(file) {
|
|
21
|
+
function componentIdOf(file, prefix = "") {
|
|
22
22
|
const name = file.split(/[\\/]/).pop() ?? file;
|
|
23
|
-
return name.replace(/\.lsp$/, "").toLowerCase();
|
|
23
|
+
return (prefix + name.replace(/\.lsp$/, "")).toLowerCase();
|
|
24
24
|
}
|
|
25
25
|
function resolveComponentIds(found, declaredOverrides = []) {
|
|
26
26
|
const declared = new Set(declaredOverrides.map((id) => id.toLowerCase()));
|
|
27
27
|
const files = new Map;
|
|
28
28
|
const rootOf = new Map;
|
|
29
|
-
const
|
|
29
|
+
const roots = new Map;
|
|
30
|
+
const result = { files, overrides: [], conflicts: [], unusedOverrides: [], roots };
|
|
30
31
|
const used = new Set;
|
|
31
|
-
for (const { file, root } of found) {
|
|
32
|
-
const id = componentIdOf(file);
|
|
32
|
+
for (const { file, root, prefix = "" } of found) {
|
|
33
|
+
const id = componentIdOf(file, prefix);
|
|
33
34
|
const kept = files.get(id);
|
|
34
35
|
if (kept === undefined) {
|
|
35
36
|
files.set(id, file);
|
|
36
37
|
rootOf.set(id, root);
|
|
38
|
+
roots.set(id, { root, prefix });
|
|
37
39
|
continue;
|
|
38
40
|
}
|
|
39
41
|
if (rootOf.get(id) === root) {
|
|
@@ -60,7 +62,7 @@ function describeConflict(conflict, rel) {
|
|
|
60
62
|
// src/project/components.ts
|
|
61
63
|
import { readFile, readdir } from "fs/promises";
|
|
62
64
|
import { createRequire as createRequire2 } from "node:module";
|
|
63
|
-
import { dirname as dirname2, join, relative as relative2, resolve as resolve2 } from "path";
|
|
65
|
+
import { dirname as dirname2, join, relative as relative2, resolve as resolve2, sep } from "path";
|
|
64
66
|
|
|
65
67
|
// src/runtime/diagnostics.ts
|
|
66
68
|
var PREFIX = "[lightspeed]";
|
|
@@ -782,7 +784,11 @@ class Mounter {
|
|
|
782
784
|
};
|
|
783
785
|
if (adopt) {
|
|
784
786
|
const adopted = [];
|
|
785
|
-
if (
|
|
787
|
+
if (!parent)
|
|
788
|
+
context.rootSlots = groupSlotFills(slotNodes);
|
|
789
|
+
const adoptedAll = this.adoptNodes(Array.from(fragment.childNodes), adopt, context, adopted);
|
|
790
|
+
delete context.rootSlots;
|
|
791
|
+
if (adoptedAll) {
|
|
786
792
|
for (const marker of adopted) {
|
|
787
793
|
await this.mountMarker(marker, instance, context);
|
|
788
794
|
}
|
|
@@ -1040,9 +1046,15 @@ class Mounter {
|
|
|
1040
1046
|
d = found.length;
|
|
1041
1047
|
continue;
|
|
1042
1048
|
}
|
|
1049
|
+
const name = found[d].data.slice(SLOT_MARK.length);
|
|
1050
|
+
const region = [];
|
|
1051
|
+
d++;
|
|
1043
1052
|
while (d < found.length && !isSlotEnd(found[d]))
|
|
1044
|
-
d
|
|
1053
|
+
region.push(found[d++]);
|
|
1045
1054
|
d++;
|
|
1055
|
+
if (context.rootSlots && !this.adoptNodes(context.rootSlots.get(name) ?? [], region, context, markers)) {
|
|
1056
|
+
return false;
|
|
1057
|
+
}
|
|
1046
1058
|
continue;
|
|
1047
1059
|
}
|
|
1048
1060
|
const target = found[d++];
|
|
@@ -2556,12 +2568,14 @@ function emitMountPlaceholders(template, componentRegistry, depth = 0) {
|
|
|
2556
2568
|
let result = template;
|
|
2557
2569
|
result = result.replace(/<([A-Z][a-zA-Z0-9]*)((?:\s+[^>]*?)?)\s*\/>/g, (_match, name, attributes) => {
|
|
2558
2570
|
warnUnknown(name);
|
|
2559
|
-
|
|
2571
|
+
const entry = componentRegistry?.[name.toLowerCase()];
|
|
2572
|
+
return buildPlaceholder(entry?.id ?? name, attributes || "", "", entry?.needs);
|
|
2560
2573
|
});
|
|
2561
2574
|
result = result.replace(/<([A-Z][a-zA-Z0-9]*)((?:\s+[^>]*?)?)\s*>([\s\S]*?)<\/\1\s*>/g, (_match, name, attributes, content) => {
|
|
2562
2575
|
warnUnknown(name);
|
|
2563
2576
|
const inner = emitMountPlaceholders(content, componentRegistry, depth + 1);
|
|
2564
|
-
|
|
2577
|
+
const entry = componentRegistry?.[name.toLowerCase()];
|
|
2578
|
+
return buildPlaceholder(entry?.id ?? name, attributes || "", inner, entry?.needs);
|
|
2565
2579
|
});
|
|
2566
2580
|
return result;
|
|
2567
2581
|
}
|
|
@@ -2659,6 +2673,7 @@ function splitAttributes(componentId, attrString) {
|
|
|
2659
2673
|
}
|
|
2660
2674
|
|
|
2661
2675
|
// src/project/components.ts
|
|
2676
|
+
var componentDirPath = (entry) => typeof entry === "string" ? entry : entry.dir;
|
|
2662
2677
|
function createComponentProject(options) {
|
|
2663
2678
|
const rootOf = () => typeof options.root === "function" ? options.root() : options.root;
|
|
2664
2679
|
const extraComponentDirs = options.componentDirs ?? [];
|
|
@@ -2670,18 +2685,43 @@ function createComponentProject(options) {
|
|
|
2670
2685
|
const announced = new Set;
|
|
2671
2686
|
let rebuilding = null;
|
|
2672
2687
|
const projectModules = new Map;
|
|
2673
|
-
function
|
|
2688
|
+
function rootSpecs() {
|
|
2674
2689
|
const root = rootOf();
|
|
2675
|
-
return [
|
|
2690
|
+
return [
|
|
2691
|
+
{ dir: resolve2(root, "src"), prefix: "" },
|
|
2692
|
+
...extraComponentDirs.map((entry) => typeof entry === "string" ? { dir: resolve2(root, entry), prefix: "" } : { dir: resolve2(root, entry.dir), prefix: entry.prefix ?? "" })
|
|
2693
|
+
];
|
|
2694
|
+
}
|
|
2695
|
+
function roots() {
|
|
2696
|
+
return rootSpecs().map((spec) => spec.dir);
|
|
2676
2697
|
}
|
|
2677
2698
|
async function findComponentFiles() {
|
|
2699
|
+
const specs = rootSpecs();
|
|
2678
2700
|
const found = [];
|
|
2679
|
-
for (const
|
|
2680
|
-
|
|
2681
|
-
|
|
2701
|
+
for (const spec of specs) {
|
|
2702
|
+
const nested = new Set(specs.map((s) => s.dir).filter((d) => d !== spec.dir && d.startsWith(spec.dir + sep)));
|
|
2703
|
+
for (const file of await findLspFiles(spec.dir, nested)) {
|
|
2704
|
+
found.push({ file, root: spec.dir, ...spec.prefix ? { prefix: spec.prefix } : {} });
|
|
2705
|
+
}
|
|
2682
2706
|
}
|
|
2683
2707
|
return found;
|
|
2684
2708
|
}
|
|
2709
|
+
let rootOfId = new Map;
|
|
2710
|
+
function registryFor(id) {
|
|
2711
|
+
const all = Object.fromEntries(registry);
|
|
2712
|
+
const home = rootOfId.get(id);
|
|
2713
|
+
if (!home?.prefix)
|
|
2714
|
+
return all;
|
|
2715
|
+
for (const [sibling, where] of rootOfId) {
|
|
2716
|
+
if (where.root !== home.root)
|
|
2717
|
+
continue;
|
|
2718
|
+
const local = sibling.slice(home.prefix.length);
|
|
2719
|
+
const entry = registry.get(sibling);
|
|
2720
|
+
if (local && entry)
|
|
2721
|
+
all[local] = { ...entry, id: sibling };
|
|
2722
|
+
}
|
|
2723
|
+
return all;
|
|
2724
|
+
}
|
|
2685
2725
|
function announce(ids) {
|
|
2686
2726
|
const rel = (file) => relative2(rootOf(), file);
|
|
2687
2727
|
for (const conflict of ids.conflicts) {
|
|
@@ -2707,6 +2747,7 @@ function createComponentProject(options) {
|
|
|
2707
2747
|
const nextFiles = new Map;
|
|
2708
2748
|
try {
|
|
2709
2749
|
const ids = resolveComponentIds(await findComponentFiles(), declaredOverrides);
|
|
2750
|
+
rootOfId = ids.roots;
|
|
2710
2751
|
conflicts = ids.conflicts;
|
|
2711
2752
|
announce(ids);
|
|
2712
2753
|
for (const [componentId, filePath] of ids.files) {
|
|
@@ -2771,9 +2812,9 @@ function createComponentProject(options) {
|
|
|
2771
2812
|
}
|
|
2772
2813
|
async function definitions() {
|
|
2773
2814
|
await rebuild();
|
|
2774
|
-
const registryObj = Object.fromEntries(registry);
|
|
2775
2815
|
const out = [];
|
|
2776
2816
|
for (const [componentId, raw] of registry) {
|
|
2817
|
+
const registryObj = registryFor(componentId);
|
|
2777
2818
|
const scopeId = scopeIdFor(componentId);
|
|
2778
2819
|
const source = raw.logic?.trim() ? await compileLogic2(raw.logic) : "";
|
|
2779
2820
|
const setup = await buildSetup(source, {
|
|
@@ -2809,6 +2850,7 @@ function createComponentProject(options) {
|
|
|
2809
2850
|
return conflicts;
|
|
2810
2851
|
},
|
|
2811
2852
|
roots,
|
|
2853
|
+
registryFor,
|
|
2812
2854
|
rebuild,
|
|
2813
2855
|
definitions,
|
|
2814
2856
|
compileMarkup: (html, id = "index") => transformTemplate(html, id, Object.fromEntries(registry)),
|
|
@@ -2824,14 +2866,15 @@ function withSharedStyles(parsed, watch) {
|
|
|
2824
2866
|
watch(file);
|
|
2825
2867
|
return inlineStyleSources(parsed, nodeStyleReader());
|
|
2826
2868
|
}
|
|
2827
|
-
async function findLspFiles(dir) {
|
|
2869
|
+
async function findLspFiles(dir, skip = new Set) {
|
|
2828
2870
|
const found = [];
|
|
2829
2871
|
try {
|
|
2830
2872
|
const entries = (await readdir(dir, { withFileTypes: true })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
2831
2873
|
for (const entry of entries) {
|
|
2832
2874
|
const fullPath = join(dir, entry.name);
|
|
2833
2875
|
if (entry.isDirectory()) {
|
|
2834
|
-
|
|
2876
|
+
if (!skip.has(fullPath))
|
|
2877
|
+
found.push(...await findLspFiles(fullPath, skip));
|
|
2835
2878
|
} else if (entry.isFile() && entry.name.endsWith(".lsp")) {
|
|
2836
2879
|
found.push(fullPath);
|
|
2837
2880
|
}
|
|
@@ -4492,7 +4535,13 @@ class LightspeedRuntime {
|
|
|
4492
4535
|
}
|
|
4493
4536
|
}
|
|
4494
4537
|
function serializeNodes(nodes) {
|
|
4495
|
-
|
|
4538
|
+
const document2 = nodes[0]?.ownerDocument;
|
|
4539
|
+
if (!document2)
|
|
4540
|
+
return "";
|
|
4541
|
+
const template = document2.createElement("template");
|
|
4542
|
+
for (const node of nodes)
|
|
4543
|
+
template.content.appendChild(node.cloneNode(true));
|
|
4544
|
+
return template.innerHTML;
|
|
4496
4545
|
}
|
|
4497
4546
|
|
|
4498
4547
|
// src/ssg/render.ts
|
|
@@ -4790,18 +4839,16 @@ function allComments(document2) {
|
|
|
4790
4839
|
return comments;
|
|
4791
4840
|
}
|
|
4792
4841
|
function serialiseBetween(start, end) {
|
|
4793
|
-
|
|
4794
|
-
let current = start.nextSibling;
|
|
4795
|
-
|
|
4796
|
-
html += current.nodeType === 1 ? current.outerHTML : current.textContent ?? "";
|
|
4797
|
-
current = current.nextSibling;
|
|
4842
|
+
const template = start.ownerDocument.createElement("template");
|
|
4843
|
+
for (let current = start.nextSibling;current && current !== end; current = current.nextSibling) {
|
|
4844
|
+
template.content.appendChild(current.cloneNode(true));
|
|
4798
4845
|
}
|
|
4799
|
-
return
|
|
4846
|
+
return template.innerHTML;
|
|
4800
4847
|
}
|
|
4801
4848
|
|
|
4802
4849
|
// src/vite/layout.ts
|
|
4803
4850
|
import { readFile as readFile2 } from "fs/promises";
|
|
4804
|
-
import { dirname as dirname3, join as join2, relative as relative3, resolve as resolve3, sep } from "path";
|
|
4851
|
+
import { dirname as dirname3, join as join2, relative as relative3, resolve as resolve3, sep as sep2 } from "path";
|
|
4805
4852
|
|
|
4806
4853
|
// src/parser/layout.ts
|
|
4807
4854
|
var DECLARATION2 = /<!--\s*lsp:layout\s+([^\s>][^>]*?)\s*-->/;
|
|
@@ -4903,7 +4950,7 @@ async function findLayout(name, from, root) {
|
|
|
4903
4950
|
return { path: candidate, html: await readFile2(candidate, "utf-8") };
|
|
4904
4951
|
} catch {
|
|
4905
4952
|
}
|
|
4906
|
-
if (dir === stop || !dir.startsWith(stop +
|
|
4953
|
+
if (dir === stop || !dir.startsWith(stop + sep2))
|
|
4907
4954
|
return null;
|
|
4908
4955
|
const parent = dirname3(dir);
|
|
4909
4956
|
if (parent === dir)
|
|
@@ -5091,7 +5138,7 @@ function resolvedByHost(entry) {
|
|
|
5091
5138
|
// src/vite/plugin.ts
|
|
5092
5139
|
import { readFile as readFile3, readdir as readdir2, writeFile as writeFile2, mkdir as mkdir2, rm } from "fs/promises";
|
|
5093
5140
|
import { existsSync } from "fs";
|
|
5094
|
-
import { basename, dirname as dirname5, isAbsolute, join as join4, relative as relative4, resolve as resolve4, sep as
|
|
5141
|
+
import { basename, dirname as dirname5, isAbsolute, join as join4, relative as relative4, resolve as resolve4, sep as sep3 } from "path";
|
|
5095
5142
|
var VIRTUAL_COMPONENTS = "virtual:lightspeed/components";
|
|
5096
5143
|
var RESOLVED_COMPONENTS = "\x00" + VIRTUAL_COMPONENTS;
|
|
5097
5144
|
function lightspeedPlugin(options = {}) {
|
|
@@ -5214,7 +5261,9 @@ function lightspeedPlugin(options = {}) {
|
|
|
5214
5261
|
},
|
|
5215
5262
|
...extraComponentDirs.length > 0 ? {
|
|
5216
5263
|
server: {
|
|
5217
|
-
fs: {
|
|
5264
|
+
fs: {
|
|
5265
|
+
allow: [userRoot, ...extraComponentDirs.map((dir) => resolve4(userRoot, componentDirPath(dir)))]
|
|
5266
|
+
}
|
|
5218
5267
|
}
|
|
5219
5268
|
} : {}
|
|
5220
5269
|
};
|
|
@@ -5224,7 +5273,7 @@ function lightspeedPlugin(options = {}) {
|
|
|
5224
5273
|
const pages2 = await findPages(userRoot);
|
|
5225
5274
|
if (pages2.length === 0)
|
|
5226
5275
|
return fsAccess;
|
|
5227
|
-
const named = pages2.map((page) => relative4(userRoot, page).split(
|
|
5276
|
+
const named = pages2.map((page) => relative4(userRoot, page).split(sep3).join("/")).join(", ");
|
|
5228
5277
|
console.log(pages2.length === 1 ? `⚡ 1 page: ${named}` : `⚡ ${pages2.length} pages: ${named}`);
|
|
5229
5278
|
return {
|
|
5230
5279
|
...fsAccess,
|
|
@@ -5232,7 +5281,7 @@ function lightspeedPlugin(options = {}) {
|
|
|
5232
5281
|
build: {
|
|
5233
5282
|
rollupOptions: {
|
|
5234
5283
|
input: Object.fromEntries(pages2.map((page) => [
|
|
5235
|
-
relative4(userRoot, page).split(
|
|
5284
|
+
relative4(userRoot, page).split(sep3).join("/").replace(/\.html$/, ""),
|
|
5236
5285
|
page
|
|
5237
5286
|
]))
|
|
5238
5287
|
}
|
|
@@ -5293,12 +5342,12 @@ ${entries.join(`,
|
|
|
5293
5342
|
await project.rebuild();
|
|
5294
5343
|
const content = await readFile3(id, "utf-8");
|
|
5295
5344
|
const parsed = withSharedStyles(parseLSP(content, id), (file) => this.addWatchFile?.(file));
|
|
5296
|
-
const componentId = basename(id, ".lsp").toLowerCase();
|
|
5345
|
+
const componentId = [...componentFiles].find(([, file]) => resolve4(root, file) === resolve4(id))?.[0] ?? basename(id, ".lsp").toLowerCase();
|
|
5297
5346
|
let compiledLogic = parsed.logic;
|
|
5298
5347
|
if (useTypeScript && parsed.logic.trim()) {
|
|
5299
5348
|
compiledLogic = await project.compileLogic(parsed.logic);
|
|
5300
5349
|
}
|
|
5301
|
-
const registryObj =
|
|
5350
|
+
const registryObj = project.registryFor(componentId);
|
|
5302
5351
|
const transformedTemplate = transformTemplate(parsed.view, componentId, registryObj);
|
|
5303
5352
|
const transformedSkeleton = parsed.skeleton ? transformTemplate(parsed.skeleton, componentId, registryObj) : "";
|
|
5304
5353
|
const scopeId = scopeIdFor(componentId);
|
|
@@ -5450,13 +5499,27 @@ ${entries.join(`,
|
|
|
5450
5499
|
const registryObj = Object.fromEntries(componentRegistry);
|
|
5451
5500
|
const templates = {};
|
|
5452
5501
|
for (const [componentId, raw] of componentRegistry) {
|
|
5453
|
-
templates[componentId] = transformTemplate(raw.template, componentId,
|
|
5502
|
+
templates[componentId] = transformTemplate(raw.template, componentId, project.registryFor(componentId));
|
|
5454
5503
|
}
|
|
5504
|
+
const flattens = !isDevMode && shouldRender && options.flattenStatic !== false;
|
|
5505
|
+
const definitions = flattens ? await project.definitions() : null;
|
|
5455
5506
|
const eager = new Set;
|
|
5456
5507
|
for (const page of await findPages(root)) {
|
|
5457
5508
|
const html = await readFile3(page, "utf-8");
|
|
5458
5509
|
const compiled = transformTemplate(html, "index", registryObj);
|
|
5459
|
-
|
|
5510
|
+
let markup = compiled;
|
|
5511
|
+
if (definitions) {
|
|
5512
|
+
try {
|
|
5513
|
+
markup = (await renderPage({
|
|
5514
|
+
html: compiled,
|
|
5515
|
+
definitions,
|
|
5516
|
+
css: cssMode,
|
|
5517
|
+
pageExpressions: buildExpressionTable(extractExpressions(compiled))
|
|
5518
|
+
})).html;
|
|
5519
|
+
} catch {
|
|
5520
|
+
}
|
|
5521
|
+
}
|
|
5522
|
+
eagerComponents(markup, templates).forEach((id) => eager.add(id));
|
|
5460
5523
|
}
|
|
5461
5524
|
return [...eager];
|
|
5462
5525
|
}
|
|
@@ -5606,7 +5669,7 @@ ${entries.join(`,
|
|
|
5606
5669
|
return existsSync(join4(dir, ROUTES_DIR)) ? join4(dir, ROUTES_DIR) : dir;
|
|
5607
5670
|
}
|
|
5608
5671
|
function pageKey(file) {
|
|
5609
|
-
return relative4(root, file).split(
|
|
5672
|
+
return relative4(root, file).split(sep3).join("/");
|
|
5610
5673
|
}
|
|
5611
5674
|
async function findPages(dir) {
|
|
5612
5675
|
if (host)
|
|
@@ -5646,7 +5709,7 @@ ${entries.join(`,
|
|
|
5646
5709
|
routeTable.length = 0;
|
|
5647
5710
|
routeDeclarations.clear();
|
|
5648
5711
|
for (const file of await findPages(root)) {
|
|
5649
|
-
const rel = relative4(base, file).split(
|
|
5712
|
+
const rel = relative4(base, file).split(sep3).join("/");
|
|
5650
5713
|
const key = pageKey(file);
|
|
5651
5714
|
let declared = null;
|
|
5652
5715
|
try {
|
|
@@ -5721,9 +5784,9 @@ ${entries.join(`,
|
|
|
5721
5784
|
const absolute = resolve4(root, file);
|
|
5722
5785
|
const within = relative4(root, absolute);
|
|
5723
5786
|
if (!within.startsWith("..") && !isAbsolute(within)) {
|
|
5724
|
-
return "/" + within.split(
|
|
5787
|
+
return "/" + within.split(sep3).join("/");
|
|
5725
5788
|
}
|
|
5726
|
-
return absolute.split(
|
|
5789
|
+
return absolute.split(sep3).join("/");
|
|
5727
5790
|
}
|
|
5728
5791
|
}
|
|
5729
5792
|
function generateComponentModule(parsed, transformedTemplate, componentId, scopeId) {
|