@lightspeed-js/core 0.1.1 → 0.2.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.
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which file answers to each component name (ADR-058).
|
|
3
|
+
*
|
|
4
|
+
* Names are a flat namespace: a component is its filename, lowercased, from
|
|
5
|
+
* whichever root it was found in. The first root to claim a name keeps it, and
|
|
6
|
+
* `src` comes first, so an app can replace a package's component.
|
|
7
|
+
*
|
|
8
|
+
* Two files answering to one name is either that deliberate override or a
|
|
9
|
+
* collision nobody meant, and the two look identical from the files alone. So
|
|
10
|
+
* the app says which it means: an override across roots has to be declared,
|
|
11
|
+
* and two files under the same root are always a mistake.
|
|
12
|
+
*/
|
|
13
|
+
export interface FoundComponent {
|
|
14
|
+
/** Absolute path to the `.lsp` file. */
|
|
15
|
+
file: string;
|
|
16
|
+
/** The component root it was found under. */
|
|
17
|
+
root: string;
|
|
18
|
+
}
|
|
19
|
+
export type Conflict = {
|
|
20
|
+
kind: "same-root";
|
|
21
|
+
id: string;
|
|
22
|
+
kept: string;
|
|
23
|
+
other: string;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "undeclared-override";
|
|
26
|
+
id: string;
|
|
27
|
+
kept: string;
|
|
28
|
+
other: string;
|
|
29
|
+
};
|
|
30
|
+
export interface ComponentIds {
|
|
31
|
+
/** Component id to the file that answers to it. */
|
|
32
|
+
files: Map<string, string>;
|
|
33
|
+
/** Declared overrides that replaced something. */
|
|
34
|
+
overrides: Array<{
|
|
35
|
+
id: string;
|
|
36
|
+
kept: string;
|
|
37
|
+
replaced: string;
|
|
38
|
+
}>;
|
|
39
|
+
conflicts: Conflict[];
|
|
40
|
+
/** Names in `overrides` that replace nothing - left over after a rename or removal. */
|
|
41
|
+
unusedOverrides: string[];
|
|
42
|
+
}
|
|
43
|
+
export declare function componentIdOf(file: string): string;
|
|
44
|
+
/** `found` must be in precedence order: every file of the first root, then the next. */
|
|
45
|
+
export declare function resolveComponentIds(found: FoundComponent[], declaredOverrides?: readonly string[]): ComponentIds;
|
|
46
|
+
/** One message per conflict, each ending in the change that fixes it (ADR-055). */
|
|
47
|
+
export declare function describeConflict(conflict: Conflict, rel: (file: string) => string): string;
|
|
@@ -61,9 +61,18 @@ interface LightspeedPluginOptions {
|
|
|
61
61
|
* Names are a flat namespace: a component is its filename, lowercased, from
|
|
62
62
|
* wherever it was found. The project's own `src` is searched first and wins,
|
|
63
63
|
* so an app can replace a package's component by giving a file the same
|
|
64
|
-
* name -
|
|
64
|
+
* name - once it says so in `overrides` (ADR-058).
|
|
65
65
|
*/
|
|
66
66
|
componentDirs?: string[];
|
|
67
|
+
/**
|
|
68
|
+
* Component names the app replaces on purpose: a file in `src` (or an
|
|
69
|
+
* earlier root) with the same name as one in a later `componentDirs` root.
|
|
70
|
+
*
|
|
71
|
+
* Any other clash fails the build and warns in dev, because a deliberate
|
|
72
|
+
* override and an accidental collision look identical from the files alone.
|
|
73
|
+
* Two files under the same root are never an override.
|
|
74
|
+
*/
|
|
75
|
+
overrides?: string[];
|
|
67
76
|
}
|
|
68
77
|
export declare function lightspeedPlugin(options?: LightspeedPluginOptions): Plugin[];
|
|
69
78
|
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/vite.js
CHANGED
|
@@ -17,6 +17,46 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
17
17
|
};
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
|
+
// src/vite/component-ids.ts
|
|
21
|
+
function componentIdOf(file) {
|
|
22
|
+
const name = file.split(/[\\/]/).pop() ?? file;
|
|
23
|
+
return name.replace(/\.lsp$/, "").toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
function resolveComponentIds(found, declaredOverrides = []) {
|
|
26
|
+
const declared = new Set(declaredOverrides.map((id) => id.toLowerCase()));
|
|
27
|
+
const files = new Map;
|
|
28
|
+
const rootOf = new Map;
|
|
29
|
+
const result = { files, overrides: [], conflicts: [], unusedOverrides: [] };
|
|
30
|
+
const used = new Set;
|
|
31
|
+
for (const { file, root } of found) {
|
|
32
|
+
const id = componentIdOf(file);
|
|
33
|
+
const kept = files.get(id);
|
|
34
|
+
if (kept === undefined) {
|
|
35
|
+
files.set(id, file);
|
|
36
|
+
rootOf.set(id, root);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (rootOf.get(id) === root) {
|
|
40
|
+
result.conflicts.push({ kind: "same-root", id, kept, other: file });
|
|
41
|
+
} else if (declared.has(id)) {
|
|
42
|
+
used.add(id);
|
|
43
|
+
result.overrides.push({ id, kept, replaced: file });
|
|
44
|
+
} else {
|
|
45
|
+
result.conflicts.push({ kind: "undeclared-override", id, kept, other: file });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
result.unusedOverrides = [...declared].filter((id) => !used.has(id)).sort();
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
function describeConflict(conflict, rel) {
|
|
52
|
+
const kept = rel(conflict.kept);
|
|
53
|
+
const other = rel(conflict.other);
|
|
54
|
+
if (conflict.kind === "same-root") {
|
|
55
|
+
return `${conflict.id}: ${kept} and ${other} both answer to <${conflict.id}>. ` + `A component's name is its filename, lowercased, across every component directory. Rename one of them.`;
|
|
56
|
+
}
|
|
57
|
+
return `${conflict.id}: ${kept} replaces ${other}. ` + `If that is deliberate, add "${conflict.id}" to lightspeedPlugin({ overrides }); otherwise rename one of them.`;
|
|
58
|
+
}
|
|
59
|
+
|
|
20
60
|
// src/vite/csp.ts
|
|
21
61
|
import { createHash } from "node:crypto";
|
|
22
62
|
function styleHashes(html) {
|
|
@@ -4783,7 +4823,9 @@ function lightspeedPlugin(options = {}) {
|
|
|
4783
4823
|
const cssMode = options.css ?? "inline";
|
|
4784
4824
|
const cleanUrls = options.routes === true;
|
|
4785
4825
|
const extraComponentDirs = options.componentDirs ?? [];
|
|
4826
|
+
const declaredOverrides = options.overrides ?? [];
|
|
4786
4827
|
const announcedShadowing = new Set;
|
|
4828
|
+
let componentConflicts = [];
|
|
4787
4829
|
const rewriteFormat = options.rewrites ?? "_redirects";
|
|
4788
4830
|
const routeDeclarations = new Map;
|
|
4789
4831
|
let relativeBase = false;
|
|
@@ -4921,6 +4963,10 @@ function lightspeedPlugin(options = {}) {
|
|
|
4921
4963
|
},
|
|
4922
4964
|
async buildStart() {
|
|
4923
4965
|
await rebuildComponentRegistry();
|
|
4966
|
+
if (!isDevMode && componentConflicts.length) {
|
|
4967
|
+
this.error(componentConflicts.map((conflict) => describeConflict(conflict, (file) => relative3(root, file))).join(`
|
|
4968
|
+
`));
|
|
4969
|
+
}
|
|
4924
4970
|
},
|
|
4925
4971
|
resolveId(id) {
|
|
4926
4972
|
return id === VIRTUAL_COMPONENTS ? RESOLVED_COMPONENTS : null;
|
|
@@ -5517,20 +5563,13 @@ ${entries.join(`,
|
|
|
5517
5563
|
const registry = new Map;
|
|
5518
5564
|
const files = new Map;
|
|
5519
5565
|
try {
|
|
5520
|
-
|
|
5566
|
+
const ids = resolveComponentIds(await findComponentFiles(), declaredOverrides);
|
|
5567
|
+
componentConflicts = ids.conflicts;
|
|
5568
|
+
announceComponentIds(ids);
|
|
5569
|
+
for (const [componentId, filePath] of ids.files) {
|
|
5521
5570
|
try {
|
|
5522
5571
|
const content = await readFile2(filePath, "utf-8");
|
|
5523
5572
|
const parsed = withSharedStyles(parseLSP(content, filePath));
|
|
5524
|
-
const componentId = basename(filePath, ".lsp").toLowerCase();
|
|
5525
|
-
const claimed = files.get(componentId);
|
|
5526
|
-
if (claimed !== undefined) {
|
|
5527
|
-
const pair = `${claimed}\x00${filePath}`;
|
|
5528
|
-
if (!announcedShadowing.has(pair)) {
|
|
5529
|
-
announcedShadowing.add(pair);
|
|
5530
|
-
console.log(`⚡ ${componentId}: using ${relative3(root, claimed)}, shadowing ${relative3(root, filePath)}`);
|
|
5531
|
-
}
|
|
5532
|
-
continue;
|
|
5533
|
-
}
|
|
5534
5573
|
files.set(componentId, filePath);
|
|
5535
5574
|
registry.set(componentId, {
|
|
5536
5575
|
template: parsed.view,
|
|
@@ -5575,14 +5614,32 @@ ${entries.join(`,
|
|
|
5575
5614
|
async function findComponentFiles() {
|
|
5576
5615
|
const found = [];
|
|
5577
5616
|
for (const dir of componentRoots()) {
|
|
5578
|
-
|
|
5617
|
+
for (const file of await findLspFiles(dir))
|
|
5618
|
+
found.push({ file, root: dir });
|
|
5579
5619
|
}
|
|
5580
5620
|
return found;
|
|
5581
5621
|
}
|
|
5622
|
+
function announceComponentIds(ids) {
|
|
5623
|
+
const rel = (file) => relative3(root, file);
|
|
5624
|
+
for (const conflict of ids.conflicts) {
|
|
5625
|
+
const key = `${conflict.kind}\x00${conflict.kept}\x00${conflict.other}`;
|
|
5626
|
+
if (announcedShadowing.has(key))
|
|
5627
|
+
continue;
|
|
5628
|
+
announcedShadowing.add(key);
|
|
5629
|
+
console.warn(`⚠️ ${describeConflict(conflict, rel)}`);
|
|
5630
|
+
}
|
|
5631
|
+
for (const id of ids.unusedOverrides) {
|
|
5632
|
+
const key = `unused\x00${id}`;
|
|
5633
|
+
if (announcedShadowing.has(key))
|
|
5634
|
+
continue;
|
|
5635
|
+
announcedShadowing.add(key);
|
|
5636
|
+
console.warn(`⚠️ overrides lists "${id}", but nothing replaces a component of that name. Remove it from lightspeedPlugin({ overrides }).`);
|
|
5637
|
+
}
|
|
5638
|
+
}
|
|
5582
5639
|
async function findLspFiles(dir) {
|
|
5583
5640
|
const files = [];
|
|
5584
5641
|
try {
|
|
5585
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
5642
|
+
const entries = (await readdir(dir, { withFileTypes: true })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
5586
5643
|
for (const entry of entries) {
|
|
5587
5644
|
const fullPath = join3(dir, entry.name);
|
|
5588
5645
|
if (entry.isDirectory()) {
|