eleventy-plugin-vento 3.1.0 → 3.3.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 CHANGED
@@ -1,5 +1,51 @@
1
1
  # eleventy-plugin-vento
2
2
 
3
+ ## 3.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 387eb48: ⚠️ DEPRECATION NOTICE: Ignore tag `{{! ... }}` syntax is now deprecated and will be removed in 4.0.0.
8
+
9
+ This change comes in favor of using string literals to "pass through" tags without rendering them, like so:
10
+
11
+ ```diff
12
+ - {{! if condition }}
13
+ + {{ '{{ if condition }}' }}
14
+ do something
15
+ - {{! /if }}
16
+ - {{ '{{ /if }}' }}
17
+ ```
18
+
19
+ While more verbose, this change ensures that there isn't any ambiguity between a tag that needs ignoring and JS negation expressions:
20
+
21
+ ```hbs
22
+ <!-- These are functionally equivalent and can lead to collisions in rendering functionality -->
23
+
24
+ {{! conditionStatementToIgnore }}
25
+
26
+ {{ !conditionStatementToIgnore }}
27
+ ```
28
+
29
+ If an improved syntax for deferred rendering scenarios ever makes its way to Vento, it will be made available in this plugin.
30
+
31
+ ## 3.2.0
32
+
33
+ ### Minor Changes
34
+
35
+ - f9388e1: Further refine caching between Eleventy and Vento: commit ec02a765 deferred **all** caching to Vento and turned off Eleventy's cache (released in 3.1.0). This release allows Vento to cache only the templates that Eleventy can't cache (permalinks, includes, etc.), and Eleventy caches everything else.
36
+
37
+ A performance oversight has also been resolved by this change. Now, template functions are pre-compiled and saved by Eleventy, only running the template on render without having to go through a re-compile or get a template from the cache.
38
+
39
+ ### Patch Changes
40
+
41
+ - 693e1a2: Refactored the following features internally:
42
+
43
+ - Reverts 2c3548c1 for modularity: The compatibility check has moved back to `modules/utils.js`
44
+ - Revises caching further: Instead of deferring to Vento for everything, permalink templates and templates loaded by Vento (like includes) are cached by Vento. Everything else is cached by Eleventy.
45
+ - Rename some internal variables to help with readability (`_11ty.ctx => _11Ctx`)
46
+ - `DEBUG` key `Eleventy:Vento:Template` renamed to `Eleventy:Vento:Render`
47
+ - Declare engine functions separately and return as an object after everything has been declared.
48
+
3
49
  ## 3.1.0
4
50
 
5
51
  ### Minor Changes
package/package.json CHANGED
@@ -1,14 +1,11 @@
1
1
  {
2
2
  "name": "eleventy-plugin-vento",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "description": "Adds support for the Vento templating language to Eleventy.",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": ">=16"
8
8
  },
9
- "imports": {
10
- "#11ty-test": "./tests/_utils/eleventy-test.js"
11
- },
12
9
  "exports": {
13
10
  ".": {
14
11
  "import": "./src/index.js"
@@ -47,7 +44,7 @@
47
44
  "eslint-config-prettier": "9.1.0",
48
45
  "eslint-plugin-unicorn": "56.0.0",
49
46
  "globals": "15.11.0",
50
- "vitest": "2.1.3"
47
+ "vitest": "2.1.4"
51
48
  },
52
49
  "prettier": {
53
50
  "bracketSameLine": true,
@@ -56,7 +53,7 @@
56
53
  "trailingComma": "es5"
57
54
  },
58
55
  "scripts": {
59
- "test": "vitest --run",
56
+ "test": "vitest",
60
57
  "lint": "eslint '**/*.js'",
61
58
  "ci:version": "changeset version && pnpm install --no-frozen-lockfile",
62
59
  "ci:publish": "changeset publish",
package/readme.md CHANGED
@@ -17,6 +17,7 @@ An [Eleventy](https://11ty.dev/) plugin that adds support for [Vento](https://ve
17
17
  [Vento Plugins](#vento-plugins)<br>
18
18
  [Auto-Trimming Tags](#auto-trimming-tags)<br>
19
19
  [Ignoring Tags](#ignoring-tags)<br>
20
+ [Debugging](#debugging)<br>
20
21
 
21
22
  ## Installing
22
23
 
@@ -28,7 +29,7 @@ npm install eleventy-plugin-vento
28
29
 
29
30
  This plugin is ESM only and cannot be required from CommonJS.
30
31
 
31
- If you're using CommonJS and loading it asynchronously (ie `await import`), you will need at **minimum** Eleventy v3.0.0-alpha.15 which provides internal methods this plugin uses to get Eleventy stuff from the config API.
32
+ If you're using CommonJS and loading it asynchronously (ie `await import`), you will need at **minimum** Eleventy v3.0.0-alpha.15 which provides internal methods this plugin uses to retrieve Eleventy features from the config API.
32
33
 
33
34
  In your Eleventy config:
34
35
 
@@ -101,7 +102,7 @@ Eleventy: See [Filters](https://www.11ty.dev/docs/filters/)
101
102
  > [!NOTE]
102
103
  > Remember, Vento can print any return value from a Javascript expression, as well as [run arbitrary JavaScript](https://vento.js.org/syntax/javascript/) in templates through its `{{> ...}}` operator. In these cases, shortcodes may not be needed depending on your usage.
103
104
 
104
- Single and Paired Shortcodes added via Eleventy's `.addShortcode()`, `addAsyncShortcode()`, `addPairedShortcode()` or `.addAsyncPairedShortcode()` will be automatically loaded into Vento.
105
+ Single and Paired Shortcodes added via Eleventy's `.addShortcode()`, `.addAsyncShortcode()`, `.addPairedShortcode()` or `.addAsyncPairedShortcode()` will be automatically loaded into Vento.
105
106
 
106
107
  When using shortcodes in your templates, write them like any other Vento tag:
107
108
 
@@ -135,7 +136,7 @@ For paired shortcodes, the syntax is the same, just add a closing tag. Paired sh
135
136
 
136
137
  If you'd prefer to set shortcodes yourself (via a plugin or other method) or prevent Eleventy from loading shortcodes into Vento, set `shortcodes: false` and/or `pairedShortcodes: false` in the plugin options.
137
138
 
138
- > [!CAUTION]
139
+ > [!IMPORTANT]
139
140
  > While it's straightforward to load filters via a Vento plugin that appends filters to the filters object as `env.filters.[filter_name]()`, creating custom tags in Vento is more involved. It's highly advised to keep these two options enabled unless you know what you're doing.
140
141
 
141
142
  ### Relevant Documentation
@@ -200,6 +201,9 @@ Vento: See [Auto Trim Plugin](https://vento.js.org/plugins/auto-trim/).
200
201
 
201
202
  ## Ignoring Tags
202
203
 
204
+ > [!WARNING]
205
+ > This feature is now deprecated as of version 3.3.0. I added it primarily for my own use case, however it can cause more issues than it solves (conflict with `!` negation syntax in JS, ambiguity between ignoring a tag and negating an expression in a template). See the 3.3.0 release notes for more information.
206
+
203
207
  Exclusive to this plugin is the ability to skip processing a vento tag entirely and instead preserve the tag in the markup. This could be useful if you're doing some hybrid rendering and would like to defer certain tags from being processed until load time, so they can be rendered on the server.
204
208
 
205
209
  This feature is enabled through use of the `enableIgnoreTag` plugin option.
@@ -272,7 +276,7 @@ Like Eleventy, this plugin uses the [`debug`](https://www.npmjs.com/package/debu
272
276
 
273
277
  - `Eleventy:Vento:Setup` - Logs initial setup of the plugin, loading features, pre-page compile setup steps (like changing `page` and `eleventy` objects)
274
278
  - `Eleventy:Vento:Cache` - Logs updates to Vento's own internal cache, which is used in tandem with Eleventy's cache.
275
- - `Eleventy:Vento:Template` - Logs rendered templates and other template related actions
279
+ - `Eleventy:Vento:Render` - Logs when templates are rendered
276
280
 
277
281
  Because it is a child of the `Eleventy:` namespace, the following command will include output from this plugin as well:
278
282
 
package/src/engine.js CHANGED
@@ -3,11 +3,15 @@
3
3
  * a simple API for Eleventy to interface with.
4
4
  *
5
5
  * @typedef {{eleventy?: Record<string, unknown>, page?: Record<string, unknown>}} EleventyContext
6
- * @typedef {Context & Record<string, unknown>} EleventyData
6
+ * @typedef {EleventyContext & Record<string, unknown>} EleventyData
7
7
  * @typedef {(...args: unknown[]) => unknown} EleventyFunction
8
+ * @typedef {Record<string, EleventyFunction>} EleventyFunctionSet
8
9
  * @typedef {import('ventojs/src/environment.js').Environment} VentoEnvironment
9
10
  * @typedef {VentoEnvironment & {
10
- * utils: { _11ty: { ctx: Context, tags: Record<string, EleventyFunction> } }
11
+ * utils: {
12
+ * _11tyFns: { shortcodes: EleventyFunctionSet, pairedShortcodes: EleventyFunctionSet }
13
+ * _11tyCtx: EleventyContext
14
+ * }
11
15
  * }} EleventyVentoEnvironment
12
16
  */
13
17
 
@@ -16,80 +20,107 @@ import ventojs from 'ventojs';
16
20
 
17
21
  // Internal modules
18
22
  import { createVentoTag } from './modules/create-vento-tag.js';
19
- import { DEBUG, CONTEXT_DATA_KEYS } from './modules/utils.js';
23
+ import { CONTEXT_DATA_KEYS, DEBUG } from './modules/utils.js';
20
24
 
21
25
  /** @param {import('ventojs').Options} options */
22
26
  export function createVentoEngine(options) {
23
27
  /** @type {EleventyVentoEnvironment} */
24
28
  const env = ventojs(options);
25
- env.utils._11ty = { ctx: {}, shortcodes: {}, pairedShortcodes: {} };
29
+ env.utils._11tyFns = { shortcodes: {}, pairedShortcodes: {} };
30
+ env.utils._11tyCtx = {};
26
31
 
27
- return {
28
- cache: env.cache,
32
+ /** @param {EleventyData} newContext */
33
+ function setContext(newContext) {
34
+ if (env.utils._11tyCtx?.page?.inputPath === newContext?.page?.inputPath) {
35
+ return;
36
+ }
29
37
 
30
- /** @param {import('ventojs/src/environment.js').Plugin[]} plugins */
31
- loadPlugins(plugins) {
32
- for (const plugin of plugins) {
33
- env.use(plugin);
34
- }
35
- },
38
+ for (const K of CONTEXT_DATA_KEYS) {
39
+ env.utils._11tyCtx[K] = newContext[K];
40
+ }
36
41
 
37
- /** @param {PageData} newContext */
38
- loadContext(newContext) {
39
- // Loop through allowed keys and load those into the context
40
- for (const K of CONTEXT_DATA_KEYS) {
41
- env.utils._11ty.ctx[K] = newContext[K];
42
- }
43
- },
42
+ DEBUG.setup('Reload context, new context is: %o', env.utils._11tyCtx);
43
+ }
44
44
 
45
- /** @param {Record<string, EleventyFunction>} filters */
46
- loadFilters(filters) {
47
- for (const [name, fn] of Object.entries(filters)) {
48
- env.filters[name] = (...args) => fn.apply(env.utils._11ty.ctx, args);
49
- }
50
- },
45
+ /** @param {import('ventojs/src/environment.js').Plugin[]} plugins */
46
+ function loadPlugins(plugins) {
47
+ for (const plugin of plugins) {
48
+ env.use(plugin);
49
+ }
50
+ }
51
51
 
52
- /** @param {Record<string, EleventyFunction>} shortcodes */
53
- loadShortcodes(shortcodes) {
54
- for (const [name, fn] of Object.entries(shortcodes)) {
55
- env.utils._11ty.shortcodes[name] = fn;
56
- env.tags.push(createVentoTag({ name, group: 'shortcodes' }));
57
- }
58
- },
52
+ /** @param {Record<string, EleventyFunction>} filters */
53
+ function loadFilters(filters) {
54
+ for (const [name, fn] of Object.entries(filters)) {
55
+ env.filters[name] = fn.bind(env.utils._11tyCtx);
56
+ }
57
+ }
59
58
 
60
- /** @param {Record<string, EleventyFunction>} pairedShortcodes */
61
- loadPairedShortcodes(pairedShortcodes) {
62
- for (const [name, fn] of Object.entries(pairedShortcodes)) {
63
- env.utils._11ty.pairedShortcodes[name] = fn;
64
- env.tags.push(createVentoTag({ name, group: 'pairedShortcodes' }));
65
- }
66
- },
67
-
68
- /** @param {{ source: string, data: PageData, file?: string }} input */
69
- async process(input) {
70
- // Reload context
71
- this.loadContext(input.data);
72
- DEBUG.setup('Reloaded Eleventy/Vento context. New value: %o', env.utils._11ty.ctx);
73
-
74
- // Retrieve the template from the cache
75
- let template = env.cache.get(input.file);
76
-
77
- if (template?.source === input.source) {
78
- DEBUG.cache('Cache HIT for `%s`, used precompiled template', input.file);
79
- } else {
80
- template = env.compile(input.source, input.file);
81
-
82
- if (input.file) {
83
- DEBUG.cache('Cache MISS for `%s`, compiled new template', input.file);
84
- env.cache.set(input.file, template);
85
- }
59
+ /** @param {Record<string, EleventyFunction>} shortcodes */
60
+ function loadShortcodes(shortcodes) {
61
+ for (const [name, fn] of Object.entries(shortcodes)) {
62
+ env.utils._11tyFns.shortcodes[name] = fn;
63
+ env.tags.push(createVentoTag({ name, group: 'shortcodes' }));
64
+ }
65
+ }
66
+
67
+ /** @param {Record<string, EleventyFunction>} pairedShortcodes */
68
+ function loadPairedShortcodes(pairedShortcodes) {
69
+ for (const [name, fn] of Object.entries(pairedShortcodes)) {
70
+ env.utils._11tyFns.pairedShortcodes[name] = fn;
71
+ env.tags.push(createVentoTag({ name, group: 'pairedShortcodes' }));
72
+ }
73
+ }
74
+
75
+ /**
76
+ * @param {string} source
77
+ * @param {string} file
78
+ * @param {boolean} [useVentoCache=true]
79
+ */
80
+ function getTemplateFunction(source, file, useVentoCache = true) {
81
+ // Attempt to retrieve template function from cache
82
+ let template = env.cache.get(file);
83
+
84
+ if (template?.source === source) {
85
+ DEBUG.cache('Cache HIT for `%s`, used precompiled template', file);
86
+ } else {
87
+ template = env.compile(source, file);
88
+
89
+ DEBUG.cache(
90
+ `Cache MISS for \`%s\`, compiled new template:\nUse Vento cache: %o\n\n${template}`,
91
+ file,
92
+ useVentoCache
93
+ );
94
+ if (useVentoCache) {
95
+ env.cache.set(file, template);
86
96
  }
97
+ }
98
+
99
+ return template;
100
+ }
87
101
 
88
- // Run the template
89
- DEBUG.template(template.code);
90
- const { content } = await template(input.data);
102
+ /**
103
+ * @param {import('ventojs/src/environment.js').Template} template
104
+ * @param {EleventyData} data
105
+ * @param {string} from
106
+ */
107
+ async function render(template, data, from) {
108
+ // Load new context
109
+ setContext(data);
91
110
 
92
- return content;
93
- },
111
+ // Render template
112
+ DEBUG.render('Rendering `%s`', from);
113
+ const { content } = await template(data);
114
+ return content;
115
+ }
116
+
117
+ return {
118
+ cache: env.cache,
119
+ loadPlugins,
120
+ loadFilters,
121
+ loadShortcodes,
122
+ loadPairedShortcodes,
123
+ getTemplateFunction,
124
+ render,
94
125
  };
95
126
  }
package/src/index.js CHANGED
@@ -29,7 +29,7 @@ import autotrimPlugin, { defaultTags as autotrimDefaultTags } from 'ventojs/plug
29
29
  // Local modules
30
30
  import { createVentoEngine } from './engine.js';
31
31
  import { ignoreTagPlugin } from './modules/ignore-tag.js';
32
- import { DEBUG, PERMALINK_PREFIX, REQUIRED_API_METHODS } from './modules/utils.js';
32
+ import { DEBUG, runCompatibilityCheck } from './modules/utils.js';
33
33
 
34
34
  /**
35
35
  * @param {import('@11ty/eleventy').UserConfig} eleventyConfig
@@ -37,18 +37,7 @@ import { DEBUG, PERMALINK_PREFIX, REQUIRED_API_METHODS } from './modules/utils.j
37
37
  */
38
38
  export function VentoPlugin(eleventyConfig, userOptions) {
39
39
  DEBUG.setup('Initializing eleventy-plugin-vento');
40
- DEBUG.setup('Run compatibility check');
41
- for (const [method, version] of REQUIRED_API_METHODS) {
42
- if (!eleventyConfig?.[method]) {
43
- console.error(
44
- '[eleventy-plugin-vento] Plugin compatibility error:',
45
- `\`${method}\``,
46
- 'not found. Please use Eleventy',
47
- version,
48
- 'or later.'
49
- );
50
- }
51
- }
40
+ runCompatibilityCheck(eleventyConfig);
52
41
 
53
42
  /** @type {VentoPluginOptions} */
54
43
  const options = {
@@ -69,6 +58,14 @@ export function VentoPlugin(eleventyConfig, userOptions) {
69
58
 
70
59
  DEBUG.setup('Merged default and user config: %O', options);
71
60
 
61
+ if (options.ignoreTag) {
62
+ console.error(
63
+ '[eleventy-plugin-vento] DEPRECATION NOTICE:',
64
+ 'Plugin was called with `ignoreTag` option, which has been deprecated.',
65
+ 'For more information, see: https://github.com/noelforte/eleventy-plugin-vento/blob/main/readme.md#ignoring-tags'
66
+ );
67
+ }
68
+
72
69
  // Get list of filters, shortcodes and paired shortcodes
73
70
  const filters = eleventyConfig.getFilters();
74
71
  DEBUG.setup('Reading filters from Eleventy: %o', filters);
@@ -153,26 +150,36 @@ export function VentoPlugin(eleventyConfig, userOptions) {
153
150
  outputFileExtension: 'html',
154
151
  read: true,
155
152
 
156
- // Main compile function
157
- async compile(source, file) {
158
- file = path.normalize(file);
159
- return async (data) => await engine.process({ source, data, file });
153
+ compile(inputContent, inputPath) {
154
+ // Normalize input path
155
+ inputPath = path.normalize(inputPath);
156
+
157
+ // Retrieve the template function
158
+ const template = engine.getTemplateFunction(inputContent, inputPath, false);
159
+
160
+ // Return a render function
161
+ return async (data) => await engine.render(template, data, inputPath);
160
162
  },
161
163
 
162
164
  compileOptions: {
163
- // Defer all caching to Vento
164
- cache: false,
165
165
  // Custom permalink compilation
166
- permalink(source, file) {
166
+ permalink(permalinkContent, inputPath) {
167
167
  // Short circuit if input isn't a string and doesn't look like a vento template
168
- if (typeof source === 'string' && /\{\{\s+.+\s+\}\}/.test(source)) {
169
- file = PERMALINK_PREFIX + path.normalize(file);
170
- return async (data) => engine.process({ source, data, file });
168
+ if (typeof permalinkContent === 'string' && /\{\{\s+.+\s+\}\}/.test(permalinkContent)) {
169
+ // Normalize input path
170
+ inputPath = 'Permalink::' + path.normalize(inputPath);
171
+
172
+ // Retrieve the template function
173
+ const template = engine.getTemplateFunction(permalinkContent, inputPath);
174
+
175
+ // Return a render function
176
+ return async (data) => await engine.render(template, data, inputPath);
171
177
  }
172
178
 
173
- return source;
179
+ return permalinkContent;
174
180
  },
175
181
  },
176
182
  });
183
+
177
184
  DEBUG.setup('eleventy-plugin-vento initialized');
178
185
  }
@@ -12,8 +12,8 @@ export function createVentoTag(options) {
12
12
  if (!code.startsWith(options.name)) return;
13
13
 
14
14
  // Declare helper variables for repeated strings in template
15
- const fn = `__env.utils._11ty.${options.group}.${options.name}`;
16
- const ctx = '__env.utils._11ty.ctx';
15
+ const fn = `__env.utils._11tyFns.${options.group}.${options.name}`;
16
+ const ctx = '__env.utils._11tyCtx';
17
17
  const args = [code.replace(options.name, '').trim()];
18
18
 
19
19
  const varname = output.startsWith('__shortcode_content')
@@ -4,7 +4,7 @@ const debugBaseNamespace = 'Eleventy:Vento';
4
4
  export const DEBUG = {
5
5
  setup: createDebugger(`${debugBaseNamespace}:Setup`),
6
6
  cache: createDebugger(`${debugBaseNamespace}:Cache`),
7
- template: createDebugger(`${debugBaseNamespace}:Template`),
7
+ render: createDebugger(`${debugBaseNamespace}:Render`),
8
8
  };
9
9
 
10
10
  // Project-wide constants
@@ -17,4 +17,18 @@ export const REQUIRED_API_METHODS = [
17
17
 
18
18
  export const CONTEXT_DATA_KEYS = ['page', 'eleventy'];
19
19
 
20
- export const PERMALINK_PREFIX = 'EleventyVentoPermalink:';
20
+ // Helper functions
21
+ export function runCompatibilityCheck(config) {
22
+ DEBUG.setup('Run compatibility check');
23
+ for (const [method, version] of REQUIRED_API_METHODS) {
24
+ if (!config?.[method]) {
25
+ console.error(
26
+ '[eleventy-plugin-vento] Plugin compatibility error:',
27
+ `\`${method}\``,
28
+ 'not found. Please use Eleventy',
29
+ version,
30
+ 'or later.'
31
+ );
32
+ }
33
+ }
34
+ }