@lightspeed-js/core 0.3.1 → 0.4.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/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
- return buildPlaceholder(name, attributes || "", "", componentRegistry?.[name.toLowerCase()]?.needs);
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
- return buildPlaceholder(name, attributes || "", inner, componentRegistry?.[name.toLowerCase()]?.needs);
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/ssg.js CHANGED
@@ -3601,22 +3601,24 @@ function addMarkerBinds(payload, params, add) {
3601
3601
  }
3602
3602
 
3603
3603
  // src/project/component-ids.ts
3604
- function componentIdOf(file) {
3604
+ function componentIdOf(file, prefix = "") {
3605
3605
  const name = file.split(/[\\/]/).pop() ?? file;
3606
- return name.replace(/\.lsp$/, "").toLowerCase();
3606
+ return (prefix + name.replace(/\.lsp$/, "")).toLowerCase();
3607
3607
  }
3608
3608
  function resolveComponentIds(found, declaredOverrides = []) {
3609
3609
  const declared = new Set(declaredOverrides.map((id) => id.toLowerCase()));
3610
3610
  const files = new Map;
3611
3611
  const rootOf = new Map;
3612
- const result = { files, overrides: [], conflicts: [], unusedOverrides: [] };
3612
+ const roots = new Map;
3613
+ const result = { files, overrides: [], conflicts: [], unusedOverrides: [], roots };
3613
3614
  const used = new Set;
3614
- for (const { file, root } of found) {
3615
- const id = componentIdOf(file);
3615
+ for (const { file, root, prefix = "" } of found) {
3616
+ const id = componentIdOf(file, prefix);
3616
3617
  const kept = files.get(id);
3617
3618
  if (kept === undefined) {
3618
3619
  files.set(id, file);
3619
3620
  rootOf.set(id, root);
3621
+ roots.set(id, { root, prefix });
3620
3622
  continue;
3621
3623
  }
3622
3624
  if (rootOf.get(id) === root) {
@@ -3643,7 +3645,7 @@ function describeConflict(conflict, rel) {
3643
3645
  // src/project/components.ts
3644
3646
  import { readFile, readdir } from "fs/promises";
3645
3647
  import { createRequire as createRequire2 } from "node:module";
3646
- import { dirname as dirname2, join, relative as relative2, resolve as resolve2 } from "path";
3648
+ import { dirname as dirname2, join, relative as relative2, resolve as resolve2, sep } from "path";
3647
3649
 
3648
3650
  // src/specifiers.ts
3649
3651
  var PACKAGE_NAME = "@lightspeed-js/core";
@@ -4292,12 +4294,14 @@ function emitMountPlaceholders(template, componentRegistry, depth = 0) {
4292
4294
  let result = template;
4293
4295
  result = result.replace(/<([A-Z][a-zA-Z0-9]*)((?:\s+[^>]*?)?)\s*\/>/g, (_match, name, attributes) => {
4294
4296
  warnUnknown(name);
4295
- return buildPlaceholder(name, attributes || "", "", componentRegistry?.[name.toLowerCase()]?.needs);
4297
+ const entry = componentRegistry?.[name.toLowerCase()];
4298
+ return buildPlaceholder(entry?.id ?? name, attributes || "", "", entry?.needs);
4296
4299
  });
4297
4300
  result = result.replace(/<([A-Z][a-zA-Z0-9]*)((?:\s+[^>]*?)?)\s*>([\s\S]*?)<\/\1\s*>/g, (_match, name, attributes, content) => {
4298
4301
  warnUnknown(name);
4299
4302
  const inner = emitMountPlaceholders(content, componentRegistry, depth + 1);
4300
- return buildPlaceholder(name, attributes || "", inner, componentRegistry?.[name.toLowerCase()]?.needs);
4303
+ const entry = componentRegistry?.[name.toLowerCase()];
4304
+ return buildPlaceholder(entry?.id ?? name, attributes || "", inner, entry?.needs);
4301
4305
  });
4302
4306
  return result;
4303
4307
  }
@@ -4406,18 +4410,43 @@ function createComponentProject(options) {
4406
4410
  const announced = new Set;
4407
4411
  let rebuilding = null;
4408
4412
  const projectModules = new Map;
4409
- function roots() {
4413
+ function rootSpecs() {
4410
4414
  const root = rootOf();
4411
- return [resolve2(root, "src"), ...extraComponentDirs.map((dir) => resolve2(root, dir))];
4415
+ return [
4416
+ { dir: resolve2(root, "src"), prefix: "" },
4417
+ ...extraComponentDirs.map((entry) => typeof entry === "string" ? { dir: resolve2(root, entry), prefix: "" } : { dir: resolve2(root, entry.dir), prefix: entry.prefix ?? "" })
4418
+ ];
4419
+ }
4420
+ function roots() {
4421
+ return rootSpecs().map((spec) => spec.dir);
4412
4422
  }
4413
4423
  async function findComponentFiles() {
4424
+ const specs = rootSpecs();
4414
4425
  const found = [];
4415
- for (const dir of roots()) {
4416
- for (const file of await findLspFiles(dir))
4417
- found.push({ file, root: dir });
4426
+ for (const spec of specs) {
4427
+ const nested = new Set(specs.map((s) => s.dir).filter((d) => d !== spec.dir && d.startsWith(spec.dir + sep)));
4428
+ for (const file of await findLspFiles(spec.dir, nested)) {
4429
+ found.push({ file, root: spec.dir, ...spec.prefix ? { prefix: spec.prefix } : {} });
4430
+ }
4418
4431
  }
4419
4432
  return found;
4420
4433
  }
4434
+ let rootOfId = new Map;
4435
+ function registryFor(id) {
4436
+ const all = Object.fromEntries(registry);
4437
+ const home = rootOfId.get(id);
4438
+ if (!home?.prefix)
4439
+ return all;
4440
+ for (const [sibling, where] of rootOfId) {
4441
+ if (where.root !== home.root)
4442
+ continue;
4443
+ const local = sibling.slice(home.prefix.length);
4444
+ const entry = registry.get(sibling);
4445
+ if (local && entry)
4446
+ all[local] = { ...entry, id: sibling };
4447
+ }
4448
+ return all;
4449
+ }
4421
4450
  function announce(ids) {
4422
4451
  const rel = (file) => relative2(rootOf(), file);
4423
4452
  for (const conflict of ids.conflicts) {
@@ -4443,6 +4472,7 @@ function createComponentProject(options) {
4443
4472
  const nextFiles = new Map;
4444
4473
  try {
4445
4474
  const ids = resolveComponentIds(await findComponentFiles(), declaredOverrides);
4475
+ rootOfId = ids.roots;
4446
4476
  conflicts = ids.conflicts;
4447
4477
  announce(ids);
4448
4478
  for (const [componentId, filePath] of ids.files) {
@@ -4507,9 +4537,9 @@ function createComponentProject(options) {
4507
4537
  }
4508
4538
  async function definitions() {
4509
4539
  await rebuild();
4510
- const registryObj = Object.fromEntries(registry);
4511
4540
  const out = [];
4512
4541
  for (const [componentId, raw] of registry) {
4542
+ const registryObj = registryFor(componentId);
4513
4543
  const scopeId = scopeIdFor(componentId);
4514
4544
  const source = raw.logic?.trim() ? await compileLogic2(raw.logic) : "";
4515
4545
  const setup = await buildSetup(source, {
@@ -4545,6 +4575,7 @@ function createComponentProject(options) {
4545
4575
  return conflicts;
4546
4576
  },
4547
4577
  roots,
4578
+ registryFor,
4548
4579
  rebuild,
4549
4580
  definitions,
4550
4581
  compileMarkup: (html, id = "index") => transformTemplate(html, id, Object.fromEntries(registry)),
@@ -4560,14 +4591,15 @@ function withSharedStyles(parsed, watch) {
4560
4591
  watch(file);
4561
4592
  return inlineStyleSources(parsed, nodeStyleReader());
4562
4593
  }
4563
- async function findLspFiles(dir) {
4594
+ async function findLspFiles(dir, skip = new Set) {
4564
4595
  const found = [];
4565
4596
  try {
4566
4597
  const entries = (await readdir(dir, { withFileTypes: true })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
4567
4598
  for (const entry of entries) {
4568
4599
  const fullPath = join(dir, entry.name);
4569
4600
  if (entry.isDirectory()) {
4570
- found.push(...await findLspFiles(fullPath));
4601
+ if (!skip.has(fullPath))
4602
+ found.push(...await findLspFiles(fullPath, skip));
4571
4603
  } else if (entry.isFile() && entry.name.endsWith(".lsp")) {
4572
4604
  found.push(fullPath);
4573
4605
  }
@@ -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
- export declare function componentIdOf(file: string): string;
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
- /** Roots beyond the project's own `src`, relative to the root. */
18
- componentDirs?: string[];
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?: string[];
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?: string[];
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 result = { files, overrides: [], conflicts: [], unusedOverrides: [] };
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]";
@@ -2556,12 +2558,14 @@ function emitMountPlaceholders(template, componentRegistry, depth = 0) {
2556
2558
  let result = template;
2557
2559
  result = result.replace(/<([A-Z][a-zA-Z0-9]*)((?:\s+[^>]*?)?)\s*\/>/g, (_match, name, attributes) => {
2558
2560
  warnUnknown(name);
2559
- return buildPlaceholder(name, attributes || "", "", componentRegistry?.[name.toLowerCase()]?.needs);
2561
+ const entry = componentRegistry?.[name.toLowerCase()];
2562
+ return buildPlaceholder(entry?.id ?? name, attributes || "", "", entry?.needs);
2560
2563
  });
2561
2564
  result = result.replace(/<([A-Z][a-zA-Z0-9]*)((?:\s+[^>]*?)?)\s*>([\s\S]*?)<\/\1\s*>/g, (_match, name, attributes, content) => {
2562
2565
  warnUnknown(name);
2563
2566
  const inner = emitMountPlaceholders(content, componentRegistry, depth + 1);
2564
- return buildPlaceholder(name, attributes || "", inner, componentRegistry?.[name.toLowerCase()]?.needs);
2567
+ const entry = componentRegistry?.[name.toLowerCase()];
2568
+ return buildPlaceholder(entry?.id ?? name, attributes || "", inner, entry?.needs);
2565
2569
  });
2566
2570
  return result;
2567
2571
  }
@@ -2659,6 +2663,7 @@ function splitAttributes(componentId, attrString) {
2659
2663
  }
2660
2664
 
2661
2665
  // src/project/components.ts
2666
+ var componentDirPath = (entry) => typeof entry === "string" ? entry : entry.dir;
2662
2667
  function createComponentProject(options) {
2663
2668
  const rootOf = () => typeof options.root === "function" ? options.root() : options.root;
2664
2669
  const extraComponentDirs = options.componentDirs ?? [];
@@ -2670,18 +2675,43 @@ function createComponentProject(options) {
2670
2675
  const announced = new Set;
2671
2676
  let rebuilding = null;
2672
2677
  const projectModules = new Map;
2673
- function roots() {
2678
+ function rootSpecs() {
2674
2679
  const root = rootOf();
2675
- return [resolve2(root, "src"), ...extraComponentDirs.map((dir) => resolve2(root, dir))];
2680
+ return [
2681
+ { dir: resolve2(root, "src"), prefix: "" },
2682
+ ...extraComponentDirs.map((entry) => typeof entry === "string" ? { dir: resolve2(root, entry), prefix: "" } : { dir: resolve2(root, entry.dir), prefix: entry.prefix ?? "" })
2683
+ ];
2684
+ }
2685
+ function roots() {
2686
+ return rootSpecs().map((spec) => spec.dir);
2676
2687
  }
2677
2688
  async function findComponentFiles() {
2689
+ const specs = rootSpecs();
2678
2690
  const found = [];
2679
- for (const dir of roots()) {
2680
- for (const file of await findLspFiles(dir))
2681
- found.push({ file, root: dir });
2691
+ for (const spec of specs) {
2692
+ const nested = new Set(specs.map((s) => s.dir).filter((d) => d !== spec.dir && d.startsWith(spec.dir + sep)));
2693
+ for (const file of await findLspFiles(spec.dir, nested)) {
2694
+ found.push({ file, root: spec.dir, ...spec.prefix ? { prefix: spec.prefix } : {} });
2695
+ }
2682
2696
  }
2683
2697
  return found;
2684
2698
  }
2699
+ let rootOfId = new Map;
2700
+ function registryFor(id) {
2701
+ const all = Object.fromEntries(registry);
2702
+ const home = rootOfId.get(id);
2703
+ if (!home?.prefix)
2704
+ return all;
2705
+ for (const [sibling, where] of rootOfId) {
2706
+ if (where.root !== home.root)
2707
+ continue;
2708
+ const local = sibling.slice(home.prefix.length);
2709
+ const entry = registry.get(sibling);
2710
+ if (local && entry)
2711
+ all[local] = { ...entry, id: sibling };
2712
+ }
2713
+ return all;
2714
+ }
2685
2715
  function announce(ids) {
2686
2716
  const rel = (file) => relative2(rootOf(), file);
2687
2717
  for (const conflict of ids.conflicts) {
@@ -2707,6 +2737,7 @@ function createComponentProject(options) {
2707
2737
  const nextFiles = new Map;
2708
2738
  try {
2709
2739
  const ids = resolveComponentIds(await findComponentFiles(), declaredOverrides);
2740
+ rootOfId = ids.roots;
2710
2741
  conflicts = ids.conflicts;
2711
2742
  announce(ids);
2712
2743
  for (const [componentId, filePath] of ids.files) {
@@ -2771,9 +2802,9 @@ function createComponentProject(options) {
2771
2802
  }
2772
2803
  async function definitions() {
2773
2804
  await rebuild();
2774
- const registryObj = Object.fromEntries(registry);
2775
2805
  const out = [];
2776
2806
  for (const [componentId, raw] of registry) {
2807
+ const registryObj = registryFor(componentId);
2777
2808
  const scopeId = scopeIdFor(componentId);
2778
2809
  const source = raw.logic?.trim() ? await compileLogic2(raw.logic) : "";
2779
2810
  const setup = await buildSetup(source, {
@@ -2809,6 +2840,7 @@ function createComponentProject(options) {
2809
2840
  return conflicts;
2810
2841
  },
2811
2842
  roots,
2843
+ registryFor,
2812
2844
  rebuild,
2813
2845
  definitions,
2814
2846
  compileMarkup: (html, id = "index") => transformTemplate(html, id, Object.fromEntries(registry)),
@@ -2824,14 +2856,15 @@ function withSharedStyles(parsed, watch) {
2824
2856
  watch(file);
2825
2857
  return inlineStyleSources(parsed, nodeStyleReader());
2826
2858
  }
2827
- async function findLspFiles(dir) {
2859
+ async function findLspFiles(dir, skip = new Set) {
2828
2860
  const found = [];
2829
2861
  try {
2830
2862
  const entries = (await readdir(dir, { withFileTypes: true })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
2831
2863
  for (const entry of entries) {
2832
2864
  const fullPath = join(dir, entry.name);
2833
2865
  if (entry.isDirectory()) {
2834
- found.push(...await findLspFiles(fullPath));
2866
+ if (!skip.has(fullPath))
2867
+ found.push(...await findLspFiles(fullPath, skip));
2835
2868
  } else if (entry.isFile() && entry.name.endsWith(".lsp")) {
2836
2869
  found.push(fullPath);
2837
2870
  }
@@ -4801,7 +4834,7 @@ function serialiseBetween(start, end) {
4801
4834
 
4802
4835
  // src/vite/layout.ts
4803
4836
  import { readFile as readFile2 } from "fs/promises";
4804
- import { dirname as dirname3, join as join2, relative as relative3, resolve as resolve3, sep } from "path";
4837
+ import { dirname as dirname3, join as join2, relative as relative3, resolve as resolve3, sep as sep2 } from "path";
4805
4838
 
4806
4839
  // src/parser/layout.ts
4807
4840
  var DECLARATION2 = /<!--\s*lsp:layout\s+([^\s>][^>]*?)\s*-->/;
@@ -4903,7 +4936,7 @@ async function findLayout(name, from, root) {
4903
4936
  return { path: candidate, html: await readFile2(candidate, "utf-8") };
4904
4937
  } catch {
4905
4938
  }
4906
- if (dir === stop || !dir.startsWith(stop + sep))
4939
+ if (dir === stop || !dir.startsWith(stop + sep2))
4907
4940
  return null;
4908
4941
  const parent = dirname3(dir);
4909
4942
  if (parent === dir)
@@ -5091,7 +5124,7 @@ function resolvedByHost(entry) {
5091
5124
  // src/vite/plugin.ts
5092
5125
  import { readFile as readFile3, readdir as readdir2, writeFile as writeFile2, mkdir as mkdir2, rm } from "fs/promises";
5093
5126
  import { existsSync } from "fs";
5094
- import { basename, dirname as dirname5, isAbsolute, join as join4, relative as relative4, resolve as resolve4, sep as sep2 } from "path";
5127
+ import { basename, dirname as dirname5, isAbsolute, join as join4, relative as relative4, resolve as resolve4, sep as sep3 } from "path";
5095
5128
  var VIRTUAL_COMPONENTS = "virtual:lightspeed/components";
5096
5129
  var RESOLVED_COMPONENTS = "\x00" + VIRTUAL_COMPONENTS;
5097
5130
  function lightspeedPlugin(options = {}) {
@@ -5214,7 +5247,9 @@ function lightspeedPlugin(options = {}) {
5214
5247
  },
5215
5248
  ...extraComponentDirs.length > 0 ? {
5216
5249
  server: {
5217
- fs: { allow: [userRoot, ...extraComponentDirs.map((dir) => resolve4(userRoot, dir))] }
5250
+ fs: {
5251
+ allow: [userRoot, ...extraComponentDirs.map((dir) => resolve4(userRoot, componentDirPath(dir)))]
5252
+ }
5218
5253
  }
5219
5254
  } : {}
5220
5255
  };
@@ -5224,7 +5259,7 @@ function lightspeedPlugin(options = {}) {
5224
5259
  const pages2 = await findPages(userRoot);
5225
5260
  if (pages2.length === 0)
5226
5261
  return fsAccess;
5227
- const named = pages2.map((page) => relative4(userRoot, page).split(sep2).join("/")).join(", ");
5262
+ const named = pages2.map((page) => relative4(userRoot, page).split(sep3).join("/")).join(", ");
5228
5263
  console.log(pages2.length === 1 ? `⚡ 1 page: ${named}` : `⚡ ${pages2.length} pages: ${named}`);
5229
5264
  return {
5230
5265
  ...fsAccess,
@@ -5232,7 +5267,7 @@ function lightspeedPlugin(options = {}) {
5232
5267
  build: {
5233
5268
  rollupOptions: {
5234
5269
  input: Object.fromEntries(pages2.map((page) => [
5235
- relative4(userRoot, page).split(sep2).join("/").replace(/\.html$/, ""),
5270
+ relative4(userRoot, page).split(sep3).join("/").replace(/\.html$/, ""),
5236
5271
  page
5237
5272
  ]))
5238
5273
  }
@@ -5293,12 +5328,12 @@ ${entries.join(`,
5293
5328
  await project.rebuild();
5294
5329
  const content = await readFile3(id, "utf-8");
5295
5330
  const parsed = withSharedStyles(parseLSP(content, id), (file) => this.addWatchFile?.(file));
5296
- const componentId = basename(id, ".lsp").toLowerCase();
5331
+ const componentId = [...componentFiles].find(([, file]) => resolve4(root, file) === resolve4(id))?.[0] ?? basename(id, ".lsp").toLowerCase();
5297
5332
  let compiledLogic = parsed.logic;
5298
5333
  if (useTypeScript && parsed.logic.trim()) {
5299
5334
  compiledLogic = await project.compileLogic(parsed.logic);
5300
5335
  }
5301
- const registryObj = Object.fromEntries(componentRegistry);
5336
+ const registryObj = project.registryFor(componentId);
5302
5337
  const transformedTemplate = transformTemplate(parsed.view, componentId, registryObj);
5303
5338
  const transformedSkeleton = parsed.skeleton ? transformTemplate(parsed.skeleton, componentId, registryObj) : "";
5304
5339
  const scopeId = scopeIdFor(componentId);
@@ -5353,6 +5388,8 @@ ${entries.join(`,
5353
5388
  }
5354
5389
  for (const file of await findFragments())
5355
5390
  server.watcher.add(file);
5391
+ if (host)
5392
+ return;
5356
5393
  const proxied = Object.keys(server.config.server.proxy ?? {});
5357
5394
  const isProxied = (pathname) => proxied.some((key) => key.startsWith("^") ? new RegExp(key).test(pathname) : pathname.startsWith(key));
5358
5395
  const missing = (res) => {
@@ -5385,6 +5422,8 @@ ${entries.join(`,
5385
5422
  });
5386
5423
  },
5387
5424
  configurePreviewServer(server) {
5425
+ if (host)
5426
+ return;
5388
5427
  const ready = buildRouteTable();
5389
5428
  server.middlewares.use(async (req, res, next) => {
5390
5429
  const url = req.url;
@@ -5446,13 +5485,27 @@ ${entries.join(`,
5446
5485
  const registryObj = Object.fromEntries(componentRegistry);
5447
5486
  const templates = {};
5448
5487
  for (const [componentId, raw] of componentRegistry) {
5449
- templates[componentId] = transformTemplate(raw.template, componentId, registryObj);
5488
+ templates[componentId] = transformTemplate(raw.template, componentId, project.registryFor(componentId));
5450
5489
  }
5490
+ const flattens = !isDevMode && shouldRender && options.flattenStatic !== false;
5491
+ const definitions = flattens ? await project.definitions() : null;
5451
5492
  const eager = new Set;
5452
5493
  for (const page of await findPages(root)) {
5453
5494
  const html = await readFile3(page, "utf-8");
5454
5495
  const compiled = transformTemplate(html, "index", registryObj);
5455
- eagerComponents(compiled, templates).forEach((id) => eager.add(id));
5496
+ let markup = compiled;
5497
+ if (definitions) {
5498
+ try {
5499
+ markup = (await renderPage({
5500
+ html: compiled,
5501
+ definitions,
5502
+ css: cssMode,
5503
+ pageExpressions: buildExpressionTable(extractExpressions(compiled))
5504
+ })).html;
5505
+ } catch {
5506
+ }
5507
+ }
5508
+ eagerComponents(markup, templates).forEach((id) => eager.add(id));
5456
5509
  }
5457
5510
  return [...eager];
5458
5511
  }
@@ -5602,7 +5655,7 @@ ${entries.join(`,
5602
5655
  return existsSync(join4(dir, ROUTES_DIR)) ? join4(dir, ROUTES_DIR) : dir;
5603
5656
  }
5604
5657
  function pageKey(file) {
5605
- return relative4(root, file).split(sep2).join("/");
5658
+ return relative4(root, file).split(sep3).join("/");
5606
5659
  }
5607
5660
  async function findPages(dir) {
5608
5661
  if (host)
@@ -5642,7 +5695,7 @@ ${entries.join(`,
5642
5695
  routeTable.length = 0;
5643
5696
  routeDeclarations.clear();
5644
5697
  for (const file of await findPages(root)) {
5645
- const rel = relative4(base, file).split(sep2).join("/");
5698
+ const rel = relative4(base, file).split(sep3).join("/");
5646
5699
  const key = pageKey(file);
5647
5700
  let declared = null;
5648
5701
  try {
@@ -5717,9 +5770,9 @@ ${entries.join(`,
5717
5770
  const absolute = resolve4(root, file);
5718
5771
  const within = relative4(root, absolute);
5719
5772
  if (!within.startsWith("..") && !isAbsolute(within)) {
5720
- return "/" + within.split(sep2).join("/");
5773
+ return "/" + within.split(sep3).join("/");
5721
5774
  }
5722
- return absolute.split(sep2).join("/");
5775
+ return absolute.split(sep3).join("/");
5723
5776
  }
5724
5777
  }
5725
5778
  function generateComponentModule(parsed, transformedTemplate, componentId, scopeId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightspeed-js/core",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Lightspeed: a compile-first framework for islands of reactivity in static HTML.",
5
5
  "keywords": [
6
6
  "lightspeed",