ventojs 2.0.0-canary.2 → 2.0.0-canary.3

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/CHANGELOG.md CHANGED
@@ -14,6 +14,7 @@ Vento 2.0 is now dependency-free and compatible with browsers without a build st
14
14
  - New filesystem loader to use File System API.
15
15
  - Better errors reporting [#131], [#137]
16
16
  - `core/errors.ts` module to format errors.
17
+ - New `{{ slot }}` tag to pass extra variables to `{{ layout }}` [#140]
17
18
 
18
19
  ### Changed
19
20
  - Renamed `src` directory to `core`.
@@ -48,3 +49,4 @@ Vento 2.0 is now dependency-free and compatible with browsers without a build st
48
49
  [#131]: https://github.com/ventojs/vento/issues/131
49
50
  [#134]: https://github.com/ventojs/vento/issues/134
50
51
  [#137]: https://github.com/ventojs/vento/issues/137
52
+ [#140]: https://github.com/ventojs/vento/issues/140
package/core/errors.js CHANGED
@@ -58,7 +58,9 @@ export class RuntimeError extends VentoError {
58
58
  }
59
59
  // Capture the exact position of the error in the compiled code
60
60
  for (const frame of getStackFrames(this.cause)) {
61
- if (frame.file !== "<anonymous>") {
61
+ if (frame.file !== "<anonymous>" &&
62
+ path &&
63
+ ![path + ".js", path + ".mjs"].some((p) => frame.file.endsWith(p))) {
62
64
  continue;
63
65
  }
64
66
  return {
package/core/js.js CHANGED
@@ -30,8 +30,10 @@ export default function* iterateTopLevel(source, start = 0) {
30
30
  const [stop, variable] = match;
31
31
  if (variable) {
32
32
  cursor += variable.length;
33
- if (!reserved.has(variable))
33
+ // Words used internally by Vento start with two underscores
34
+ if (!reserved.has(variable) && !variable.startsWith("__")) {
34
35
  variables.add(variable);
36
+ }
35
37
  continue;
36
38
  }
37
39
  // Check the type of the stopping point.
package/core/reserved.js CHANGED
@@ -1,14 +1,4 @@
1
1
  const variables = new Set([
2
- // Words reserved by Vento, used internally. In general, don't use variable
3
- // names starting with two underscores to be future-proof and avoid clashes.
4
- "__file",
5
- "__template",
6
- "__env",
7
- "__defaults",
8
- "__err",
9
- "__exports",
10
- "__pos",
11
- "__tmp",
12
2
  // JS reserved words, and some "dangerous" words like `let`, `async`, `of` or
13
3
  // `undefined`, which aren't technically reserved but don't name your
14
4
  // variables that.
package/loaders/module.js CHANGED
@@ -4,11 +4,13 @@
4
4
  */
5
5
  export class ModuleLoader {
6
6
  #root;
7
- constructor(root) {
7
+ #extension;
8
+ constructor(root, extension = ".js") {
8
9
  this.#root = root;
10
+ this.#extension = extension;
9
11
  }
10
12
  async load(file) {
11
- const url = new URL(join(this.#root.pathname, file + ".js"), this.#root);
13
+ const url = new URL(join(this.#root.pathname, file + this.#extension), this.#root);
12
14
  const module = await import(url.toString());
13
15
  return module.default;
14
16
  }
@@ -18,34 +20,38 @@ export class ModuleLoader {
18
20
  }
19
21
  return join("/", file);
20
22
  }
21
- }
22
- function join(...parts) {
23
- return parts.join("/").replace(/\/+/g, "/");
24
- }
25
- function dirname(path) {
26
- const lastSlash = path.lastIndexOf("/");
27
- return lastSlash === -1 ? "." : path.slice(0, lastSlash);
28
- }
29
- /**
30
- * Exports a template as a string that can be used in an ES module.
31
- * This is useful for precompiled templates.
32
- */
33
- export function exportTemplate(template, options) {
34
- if (!template.source) {
35
- throw new Error("Template source is not defined");
36
- }
37
- const exportCode = `export default function (__env) {
23
+ /**
24
+ * Outputs a template as a string that can be used in an ES module.
25
+ * This is useful for precompiled templates.
26
+ * @returns A tuple with the path and the content of the module.
27
+ */
28
+ output(template, source = false) {
29
+ if (!template.source) {
30
+ throw new Error("Template source is not defined");
31
+ }
32
+ if (!template.path) {
33
+ throw new Error("Template path is not defined");
34
+ }
35
+ const content = `export default function (__env
36
+ ) {
38
37
  ${template.toString()};
39
38
 
40
- ${options?.source
41
- ? `__template.path = ${JSON.stringify(template.path ? template.path + ".js" : undefined)};
39
+ ${source
40
+ ? `__template.path = ${JSON.stringify(template.path)};
42
41
  __template.code = ${JSON.stringify(template.code)};
43
- __template.source = ${JSON.stringify(template.source)};
44
- __template.tokens = ${JSON.stringify(template.tokens)};`
45
- : ""}
42
+ __template.source = ${JSON.stringify(template.source)};`
43
+ : ""}
46
44
  __template.defaults = ${JSON.stringify(template.defaults || {})};
47
45
 
48
46
  return __template;
49
47
  }`;
50
- return exportCode;
48
+ return [`${template.path}${this.#extension}`, content];
49
+ }
50
+ }
51
+ function join(...parts) {
52
+ return parts.join("/").replace(/\/+/g, "/");
53
+ }
54
+ function dirname(path) {
55
+ const lastSlash = path.lastIndexOf("/");
56
+ return lastSlash === -1 ? "." : path.slice(0, lastSlash);
51
57
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ventojs",
3
- "version": "2.0.0-canary.2",
3
+ "version": "2.0.0-canary.3",
4
4
  "description": "🌬 A minimal but powerful template engine",
5
5
  "type": "module",
6
6
  "repository": {
package/plugins/layout.js CHANGED
@@ -2,34 +2,49 @@ import { SourceError } from "../core/errors.js";
2
2
  export default function () {
3
3
  return (env) => {
4
4
  env.tags.push(layoutTag);
5
+ env.tags.push(slotTag);
5
6
  };
6
7
  }
8
+ const LAYOUT_TAG = /^layout\s+([^{]+|`[^`]+`)+(?:\{([^]*)\})?$/;
9
+ const SLOT_NAME = /^[a-z_]\w*$/i;
7
10
  function layoutTag(env, token, output, tokens) {
8
11
  const [, code, position] = token;
9
12
  if (!code.startsWith("layout ")) {
10
13
  return;
11
14
  }
12
- const match = code?.match(/^layout\s+([^{]+|`[^`]+`)+(?:\{([\s|\S]*)\})?$/);
15
+ const match = code?.match(LAYOUT_TAG);
13
16
  if (!match) {
14
17
  throw new SourceError("Invalid layout tag", position);
15
18
  }
16
19
  const [_, file, data] = match;
17
- const varname = output.startsWith("__layout")
18
- ? output + "_layout"
19
- : "__layout";
20
- const compiled = [];
21
- const compiledFilters = env.compileFilters(tokens, varname);
22
- compiled.push("{");
23
- compiled.push(`let ${varname} = "";`);
24
- compiled.push(...env.compileTokens(tokens, varname, "/layout"));
25
- compiled.push(`${varname} = __env.utils.safeString(${compiledFilters});`);
20
+ const compiledFilters = env.compileFilters(tokens, "__slots.content");
26
21
  const { dataVarname } = env.options;
27
- compiled.push(`const __tmp = await __env.run(${file},
28
- {...${dataVarname}${data ? `, ${data}` : ""}, content: ${env.compileFilters(tokens, varname)}},
29
- __template.path,
30
- ${position}
31
- );
32
- ${output} += __tmp.content;`);
33
- compiled.push("}");
34
- return compiled.join("\n");
22
+ return `${output} += (await (async () => {
23
+ const __slots = { content: "" };
24
+ ${env.compileTokens(tokens, "__slots.content", "/layout").join("\n")}
25
+ __slots.content = __env.utils.safeString(${compiledFilters});
26
+ return __env.run(${file}, {
27
+ ...${dataVarname},
28
+ ...__slots,
29
+ ${data ?? ""}
30
+ }, __template.path, ${position});
31
+ })()).content;`;
32
+ }
33
+ function slotTag(env, token, _output, tokens) {
34
+ const [, code, position] = token;
35
+ if (!code.startsWith("slot ")) {
36
+ return;
37
+ }
38
+ const name = code.slice(4).trim();
39
+ if (!SLOT_NAME.test(name)) {
40
+ throw new SourceError(`Invalid slot name "${name}"`, position);
41
+ }
42
+ const compiledFilters = env.compileFilters(tokens, "__tmp");
43
+ return `{
44
+ let __tmp = '';
45
+ ${env.compileTokens(tokens, "__tmp", "/slot").join("\n")}
46
+ __slots.${name} ??= '';
47
+ __slots.${name} += ${compiledFilters};
48
+ __slots.${name} = __env.utils.safeString(__slots.${name});
49
+ }`;
35
50
  }
@@ -5,15 +5,13 @@ import type { Loader, PrecompiledTemplate, Template } from "../core/environment.
5
5
  */
6
6
  export declare class ModuleLoader implements Loader {
7
7
  #private;
8
- constructor(root: URL);
8
+ constructor(root: URL, extension?: string);
9
9
  load(file: string): Promise<PrecompiledTemplate>;
10
10
  resolve(from: string, file: string): string;
11
+ /**
12
+ * Outputs a template as a string that can be used in an ES module.
13
+ * This is useful for precompiled templates.
14
+ * @returns A tuple with the path and the content of the module.
15
+ */
16
+ output(template: Template, source?: boolean): [string, string];
11
17
  }
12
- export interface ExportOptions {
13
- source?: boolean;
14
- }
15
- /**
16
- * Exports a template as a string that can be used in an ES module.
17
- * This is useful for precompiled templates.
18
- */
19
- export declare function exportTemplate(template: Template, options?: ExportOptions): string;