-
Notifications
You must be signed in to change notification settings - Fork 2
refactor(shortcodes)!: replace shortcodes with functions and filters #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,23 +21,27 @@ This package adds a `.twig` template engine to Eleventy that lets you use the pu | |
|
||
## Features | ||
|
||
- **Built-in Shortcodes**: Uses [`twig.extendFunction()`](https://twig.symfony.com/doc/2.x/advanced.html) to extend Twig | ||
- **Twig Namespaces**: Uses `Twig` built-in loaders to provide [namespaces](https://twig.symfony.com/doc/3.x/api.html#built-in-loaders) | ||
- **Responsive Images**: Uses [`@11ty/eleventy-img`](https://github.com/11ty/eleventy-img) plugin to autogenerate responsive images | ||
- **Hashed Assets**: If you have generated a manifest (e.g. with [`@factorial/eleventy-plugin-fstack`](https://github.com/factorial-io/eleventy-plugin-fstack)) you could let Eleventy replace unhashed assets like `css/js` automatically with their hashed versions | ||
- Use **functions** and [`twig.extendFunction()`](https://twig.symfony.com/doc/2.x/advanced.html#functions) to extend Twig with custom functions | ||
- Use **filters** and [`twig.extendFilter()`](https://twig.symfony.com/doc/2.x/advanced.html#filters) to extend Twig with custom filters | ||
- Uses `Twig` built-in loaders to provide **[namespaces](https://twig.symfony.com/doc/3.x/api.html#built-in-loaders)** | ||
|
||
Furthermore please take a look at some of the **sample implementations** for functions and filters to showcase how [Eleventy](https://www.11ty.dev/docs/credits/) and [Twig.js](https://github.com/twigjs/twig.js/) can work together: | ||
|
||
- **[Responsive Images](lib/functions/README.md)**: Uses [`@11ty/eleventy-img`](https://github.com/11ty/eleventy-img) plugin to autogenerate responsive images | ||
- **[Hashed Assets](lib/functions/README.md)**: If you have generated a manifest (e.g. with [`@factorial/eleventy-plugin-fstack`](https://github.com/factorial-io/eleventy-plugin-fstack)) you could let Eleventy replace unhashed assets like `css/js` automatically with their hashed versions | ||
Comment on lines
+30
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can link directly to the headings with:
|
||
|
||
## Getting Started | ||
|
||
Install the latest `@factorial/eleventy-plugin-twig` release as well as `twig` and optionally `@11ty/eleventy-img` as node modules with `yarn`: | ||
Install the latest `@factorial/eleventy-plugin-twig` release as well as `twig` as node modules with `yarn`: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo: |
||
|
||
```sh | ||
yarn add --dev @factorial/eleventy-plugin-twig @11ty/eleventy-img twig | ||
yarn add --dev @factorial/eleventy-plugin-twig twig | ||
``` | ||
|
||
or `npm`: | ||
|
||
```sh | ||
npm install --save-dev @factorial/eleventy-plugin-twig @11ty/eleventy-img twig | ||
npm install --save-dev @factorial/eleventy-plugin-twig twig | ||
``` | ||
|
||
## Usage | ||
|
@@ -58,49 +62,27 @@ As mentioned in the `eleventyConfig.addPlugin(eleventy-plugin-twig, USER_OPTIONS | |
|
||
```js | ||
/** | ||
* @typedef {object} ELEVENTY_DIRECTORIES | ||
* @property {string} input - Eleventy template path | ||
* @property {string} output - Eleventy build path | ||
* @property {string} [includes] - Eleventy includes path relativ to input | ||
* @property {string} [layouts] - Eleventy separate layouts path relative to input | ||
* @property {string} [watch] - add more watchTargets to Eleventy | ||
*/ | ||
|
||
/** | ||
* @typedef {object} ASSETS | ||
* @property {string} root - path to the root folder from projects root (e.g. src) | ||
* @property {string} base - base path for assets relative to the root folder (e.g. assets) | ||
* @property {string} css - path to the css folder relative to the base (e.g. css) | ||
* @property {string} js - path to the js folder relative to the base (e.g. js) | ||
* @property {string} images - path to the image folder relative to the base (e.g. images) | ||
*/ | ||
|
||
/** | ||
* @typedef {object} IMAGES | ||
* @property {Array<number>} widths - those image sizes will be autogenereated / aspect-ratio will be respected | ||
* @property {Array<import("@11ty/eleventy-img").ImageFormatWithAliases>} formats - jpeg/avif/webp/png/gif | ||
* @property {string} additionalAttributes - those attributes will be added to the image element | ||
* @typedef {object} FUNCTION | ||
* @property {string} symbol - method name for twig to register | ||
* @property {function(import("@11ty/eleventy").UserConfig, USER_OPTIONS, ...* ):any} callback - callback which is called by twig | ||
*/ | ||
|
||
/** | ||
* @typedef {object} SHORTCODE | ||
* @property {string} symbol - method name for twig to register | ||
* @property {function(import("@11ty/eleventy").UserConfig, USER_OPTIONS, ...* ):any} callback - callback which is called by twig | ||
* @typedef {object} FILTER | ||
* @property {string} symbol - filter name for twig to register | ||
* @property {function(import("@11ty/eleventy").UserConfig, USER_OPTIONS, ...* ):any} callback - callback which is invoked by the filter | ||
*/ | ||
|
||
/** | ||
* @typedef {object} TWIG_OPTIONS | ||
* @property {SHORTCODE[]} [shortcodes] - array of shortcodes | ||
* @property {Function[]} [functions] - array of functions to extend twig | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
* @property {FILTER[]} [filter] - array of filter to extend twig | ||
* @property {boolean} [cache] - you could enable the twig cache for whatever reasons here | ||
* @property {Object<string, string>} [namespaces] - define namespaces to include/extend templates more easily by "@name" | ||
*/ | ||
|
||
/** | ||
* @typedef {object} USER_OPTIONS | ||
* @property {string} mixManifest - path to the mixManifest file relative to the build folder | ||
* @property {ASSETS} [assets] - where to find all the assets relative to the build folder | ||
* @property {IMAGES} [images] - options for Eleventys image processing | ||
* @property {ELEVENTY_DIRECTORIES} dir - Eleventy folder decisions | ||
* @property {TWIG_OPTIONS} [twig] - twig options | ||
*/ | ||
``` | ||
|
@@ -114,72 +96,26 @@ You could use this as a starting point and customize to your individual needs: | |
const USER_OPTIONS = { | ||
twig: { | ||
namespaces: { | ||
elements: "src/include/elements", | ||
patterns: "src/include/patterns", | ||
"template-components": "src/include/template-components", | ||
templates: "src/include/templates", | ||
// for example: | ||
// elements: "src/include/elements", | ||
// patterns: "src/include/patterns", | ||
// "template-components": "src/include/template-components", | ||
// templates: "src/include/templates", | ||
}, | ||
}, | ||
mixManifest: "mix-manifest.json", | ||
assets: { | ||
root: "src", | ||
base: "assets", | ||
css: "css", | ||
js: "js", | ||
images: "images", | ||
}, | ||
images: { | ||
widths: [300, 600, 900], | ||
formats: ["webp", "avif", "jpeg"], | ||
additionalAttributes: "", | ||
}, | ||
dir: { | ||
output: "build", | ||
src: "src", | ||
input: "src/include/templates", | ||
layouts: "src/layouts", | ||
watch: "src/**/*.{css,js,twig}", | ||
filters: [ | ||
// see filters/README.md | ||
], | ||
functions: [ | ||
// see functions/README.md | ||
], | ||
Comment on lines
+105
to
+110
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The filters and functions (and readmes) are nested inside |
||
}, | ||
}; | ||
``` | ||
|
||
## Shortcodes | ||
|
||
### `mix` | ||
|
||
If you've generated a mixManifest and add the path to it to the `USER_OPTIONS` then it's possible to add the non hashed files to a template e.g.: | ||
|
||
```twig | ||
{{ mix("/path/to/unhashed/asset.css") }} --> will result in /path/to/hashed/asset.hash-1234.css | ||
``` | ||
|
||
Please provide a path relative so that `userOptions.assets.root + userOptions.base + providedPath` reaches the asset from your projects root. | ||
|
||
### `asset_path` | ||
|
||
This is a simple helper shortcode to make your defined asset path `userOptions.assets.base` available in a template: | ||
|
||
```twig | ||
{{ asset_path() }} --> will result in /userOptions.assets.base like "/assets" | ||
``` | ||
|
||
### `image` | ||
|
||
This uses `@11ty/eleventy-img` to generate responsive images in defined formats (`userOptions.images.formats`) and sizes (`userOptions.images.widths`). You could also provide certain additionalAttributes via config for lazyloading etc. | ||
|
||
```twig | ||
{{ image("src", "alt", "classes") }} --> will result in a proper <picture> element with different <source> elements for each format and defined widths | ||
``` | ||
|
||
- `src`: this has to be relative to the `userOptions.assets.images` folder | ||
- `alt`: mandatory! (`""` is possible) | ||
- optional `classes`: `Array<string>` | ||
|
||
## To be done | ||
|
||
- Proper caching | ||
- Make features optional | ||
- ... | ||
- Make `twig.exports.extendTag()` possible | ||
|
||
## Acknowledgements | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Extend filter examples | ||
|
||
<p style="font-size: 2rem"> | ||
<img | ||
style="margin:2rem; width:8rem; height:8rem;" | ||
align="center" | ||
width="128" | ||
height="128" | ||
alt="Eleventy Logo" | ||
src="https://camo.githubusercontent.com/124e337fb005b0e70eb3758b431b051eaf5419b3a709062fbcce6d661a6ea116/68747470733a2f2f7777772e313174792e6465762f696d672f6c6f676f2d6769746875622e737667">+ | ||
<img | ||
style="margin:2rem; width:8rem; height:8rem;" | ||
align="center" | ||
width="128" | ||
height="128" | ||
alt="Twig.js Logo" | ||
src="https://user-images.githubusercontent.com/3282350/29336704-ab1be05c-81dc-11e7-92e5-cf11cca7b344.png"> | ||
</p> | ||
|
||
## Side note: | ||
|
||
Please read the [Twig](https://twig.symfony.com/doc/2.x/advanced.html#extending-twig) Documentation about how to extend twig properly first. Checkout the [Twig.js] documentation for `extendFunction()` as well. Also notice that functions should be used for content generation and frequent use whereas filters are more for value transformations in general. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The “extending Twig” link points to version 2.x. The latest Twig version is 3.x. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a lonely |
||
|
||
_...in the making_ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
const twig = require("twig"); | ||
|
||
/** | ||
* This utilizes twigs extendFilter | ||
* | ||
* @param {import("@11ty/eleventy").UserConfig} eleventyConfig | ||
* @param {import("../plugin").USER_OPTIONS} userOptions | ||
* @param {import("../plugin").FILTER} filter | ||
*/ | ||
const extendTwig = (eleventyConfig, userOptions, filter) => { | ||
twig.extendFilter(filter.symbol, (...args) => { | ||
return filter.callback(eleventyConfig, userOptions, ...args); | ||
}); | ||
}; | ||
|
||
/** | ||
* Iterates over all filters and add symbols with | ||
* their corresponding callbacks to twig | ||
* | ||
* @param {import("@11ty/eleventy").UserConfig} eleventyConfig | ||
* @param {import("../plugin").USER_OPTIONS} userOptions | ||
*/ | ||
module.exports = (eleventyConfig, userOptions) => { | ||
(userOptions.twig?.filter || []).forEach((filter) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could switch to early returns to make the code more readable and to add error messages: if (!userOptions.twig?.filter) {
return;
}
if (!Array.isArray(userOptions.twig.filter)) {
// Log error to console…
return;
}
userOptions.twig.filter.forEach(… There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
try { | ||
extendTwig(eleventyConfig, userOptions, filter); | ||
} catch (error) { | ||
console.log(error); | ||
} | ||
}); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
# Extend function examples | ||
|
||
<p style="font-size: 2rem"> | ||
<img | ||
style="margin:2rem; width:8rem; height:8rem;" | ||
align="center" | ||
width="128" | ||
height="128" | ||
alt="Eleventy Logo" | ||
src="https://camo.githubusercontent.com/124e337fb005b0e70eb3758b431b051eaf5419b3a709062fbcce6d661a6ea116/68747470733a2f2f7777772e313174792e6465762f696d672f6c6f676f2d6769746875622e737667">+ | ||
<img | ||
style="margin:2rem; width:8rem; height:8rem;" | ||
align="center" | ||
width="128" | ||
height="128" | ||
alt="Twig.js Logo" | ||
src="https://user-images.githubusercontent.com/3282350/29336704-ab1be05c-81dc-11e7-92e5-cf11cca7b344.png"> | ||
</p> | ||
|
||
## Side note: | ||
|
||
Please read the [Twig](https://twig.symfony.com/doc/2.x/advanced.html#extending-twig) Documentation about how to extend twig properly first. Checkout the [Twig.js] documentation for `extendFunction()` as well. Also notice that functions should be used for content generation and frequent use whereas filters are more for value transformations in general. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The “extending Twig” link points to version 2.x. The latest Twig version is 3.x. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a lonely |
||
|
||
## `mix` | ||
|
||
Its most likely that your project uses hashed assets. Therefore you have to include them somehow dynamically and Eleventy as well as Twig should be aware of this. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo: |
||
|
||
Lets define a function `mix()` which returns the hashed path to the given non hashed asset like: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typos:
|
||
|
||
```twig | ||
{{ mix("/path/to/unhased/asset.extension") }} --> will result in /path/to/hashed/asset.hash.extension | ||
``` | ||
|
||
There is an example implementation in `examples/mix.js`. To activate that implementation you have to add this module to the `userOptions.twig.functions` in your Eleventy configuration file and define the `userOptions.mixManifest` as well as the `userOptions.dir.output` property optionally like: | ||
|
||
```js | ||
const mix = require("@factorial/eleventy-plugin-twig/functions/examples/mix"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The module path is missing |
||
|
||
const userOptions = { | ||
twig: { | ||
functions: [ | ||
symbol: "mix", | ||
callback: mix, | ||
] | ||
Comment on lines
+41
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
functions: [
{
symbol: "mix",
callback: mix,
}
] |
||
} | ||
mixManifest: "mixManifest.json" // path relative to the output directory | ||
dir: { | ||
output: "build" // optionally, eleventy has a default output folder "_site", see https://www.11ty.dev/docs/config/#output-directory | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo: |
||
} | ||
} | ||
``` | ||
|
||
## `asset_path` | ||
|
||
If you have to reference lots of assets (e.g. images etc...) or have different environments like storybook / miyagi / prod / staging / dev etc. then its sometimes helpful to define a helper function like `asset_path()` with returns the path to your assets folder in the specific environment. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typos:
|
||
|
||
For this to work lets define a `asset_path()` function which prefixes a given path with the `userOptions.assets.base` path like: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo: |
||
|
||
```twig | ||
{{ asset_path()/subfolder/filename.extension }} --> will result in /path/to/your/assets/subfolder/filename.extension | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The path should be outside of the curly brace variable print thingy: {{ asset_path() }}/subfolder/filename.extension |
||
``` | ||
|
||
There is an example implementation in `examples/assetPath.js`. To activate that implementation you have to add this module to the `userOptions.twig.functions` in your Eleventy configuration file and define the `userOptions.assets.base` property like: | ||
|
||
```js | ||
const assetPath = require("@factorial/eleventy-plugin-twig/functions/examples/assetPath"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The module path is missing |
||
|
||
const userOptions = { | ||
twig: { | ||
functions: [ | ||
symbol: "asset_path", | ||
callback: assetPath, | ||
] | ||
Comment on lines
+70
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
functions: [
{
symbol: "asset_path",
callback: assetPath,
}
] |
||
}, | ||
assets: { | ||
base: "assets" // base folder relative to the build folder; could be defined by environmetal variables for different szenarios as well | ||
} | ||
} | ||
``` | ||
|
||
## `image` | ||
|
||
Eleventy comes with a great responsive image plugin called [@11ty/eleventy-img](https://github.com/11ty/eleventy-img). This autogenerates different file formats and sizes for those images included in your templates. | ||
|
||
Lets define a `image()` function which returns a proper `<picture>` element with `<source>` elements: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typos:
|
||
|
||
```twig | ||
{{ image("src", "alt", "classes") }} --> will result in a <picture> element with different <source> elements for each format and defined widths | ||
``` | ||
|
||
First install the latest `@11ty/eleventy-img` release as a node module with `yarn`: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo: |
||
|
||
```sh | ||
yarn add --dev @11ty/eleventy-img | ||
``` | ||
|
||
or `npm`: | ||
|
||
```sh | ||
npm install --save-dev @11ty/eleventy-img | ||
``` | ||
|
||
You can find an example implementation in `examples/image.js`. To activate that implementation you have to add this module to the `userOptions.twig.functions` in your eleventy configuration file, as well as a couple of other necessary properties like: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo: |
||
|
||
```js | ||
const image = require("@factorial/eleventy-plugin-twig/functions/examples/image"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The module path is missing |
||
|
||
const userOptions = { | ||
twig: { | ||
function: [ | ||
symbol: "image", | ||
callback: image, | ||
] | ||
Comment on lines
+110
to
+113
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
functions: [
{
symbol: "image",
callback: image,
}
] |
||
}, | ||
assets: { | ||
root: "src", // path to the root folder from projects root (e.g. src) | ||
base: "assets", // base path for assets relative to the root folder (e.g. assets) | ||
images: "images", // path to the image folder relative to the base (e.g. images) | ||
}, | ||
images: { | ||
widths: [300, 600, 900], // those image sizes will be autogenereated / aspect-ratio will be respected | ||
formats: ["webp", "avif"], // jpeg/avif/webp/png/gif | ||
additionalAttributes: "" // optionally - those attributes will be added to the image element | ||
} | ||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
const twig = require("twig"); | ||
|
||
/** | ||
* This utilizes twigs extendFunction | ||
* | ||
* @param {import("@11ty/eleventy").UserConfig} eleventyConfig | ||
* @param {import("../plugin").USER_OPTIONS} userOptions | ||
* @param {import("../plugin").FUNCTION} func | ||
*/ | ||
const extendTwig = (eleventyConfig, userOptions, func) => { | ||
twig.extendFunction(func.symbol, (...args) => { | ||
return func.callback(eleventyConfig, userOptions, ...args); | ||
}); | ||
}; | ||
|
||
/** | ||
* Iterates over all functions and add symbols with | ||
* their corresponding callbacks to twig | ||
* | ||
* @param {import("@11ty/eleventy").UserConfig} eleventyConfig | ||
* @param {import("../plugin").USER_OPTIONS} userOptions | ||
*/ | ||
module.exports = (eleventyConfig, userOptions) => { | ||
(userOptions.twig?.functions || []).forEach((func) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could switch to early returns to make the code more readable and to add error messages: if (!userOptions.twig?.functions) {
return;
}
if (!Array.isArray(userOptions.twig.functions)) {
// Log error to console…
return;
}
userOptions.twig.functions.forEach(… |
||
try { | ||
extendTwig(eleventyConfig, userOptions, func); | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
twig.extendFunction()
andtwig.extendFilter()
is done automatically by the plugin. I don’t think it is necessary to mention the “internals” here.