@anyblades/eleventy-blades 0.28.0-beta.2

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.
@@ -0,0 +1,3 @@
1
+ {
2
+ "printWidth": 120
3
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Anton Staroverov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ ## <sub>Build Awesome /</sub><br> Eleventy blades <sup>![](https://img.shields.io/github/v/release/anyblades/eleventy-blades?label=&color=black)</sup>
2
+
3
+ <!--section:summary-->
4
+
5
+ Ultimate blade kit for 11ty (Build Awesome).
6
+
7
+ <!--section:docs-->
8
+
9
+ ## Documentation
10
+
11
+ <!--prettier-ignore-->
12
+ - [Filters]( https://blades.ninja/build-awesome-11ty/filters/)
13
+ - [attr_concat]( https://blades.ninja/build-awesome-11ty/filters/#attr-concat),
14
+ [attr_includes]( https://blades.ninja/build-awesome-11ty/filters/#attr-includes),
15
+ [attr_set]( https://blades.ninja/build-awesome-11ty/filters/#attr-set)
16
+ - [date]( https://blades.ninja/build-awesome-11ty/filters/#date)
17
+ - [fetch]( https://blades.ninja/build-awesome-11ty/filters/#fetch) <!--{data-marker=🥷}-->
18
+ - [if]( https://blades.ninja/build-awesome-11ty/filters/#if)
19
+ - [markdownify]( https://blades.ninja/build-awesome-11ty/filters/#markdownify)
20
+ - [merge]( https://blades.ninja/build-awesome-11ty/filters/#merge)
21
+ - [remove_tag]( https://blades.ninja/build-awesome-11ty/filters/#remove-tag),
22
+ [strip_tag]( https://blades.ninja/build-awesome-11ty/filters/#strip-tag)
23
+ - [section]( https://blades.ninja/build-awesome-11ty/filters/#section) <!--{data-marker=🥷}-->
24
+ - [unindent]( https://blades.ninja/build-awesome-11ty/filters/#unindent)
25
+ - [Processors]( https://blades.ninja/build-awesome-11ty/processors/)
26
+ - [Auto link favicons]( https://blades.ninja/build-awesome-11ty/processors/#auto-link-favicons) <!--{data-marker=🥷}-->
27
+ - [Auto-raw tags]( https://blades.ninja/build-awesome-11ty/processors/#md-auto-raw)
28
+ - [Auto newlines-to-br](https://blades.ninja/build-awesome-11ty/processors/#md-auto-br)
29
+ - [Hidden markdown attrs<i>&nbsp;🆕</i>](https://blades.ninja/build-awesome-11ty/processors/#md-hidden-attrs) <!--{data-marker=🥷}-->
30
+ - [Power tools]( https://blades.ninja/build-awesome-11ty/tools/)
31
+ - [Base config file]( https://blades.ninja/build-awesome-11ty/tools/#base-config) <!--{data-marker=🥷}-->
32
+ - [Base npm scripts]( https://blades.ninja/build-awesome-11ty/tools/#base-scripts) <!--{data-marker=🥷}-->
33
+ - [Data helpers]( https://blades.ninja/build-awesome-11ty/tools/#data-helpers)
34
+ - [Blades starters]( https://blades.ninja/build-awesome-11ty/tools/#starters) <!--{data-marker=🥷}-->
35
+
36
+ <!--{.unlist .columns}-->
37
+
38
+ ---
39
+
40
+ ## Install
41
+
42
+ <!--section:docs,install-->
43
+
44
+ ```sh
45
+ npm install @anyblades/eleventy-blades
46
+ ```
47
+
48
+ Then choose one of the following options:
49
+
50
+ <mark>A. All-in</mark> managed by Eleventy Blades:
51
+
52
+ Consider symlinking entire `eleventy.config.js` as a set-and-forget zero-config zero-maintenance solution:
53
+
54
+ ```sh
55
+ ln -s ./node_modules/@anyblades/eleventy-blades/src/eleventy.config.js
56
+ ```
57
+
58
+ Learn more: https://blades.ninja/11ty/tools/#base-config
59
+
60
+ Living examples:
61
+
62
+ - https://github.com/anyblades/build-awesome-starter
63
+ - https://github.com/anyblades/bladeswitch
64
+
65
+ <mark>B. Base config</mark> by Eleventy Blades with your additions/overrides in `eleventy.config.js`:
66
+
67
+ ```js
68
+ import baseConfig from "@anyblades/eleventy-blades/base-config";
69
+
70
+ export default function (eleventyConfig) {
71
+ baseConfig(eleventyConfig);
72
+
73
+ // Your additions/overrides
74
+ ...
75
+ }
76
+ ```
77
+
78
+ Living example: https://github.com/hostfurl/minformhf/blob/main/eleventy.config.js
79
+
80
+ <mark>C. Plug-in</mark> Eleventy Blades in your existing `eleventy.config.js`:
81
+
82
+ ```js
83
+ import eleventyBladesPlugin from "@anyblades/eleventy-blades";
84
+
85
+ export default function (eleventyConfig) {
86
+ eleventyConfig.addPlugin(eleventyBladesPlugin, {
87
+ mdAutoRawTags: true,
88
+ mdAutoNl2br: true,
89
+ autoLinkFavicons: true,
90
+ siteData: true,
91
+ filters: ["attr_set", "attr_concat", ...],
92
+ });
93
+ }
94
+ ```
95
+
96
+ <mark>D. Individual imports</mark> from Eleventy Blades in your `eleventy.config.js`:
97
+
98
+ ```js
99
+ import { siteData, mdAutoRawTags, mdAutoNl2br, autoLinkFavicons, attrSetFilter, attrConcatFilter, ... } from "@anyblades/eleventy-blades";
100
+
101
+ export default function (eleventyConfig) {
102
+ siteData(eleventyConfig);
103
+ mdAutoRawTags(eleventyConfig);
104
+ mdAutoNl2br(eleventyConfig);
105
+ autoLinkFavicons(eleventyConfig);
106
+ attrSetFilter(eleventyConfig);
107
+ attrConcatFilter(eleventyConfig);
108
+ ...
109
+ }
110
+ ```
111
+
112
+ <div><hr></div>
113
+
114
+ Or use a <mark>fully preconfigured template</mark> as an alternative option:
115
+
116
+ <nav>
117
+
118
+ [🥷 Build Awesome Starter ↗ &nbsp;<small style="white-space: nowrap">11ty + Tailwind + Typography + Blades</small>](https://github.com/anyblades/build-awesome-starter)<!--{role=button .outline}-->
119
+
120
+ [🥷 Bladeswitch Starter ↗ &nbsp;<small style="white-space: nowrap">11ty + Pico + Blades</small>](https://github.com/anyblades/bladeswitch)<!--{role=button .outline}-->
121
+
122
+ </nav>
123
+
124
+ <!--section:docs-->
125
+
126
+ ---
127
+
128
+ Featured by:
129
+
130
+ - https://11tybundle.dev/blog/11ty-bundle-83/
131
+ - https://11tybundle.dev/categories/getting-started/
132
+ - https://hamatti.org/posts/markdown-content-split-to-sections-in-eleventy-and-nunjucks/#:~:text=anydigital
133
+ - https://github.com/anydigital/awesome-11ty-build-awesome
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@anyblades/eleventy-blades",
3
+ "version": "0.28.0-beta.2",
4
+ "description": "A collection of helpful utilities and filters for Eleventy (11ty)",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./src/index.js",
10
+ "require": "./src/index.cjs"
11
+ },
12
+ "./base-config": "./src/eleventy.config.js"
13
+ },
14
+ "scripts": {
15
+ "test": "node --test src/**/*.test.js",
16
+ "prepublishOnly": "npm run test"
17
+ },
18
+ "keywords": [
19
+ "11ty",
20
+ "eleventy",
21
+ "plugin",
22
+ "helpers",
23
+ "filters",
24
+ "markdown",
25
+ "nunjucks"
26
+ ],
27
+ "author": "Anton Staroverov",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/anyblades/eleventy-blades.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/anyblades/eleventy-blades/issues"
35
+ },
36
+ "homepage": "https://github.com/anyblades/eleventy-blades#readme",
37
+ "peerDependencies": {
38
+ "@11ty/eleventy": "^3.0.0 || ^4.0.0-alpha.6"
39
+ },
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@11ty/lodash-custom": "^4.17.21"
45
+ }
46
+ }
@@ -0,0 +1,12 @@
1
+ <!-- https://sveltiacms.app/en/docs/start#manual-installation -->
2
+ <!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta name="robots" content="noindex" />
7
+ <title>Sveltia CMS</title>
8
+ </head>
9
+ <body>
10
+ <script src="https://cdn.jsdelivr.net/npm/@sveltia/cms@0.128/dist/sveltia-cms.min.js"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,53 @@
1
+ ### Base 11ty npm scripts <small>via npm workspace</small> {#base-scripts}
2
+
3
+ This package provides a pre-configured `do` folder setup that helps organize your development workflow using npm workspaces. The `do` folder contains scripts for building and running your Eleventy project.
4
+
5
+ **Installation:**
6
+
7
+ 1. Install https://github.com/anyblades/eleventy-blades to reuse pre-defined 11ty scripts from there:
8
+
9
+ ```sh
10
+ npm install @anyblades/eleventy-blades
11
+ ```
12
+
13
+ 2. Create a helper folder `do` to symlink the `do/package.json` within:
14
+
15
+ ```sh
16
+ mkdir do
17
+ cd ./do
18
+ ln -s ../node_modules/@anyblades/eleventy-blades/src/do/package.json
19
+ ```
20
+
21
+ 3. Finally register `do` folder as npm workspace in your root `package.json`:
22
+
23
+ ```json {data-caption=./package.json}
24
+ {
25
+ ...
26
+ "workspaces": ["do"],
27
+ "scripts": {
28
+ "start": "npm -w do run start",
29
+ "stage": "npm -w do run stage",
30
+ "build": "npm -w do run build"
31
+ },
32
+ ...
33
+ }
34
+ ```
35
+
36
+ **Done!** 🎉 Now you can run:
37
+
38
+ - `npm start` to start 11ty dev server with live reload and Tailwind watch mode
39
+ - `npm run stage` to build and serve production-like site locally
40
+ - `npm run build` to finally build the site for production
41
+ - all available scripts: https://github.com/anyblades/eleventy-blades/blob/main/src/do/package.json
42
+
43
+ Living examples:
44
+
45
+ - https://github.com/anyblades/build-awesome-starter
46
+ - https://github.com/anyblades/bladeswitch
47
+
48
+ **Benefits:**
49
+
50
+ - **Clean separation**: Keep build scripts separate from project configuration
51
+ - **Reusable workflows**: Update scripts by upgrading the package
52
+ - **Workspace isolation**: Scripts run in their own workspace context
53
+ - **Easy maintenance**: No need to manually maintain build scripts
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@anyblades/eleventy-blades-do",
3
+ "private": true,
4
+ "scripts": {
5
+ "build": "npm run tw && npm run 11ty -- $ELTY_OPTIONS",
6
+ "start": "npm run 11ty -- $ELTY_OPTIONS --serve --incremental & npm run tw -- --watch",
7
+ "prerestart": "npm run 11ty:clean",
8
+ "stage": "npm run 11ty:clean; npm run build && serve ../_site",
9
+ "11ty": "cd ../ && NODE_OPTIONS='--preserve-symlinks' eleventy",
10
+ "11ty:clean": "rm -r ../_site",
11
+ "11ty:debug": "DEBUG=* npm run 11ty --",
12
+ "tw": "cd ../ && tailwindcss -i ./_styles/styles.css -o ./_public/styles.css",
13
+ "tw:debug": "DEBUG=* npm run tw --"
14
+ }
15
+ }
@@ -0,0 +1,178 @@
1
+ // <!--section:code-->```js
2
+
3
+ /* Plugins */
4
+ import { RenderPlugin } from "@11ty/eleventy";
5
+ import eleventyBladesPlugin from "@anyblades/eleventy-blades";
6
+ /* Dynamic plugins */
7
+ let eleventyNavigationPlugin;
8
+ try {
9
+ eleventyNavigationPlugin = (await import("@11ty/eleventy-navigation")).default;
10
+ } catch (e) {
11
+ // @11ty/eleventy-navigation not installed
12
+ }
13
+ let pluginTOC;
14
+ try {
15
+ pluginTOC = (await import("@uncenter/eleventy-plugin-toc")).default;
16
+ } catch (e) {
17
+ // @uncenter/eleventy-plugin-toc not installed
18
+ }
19
+ let feedPlugin;
20
+ try {
21
+ feedPlugin = (await import("@11ty/eleventy-plugin-rss")).feedPlugin;
22
+ } catch (e) {
23
+ // @11ty/eleventy-plugin-rss not installed
24
+ }
25
+ /* Libraries */
26
+ import markdownIt from "markdown-it";
27
+ /* Dynamic libraries */
28
+ let slugify;
29
+ try {
30
+ slugify = (await import("@sindresorhus/slugify")).default;
31
+ } catch (e) {
32
+ // @sindresorhus/slugify not installed
33
+ }
34
+ let markdownItAnchor;
35
+ try {
36
+ markdownItAnchor = (await import("markdown-it-anchor")).default;
37
+ } catch (e) {
38
+ // markdown-it-anchor not installed
39
+ }
40
+ let markdownItAttrs;
41
+ try {
42
+ markdownItAttrs = (await import("markdown-it-attrs")).default;
43
+ } catch (e) {
44
+ // markdown-it-attrs not installed
45
+ }
46
+ /* Data */
47
+ import yaml from "js-yaml";
48
+ import { readFileSync } from "node:fs";
49
+
50
+ /**
51
+ * Eleventy Configuration
52
+ * @param {Object} eleventyConfig - The Eleventy configuration object
53
+ * @returns {Object} The Eleventy configuration object
54
+ */
55
+ export default function (eleventyConfig) {
56
+ const inputDir = eleventyConfig.directories.input;
57
+
58
+ /* Jekyll parity */
59
+ eleventyConfig.addPassthroughCopy("assets");
60
+ eleventyConfig.addGlobalData("layout", "default");
61
+ eleventyConfig.setLiquidOptions({ dynamicPartials: false }); // allows unquoted Jekyll-style includes
62
+ eleventyConfig.addFilter("relative_url", (content) => content); // dummy
63
+
64
+ /* Plugins */
65
+ eleventyConfig.addPlugin(RenderPlugin);
66
+ if (eleventyNavigationPlugin) eleventyConfig.addPlugin(eleventyNavigationPlugin);
67
+ eleventyConfig.addPlugin(eleventyBladesPlugin, {
68
+ mdAutoNl2br: true,
69
+ mdAutoUncommentAttrs: true,
70
+ mdAutoRawTags: true,
71
+ autoLinkFavicons: true,
72
+ siteData: true,
73
+ filters: [
74
+ "attr_set",
75
+ "attr_includes",
76
+ "merge",
77
+ "remove_tag",
78
+ "if",
79
+ "attr_concat",
80
+ "fetch",
81
+ "section",
82
+ "strip_tag",
83
+ "unindent",
84
+ "date",
85
+ ],
86
+ });
87
+ if (pluginTOC) {
88
+ eleventyConfig.addPlugin(pluginTOC, {
89
+ ignoredElements: ["sub", "[data-is-anchor]"],
90
+ ul: true,
91
+ wrapper: (toc) => `<div data-is-toc>${toc}</div>`,
92
+ });
93
+ }
94
+ // https://www.11ty.dev/docs/plugins/rss/#virtual-template
95
+ if (feedPlugin) {
96
+ eleventyConfig.addCollection("feed", (collectionApi) => collectionApi.getAll().filter((item) => item.data.revised));
97
+ let siteData = {};
98
+ try {
99
+ siteData = yaml.load(readFileSync(`${inputDir}/_data/site.yml`, "utf8"));
100
+ } catch (e) {
101
+ // _data/site.yml not found
102
+ }
103
+ eleventyConfig.addPlugin(feedPlugin, {
104
+ type: "atom", // or "rss", "json"
105
+ outputPath: "/feed.xml",
106
+ collection: {
107
+ name: "feed", // iterate over `collections.posts`
108
+ limit: 100, // 0 means no limit
109
+ },
110
+ metadata: siteData,
111
+ });
112
+ }
113
+
114
+ /* Libraries */
115
+ let md = markdownIt({
116
+ html: true,
117
+ linkify: true,
118
+ });
119
+ if (markdownItAnchor) {
120
+ md = md.use(markdownItAnchor, {
121
+ slugify: slugify, // @TODO: TRICKS
122
+ permalink: markdownItAnchor.permalink.ariaHidden({
123
+ class: null,
124
+ renderAttrs: () => ({ "data-is-anchor": true }),
125
+ }),
126
+ });
127
+ }
128
+ if (markdownItAttrs) md = md.use(markdownItAttrs);
129
+ eleventyConfig.setLibrary("md", md);
130
+ eleventyConfig.addFilter("markdownify", (content) => md.render(String(content ?? "")));
131
+
132
+ /* Data */
133
+ eleventyConfig.addDataExtension("yml", (contents) => yaml.load(contents));
134
+
135
+ /* Build */
136
+ eleventyConfig.addPassthroughCopy(
137
+ {
138
+ _public: ".",
139
+ ...(inputDir !== "." && { [`${inputDir}/_public`]: "." }),
140
+ },
141
+ { expand: true }, // This follows/resolves symbolic links
142
+ );
143
+
144
+ /* Dev tools */
145
+ // Follow symlinks in Chokidar used by 11ty to watch files
146
+ eleventyConfig.setChokidarConfig({ followSymlinks: true });
147
+ }
148
+ /*```
149
+
150
+ <!--section:docs-->
151
+ ### Base `eleventy.config.js` {#base-config}
152
+
153
+ The package includes a fully-configured Eleventy config file `eleventy.config.js` that you can symlink to your project to get:
154
+
155
+ - All eleventy-blades plugins enabled
156
+ - Eleventy Navigation plugin
157
+ - Table of Contents plugin (conditionally loaded if installed)
158
+ - Markdown-it with anchors and attributes
159
+ - YAML data support
160
+ - CLI input directory support
161
+ - Symlink support for development
162
+ - _and more_
163
+
164
+ **Benefits of symlinking:**
165
+
166
+ - **Always up-to-date**: Configuration automatically updates when you upgrade the package
167
+ - **Less maintenance**: No need to manually sync configuration changes
168
+ - **Quick setup**: Get started immediately with best-practice configurations
169
+ - **Easy customization**: Override specific settings by creating your own config that imports from the symlinked version
170
+
171
+ **Installation as simple as:**
172
+
173
+ ```sh
174
+ npm install @anyblades/eleventy-blades
175
+ ln -s ./node_modules/@anyblades/eleventy-blades/src/eleventy.config.js
176
+ ```
177
+ <!--section-->
178
+ */
@@ -0,0 +1,107 @@
1
+ // <!--section:code-->```js
2
+ /**
3
+ * Concatenate values to an attribute array
4
+ *
5
+ * This function takes an object, an attribute name, and values to append.
6
+ * It returns a new object with the attribute as a combined array of unique items.
7
+ *
8
+ * @param {Object} obj - The object to modify
9
+ * @param {string} attr - The attribute name
10
+ * @param {Array|string|*} values - Values to concatenate (array, JSON string array, or single value)
11
+ * @returns {Object} A new object with the combined unique array
12
+ */
13
+ export function attrConcat(obj, attr, values) {
14
+ // Get the existing attribute value, default to empty array if not present
15
+ const existingArray = obj?.[attr] || [];
16
+
17
+ // Check if existing value is an array, convert if not
18
+ if (!Array.isArray(existingArray)) {
19
+ console.error(`attrConcat: Expected ${attr} to be an array, got ${typeof existingArray}`);
20
+ }
21
+
22
+ // Process the values argument
23
+ let valuesToAdd = [];
24
+ if (Array.isArray(values)) {
25
+ valuesToAdd = values;
26
+ } else if (typeof values === "string" && values.length >= 2 && values.at(0) == "[" && values.at(-1) == "]") {
27
+ // Try to parse as JSON array
28
+ try {
29
+ const parsed = JSON.parse(values);
30
+ if (Array.isArray(parsed)) {
31
+ valuesToAdd = parsed;
32
+ } else {
33
+ valuesToAdd = [values];
34
+ }
35
+ } catch {
36
+ // Not valid JSON, treat as single value
37
+ valuesToAdd = [values];
38
+ }
39
+ } else {
40
+ // If it's a single value, wrap it in an array
41
+ valuesToAdd = [values];
42
+ }
43
+
44
+ // Combine arrays and remove duplicates using Set
45
+ const combinedArray = [...new Set([...existingArray, ...valuesToAdd])];
46
+
47
+ // Return a new object with the combined array
48
+ return {
49
+ ...obj,
50
+ [attr]: combinedArray,
51
+ };
52
+ }
53
+
54
+ /**
55
+ * attr_concat filter - Concatenate values to an attribute array
56
+ *
57
+ * This filter takes an object, an attribute name, and values to append.
58
+ * It returns a new object with the attribute as a combined array of unique items.
59
+ *
60
+ * @param {Object} eleventyConfig - The Eleventy configuration object
61
+ */
62
+ export function attrConcatFilter(eleventyConfig) {
63
+ eleventyConfig.addFilter("attr_concat", attrConcat);
64
+ }
65
+ /*```
66
+
67
+ <!--section:docs-->
68
+ ### `attr_concat`
69
+
70
+ A filter that concatenates values to an attribute array, returning a new object with the combined array. Useful for adding items to arrays like tags, classes, or other list-based attributes.
71
+
72
+ **Why use this?** When working with objects that have array attributes (like tags), you often need to add additional values without mutating the original object. The `attr_concat` filter provides a clean way to combine existing array values with new ones, automatically handling duplicates.
73
+
74
+ **Features:**
75
+
76
+ - Non-mutating: Creates a new object, leaving the original unchanged
77
+ - Automatically removes duplicates using Set
78
+ - Handles multiple input types: arrays, JSON string arrays (killer feature for `.liquid`), or single values
79
+ - Creates the attribute as an empty array if it doesn't exist
80
+ - Logs an error if the existing attribute is not an array
81
+ - `TBC:` Supports nested attributes (e.g., `data.tags`)
82
+
83
+ #### Example: Add tags to a post object in `.njk`:
84
+
85
+ ```jinja2
86
+ {% set enhancedPost = post | attr_concat('tags', ['featured', 'popular']) %}
87
+ ```
88
+
89
+ #### `PRO` Example: Add scripts and styles to the `site` object in `.liquid`:
90
+
91
+ ```liquid
92
+ {% capture _ %}[
93
+ "https://cdn.jsdelivr.net/npm/prismjs@1/themes/prism-tomorrow.min.css",
94
+ "https://cdn.jsdelivr.net/npm/prismjs@1/plugins/treeview/prism-treeview.min.css",
95
+ "https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@7/css/all.min.css",
96
+ "/styles.css"
97
+ ]{% endcapture %}
98
+ {% assign site = site | attr_concat: 'styles', _ %}
99
+
100
+ {% capture _ %}[
101
+ "https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-core.min.js",
102
+ "https://cdn.jsdelivr.net/npm/prismjs@1/plugins/autoloader/prism-autoloader.min.js",
103
+ "https://cdn.jsdelivr.net/npm/prismjs@1/plugins/treeview/prism-treeview.min.js"
104
+ ]{% endcapture %}
105
+ {% assign site = site | attr_concat: 'scripts', _ %}
106
+ ```
107
+ <!--section--> */