htmv 0.0.55 → 0.0.56

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.
@@ -74,6 +74,34 @@ export function parse(tokens) {
74
74
  });
75
75
  continue;
76
76
  }
77
+ const pathName = token.tag.split("/").at(-1);
78
+ if (pathName !== undefined && startsWithUppercase(pathName)) {
79
+ // component
80
+ const rawArgs = [];
81
+ let nextToken = tokens[i];
82
+ while (nextToken?.type === "arguments") {
83
+ rawArgs.push(nextToken.value);
84
+ i++;
85
+ nextToken = tokens[i];
86
+ }
87
+ if (rawArgs.at(-1) === "/") {
88
+ // is self closing
89
+ nodes.push({
90
+ type: "component",
91
+ path: token.tag,
92
+ props: rawArgsToProps(rawArgs),
93
+ children: [],
94
+ });
95
+ continue;
96
+ }
97
+ nodes.push({
98
+ type: "component",
99
+ path: token.tag,
100
+ props: rawArgsToProps(rawArgs.slice(0, -1)),
101
+ children: parseChildren(tag),
102
+ });
103
+ continue;
104
+ }
77
105
  nodes.push({
78
106
  type: "text",
79
107
  text: `<${tag} `,
@@ -155,3 +183,27 @@ export function parse(tokens) {
155
183
  }
156
184
  }
157
185
  }
186
+ function startsWithUppercase(text) {
187
+ return text[0] === text[0]?.toUpperCase();
188
+ }
189
+ function rawArgsToProps(rawArgs) {
190
+ const props = {};
191
+ for (const arg of rawArgs) {
192
+ if (arg === "/")
193
+ continue;
194
+ const eqIndex = arg.indexOf("=");
195
+ if (eqIndex === -1) {
196
+ // boolean prop
197
+ props[arg] = true;
198
+ continue;
199
+ }
200
+ const key = arg.slice(0, eqIndex);
201
+ let value = arg.slice(eqIndex + 1);
202
+ if ((value.startsWith('"') && value.endsWith('"')) ||
203
+ (value.startsWith("'") && value.endsWith("'"))) {
204
+ value = value.slice(1, -1);
205
+ }
206
+ props[key] = value;
207
+ }
208
+ return props;
209
+ }
@@ -1,4 +1,10 @@
1
- export type Node = RootNode | TextNode | InterpolationNode | IssetNode | ForNode | AttributeBindingNode;
1
+ export type Node = RootNode | TextNode | InterpolationNode | IssetNode | ForNode | AttributeBindingNode | ComponentNode;
2
+ export interface ComponentNode {
3
+ type: "component";
4
+ path: string;
5
+ props: Record<string, unknown>;
6
+ children: Node[];
7
+ }
2
8
  export interface RootNode {
3
9
  type: "root";
4
10
  children: Node[];
@@ -1,3 +1,4 @@
1
+ import { view } from "../views";
1
2
  export function render(node, context) {
2
3
  if (node.type === "attr-binding") {
3
4
  const prop = resolvePropertyPath(node.expr);
@@ -32,6 +33,14 @@ export function render(node, context) {
32
33
  }
33
34
  return "";
34
35
  }
36
+ if (node.type === "component") {
37
+ const renderedView = view(node.path, {
38
+ ...node.props,
39
+ children: node.children.map((node) => render(node, context)).join(""),
40
+ ...context,
41
+ }).body;
42
+ return renderedView ?? "";
43
+ }
35
44
  const output = node.children.map((node) => render(node, context));
36
45
  return output.join("");
37
46
  function resolvePropertyPath(path) {
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { createApp } from "./app";
2
2
  import { registerRoutes } from "./routing/routing";
3
3
  import { setViewsPath } from "./views";
4
+ import { registerViews } from "./views-registry";
4
5
  export { view } from "./views";
5
6
  export async function setup(paths) {
6
7
  setViewsPath(paths.views);
8
+ await registerViews();
7
9
  const app = createApp(paths.public);
8
10
  await registerRoutes(app, paths.routes);
9
11
  app.listen(paths.port);
@@ -0,0 +1,3 @@
1
+ export declare const viewRegistry: Record<string, string>;
2
+ export declare function addToViewRegistry(name: string, code: string): void;
3
+ export declare function registerViews(): Promise<void>;
@@ -0,0 +1,31 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { viewsPath } from "./views";
4
+ export const viewRegistry = {};
5
+ export function addToViewRegistry(name, code) {
6
+ viewRegistry[name] = code;
7
+ }
8
+ export async function registerViews() {
9
+ const files = await deepReadDir(viewsPath);
10
+ for (const file of files) {
11
+ if (file.endsWith(".html")) {
12
+ const name = file.slice(0, -".html".length);
13
+ const code = await fs.readFile(name, "utf-8");
14
+ addToViewRegistry(name, code);
15
+ }
16
+ }
17
+ }
18
+ async function deepReadDir(dir) {
19
+ const entries = await fs.readdir(dir, { withFileTypes: true });
20
+ const files = [];
21
+ for (const entry of entries) {
22
+ const fullPath = path.join(dir, entry.name);
23
+ if (entry.isDirectory()) {
24
+ files.push(...(await deepReadDir(fullPath)));
25
+ }
26
+ else if (entry.isFile()) {
27
+ files.push(fullPath);
28
+ }
29
+ }
30
+ return files;
31
+ }
package/dist/views.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  import type { HttpResponse } from "./http/response";
2
+ export declare let viewsPath: string;
2
3
  export declare function setViewsPath(path: string): void;
3
- export declare function view(view: string, props?: Record<string, unknown>): Promise<HttpResponse>;
4
+ export declare function view(view: string, props?: Record<string, unknown>): HttpResponse;
package/dist/views.js CHANGED
@@ -3,15 +3,17 @@ import path from "node:path";
3
3
  import { parse } from "./compiler/parser";
4
4
  import { render } from "./compiler/renderer";
5
5
  import { tokenize } from "./compiler/tokenizer";
6
- let viewsPath = "";
6
+ import { viewRegistry } from "./views-registry";
7
+ export let viewsPath = "";
7
8
  export function setViewsPath(path) {
8
9
  viewsPath = path;
9
10
  }
10
- export async function view(view, props) {
11
+ export function view(view, props) {
11
12
  if (viewsPath === "")
12
13
  throw new Error("Views folder path not yet configured. Use `Htmv.setup` before rendering a view.");
13
- const filePath = path.join(viewsPath, `${view}.html`);
14
- const code = await fs.readFile(filePath, "utf-8");
14
+ const code = viewRegistry[view];
15
+ if (code === undefined)
16
+ throw new Error(`View ${view} not found. Did you use the correct name?`);
15
17
  const tokens = tokenize(code);
16
18
  const root = parse(tokens);
17
19
  const rendered = render(root, props ? props : {});
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "htmv",
3
3
  "main": "dist/index.js",
4
4
  "type": "module",
5
- "version": "0.0.55",
5
+ "version": "0.0.56",
6
6
  "exports": {
7
7
  ".": {
8
8
  "types": "./dist/index.d.ts",