ventojs 0.6.0 → 0.7.1
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/README.md +23 -7
- package/esm/mod.js +6 -0
- package/esm/plugins/export.d.ts +2 -0
- package/esm/plugins/export.js +46 -0
- package/esm/plugins/function.d.ts +2 -0
- package/esm/plugins/function.js +37 -0
- package/esm/plugins/import.d.ts +2 -0
- package/esm/plugins/import.js +24 -0
- package/esm/plugins/include.js +7 -1
- package/esm/plugins/layout.js +5 -1
- package/esm/src/environment.d.ts +15 -4
- package/esm/src/environment.js +10 -5
- package/esm/test/function.test.d.ts +1 -0
- package/esm/test/import.test.d.ts +1 -0
- package/esm/test/utils.d.ts +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -91,13 +91,26 @@ First, let's take a look at this syntax example:
|
|
|
91
91
|
|
|
92
92
|
## Getting started
|
|
93
93
|
|
|
94
|
-
This is a library for Deno. I'm planning to release an NPM version in the
|
|
95
|
-
future
|
|
94
|
+
This is a library for Deno. ~~I'm planning to release an NPM version in the
|
|
95
|
+
future.~~
|
|
96
|
+
[There's already an NPM version](https://www.npmjs.com/package/ventojs) that you
|
|
97
|
+
can install with `npm install ventojs`.
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
Import the library and create an instance:
|
|
98
100
|
|
|
99
101
|
```ts
|
|
100
|
-
import vento from "https://deno.land/x/vento
|
|
102
|
+
import vento from "https://deno.land/x/vento/mod.ts";
|
|
103
|
+
|
|
104
|
+
const vto = vento({
|
|
105
|
+
// Resolve the non-relative includes paths
|
|
106
|
+
includes: "./path/to/includes",
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Or in Node:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import vento from "ventojs";
|
|
101
114
|
|
|
102
115
|
const vto = vento({
|
|
103
116
|
// Resolve the non-relative includes paths
|
|
@@ -113,19 +126,22 @@ can use `load` to load and compile a template file and return it.
|
|
|
113
126
|
const template = vto.load("my-template.vto");
|
|
114
127
|
|
|
115
128
|
// Now you can use it passing the data
|
|
116
|
-
template({ title: "Hello world" });
|
|
129
|
+
const result = template({ title: "Hello world" });
|
|
130
|
+
console.log(result.content);
|
|
117
131
|
```
|
|
118
132
|
|
|
119
133
|
Alternatively, you can load and run the template file in a single call:
|
|
120
134
|
|
|
121
135
|
```ts
|
|
122
|
-
vto.run("my-template.vto", { title: "Hello world" });
|
|
136
|
+
const result = vto.run("my-template.vto", { title: "Hello world" });
|
|
137
|
+
console.log(result.content);
|
|
123
138
|
```
|
|
124
139
|
|
|
125
140
|
If the template code is not a file, you can run it directly:
|
|
126
141
|
|
|
127
142
|
```ts
|
|
128
|
-
vto.runString("<h1>{{ title }}</h1>", { title: "Hello world" });
|
|
143
|
+
const result = vto.runString("<h1>{{ title }}</h1>", { title: "Hello world" });
|
|
144
|
+
console.log(result.content);
|
|
129
145
|
```
|
|
130
146
|
|
|
131
147
|
## Visual Studio Code Support
|
package/esm/mod.js
CHANGED
|
@@ -7,6 +7,9 @@ import includeTag from "./plugins/include.js";
|
|
|
7
7
|
import setTag from "./plugins/set.js";
|
|
8
8
|
import jsTag from "./plugins/js.js";
|
|
9
9
|
import layoutTag from "./plugins/layout.js";
|
|
10
|
+
import functionTag from "./plugins/function.js";
|
|
11
|
+
import importTag from "./plugins/import.js";
|
|
12
|
+
import exportTag from "./plugins/export.js";
|
|
10
13
|
import escape from "./plugins/escape.js";
|
|
11
14
|
export default function (options = {}) {
|
|
12
15
|
const loader = typeof options.includes === "object"
|
|
@@ -23,6 +26,9 @@ export default function (options = {}) {
|
|
|
23
26
|
env.use(includeTag());
|
|
24
27
|
env.use(setTag());
|
|
25
28
|
env.use(layoutTag());
|
|
29
|
+
env.use(functionTag());
|
|
30
|
+
env.use(importTag());
|
|
31
|
+
env.use(exportTag());
|
|
26
32
|
env.use(escape());
|
|
27
33
|
return env;
|
|
28
34
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export default function () {
|
|
2
|
+
return (env) => {
|
|
3
|
+
env.tags.push(exportTag);
|
|
4
|
+
};
|
|
5
|
+
}
|
|
6
|
+
function exportTag(env, code, _output, tokens) {
|
|
7
|
+
if (!code.startsWith("export ")) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const expression = code.replace(/^export\s+/, "");
|
|
11
|
+
// Value is set (e.g. {{ export foo = "bar" }})
|
|
12
|
+
if (expression.includes("=")) {
|
|
13
|
+
const match = code.match(/^export\s+([\w]+)\s*=\s*([\s\S]+)$/);
|
|
14
|
+
if (!match) {
|
|
15
|
+
throw new Error(`Invalid export tag: ${code}`);
|
|
16
|
+
}
|
|
17
|
+
const [, variable, value] = match;
|
|
18
|
+
const val = env.compileFilters(tokens, value);
|
|
19
|
+
return `if (__data.hasOwnProperty("${variable}")) {
|
|
20
|
+
${variable} = ${val};
|
|
21
|
+
} else {
|
|
22
|
+
var ${variable} = ${val};
|
|
23
|
+
}
|
|
24
|
+
__data["${variable}"] = ${variable};
|
|
25
|
+
__exports["${variable}"] = ${variable};
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
// Value is captured (eg: {{ export foo }}bar{{ /export }})
|
|
29
|
+
const compiled = [];
|
|
30
|
+
const compiledFilters = env.compileFilters(tokens, expression);
|
|
31
|
+
compiled.push(`if (__data.hasOwnProperty("${expression}")) {
|
|
32
|
+
${expression} = "";
|
|
33
|
+
} else {
|
|
34
|
+
var ${expression} = "";
|
|
35
|
+
}
|
|
36
|
+
`);
|
|
37
|
+
compiled.push(...env.compileTokens(tokens, expression, ["/export"]));
|
|
38
|
+
if (tokens.length && (tokens[0][0] !== "tag" || tokens[0][1] !== "/export")) {
|
|
39
|
+
throw new Error(`Missing closing tag for export tag: ${code}`);
|
|
40
|
+
}
|
|
41
|
+
tokens.shift();
|
|
42
|
+
compiled.push(`${expression} = ${compiledFilters};`);
|
|
43
|
+
compiled.push(`__data["${expression.trim()}"] = ${expression};`);
|
|
44
|
+
compiled.push(`__exports["${expression.trim()}"] = ${expression};`);
|
|
45
|
+
return compiled.join("\n");
|
|
46
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export default function () {
|
|
2
|
+
return (env) => {
|
|
3
|
+
env.tags.push(functionTag);
|
|
4
|
+
};
|
|
5
|
+
}
|
|
6
|
+
function functionTag(env, code, _output, tokens) {
|
|
7
|
+
if (!code.match(/(export\s+)?(async\s+)?function\s/)) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const match = code.match(/^(export\s+)?(async\s+)?function\s+(\w+)\s*(\([^)]+\))?$/);
|
|
11
|
+
if (!match) {
|
|
12
|
+
throw new Error(`Invalid function: ${code}`);
|
|
13
|
+
}
|
|
14
|
+
const [_, exp, as, name, args] = match;
|
|
15
|
+
const compiled = [];
|
|
16
|
+
compiled.push(`${as || ""} function ${name} ${args || "()"} {`);
|
|
17
|
+
compiled.push(`let __output = "";`);
|
|
18
|
+
if (exp) {
|
|
19
|
+
compiled.push(...env.compileTokens(tokens, "__output", ["/export"]));
|
|
20
|
+
if (tokens.length && (tokens[0][0] !== "tag" || tokens[0][1] !== "/export")) {
|
|
21
|
+
throw new Error(`Missing closing tag for export function tag: ${code}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
compiled.push(...env.compileTokens(tokens, "__output", ["/function"]));
|
|
26
|
+
if (tokens.length && (tokens[0][0] !== "tag" || tokens[0][1] !== "/function")) {
|
|
27
|
+
throw new Error(`Missing closing tag for function tag: ${code}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
tokens.shift();
|
|
31
|
+
compiled.push(`return __output;`);
|
|
32
|
+
compiled.push(`}`);
|
|
33
|
+
if (exp) {
|
|
34
|
+
compiled.push(`__exports["${name}"] = ${name}`);
|
|
35
|
+
}
|
|
36
|
+
return compiled.join("\n");
|
|
37
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export default function () {
|
|
2
|
+
return (env) => {
|
|
3
|
+
env.tags.push(importTag);
|
|
4
|
+
};
|
|
5
|
+
}
|
|
6
|
+
function importTag(_env, code) {
|
|
7
|
+
if (!code.startsWith("import ")) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const match = code?.match(/^import\s+(\{[\s|\S]*\}|\w+)\s+from\s+(.+)$/);
|
|
11
|
+
if (!match) {
|
|
12
|
+
throw new Error(`Invalid import: ${code}`);
|
|
13
|
+
}
|
|
14
|
+
const [_, vars, file] = match;
|
|
15
|
+
const compiled = [];
|
|
16
|
+
compiled.push(`__tmp = await __env.run(${file}, {...__data}, __file);`);
|
|
17
|
+
if (vars.startsWith("{")) {
|
|
18
|
+
compiled.push(`let ${vars} = __tmp;`);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
compiled.push(`let ${vars} = __tmp;`);
|
|
22
|
+
}
|
|
23
|
+
return compiled.join("\n");
|
|
24
|
+
}
|
package/esm/plugins/include.js
CHANGED
|
@@ -12,5 +12,11 @@ function includeTag(_env, code, output) {
|
|
|
12
12
|
throw new Error(`Invalid include: ${code}`);
|
|
13
13
|
}
|
|
14
14
|
const [_, file, data] = match;
|
|
15
|
-
return
|
|
15
|
+
return `{
|
|
16
|
+
__tmp = await __env.run(${file},
|
|
17
|
+
{...__data${data ? `, ${data}` : ""}},
|
|
18
|
+
__file
|
|
19
|
+
);
|
|
20
|
+
${output} += __tmp.content;
|
|
21
|
+
}`;
|
|
16
22
|
}
|
package/esm/plugins/layout.js
CHANGED
|
@@ -23,7 +23,11 @@ function layoutTag(env, code, output, tokens) {
|
|
|
23
23
|
}
|
|
24
24
|
tokens.shift();
|
|
25
25
|
compiled.push(`${varname} = ${compiledFilters};`);
|
|
26
|
-
compiled.push(
|
|
26
|
+
compiled.push(`__tmp = await __env.run(${file},
|
|
27
|
+
{...__data${data ? `, ${data}` : ""}, content: ${varname}},
|
|
28
|
+
__file
|
|
29
|
+
);
|
|
30
|
+
${output} += __tmp.content;`);
|
|
27
31
|
compiled.push("}");
|
|
28
32
|
return compiled.join("\n");
|
|
29
33
|
}
|
package/esm/src/environment.d.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { Token } from "./tokenizer.js";
|
|
2
2
|
import type { Loader } from "./loader.js";
|
|
3
|
+
export interface TemplateResult {
|
|
4
|
+
content: string;
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
}
|
|
3
7
|
export interface Template {
|
|
4
|
-
(data?: Record<string, unknown>): Promise<
|
|
8
|
+
(data?: Record<string, unknown>): Promise<TemplateResult>;
|
|
9
|
+
code: string;
|
|
10
|
+
file?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface TemplateSync {
|
|
13
|
+
(data?: Record<string, unknown>): TemplateResult;
|
|
5
14
|
code: string;
|
|
6
15
|
file?: string;
|
|
7
16
|
}
|
|
@@ -20,9 +29,11 @@ export declare class Environment {
|
|
|
20
29
|
utils: Record<string, unknown>;
|
|
21
30
|
constructor(options: Options);
|
|
22
31
|
use(plugin: Plugin): void;
|
|
23
|
-
run(file: string, data: Record<string, unknown>, from?: string): Promise<
|
|
24
|
-
runString(source: string, data?: Record<string, unknown>, file?: string): Promise<
|
|
25
|
-
|
|
32
|
+
run(file: string, data: Record<string, unknown>, from?: string): Promise<TemplateResult>;
|
|
33
|
+
runString(source: string, data?: Record<string, unknown>, file?: string): Promise<TemplateResult>;
|
|
34
|
+
runStringSync(source: string, data?: Record<string, unknown>): TemplateResult;
|
|
35
|
+
compile(source: string, path?: string, defaults?: Record<string, unknown>, sync?: false): Template;
|
|
36
|
+
compile(source: string, path?: string, defaults?: Record<string, unknown>, sync?: true): TemplateSync;
|
|
26
37
|
load(file: string, from?: string): Promise<Template>;
|
|
27
38
|
compileTokens(tokens: Token[], outputVar?: string, stopAt?: string[]): string[];
|
|
28
39
|
compileFilters(tokens: Token[], output: string): string;
|
package/esm/src/environment.js
CHANGED
|
@@ -29,19 +29,24 @@ export class Environment {
|
|
|
29
29
|
const template = this.compile(source, file);
|
|
30
30
|
return await template(data);
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
runStringSync(source, data) {
|
|
33
|
+
const template = this.compile(source, "", {}, true);
|
|
34
|
+
return template(data);
|
|
35
|
+
}
|
|
36
|
+
compile(source, path, defaults, sync = false) {
|
|
33
37
|
try {
|
|
34
38
|
const tokens = tokenize(source);
|
|
35
39
|
const code = this.compileTokens(tokens).join("\n");
|
|
36
|
-
const constructor = new Function("__file", "__env", "__defaults", `return async function (__data) {
|
|
40
|
+
const constructor = new Function("__file", "__env", "__defaults", `return${sync ? "" : " async"} function (__data) {
|
|
37
41
|
try {
|
|
38
42
|
__data = Object.assign({}, __defaults, __data);
|
|
39
43
|
const ${this.options.dataVarname} = __data;
|
|
40
|
-
let
|
|
44
|
+
let __tmp;
|
|
45
|
+
const __exports = { content: "" };
|
|
41
46
|
with (__data) {
|
|
42
47
|
${code}
|
|
43
48
|
}
|
|
44
|
-
return
|
|
49
|
+
return __exports;
|
|
45
50
|
} catch (cause) {
|
|
46
51
|
throw new Error(\`Error rendering template: \${__file}\`, { cause });
|
|
47
52
|
}
|
|
@@ -66,7 +71,7 @@ export class Environment {
|
|
|
66
71
|
}
|
|
67
72
|
return this.cache.get(file);
|
|
68
73
|
}
|
|
69
|
-
compileTokens(tokens, outputVar = "
|
|
74
|
+
compileTokens(tokens, outputVar = "__exports.content", stopAt) {
|
|
70
75
|
const compiled = [];
|
|
71
76
|
tokens: while (tokens.length > 0) {
|
|
72
77
|
if (stopAt && tokens[0][0] === "tag" && stopAt.includes(tokens[0][1])) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "../_dnt.test_polyfills.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "../_dnt.test_polyfills.js";
|
package/esm/test/utils.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface TestOptions {
|
|
|
9
9
|
includes?: Record<string, string>;
|
|
10
10
|
}
|
|
11
11
|
export declare function test(options: TestOptions): Promise<void>;
|
|
12
|
+
export declare function testSync(options: TestOptions): void;
|
|
12
13
|
export declare class FileLoader implements Loader {
|
|
13
14
|
files: Record<string, string>;
|
|
14
15
|
constructor(files: Record<string, string>);
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"module": "./esm/mod.js",
|
|
3
3
|
"name": "ventojs",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.1",
|
|
5
5
|
"description": "🌬 A minimal but powerful template engine",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "github:oscarotero/vento",
|
|
8
|
-
"homepage": "https://
|
|
8
|
+
"homepage": "https://vento.js.org/",
|
|
9
9
|
"bugs": "https://github.com/oscarotero/vento/issues",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|