Skip to content

Commit

Permalink
rewrite plugin to implement single and paired shortcodes as Vento tags
Browse files Browse the repository at this point in the history
fixes #9, fixes #14
  • Loading branch information
noelforte committed Sep 19, 2024
1 parent 3d57329 commit 03c2a89
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 33 deletions.
38 changes: 38 additions & 0 deletions .changeset/1-eleventy-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
'eleventy-plugin-vento': major
---

Adds complete support for Eleventy shortcodes and filters within Vento. Shortcodes are now loaded by default as Vento tags and will no longer be exposed as functions in your page data.

The implementation of single shortcodes remains largely similar, just replace function-like calls with Vento tags.

```diff
- {{ nametag('Noel', 'Forte') }}
+ {{ nametag 'Noel', 'Forte' }}
```

Of course, the big news is that **paired** shortcodes are now officially supported by this plugin!! Prior to this release, paired shortcodes were exposed just like regular shortcodes, but were plain JS functions. With this release you can now use paired shortcodes just like any other tag.

```hbs
<!-- Before: everything is jammed into the function call :( -->
{{ blockquote("<p>Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world.</p>\n<p>It is a way I have of driving off the spleen and regulating the circulation.</p>", "Herman Melville", "1851") }}
<!-- After: opening and closing tags with arguments separated :) -->
{{ blockquote 'Herman Melville', '1851' }}
<p>Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world.</p>
<p>It is a way I have of driving off the spleen and regulating the circulation.</p>
{{ /blockquote }}
```

This functionality became possible with the addition of the `getShortcodes()` and `getPairedShortcodes()` functions that were added in [Eleventy v3.0.0-alpha.15](https:/11ty/eleventy/releases/tag/v3.0.0-alpha.15). Because of dependence on these features, Eleventy 3.0.0-alpha.15 is now the **minimum required version** in order to use this plugin.

Because these changes removed direct dependence on Eleventy JavaScript functions, the `addHelpers` option has been replaced with 3 new options: `shortcodes`, `pairedShortcodes` and `filters`. **All of them are enabled by default** but can be disabled in your plugin config like so.

```diff
eleventyConfig.addPlugin(VentoPlugin, {
- addHelpers: false,
+ shortcodes: false,
+ pairedShortcodes: false,
+ filters: false,
});
```
49 changes: 32 additions & 17 deletions src/engine.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
/**
* @file Class to facilitate ventojs processing
*
* @typedef {{eleventy?: Record<string, unknown>, page?: Record<string, unknown>}} Context
* @typedef {Context & {[K: string]: unknown}} PageData
* @typedef {{eleventy?: Record<string, unknown>, page?: Record<string, unknown>}} EleventyContext
* @typedef {Context & Record<string, unknown>} EleventyData
* @typedef {(...args: unknown[]) => unknown} EleventyFunction
* @typedef {import('ventojs/src/environment.js').Environment} VentoEnvironment
* @typedef {VentoEnvironment & { _11ty: { ctx: Context, tags: Record<string, EleventyFunction> }}} EleventyVentoEnvironment
*/

// External library
import vento from 'ventojs';
import ventojs from 'ventojs';

// Local modules
import { createVentoFilter } from './modules/create-filter.js';
// Internal modules
import { createVentoTag } from './modules/create-vento-tag.js';

// TODO: Update this if it becomes possible to import `augmentKeys` from Eleventy.
const DATA_KEYS = ['page', 'eleventy'];

export class VentoEngine {
/** @type {Context} */
#context = {};
#env;

/** @param {import('ventojs').Options} options */
/** @param {import('ventojs/src/environment.js').Options} options */
constructor(options) {
this.#env = vento(options);
/** Initialize vento @type {EleventyVentoEnvironment} */
this.#env = ventojs(options);
this.#env._11ty = { ctx: {}, functions: {} };
}

/** @param {string?} key */
Expand All @@ -36,17 +39,29 @@ export class VentoEngine {
}
}

/** @param {Record<string, import('ventojs/src/environment.js').Filter>} filters */
/** @param {PageData} newContext */
loadContext(newContext) {
// Loop through allowed keys and load those into the context
for (const key of DATA_KEYS) {
this.#env._11ty.ctx[key] = newContext[key];
}
}

/** @param {Record<string, EleventyFunction>} filters */
loadFilters(filters) {
for (const name in filters) {
this.#env.filters[name] = createVentoFilter(filters[name], this.#context);
for (const [name, fn] of Object.entries(filters)) {
this.#env.filters[name] = (...args) => fn.apply(this.#env._11ty.ctx, args);
}
}

/** @param {PageData} newContext */
loadContext(newContext) {
for (const key of DATA_KEYS) {
this.#context[key] = newContext[key];
/**
* @param {Record<string, EleventyFunction>} shortcodes
* @param {boolean} paired
*/
loadShortcodes(shortcodes, paired = false) {
for (const [name, fn] of Object.entries(shortcodes)) {
this.#env._11ty.functions[name] = fn;
this.#env.tags.push(createVentoTag(name, paired));
}
}

Expand All @@ -63,7 +78,7 @@ export class VentoEngine {
const result = await this.#env.runString(content, data, path);

// Clear the cache for this path if the input doesn't match
if (data.page?.rawInput !== content) this.#env.cache.clear();
if (data.page?.rawInput !== content) this.#env.cache.clear(path);

return result.content;
}
Expand Down
25 changes: 19 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,30 @@ export function VentoPlugin(eleventyConfig, userOptions) {
options.plugins.push(autotrimPlugin({ tags: [...tagSet] }));
}

// Add preserve tag plugin if enabled
if (options.ignoreTag) options.plugins.push(ignoreTagPlugin);
// Add ignore tag plugin if enabled
if (options.ignoreTag) {
options.plugins.push(ignoreTagPlugin);
}

// Create the vento engine instance
const vento = new VentoEngine(options.ventoOptions);

vento.emptyCache(); // Ensure cache is empty
vento.loadPlugins(options.plugins); // Load plugin functions
// Ensure cache is empty
vento.emptyCache();

// Load plugins
vento.loadPlugins(options.plugins);

if (options.filters) vento.loadFilters(eleventyConfig.getFilters());
if (options.shortcodes) vento.loadShortcodes(eleventyConfig.getShortcodes());
// Add filters, single and paired shortcodes if enabled
if (options.filters) {
vento.loadFilters(eleventyConfig.getFilters());
}
if (options.shortcodes) {
vento.loadShortcodes(eleventyConfig.getShortcodes(), false);
}
if (options.pairedShortcodes) {
vento.loadShortcodes(eleventyConfig.getPairedShortcodes(), true);
}

// Add vto as a template format
eleventyConfig.addTemplateFormats('vto');
Expand Down
10 changes: 0 additions & 10 deletions src/modules/create-filter.js

This file was deleted.

54 changes: 54 additions & 0 deletions src/modules/create-vento-tag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* @file Helper function that creates vento tags from eleventy functions
*
* @param {string} name
* @param {boolean} paired
*/

export function createVentoTag(name, paired) {
/** @type {import("ventojs/src/environment.js").Tag} */
const tag = (env, code, output, tokens) => {
if (!code.startsWith(name)) return;

// Declare helper object path strings
const fn = `__env._11ty.functions.${name}`;
const ctx = '__env._11ty.ctx';
let args = [code.replace(name, '').trim()];

const varname = output.startsWith('__shortcode_content')
? `${output}_precomp`
: '__shortcode_content';

// Create an array to hold compiled template code
const compiled = [];

if (paired) {
args.unshift(env.compileFilters(tokens, varname, env.options.autoescape));
compiled.push(
`{ // START paired-${name}`,
`let ${varname} = "";`,
...env.compileTokens(tokens, varname, [`/${name}`])
);
if (tokens.length > 0 && (tokens[0][0] !== 'tag' || tokens[0][1] !== `/${name}`)) {
throw new Error(`Missing closing tag for ${name} tag: ${code}`);
}
tokens.shift();
}

// Compile arguments into a string
args = args.some(Boolean) ? `, ${args.filter(Boolean).join(', ')}` : '';

compiled.push(
`{ // START ${name}`,
`const __shortcode_result = await ${fn}.call(${ctx + args});`,
`${output} += ${env.compileFilters(tokens, '__shortcode_result', env.options.autoescape)}`,
`} // END ${name}`
);

if (paired) compiled.push(`} // END paired-${name}`);

return compiled.join('\n');
};

return Object.defineProperty(tag, 'name', { value: paired ? `${name}PairedTag` : `${name}Tag` });
}

0 comments on commit 03c2a89

Please sign in to comment.