ventojs 0.7.2 → 0.8.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/esm/mod.d.ts CHANGED
@@ -3,5 +3,6 @@ import { Loader } from "./src/loader.js";
3
3
  export interface Options {
4
4
  includes?: string | Loader;
5
5
  dataVarname?: string;
6
+ autoescape?: boolean;
6
7
  }
7
8
  export default function (options?: Options): Environment;
package/esm/mod.js CHANGED
@@ -10,7 +10,9 @@ import layoutTag from "./plugins/layout.js";
10
10
  import functionTag from "./plugins/function.js";
11
11
  import importTag from "./plugins/import.js";
12
12
  import exportTag from "./plugins/export.js";
13
+ import echoTag from "./plugins/echo.js";
13
14
  import escape from "./plugins/escape.js";
15
+ import unescape from "./plugins/unescape.js";
14
16
  export default function (options = {}) {
15
17
  const loader = typeof options.includes === "object"
16
18
  ? options.includes
@@ -18,6 +20,7 @@ export default function (options = {}) {
18
20
  const env = new Environment({
19
21
  loader,
20
22
  dataVarname: options.dataVarname || "it",
23
+ autoescape: options.autoescape || false,
21
24
  });
22
25
  // Register basic plugins
23
26
  env.use(ifTag());
@@ -29,6 +32,8 @@ export default function (options = {}) {
29
32
  env.use(functionTag());
30
33
  env.use(importTag());
31
34
  env.use(exportTag());
35
+ env.use(echoTag());
32
36
  env.use(escape());
37
+ env.use(unescape());
33
38
  return env;
34
39
  }
@@ -0,0 +1,2 @@
1
+ import type { Environment } from "../src/environment.js";
2
+ export default function (): (env: Environment) => void;
@@ -0,0 +1,13 @@
1
+ export default function () {
2
+ return (env) => {
3
+ env.tags.push(setTag);
4
+ };
5
+ }
6
+ function setTag(env, code, output, tokens) {
7
+ if (!code.startsWith("echo ")) {
8
+ return;
9
+ }
10
+ const value = code.replace(/^echo\s+/, "");
11
+ const val = env.compileFilters(tokens, value, env.options.autoescape);
12
+ return `${output} += ${val};`;
13
+ }
@@ -3,7 +3,7 @@ export default function () {
3
3
  env.tags.push(includeTag);
4
4
  };
5
5
  }
6
- function includeTag(_env, code, output) {
6
+ function includeTag(env, code, output, tokens) {
7
7
  if (!code.startsWith("include ")) {
8
8
  return;
9
9
  }
@@ -17,6 +17,6 @@ function includeTag(_env, code, output) {
17
17
  {...__data${data ? `, ${data}` : ""}},
18
18
  __file
19
19
  );
20
- ${output} += __tmp.content;
20
+ ${output} += ${env.compileFilters(tokens, "__tmp.content")};
21
21
  }`;
22
22
  }
@@ -24,7 +24,7 @@ function layoutTag(env, code, output, tokens) {
24
24
  tokens.shift();
25
25
  compiled.push(`${varname} = ${compiledFilters};`);
26
26
  compiled.push(`__tmp = await __env.run(${file},
27
- {...__data${data ? `, ${data}` : ""}, content: ${varname}},
27
+ {...__data${data ? `, ${data}` : ""}, content: ${env.compileFilters(tokens, varname)}},
28
28
  __file
29
29
  );
30
30
  ${output} += __tmp.content;`);
@@ -0,0 +1,2 @@
1
+ import type { Environment } from "../src/environment.js";
2
+ export default function (): (env: Environment) => void;
@@ -0,0 +1,16 @@
1
+ export default function () {
2
+ return (env) => {
3
+ env.filters.unescape = unescape;
4
+ };
5
+ }
6
+ const unescapeMap = {
7
+ "&": "&",
8
+ "&lt;": "<",
9
+ "&gt;": ">",
10
+ "&quot;": '"',
11
+ "&#39;": "'",
12
+ "&#x60;": "`",
13
+ };
14
+ function unescape(str) {
15
+ return str.replace(/(&amp;|&lt;|&gt;|&quot;|&#39;|&#x60;)/g, (match) => unescapeMap[match]);
16
+ }
@@ -19,7 +19,8 @@ export type Filter = (...args: any[]) => any;
19
19
  export type Plugin = (env: Environment) => void;
20
20
  export interface Options {
21
21
  loader: Loader;
22
- dataVarname?: string;
22
+ dataVarname: string;
23
+ autoescape: boolean;
23
24
  }
24
25
  export declare class Environment {
25
26
  cache: Map<string, Template>;
@@ -36,5 +37,5 @@ export declare class Environment {
36
37
  compile(source: string, path?: string, defaults?: Record<string, unknown>, sync?: true): TemplateSync;
37
38
  load(file: string, from?: string): Promise<Template>;
38
39
  compileTokens(tokens: Token[], outputVar?: string, stopAt?: string[]): string[];
39
- compileFilters(tokens: Token[], output: string): string;
40
+ compileFilters(tokens: Token[], output: string, autoescape?: boolean): string;
40
41
  }
@@ -67,9 +67,9 @@ export class Environment {
67
67
  if (!this.cache.has(path)) {
68
68
  const { source, data } = await this.options.loader.load(path);
69
69
  const template = this.compile(source, path, data);
70
- this.cache.set(file, template);
70
+ this.cache.set(path, template);
71
71
  }
72
- return this.cache.get(file);
72
+ return this.cache.get(path);
73
73
  }
74
74
  compileTokens(tokens, outputVar = "__exports.content", stopAt) {
75
75
  const compiled = [];
@@ -81,10 +81,8 @@ export class Environment {
81
81
  if (type === "comment") {
82
82
  continue;
83
83
  }
84
- if (type === "string" || type === "raw") {
85
- compiled.push(`${outputVar} += \`${code
86
- .replaceAll("`", "\\`")
87
- .replaceAll("${", "\\${")}\`;`);
84
+ if (type === "string") {
85
+ compiled.push(`${outputVar} += ${JSON.stringify(code)};`);
88
86
  continue;
89
87
  }
90
88
  if (type === "tag") {
@@ -96,7 +94,7 @@ export class Environment {
96
94
  }
97
95
  }
98
96
  // Unknown tag, just print it
99
- const expression = this.compileFilters(tokens, code);
97
+ const expression = this.compileFilters(tokens, code, this.options.autoescape);
100
98
  compiled.push(`${outputVar} += (${expression}) ?? "";`);
101
99
  continue;
102
100
  }
@@ -104,7 +102,8 @@ export class Environment {
104
102
  }
105
103
  return compiled;
106
104
  }
107
- compileFilters(tokens, output) {
105
+ compileFilters(tokens, output, autoescape = false) {
106
+ let unescaped = false;
108
107
  while (tokens.length > 0 && tokens[0][0] === "filter") {
109
108
  const [, code] = tokens.shift();
110
109
  const match = code.match(/^(await\s+)?([\w.]+)(?:\((.*)\))?$/);
@@ -123,10 +122,17 @@ export class Environment {
123
122
  }
124
123
  }
125
124
  else {
125
+ if (name === "unescape") {
126
+ unescaped = true;
127
+ }
126
128
  // It's a filter (e.g. filters.upper())
127
129
  output = `${isAsync ? "await " : ""}__env.filters.${name}(${output}${args ? `, ${args}` : ""})`;
128
130
  }
129
131
  }
132
+ // Escape by default
133
+ if (autoescape && !unescaped) {
134
+ output = `__env.filters.escape(${output})`;
135
+ }
130
136
  return output;
131
137
  }
132
138
  }
@@ -1,4 +1,4 @@
1
- export type TokenType = "string" | "tag" | "filter" | "comment" | "raw";
1
+ export type TokenType = "string" | "tag" | "filter" | "comment";
2
2
  export type Token = [TokenType, string];
3
3
  export default function tokenize(source: string): Token[];
4
4
  /**
@@ -17,15 +17,6 @@ export default function tokenize(source) {
17
17
  break;
18
18
  }
19
19
  source = source.slice(index);
20
- // Check if it's a {{raw}} tag
21
- const raw = parseRawTag(source);
22
- if (raw) {
23
- const rawCode = source.slice(raw[0], raw[1]);
24
- tokens.push(["raw", rawCode]);
25
- source = source.slice(raw[2]);
26
- type = "string";
27
- continue;
28
- }
29
20
  type = source.startsWith("{{#") ? "comment" : "tag";
30
21
  continue;
31
22
  }
@@ -44,6 +35,7 @@ export default function tokenize(source) {
44
35
  if (type === "tag") {
45
36
  const indexes = parseTag(source);
46
37
  const lastIndex = indexes.length - 1;
38
+ let tag;
47
39
  indexes.reduce((prev, curr, index) => {
48
40
  let code = source.slice(prev, curr - 2);
49
41
  // Tag
@@ -59,7 +51,8 @@ export default function tokenize(source) {
59
51
  code = code.slice(0, -1);
60
52
  trimNext = true;
61
53
  }
62
- tokens.push([type, code.trim()]);
54
+ tag = [type, code.trim()];
55
+ tokens.push(tag);
63
56
  return curr;
64
57
  }
65
58
  // Right trim
@@ -73,6 +66,16 @@ export default function tokenize(source) {
73
66
  });
74
67
  source = source.slice(indexes[indexes.length - 1]);
75
68
  type = "string";
69
+ // Search the closing echo tag {{ /echo }}
70
+ if (tag?.[1] === "echo") {
71
+ const end = source.match(/{{\s*\/echo\s*}}/);
72
+ if (!end) {
73
+ throw new Error("Unclosed echo tag");
74
+ }
75
+ const rawCode = source.slice(0, end.index);
76
+ tag[1] = `echo ${JSON.stringify(rawCode)}`;
77
+ source = source.slice(Number(end.index) + end[0].length);
78
+ }
76
79
  continue;
77
80
  }
78
81
  }
@@ -180,18 +183,3 @@ export function parseTag(source) {
180
183
  }
181
184
  throw new Error("Unclosed tag");
182
185
  }
183
- function parseRawTag(source) {
184
- const startResult = source.match(/^{{\s*raw\s*}}/);
185
- if (!startResult) {
186
- return;
187
- }
188
- const endResult = source.match(/{{\s*\/raw\s*}}/);
189
- if (!endResult) {
190
- throw new Error("Unclosed raw tag");
191
- }
192
- return [
193
- startResult[0].length,
194
- endResult.index,
195
- endResult.index + endResult[0].length,
196
- ];
197
- }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "module": "./esm/mod.js",
3
3
  "name": "ventojs",
4
- "version": "0.7.2",
4
+ "version": "0.8.0",
5
5
  "description": "🌬 A minimal but powerful template engine",
6
6
  "license": "MIT",
7
7
  "repository": "github:oscarotero/vento",