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 +2 -0
- package/core/errors.js +3 -1
- package/core/js.js +3 -1
- package/core/reserved.js +0 -10
- package/loaders/module.js +31 -25
- package/package.json +1 -1
- package/plugins/layout.js +33 -18
- package/types/loaders/module.d.ts +7 -9
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
|
-
|
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
|
-
|
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 +
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
export function
|
34
|
-
|
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
|
-
${
|
41
|
-
|
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
|
-
|
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
|
-
|
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
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(
|
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
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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;
|