Thi's avatar
HomeAboutNotesBlogTopicsToolsReading
About|My sketches |Cooking |Cafe icon Support Thi
πŸ’Œ [email protected]

11ty & Nunjucks

Anh-Thi Dinh
11tyWeb DevSSG
Left aside
⚠️
This is not a tutorial to create an 11ty website, this's a note! You can find some very new and useful techniques on this note alongside the official documentation.

Installation

πŸ‘‰ First, install nodejs.
πŸ‘‰ Using
this starter template or Google's high performance statrer theme (recommended).
You should follow the installation steps given in the theme you choose.

Setting up with Netlify

Sometimes, 11ty takes too much time to build (especially on the task of optimizing images. On my site, it takes almost 10 minutes). You shouldn't use branch master to build you site because every time you make a push, Netlify will rebuild your whole site. You should create and use a new branch, so-called prod instead.
The weakness of Idea 1 is that you let netlify build again your whole site with its resources. That's why it takes too much time! Remember that, you have only 300 free minutes to build.

Templating

SCSS to CSS

Using postcss?

Nunjucks inside css

Bootstrap + 11ty

πŸ‘‰ Bootstrap's homepage
πŸ‘‰
How to Isolate Bootstrap CSS to Avoid Conflicts (using LESS)
Using alongside with section "SCSS to CSS".

Google Fonts

  • Put fonts in fonts/ and use this tool to generate .woff, woff2 from Google Fonts. Be careful on the location will be used on your site.
  • Copy and paste to css/main.scss.
  • If you have a problem with Content-Security-Policy, check the section β€œTroubleshooting”.

Using fontello icons

More icon fonts than Fontawesome. Choose fonts on fontello > "Get config only".
Check the code -doc in src/fontello/config.json, field "css".
Note: Sometimes, there are duplicates hexa/decimal code (although the names are different). On fontello website, navigate to "Customize Codes" tab, find duplicates and change them. Note that, in this tab, the codes are shown in hexa base but in the downlowded config, the codes are shown in decimal based (field "code"). You can use this site to convert between them.

Layout

Includes

Split layout into parts and include them in the main file.

Template inheritance

Read this tutorial.

Post's components

πŸ‘‰ Page variable components.
They will be used as page.fileSlug!
☝
For ones who wanna get only the content (escape HTML tags and special characters) of the post:

Custom frontmatter fields

Suppose you use specialTitle in the frontmatter of your page (eg. index.njk). You can use it in the template, eg. header.njk, as

Recognize home page

If pageUrlLength > 1, it's not home page!

Frontmatter

List of posts

Normal,
☝
Other default variable (like post.url) can be found here. Note that, you can use page.templateContent for the content of a page in some collections (not tested yet but you can try!) (ref).

Sort posts by titles

πŸ‘‡ Update: Below method is old (a hard way), you can refer to this guide.

Posts by categories / tags

In this case, we consider a category as the first tag of a post. For example, if a post has tags tags: [tag 1, tag 2, tag 3], then tag 1 will be its category!

External posts

If you wanna add external posts (not generated by 11ty from .md files), do below steps. For example, on this site, I cretae some pages created by Notion and I wanna add them to category MOOC.
Remark: This section is used with section "posts by categories" and section "sort posts by title".

Next / Previous post

Custom js scripts

Using rollupjs
⚠️
Using rollup as above way, we have only one output file js.min.js!

Using custom js files for each page

Suppose that you have a custom frontmatter customJS: ["file1.js, file2.js"] containing all custom js files. It means that the files file1.js and file2.js are presented only in this page! (If we integrate them in main.js, they will be integrated ub all other pages after being rendered even if they're useless in those pages).
Where jsmin is a filter created in the next section. All files file1.js, file2.js are stored in _includes/_scripts/.

Minify js files

Usage (_includes/scripts/search.js),

Last modified date

Last modified date,

Insert code highlight

Code syntax highlight: Need this plugin. List of supported languages.

Insert liquid / nunjuck code

Inline code, put {{ "{% raw " }}%} and {{ "{% endraw " }}%} around the keyword.
Code block,

Math equations

Mathjax

Using markdown-it-mathjax.

KaTeX (my choice)

In this site, I use markdown-it-texmath. I choose this because we can overcome the weakness of markdown-it-katex in case of breaking lines in list mode & it's more flexible (you can try it online here).
An important note: the original version has a problem of whitespace before and after <eq> tag in the inline mode. That why instead of seeing aaa x aaa with aaa $x$ aaa, we see aaaxaaa. I've changed (and reported an issue). For a moment, I use a modified version here. Update: The author couldn't reproduce the issue I met (with version 0.8), he keep updating the plugin but I didn't try version 0.9. You better try it before trying my option!

Figures

Using relative path?

In sert images with the path relative to the directory of .md files. Let's use eleventy-plugin-page-assets.
In .eleventy.js

Markdown

markdown-it & its plugins

We use markdown-it and its plugins. Just use npm i <plugin-name> --save-dev to install.

Custom container

If you wanna create an advanced custom container, use plugin markdown-it-container. For example, you want export something like,
Just by using,
You can put in .eleventy.js like,

Markdown inside .njk

HTML/nunjucks tags inside .md

Custom block shortcodes

To creata the same code block like above, i.e.,
Just by using,

Custom inline shortcodes

If you wanna export something like,
by using {% raw %}{% ref "custom-url" %}{% endraw %} ("" is required). You can set,

Search

For me, the best choice for search feature in 11ty is using Elasticlunr with some customizations.
Because your site becomes bigger in future, you cannot index the whole text of your site (every time you build). My idea is to create a custom frontmatter tag called "keywords" which contains all of the important keywords used to determine the content of your posts. Of course, the cons is that you have to put the keywords manually!!
Check this repository, I've pulled and modified from this one (The author takes so long to check my pull request ^^). My customization supports:
  • Index your customizable keywords.
  • Fix UX bugs in the main repo.
  • Highlight found keywords in the search result.
  • Limit the max number of texts in the result (show around the found keywords).
  • Adapt to the newest version of 11ty.

Data files

Apply data for all posts in a directory

πŸ‘‰ Check more in official doc.
For example, we wanna add tag "posts" for all posts in a folder named "sample_posts". Inside /sample_posts/, create a file sample_posts.son (yes, the same name as "sample_posts") with following content,

Using external data files with environment env

Suppose your data files is not in ./_data/ but in ./notes/_data/.
In case you have a setting file in notes/_data/settings.json. Sometimes, you use it as a data files via dataDir (just settings.*), sometimes, you use it as a separated json file. For example, you use it in a separated js file toggle-notes.js
Solution, in .eleventy.js
Then in toggle-notes.js

Local data files

You put all your data files (.js or .json) in _data, e.g.,
For example, export a current year on site,

Fetched JSON from an external source

For example, to build a page fetching all starred github repositories on Github from the API https://api.github.com/users/dinhanhthi/starred.

Working style

Custom environment

More info, read official doc. For example, we only perform something differently on local.
An example of using in .eleventy.js
Or using in the template,

Incremental

πŸŽ‰ Update 19 Nov 2021: 11ty has already added the incremental option (--incremental). Check more in the official document.

Working with themes locally?

From version 1.0.0 (currently, I use version 1.0.0-canary.38), we can customize eleventyConfig.ignores right in .eleventy.js. Use this to make different posts folders for local and for remote. Because there are too many posts which are going to be built, it takes a very long time to see the changes. If you just wanna make changes in theme, you need a separated folder (having less number of posts).
For example,
  • Only process posts in notes/* on remote (ignore sample_posts/*).
  • Only process posts in sample_posts/* on local (ignore notes/*).

Nunjucks things

Add a new item to a list,
Create a dictionary with nunjucks
Add a new key-value to a dictionary,
String concatenations,

Troubleshooting

🐞 TypeError: Cannot read property 'type' of undefined
Class comes before ![]() of an image!

🐞 EISDIR: illegal operation on a directory
Delete _site/ and rebuild!

🐞 ENOTDIR: not a directory...
Delete _site/ and rebuild!

🐞 Invalid DateTime
Just for new posts => try to commit (on branch "dev") before building!


Reason: json-ld.js takes some beginning words to convert to json formats! If there are equation (latex likes $\Rightarrow$) at the very beginning of the notes, there will be error like this!
The err comes from src/json-ld.js and the codes at the bottom of file src/_includes/layouts/post.njk β†’ truncate(140) β†’ If math equations start at character 141, it will be fine!
Solution: Add | dump to the description tag!

References

  1. Official website.
  1. Nunjucks Documentation
  1. Moving from WordPress to Eleventy
  1. From Jekyll to Eleventy - Webstoemp
  1. Creating an 11ty Plugin - SVG Embed Tool - bryanlrobinson.com
β—†Installationβ—‹Setting up with Netlifyβ—†Templatingβ—‹SCSS to CSSβ—‹Using postcss?β—‹Nunjucks inside cssβ—‹Bootstrap + 11tyβ—‹Google Fontsβ—‹Using fontello iconsβ—‹Layoutβ—‹Includesβ—‹Template inheritanceβ—‹Post's componentsβ—‹Custom frontmatter fieldsβ—‹Recognize home pageβ—‹Frontmatterβ—‹List of postsβ—‹Sort posts by titlesβ—‹Posts by categories / tagsβ—‹External postsβ—‹Next / Previous postβ—†Custom js scriptsβ—‹Using custom js files for each pageβ—‹Minify js filesβ—†Last modified dateβ—†Insert code highlightβ—‹Insert liquid / nunjuck codeβ—†Math equationsβ—‹Mathjaxβ—‹KaTeX (my choice)β—†Figuresβ—‹Using relative path?β—†Markdownβ—‹markdown-it & its pluginsβ—‹Custom containerβ—‹Markdown inside .njkβ—‹HTML/nunjucks tags inside .mdβ—‹Custom block shortcodesβ—‹Custom inline shortcodesβ—†Searchβ—†Data filesβ—‹Apply data for all posts in a directoryβ—‹Using external data files with environment envβ—‹Local data filesβ—‹Fetched JSON from an external sourceβ—†Working styleβ—‹Custom environmentβ—‹Incrementalβ—‹Working with themes locally?β—†Nunjucks thingsβ—†Troubleshootingβ—†References
About|My sketches |Cooking |Cafe icon Support Thi
πŸ’Œ [email protected]
1# Install dependencies
2npm install
1# Install it and its plugin first
2npm install autoprefixer postcss postcss-cli
1# Create postcss.config.js on the same folder as package.json
2module.exports = {
3  plugins: [require("autoprefixer")],
4};
1// npm command in package.json
2"css:prefix": "postcss src/css/main.css --use autoprefixer --replace true"
1# Watch (cannot use with `--replace`)
2postcss --watch main.css -o main_.css --use autoprefixer
1<style>
2  .bg {
3    background-image: url({{ bg-image }});
4  }
5</style>
6
7<!-- or -->
8<div style="background-image: url({{ bg-image }});"></div>
1# install
2npm i bootstrap jquery popper.js
1# folder structure
2_assets/css/main.scss
3_______/vender/_bootstrap.scss
1// main.scss
2@import "./vender/bootstrap";
1// _bootstrap.scss
2// all components
3// @import "./../../../node_modules/bootstrap/scss/bootstrap.scss";
4
5// Required
6// check more: <https://getbootstrap.com/docs/4.5/getting-started/theming/#importing>
7@import "./../../../node_modules/bootstrap/scss/functions";
8@import "./../../../node_modules/bootstrap/scss/variables";
9@import "./../../../node_modules/bootstrap/scss/mixins";
1# install fontello-cli
2npm install -g fontello-cli
3
4# Remove old session (if changing the current icons, not adding new ones)
5rm .fontello-session
6
7# install / update new icon
8fontello-cli --config src/fontello/config.json --css src/fontello/css --font src/fontello/font install
1// .eleventy.js
2module.exports = function (eleventyConfig) {
3  eleventyConfig.addPassthroughCopy("src/fontello");
4};
1<head>
2  <link rel="stylesheet" href="/src/fontello/css/fontello.css" />
3</head>
1<!-- usage -->
2<i class="icon-doc"></i>
1mkdir _includes/layouts
2touch _includes/layouts/post.njk
1// create an alias
2module.exports = function (eleventyConfig) {
3  eleventyConfig.addLayoutAlias("post", "layouts/post.njk");
4};
1# update changes
2touch .eleventy.js
1# then use
2---
3layout: post
4---
1// in _includes/components/head.njk
2{% include "components/head.njk" %}
3
4// custom parameter
5{% set customClass = 'list-homepage' %}
6{% include "components/postsList.njk" %}
7// inside postlist.njk, just use {{ customClass }}
1<!-- _includes/layouts/base.njk -->
2<body>
3  <header>{% block headerLogo %}{% endblock %}</header>
4</body>
1<!-- _includes/layouts/post.njk -->
2{% extends "layouts/base.njk" %}
3{% block headerLogo%}
4<!-- only appear on post layout -->
5{% endblock %}
1// URL can be used in <a href> to link to other templates
2url: "/current/page/myFile/",
3
4// For permalinks: inputPath filename minus template file extension (New in v0.3.4)
5fileSlug: "myFile",
6
7// For permalinks: inputPath minus template file extension (New in v0.9.0)
8filePathStem: "/current/page/myFile",
9
10// JS Date Object for current page (used to sort collections)
11date: new Date(),
12
13// The path to the original source file for the template
14// Note: this will include your input directory path!
15inputPath: "./current/page/myFile.md",
16
17// Depends on your output directory (the default is _site)
18// You probably won’t use this: `url` is better.
19outputPath: "./_site/current/page/myFile/index.html"
20
21templateContent: //the rendered content of this template. This does not include layout wrappers.
22
23data: // all data for this piece of content (includes any data inherited from layouts)
24// self-defined frontmatter tags can be found here
1{{ page.templateContent | dump | safe | striptags(true) }}
1{% if specialTitle %} {# content #} {% endif %}
1{% set pageUrlLength = page.url | length %}
1---
2title: Title of the post
3description: description of the post
4date: 2020-09-11
5layout: layouts/post.njk
6---
1---
2tags:
3  - default
4# or
5tags: [tag 1, tag 2]
6---
1<ul>
2  {% for post in collections.posts %}
3  <li>
4    <a href="{{ post.url | url }}"> {{ post.data.title }} </a>
5  </li>
6  {% endfor %}
7</ul>
1<!-- Create a new list -->
2{% set newPostList = [] %} {% for post in collections.posts %} {% set
3newPostList = (newPostList.push({title: post.data.title, url: post.url}),
4newPostList) %} {% endfor %}
5
6<!-- list of post by alphabet -->
7<ul>
8  {% for post in newPostList|sort(attribute='title') %}
9  <li>
10    <a href="{{ post.url | url }}"> {{ post.title }} </a>
11  </li>
12  {% endfor %}
13</ul>
1// _data/categories.json
2[
3  {
4    "name": "Cat 1",
5    "icon": "/img/cats/cat_1.svg"
6  },
7  {
8    "name": "Cat 2",
9    "icon": "/img/cats/cat_2.svg"
10  }
11]
1{% for item in categories %}
2<div class="category-wrapper">
3  <h2>{{ item.icon }} {{ item.name }}</h2>
4  <ul class="post-list">
5    {% for post in collections[item.name] %}
6    <li class="post-item">
7      <a href="{{ post.url }}"><h3>{{ post.data.title }}</h3></a>
8    </li>
9    {% endfor %}
10  </ul>
11</div>
12{% endfor %}
1// _data/cat_ex_posts.json
2[
3  {
4    "title": "DL4 - Convolutional Neural Network (CNN)",
5    "url": "<https://www.notion.so/dinhanhthi/CNN-by-deeplearning-ai-a081d253fc2c4c0b99edd2757c759b9e>",
6    "tags": ["MOOC", "Deep Learning"]
7  },
8  {
9    "title": "DL5 - Sequence models",
10    "url": "<https://www.notion.so/dinhanhthi/CNN-by-deeplearning-ai-a081d253fc2c4c0b99edd2757c759b9e>",
11    "tags": ["MOOC", "Deep Learning"]
12  },
13  {
14    "title": "NLP by HSE",
15    "url": "<https://www.notion.so/dinhanhthi/NLP-by-HSE-20cec3e92201475eb4d48787954f3aa4>",
16    "tags": ["MOOC", "NLP"]
17  }
18]
1{% for item in categories %} {% set postslist = collections[item.name] %} {% set
2newPostListCat = [] %} {% for post in cat_ex_posts %} {% for tg in post.tags %}
3{% if tg == item.name %} {% set morePosts = true %} {% set newPostListCat =
4(newPostListCat.push({title: post.title, url: post.url, tags: post.tags}),
5newPostListCat) %} {% endif %} {% endfor %} {% endfor %} {% set newPostList = []
6%} {% for post in postslist %} {% set newPostList = (newPostList.push({title:
7post.data.title, url: post.url}), newPostList) %} {% endfor %} {% if morePosts
8%} {% for post in newPostListCat %} {% set newPostList =
9(newPostList.push({title: post.title, url: post.url, tags: post.tags}),
10newPostList) %} {% endfor %} {% endif %}
11
12<div class="category-wrapper">
13  <h2>{{ item.icon }} {{ item.name }}</h2>
14  <ul class="post-list">
15    {% for post in newPostList %}
16    <li class="post-item">
17      <a href="{{ post.url }}"><h3>{{ post.title }}</h3></a>
18    </li>
19    {% endfor %}
20  </ul>
21</div>
22{% endfor %}
1<ul>
2  {%- set nextPost = collections.posts | getNextCollectionItem(page) %} {%- if
3  nextPost %}
4  <li>
5    Next: <a href="{{ nextPost.url | url }}">{{ nextPost.data.title }}</a>
6  </li>
7  {% endif %} {%- set previousPost = collections.posts |
8  getPreviousCollectionItem(page) %} {%- if previousPost %}
9  <li>
10    Previous:
11    <a href="{{ previousPost.url | url }}">{{ previousPost.data.title }}</a>
12  </li>
13  {% endif %}
14</ul>
1# Put scripts in
2# /src/main.js
1<!-- in <head> -->
2<script async defer src="{{ '/js/min.js' | addHash }}"></script>
1// rollup.config.js
2export default [
3  {
4    input: "src/main.js",
5    output: [
6      {
7        file: "js/min.js",
8        format: "iife",
9        sourcemap: true,
10        plugins: [terser()],
11      },
12    ],
13  },
14];
1{% if customJS %} {% set js %} {% for file in customJS %} {% include "scripts/"
2+ file %} {% endfor %} {% endset %}
3<script>
4  {
5    {
6      js | jsmin | safe;
7    }
8  }
9</script>
10{% endif %}
1module.exports = function (eleventyConfig) {
2  eleventyConfig.addNunjucksAsyncFilter(
3    "jsmin",
4    async function (code, callback) {
5      try {
6        const minified = await minify(code);
7        callback(null, minified.code);
8      } catch (err) {
9        console.error("Terser error: ", err);
10        callback(null, code);
11      }
12    }
13  );
14};
1{% set js %} {% include "scripts/search.js" %} {% endset %}
2<script>
3  {
4    {
5      js | jsmin | safe;
6    }
7  }
8</script>
1// .eleventy.js
2const { DateTime } = require("luxon");
3module.exports = function (eleventyConfig) {
4  eleventyConfig.addFilter("readableDate", (dateObj) => {
5    return DateTime.fromJSDate(dateObj, { zone: "utc" }).toFormat(
6      "dd LLL yyyy"
7    );
8  });
9
10  // <https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-date-string>
11  eleventyConfig.addFilter("htmlDateString", (dateObj) => {
12    return DateTime.fromJSDate(dateObj, { zone: "utc" }).toFormat("dd-LL-yyyy");
13  });
14
15  eleventyConfig.addFilter("sitemapDateTimeString", (dateObj) => {
16    const dt = DateTime.fromJSDate(dateObj, { zone: "utc" });
17    if (!dt.isValid) {
18      return "";
19    }
20    return dt.toISO();
21  });
22};
1{{ page.inputPath | lastModifiedDate | htmlDateString }}
1# Highlight line 2
2
3```js/2
4// lines of codes
5```
1# Highlight line 2 to 4
2
3```js/2-4
4// lines of codes
5```
1# Highlight line 2, 4
2
3```js/2,4
4// lines of codes
5```
1# Delete line 2 (red highlight)
2# and add line 4 (green highlight)
3
4```js/4/2
5// lines of codes
6```
1~~~ js {{ "{% raw " }}%}
2// line of codes
3{{ "{% endraw " }}%}
4~~~
1# Install
2# npm i markdown-it-texmath --save-dev # original, whitespace problem
3npm i git+https://github.com/dinhanhthi/markdown-it-texmath.git --save-dev
1// .eleventy.js
2const tm = require("markdown-it-texmath"); // or local folder if you use a customized version
3module.exports = function (eleventyConfig) {
4  md = require("markdown-it")().use(tm, {
5    engine: require("katex"),
6    delimiters: "dollars",
7    katexOptions: { macros: { "\RR": "\mathbb{R}" } },
8  });
9};
1<!-- Add in <head> -->
2<link
3  rel="stylesheet"
4  href="<https://cdn.jsdelivr.net/npm/katex/dist/katex.min.css>"
5/>
6
7<!-- Save to local and modify (or just use) -->
8<link
9  rel="stylesheet"
10  href="<https://cdn.jsdelivr.net/npm/markdown-it-texmath/css/texmath.min.css>"
11/>
1# Insert normally,
2![description](/path/to/image)
1# With custom classes
2# (using markdown-it-attrs)
3![](){:.custom-class}
1|- sample_posts/
2| |- post.md
3| |- img/
4| | |- figure1.png
5| |- figure2.png
6|- src/
1const pageAssetsPlugin = require("eleventy-plugin-page-assets");
2
3module.exports = function (eleventyConfig) {
4  eleventyConfig.addPlugin(pageAssetsPlugin, {
5    mode: "parse",
6    postsMatching: "sample_posts/*.md",
7    recursive: true,
8  });
9};
1<div class="hsbox">
2  <div class="hs__title">
3    <!-- Custom Title -->
4  </div>
5  <div class="hs__content">
6    <!-- Custom markdown texts -->
7  </div>
8</div>
1::: hsbox Custom Title Custom markdown texts :::
1.use(require('markdown-it-container'), 'hsbox', {
2  validate: function (params) {
3    return params.trim().match(/^hsbox\s+(.*)$/);
4  },
5  render: function (tokens, idx) {
6    var m = tokens[idx].info.trim().match(/^hsbox\s+(.*)$/);
7    if (tokens[idx].nesting === 1) {
8      // opening tag
9      return '<div class="hsbox"><div class="hs__title">'
10        + markdownItp.renderInline(m[1])
11        + '</div><div class="hs__content">';
12    } else {
13      // closing tag
14      return '</div></div>';
15    }
16  }
17})
1// .eleventy.js
2
3module.exports = function (eleventyConfig) {
4  eleventyConfig.addPairedShortcode("markdown", (content, inline = null) => {
5    return inline
6      ? markdownLibrary.renderInline(content)
7      : markdownLibrary.render(content);
8  });
9};
1{% markdown %}
2<!-- html tags -->
3<!-- no need spaces before/after -->
4{% endmarkdown %}
1<!-- not working -->
2<div>__abc__</div>
3<!-- not working -->
4<div>
5__abc__
6</div>
1<!-- working -->
2<div>
3
4__abc__
5</div>
1<!-- not working -->
2<div class="list-of">
3  {% for item in cv.education.list %}
4  <div class="where">{{ item.where }}</div>
5  <div class="title">{{ item.title }}</div>
6  <div class="date">{{ item.date }}</div>
7  {% endfor %}
8</div>
1<!-- working -->
2<div class="list-of">
3
4{% for item in cv.education.list %}
5<div class="where">{{ item.where }}</div>
6<div class="title">{{ item.title }}</div>
7<div class="date">{{ item.date }}</div>
8{% endfor %}
9</div>
1<div class="hsbox">
2  <div class="hs__title">
3    <!-- Custom Title -->
4  </div>
5  <div class="hs__content">
6    <!-- Custom markdown texts -->
7  </div>
8</div>
1{% hsbox "Custom Title" %}
2<!-- Custom markdown texts -->
3{% endhsbox %}
1// .eleventy.js
2
3module.exports = function (eleventyConfig) {
4  eleventyConfig.addPairedShortcode("hsbox", (content, title) => {
5    return (
6      '<div class="hsbox"><div class="hs__title">' +
7      markdownLibrary.renderInline(title) +
8      '</div><div class="hs__content">' +
9      markdownLibrary.render(content) +
10      "</div></div>"
11    );
12  });
13};
1<a href="custom-url">ref</a>
1// .eleventy.js
2module.exports = function (eleventyConfig) {
3  eleventyConfig.addShortcode("ref", function (url) {
4    return (
5      '<sup><a href="' +
6      url +
7      '" rel="noopener noreferrer" target="_blank">[ref]</a></sup>'
8    );
9  });
10};
1{
2  "tags": ["posts"]
3}
1// .eleventy.js
2const dataDir = "notes/_data";
3
4module.exports = function (eleventyConfig) {
5  return {
6    dir: {
7      data: dataDir,
8    },
9  };
10};
1// You CAN'T use below
2import settings from "../../../notes/_data/settings.json";
3// ❗ There will be an error
4// Cannot use import statement outside a module
1const settings = require("./" + dataDir + "/settings.json");
2module.exports = {
3  environment: process.env.ELEVENTY_ENV, // the same place with this
4  settings: settings,
5};
1env.settings.someThing;
1// .eleventy.js
2module.exports = function (eleventyConfig) {
3  return {
4    dir: {
5      input: ".",
6      includes: "_includes",
7      data: "_data",
8      output: "_site",
9    },
10  };
11};
1// _data/dataUrls.json
2[
3  {
4    "name": "abc",
5    "url": "<http://abc.com>"
6  },
7  {
8    "name": "xyz",
9    "url": "<http://xyz.com>"
10  }
11]
1<!-- in a .njk file -->
2{% for item in dataUrls %} {{ item.name }} {{ item.url }} {% endfor %}
1// _data/cat_icon.json
2{
3  "Project-based Learning": {
4    "svg": "/img/cats/project.svg"
5  },
6  "MOOC": {
7    "svg": "/img/cats/mooc.svg"
8  }
9}
1// .eleventy.js
2const catIcon = require("./_data/cat_icon.json")
3// usage
4catIcon[page.data.tags[1]].svg,
5
6// .njk
7catIcon[page.data.tags[1]].svg,
1// _data/helpers.js
2module.exports = {
3  currentYear() {
4    const today = new Date();
5    return today.getFullYear();
6  },
7};
1<!-- in a .njk file -->
2<div>{{ helpers.currentYear() }}</div>
1// in .eleventy.js
2module.exports = function (eleventyConfig) {
3  eleventyConfig.addShortcode("list_repos", getLiList);
4  async function getRepoData(_url) {
5    const response = await fetch(_url);
6    return await response.json();
7  }
8  async function getLiList() {
9    function compare(a, b) {
10      if (a.name.toLowerCase() < b.name.toLowerCase()) {
11        return -1;
12      }
13      if (a.name.toLowerCase() > b.name.toLowerCase()) {
14        return 1;
15      }
16      return 0;
17    }
18    // escape HTML tags
19    function htmlEntities(str) {
20      return String(str)
21        .replace(/&/g, "&amp;")
22        .replace(/</g, "&lt;")
23        .replace(/>/g, "&gt;")
24        .replace(/"/g, "&quot;");
25    }
26    var repos = "";
27    const data = await getRepoData(
28      "<https://api.github.com/users/dinhanhthi/starred?page=1&per_page=10000>"
29    );
30    data.sort(compare).forEach((obj) => {
31      repos +=
32        "<li>" +
33        '<a href="' +
34        obj.html_url +
35        '" target="_blank">' +
36        obj.name +
37        "</a>" +
38        " by <i>" +
39        obj.owner.login +
40        "</i> β€” " +
41        htmlEntities(obj.description) +
42        "</li>";
43    });
44    return "<ol>" + repos + "</ol>";
45  }
46};
1// in post
2{% list_repos %}
1{
2  "scripts": {
3    "local-build": "ELEVENTY_ENV=local eleventy"
4  }
5}
1// .eleventy.js
2module.exports = function (eleventyConfig) {
3  if (process.env.ELEVENTY_ENV == "local") {
4    // do something locally
5  } else {
6    // do something on server
7  }
8};
1// _data/myProject.js
2module.exports = {
3  environment: process.env.ELEVENTY_ENV,
4};
1{% if myProject.environment == "local" %}
2  <style>{{ css | cssmin | safe }}</style>
3{% else %}
4  <style>{{ css | safe }}</style>
5{% endif %}
1// .eleventy.js
2module.exports = function (eleventyConfig) {
3  if (process.env.ELEVENTY_ENV == "local") {
4    eleventyConfig.ignores.add("notes"); // ignore folder notes
5    eleventyConfig.ignores.delete("sample_posts"); // use folder sample_posts
6  } else {
7    eleventyConfig.ignores.delete("notes"); // use folder notes
8    eleventyConfig.ignores.add("sample_posts"); // ignore folder sample_posts
9  }
10};
1// package.json
2{
3  "scripts": {
4    "local:build": "ELEVENTY_ENV=local eleventy",
5    "remote:build": "eleventy",
6  }
7}
1{% set a = [1,2] %}
2{% set a = (a.push(3),a) %}
1{% set items = {'a': 1, 'b': 2} %}
1// .eleventy.js -> create a new filter
2eleventyConfig.addFilter('setAttribute', function(dictionary, key, value) {
3	dictionary[key] = value;
4	return dictionary;
5});
6
7// usage
8{% set myDict = {"key1": 0, "key2": 0, "key3": 0}%}
9{% set myDict = myDict|setAttribute('key2', 123) %}
10{{myDict['key2']}} // pring "123" as expected
1{# Not working #} {% set url = "/bits/{{ data.slug }}" %} {# Working #} {% set
2url = ["/bits/", data.slug] | join %}
1// Problem of "Content-Security-Policy" (CSP)
2// src/_data/csp.js
3const CSP = {
4  regular: serialize([
5    // Inline CSS is allowed.
6    ["style-src", SELF, "<https://fonts.googleapis.com/>", quote("unsafe-inline")],
7    ["font-src", SELF, "<https://fonts.gstatic.com/>"],
8  ]),
9};
10
11// equivalent phrase (put in <head>)
12<meta http-equiv="Content-Security-Policy" content="default-src 'self'; font-src 'self' <https://fonts.gstatic.com/>; style-src 'self' <https://fonts.googleapis.com/> 'unsafe-inline';">
13// quote() -> for '', e.g. 'self'
14// "abc" -> doesn't mean 'abc' in <meta>
1// ./pages/search-index.json.njk
2// TypeError: Cannot read property 'svg' of undefined
3// Idea: actually, there are some posts which don't have the right front matter
4// Solution: try with below in .eleventy.js
5eleventyConfig.addCollection("onlyMarkdown", function(collectionApi) {
6	return collectionApi.getFilteredByGlob(["posts/*.*", "posts/others/*.md"]);
7});
8// and change in ./pages/search-index.json.njk
9{{ collections.onlyMarkdown | search | dump | safe | striptags(true) | escape }}