ventojs 2.1.0 → 2.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.
- package/CHANGELOG.md +19 -0
- package/core/environment.js +7 -0
- package/package.json +1 -1
- package/plugins/auto_trim.js +27 -13
- package/plugins/echo.js +7 -6
- package/plugins/function.js +5 -4
- package/plugins/import.js +4 -3
- package/plugins/include.js +3 -2
- package/plugins/layout.js +4 -3
- package/plugins/set.js +18 -2
- package/types/core/environment.d.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [2.2.0] - 2025-10-15
|
|
8
|
+
### Added
|
|
9
|
+
- Support for destructuring in set [#158] [#154].
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Possible variables naming collision [#157].
|
|
13
|
+
- `auto_trim` plugin edge cases [#159].
|
|
14
|
+
- `set`: allow `$` character in the variable name.
|
|
15
|
+
|
|
16
|
+
## [2.1.1] - 2025-09-18
|
|
17
|
+
### Fixed
|
|
18
|
+
- The tag `include` fails when it's inside a `slot`.
|
|
19
|
+
|
|
7
20
|
## [2.1.0] - 2025-09-17
|
|
8
21
|
### Added
|
|
9
22
|
- New `strict` mode to fail when using an undefined variable. This mode has a different performance profile than normal mode; it's mostly intended for testing and debug purposes. [#101], [#142]
|
|
@@ -80,8 +93,14 @@ Vento 2.0 is now dependency-free and compatible with browsers without a build st
|
|
|
80
93
|
[#148]: https://github.com/ventojs/vento/issues/148
|
|
81
94
|
[#150]: https://github.com/ventojs/vento/issues/150
|
|
82
95
|
[#151]: https://github.com/ventojs/vento/issues/151
|
|
96
|
+
[#154]: https://github.com/ventojs/vento/issues/154
|
|
83
97
|
[#156]: https://github.com/ventojs/vento/issues/156
|
|
98
|
+
[#157]: https://github.com/ventojs/vento/issues/157
|
|
99
|
+
[#158]: https://github.com/ventojs/vento/issues/158
|
|
100
|
+
[#159]: https://github.com/ventojs/vento/issues/159
|
|
84
101
|
|
|
102
|
+
[2.2.0]: https://github.com/ventojs/vento/compare/v2.1.1...v2.2.0
|
|
103
|
+
[2.1.1]: https://github.com/ventojs/vento/compare/v2.1.0...v2.1.1
|
|
85
104
|
[2.1.0]: https://github.com/ventojs/vento/compare/v2.0.2...v2.1.0
|
|
86
105
|
[2.0.2]: https://github.com/ventojs/vento/compare/v2.0.1...v2.0.2
|
|
87
106
|
[2.0.1]: https://github.com/ventojs/vento/compare/v2.0.0...v2.0.1
|
package/core/environment.js
CHANGED
|
@@ -7,6 +7,7 @@ export class Environment {
|
|
|
7
7
|
tags = [];
|
|
8
8
|
tokenPreprocessors = [];
|
|
9
9
|
filters = {};
|
|
10
|
+
#tempVariablesCreated = 0;
|
|
10
11
|
utils = {
|
|
11
12
|
callMethod,
|
|
12
13
|
createError,
|
|
@@ -236,6 +237,12 @@ export class Environment {
|
|
|
236
237
|
}
|
|
237
238
|
return output;
|
|
238
239
|
}
|
|
240
|
+
getTempVariable() {
|
|
241
|
+
const id = this.#tempVariablesCreated;
|
|
242
|
+
const variable = `__tmp${id}`;
|
|
243
|
+
this.#tempVariablesCreated++;
|
|
244
|
+
return variable;
|
|
245
|
+
}
|
|
239
246
|
}
|
|
240
247
|
function isGlobal(name) {
|
|
241
248
|
if (name == "name")
|
package/package.json
CHANGED
package/plugins/auto_trim.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export const defaultTags = [
|
|
2
2
|
">",
|
|
3
|
-
"#",
|
|
4
3
|
"set",
|
|
5
4
|
"/set",
|
|
6
5
|
"if",
|
|
@@ -15,6 +14,8 @@ export const defaultTags = [
|
|
|
15
14
|
"/export",
|
|
16
15
|
"import",
|
|
17
16
|
];
|
|
17
|
+
const LEADING_WHITESPACE = /(^|\n)[ \t]+$/;
|
|
18
|
+
const TRAILING_WHITESPACE = /^[ \t]*\r?\n/;
|
|
18
19
|
export default function (options = { tags: defaultTags }) {
|
|
19
20
|
return (env) => {
|
|
20
21
|
env.tokenPreprocessors.push((_, tokens) => autoTrim(tokens, options));
|
|
@@ -22,21 +23,34 @@ export default function (options = { tags: defaultTags }) {
|
|
|
22
23
|
}
|
|
23
24
|
export function autoTrim(tokens, options) {
|
|
24
25
|
for (let i = 0; i < tokens.length; i++) {
|
|
25
|
-
const previous = tokens[i - 1];
|
|
26
26
|
const token = tokens[i];
|
|
27
|
-
const next = tokens[i + 1];
|
|
28
27
|
const [type, code] = token;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
let needsTrim = false;
|
|
29
|
+
if (type === "comment") {
|
|
30
|
+
needsTrim = true;
|
|
31
|
+
}
|
|
32
|
+
else if (type === "tag") {
|
|
33
|
+
needsTrim = options.tags.some((tag) => {
|
|
34
|
+
if (!code.startsWith(tag))
|
|
35
|
+
return false;
|
|
36
|
+
return /\s/.test(code[tag.length] ?? " ");
|
|
37
|
+
});
|
|
37
38
|
}
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
if (!needsTrim)
|
|
40
|
+
continue;
|
|
41
|
+
// Remove leading horizontal space
|
|
42
|
+
const previous = tokens[i - 1];
|
|
43
|
+
previous[1] = previous[1].replace(LEADING_WHITESPACE, "$1");
|
|
44
|
+
// Skip "filter" tokens to find the next "string" token
|
|
45
|
+
for (let j = i + 1; j < tokens.length; j++) {
|
|
46
|
+
if (tokens[j][0] === "filter")
|
|
47
|
+
continue;
|
|
48
|
+
if (tokens[j][0] !== "string")
|
|
49
|
+
break;
|
|
50
|
+
// Remove trailing horizontal space + newline
|
|
51
|
+
const next = tokens[j];
|
|
52
|
+
next[1] = next[1].replace(TRAILING_WHITESPACE, "");
|
|
53
|
+
break;
|
|
40
54
|
}
|
|
41
55
|
}
|
|
42
56
|
}
|
package/plugins/echo.js
CHANGED
|
@@ -14,14 +14,15 @@ function echoTag(env, [, code], output, tokens) {
|
|
|
14
14
|
return `${output} += ${compiled};`;
|
|
15
15
|
}
|
|
16
16
|
// Captured echo, e.g. {{ echo |> toUpperCase }} foo {{ /echo }}
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
const tmp = env.getTempVariable();
|
|
18
|
+
const compiled = [`let ${tmp} = "";`];
|
|
19
|
+
const filters = env.compileFilters(tokens, tmp);
|
|
20
|
+
compiled.push(...env.compileTokens(tokens, tmp, "/echo"));
|
|
21
|
+
if (filters != tmp) {
|
|
22
|
+
compiled.push(`${tmp} = ${filters}`);
|
|
22
23
|
}
|
|
23
24
|
return `{
|
|
24
25
|
${compiled.join("\n")}
|
|
25
|
-
${output} +=
|
|
26
|
+
${output} += ${tmp};
|
|
26
27
|
}`;
|
|
27
28
|
}
|
package/plugins/function.js
CHANGED
|
@@ -16,13 +16,14 @@ function functionTag(env, token, _output, tokens) {
|
|
|
16
16
|
const [_, exp, as, name, args] = match;
|
|
17
17
|
const compiled = [];
|
|
18
18
|
compiled.push(`${as || ""} function ${name} ${args || "()"} {`);
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
const tmp = env.getTempVariable();
|
|
20
|
+
compiled.push(`let ${tmp} = "";`);
|
|
21
|
+
const result = env.compileFilters(tokens, tmp);
|
|
21
22
|
if (exp) {
|
|
22
|
-
compiled.push(...env.compileTokens(tokens,
|
|
23
|
+
compiled.push(...env.compileTokens(tokens, tmp, "/export"));
|
|
23
24
|
}
|
|
24
25
|
else {
|
|
25
|
-
compiled.push(...env.compileTokens(tokens,
|
|
26
|
+
compiled.push(...env.compileTokens(tokens, tmp, "/function"));
|
|
26
27
|
}
|
|
27
28
|
compiled.push(`return __env.utils.safeString(${result});`);
|
|
28
29
|
compiled.push(`}`);
|
package/plugins/import.js
CHANGED
|
@@ -21,10 +21,11 @@ function importTag(env, token) {
|
|
|
21
21
|
const variables = [];
|
|
22
22
|
const [, identifiers, specifier] = match;
|
|
23
23
|
const defaultImport = identifiers.match(DEFAULT_IMPORT);
|
|
24
|
+
const tmp = env.getTempVariable();
|
|
24
25
|
if (defaultImport) {
|
|
25
26
|
const [name] = defaultImport;
|
|
26
27
|
variables.push(name);
|
|
27
|
-
compiled.push(`${name} =
|
|
28
|
+
compiled.push(`${name} = ${tmp};`);
|
|
28
29
|
}
|
|
29
30
|
else {
|
|
30
31
|
const namedImports = identifiers.match(NAMED_IMPORTS);
|
|
@@ -46,7 +47,7 @@ function importTag(env, token) {
|
|
|
46
47
|
throw new SourceError("Invalid named import", position);
|
|
47
48
|
}
|
|
48
49
|
});
|
|
49
|
-
compiled.push(`({${chunks.join(",")}} =
|
|
50
|
+
compiled.push(`({${chunks.join(",")}} = ${tmp});`);
|
|
50
51
|
}
|
|
51
52
|
else {
|
|
52
53
|
throw new SourceError("Invalid import tag", position);
|
|
@@ -54,7 +55,7 @@ function importTag(env, token) {
|
|
|
54
55
|
}
|
|
55
56
|
const { dataVarname } = env.options;
|
|
56
57
|
return `let ${variables.join(",")}; {
|
|
57
|
-
let
|
|
58
|
+
let ${tmp} = await __env.run(${specifier}, {...${dataVarname}}, __template.path, ${position});
|
|
58
59
|
${compiled.join("\n")}
|
|
59
60
|
}`;
|
|
60
61
|
}
|
package/plugins/include.js
CHANGED
|
@@ -27,12 +27,13 @@ function includeTag(env, token, output, tokens) {
|
|
|
27
27
|
data = tagCode.slice(bracketIndex).trim();
|
|
28
28
|
}
|
|
29
29
|
const { dataVarname } = env.options;
|
|
30
|
+
const tmp = env.getTempVariable();
|
|
30
31
|
return `{
|
|
31
|
-
const
|
|
32
|
+
const ${tmp} = await __env.run(${file},
|
|
32
33
|
{...${dataVarname}${data ? `, ...${data}` : ""}},
|
|
33
34
|
__template.path,
|
|
34
35
|
${position}
|
|
35
36
|
);
|
|
36
|
-
${output} += ${env.compileFilters(tokens,
|
|
37
|
+
${output} += ${env.compileFilters(tokens, `${tmp}.content`)};
|
|
37
38
|
}`;
|
|
38
39
|
}
|
package/plugins/layout.js
CHANGED
|
@@ -39,10 +39,11 @@ function slotTag(env, token, _output, tokens) {
|
|
|
39
39
|
if (!SLOT_NAME.test(name)) {
|
|
40
40
|
throw new SourceError(`Invalid slot name "${name}"`, position);
|
|
41
41
|
}
|
|
42
|
-
const
|
|
42
|
+
const tmp = env.getTempVariable();
|
|
43
|
+
const compiledFilters = env.compileFilters(tokens, tmp);
|
|
43
44
|
return `{
|
|
44
|
-
let
|
|
45
|
-
${env.compileTokens(tokens,
|
|
45
|
+
let ${tmp} = '';
|
|
46
|
+
${env.compileTokens(tokens, tmp, "/slot").join("\n")}
|
|
46
47
|
__slots.${name} ??= '';
|
|
47
48
|
__slots.${name} += ${compiledFilters};
|
|
48
49
|
__slots.${name} = __env.utils.safeString(__slots.${name});
|
package/plugins/set.js
CHANGED
|
@@ -4,6 +4,9 @@ export default function () {
|
|
|
4
4
|
env.tags.push(setTag);
|
|
5
5
|
};
|
|
6
6
|
}
|
|
7
|
+
const VARNAME = /^[a-zA-Z_$][\w$]*$/;
|
|
8
|
+
const DETECTED_VARS = /([a-zA-Z_$][\w$]*)\b(?!\s*\:)/g;
|
|
9
|
+
const VALID_TAG = /^set\s+([\w{}[\]\s,:.$]+)\s*=\s*([\s\S]+)$/;
|
|
7
10
|
function setTag(env, token, _output, tokens) {
|
|
8
11
|
const [, code, position] = token;
|
|
9
12
|
if (!code.startsWith("set ")) {
|
|
@@ -13,12 +16,25 @@ function setTag(env, token, _output, tokens) {
|
|
|
13
16
|
const { dataVarname } = env.options;
|
|
14
17
|
// Value is set (e.g. {{ set foo = "bar" }})
|
|
15
18
|
if (expression.includes("=")) {
|
|
16
|
-
const match = code.match(
|
|
19
|
+
const match = code.match(VALID_TAG);
|
|
17
20
|
if (!match) {
|
|
18
21
|
throw new SourceError("Invalid set tag", position);
|
|
19
22
|
}
|
|
20
|
-
const
|
|
23
|
+
const variable = match[1].trim();
|
|
24
|
+
const value = match[2].trim();
|
|
21
25
|
const val = env.compileFilters(tokens, value);
|
|
26
|
+
if ((variable.startsWith("{") && variable.endsWith("}")) ||
|
|
27
|
+
(variable.startsWith("[") && variable.endsWith("]"))) {
|
|
28
|
+
const names = Array.from(variable.matchAll(DETECTED_VARS))
|
|
29
|
+
.map((n) => n[1]);
|
|
30
|
+
return `
|
|
31
|
+
var ${variable} = ${val};
|
|
32
|
+
Object.assign(${dataVarname}, { ${names.join(", ")} });
|
|
33
|
+
`;
|
|
34
|
+
}
|
|
35
|
+
if (!VARNAME.test(variable)) {
|
|
36
|
+
throw new SourceError("Invalid variable name", position);
|
|
37
|
+
}
|
|
22
38
|
return `var ${variable} = ${dataVarname}["${variable}"] = ${val};`;
|
|
23
39
|
}
|
|
24
40
|
// Value is captured (eg: {{ set foo }}bar{{ /set }})
|
|
@@ -37,6 +37,7 @@ export interface Options {
|
|
|
37
37
|
strict: boolean;
|
|
38
38
|
}
|
|
39
39
|
export declare class Environment {
|
|
40
|
+
#private;
|
|
40
41
|
cache: Map<string, Template | Promise<Template>>;
|
|
41
42
|
options: Options;
|
|
42
43
|
tags: Tag[];
|
|
@@ -52,6 +53,7 @@ export declare class Environment {
|
|
|
52
53
|
load(file: string, from?: string, position?: number): Promise<Template>;
|
|
53
54
|
compileTokens(tokens: Token[], outputVar?: string, closeToken?: string, closeTokenOptional?: boolean): string[];
|
|
54
55
|
compileFilters(tokens: Token[], output: string, autoescape?: boolean): string;
|
|
56
|
+
getTempVariable(): string;
|
|
55
57
|
}
|
|
56
58
|
export declare class SafeString extends String {
|
|
57
59
|
}
|