Salesforce, Python, SQL, & other ways to put your data where you need it

Need event music? 🎸

Live and recorded jazz, pop, and meditative music for your virtual conference / Zoom wedding / yoga class / private party with quality sound and a smooth technical experience

Inspecting an existing 11ty project

04 Oct 2021 🔖 web development jamstack
💬 EN

Table of Contents

Cassey Lottman’s “Diving into an existing 11ty project” from the 11ty Meetup is great. You can skim a TL;DW on Cassey’s blog, but I recommend listening to the talk, because there’s a bit more “why” behind each of the 6 concepts to orient yourself to in someone else’s 11ty project.

As a case study, let’s follow Cassey’s methodology to look at web-site-11ty-07-movable-git, the most recent 11ty example code I posted on GitHub (which supports my article “Jekyll doesn’t do components? Liar!“).

Screenshot from Cassey's talk


1. Installation

My site has a package.json file – use Node Package Manager commands, not Yarn package manager commands, to install the Node.js (JavaScript) libraries (Eleventy, Cross-Env, & Dotenv) that my sample site depends on onto your local computer. Doing so allows you to run the site in development mode as https://localhost.

Interestingly, at the time I wrote this article, my package.json used a lot of npx eleventy, but Cassey pointed out that I needed to change that to npx @11ty/eleventy. It seems I’ve only been getting away with npx eleventy because Netlify and my local installation of @11ty/eleventy know what to do, so I guess I have a lot of retrofitting to do on all my published 11ty repos. Whoops!


2. Inputs & outputs

Outputs

I can’t remember when or why I started doing this, but for some reason, I always hard-code my .eleventy.js configuration files to write output to a folder called dist instead of the default folder called _site (which also happens to be Jekyll’s default output folder name).

// Clarify which folder is for input and which folder is for output
return {
  dir: {
    input: "src",
    output: "dist",
  },
};

Gatsby outputs to a folder called public, so I really couldn’t tell you whose random 11ty starter site I seem to have picked this habit up from back when I was learning 11ty.

Maybe I was trying to use an output folder that made sense but wasn’t the default of any major static site generator. That sounds like something I’d do.

Inputs

I don’t bother, in this project, to change where 11ty finds “include” templates (HTML that goes inside things), “layout” templates (HTML that goes around things), or “data” files, but Cassey explains where to look to verify that I didn’t.


3. Configuration

In my .eleventy.js:

  • I enable parameters for includes
      eleventyConfig.setLiquidOptions({
        dynamicPartials: true,
        strict_filters: true,
      });
    
  • I set up a couple of universal shortcodes that make it easier to leverage little bits of JavaScript like JSON.stringify(paramHere) and String(Date.now()) and { ...paramHere }.
      // Make it easy to throw a build-timestamp into my HTML
      eleventyConfig.addShortcode("version", function () {
        return String(Date.now());
      });
    
      // JSONify filter
      eleventyConfig.addShortcode("jsonify", function (text) {
        return JSON.stringify(text);
      });
    
      // getObjectSpread filter
      eleventyConfig.addFilter("getObjectSpread", function (obj) {
        return { ...obj };
      });
    
  • I make sure images, hand-written JavaScript, hand-written CSS, etc. have a way of getting into my production site:
      eleventyConfig.addPassthroughCopy("./src/static/");
    
  • Since I make heavy use of *.11tydata.json files when I use 11ty (more on that under “gotchas” below), I always include this:
      // Get proper data into .11tydata.json files
      eleventyConfig.setDataDeepMerge(true);
    
  • I don’t want to let Git tracking ignore my /README.md, but I don’t want 11ty to try to mess with it (although I’m pretty sure that might not actually be an issue anymore now that I put things in a src folder and set input to src), so I tend to use an explicit /.eleventyignore file and then tell 11ty not to skip over processing files just because they’re ignored by Git:
      module.exports = function (eleventyConfig) {
        eleventyConfig.setUseGitIgnore(false);
      };
    

I don’t typically do any of the things Cassey demonstrates as examples of configuration, like explicitly declare which template formats I plan to use, putting my layout files in nonstandard places, running my Markdown through a custom Markdown engine, etc.

One exception is an experiment I’ve been working on, trying to migrate a site from Jekyll to 11ty. (I haven’t quite gotten it working yet.)

With migrations, it can be handy to put my includes and layouts in the same folders they started in over in Jekyll, and just change where 11ty expects to see them. But I don’t believe I’ve done that in any public repositories.


4. Debugging

Interesting – it looks like not only is my script in package.json for debugging using an older style of debugging, but it’s not even compatible with the Windows environment on which I typically develop.

I should probably change this:

{
  ...
  "scripts": {
    ...
    "debug": "DEBUG=* npx eleventy"
  }
  ...
}

To this:

{
  ...
  "scripts": {
    ...
    "debug": "cross-env DEBUG=* npx @11ty/eleventy"
  }
  ...
}

Honestly, it doesn’t matter much, because not only have I never used the new {{ myTemplateVariableName | log }} syntax … I haven’t even used debug mode.


5. Style processing

Plain old CSS

I don’t do any special CSS processing in this one.

There’s just this in .eleventy.js:

// Pass things straight through from "src" to "dist"
eleventyConfig.addPassthroughCopy("./src/static/");

And a /src/static/css/global.css file:

.pink-div {
	background-color: #d81b60;
	color: black;
}
.blue-div {
	background-color: #1e88e5;
	color: white;
}
.task.task-odd {
	background-color: #ffc107;
	color: black;
}
.task.task-even {
	background-color: #004d40;
	color: white;
}

And a <LINK> tag consuming it in the HTML at /src/_includes/layouts/base.liquid:

<!DOCTYPE html>
<html>
  <head><link rel="stylesheet" href="/static/css/global.css"/></head>
  ...
</html>

Tailwind CSS

But I definitely have plenty of preprocessing going on in the “11ty-tailwind-jit” repository. Cassey walks you through figuring out what people like me are up to in 11ty codebases like that.

In my “Tailwind” case (which might be out-of-date as of Tailwind CSS version 2.2… I should look into whether somehow using the Tailwind CLI is useful…):

  1. The .eleventy.js config file, I tell 11ty that when it’s running locally in “development” mode, it needs to rebuild when it sees that PostCSS has altered the contents of /dist/tailwindoutlive.css.
     // Because we're running PostCSS as a separate process, Eleventy doesn't know when styles have changed.
     // Tell Eleventy to watch this CSS file so it can live-update changes into the browser for us.
     eleventyConfig.addWatchTarget("./dist/tailwindoutlive.css");
    
  2. The scripts in the package.json file all ensure that an environment variable named NODE_ENV gets explicitly set to production or development before running 11ty, which is something Tailwind CSS relies on to decide how it should run.
  3. There’s a /utils/postcss.js file that actually makes running Tailwind CSS with PostCSS happen.
  4. There’s a /_data/tailwindcss.js file importing the /utils/postcss.js util the same way it might import, say, the “Sanity client” library or a “Contentful client” library. It passes this utility function the contents of the /src/tailwind/tailwind.css file (a pretty standard type of file to have in a Tailwind project).
     let thePostCSS = require("../../utils/postcss.js");
     module.exports = async function () {
       return await thePostCSS("tailwind/tailwind.css", (input) => input);
     };
    
  5. There’s a “computed pseudo-page” (see “gotchas” below) whose job is to summon this tailwindcss.js file and turn its output into a URL called ...mysite.com.../tailwindoutlive.css. I’ve put it at /src/computed_pseudo_pages/singleton_css_file.njk.

     ---
     permalink: /tailwindoutlive.css
     ---
     {{ tailwindcss | safe }}
    
  6. There’s a /src/_includes/layouts/html_head.liquid file that makes sure ...mysite.com.../tailwindoutlive.css gets used by every page on the site:
     <link rel="stylesheet" href="/tailwindoutlive.css"/>
    
  7. There’s a tailwind.css file, which is a pretty common file to have in a Tailwind project, at /src/tailwind/tailwind.css specifically. In this project specifically, a “data” file used in a singleton-page template explicitly passes the contents of this CSS file to the “util” that actually knows how to run Tailwind CSS.
     @tailwind base;
     @tailwind components;
     @tailwind utilities;
    
     a {
       @apply text-indigo-500;
     }
    
  8. There’s a tailwind.config.js file, which is a pretty common file to have in a Tailwind project, at /src/tailwind/tailwind.config.js specifically. The most important thing in this one is that it tells Tailwind to look for Tailwind-esque vocabulary like bg-red-500 inside of all files found in certain filepaths that end in certain file extensions (HTML under dist or Liquid/Nunjucks/HTML under src). The “util” file I mentioned above – the one that “knows how to run Tailwind CSS – is hard-coded with the path to this file in it, as is /src/postcss.config.js, which I’ll mention next.
     module.exports = {
       purge: {
         content: [
         "./dist/**/*.html",
         "./src/**/*.liquid",
         "./src/**/*.njk",
         "./src/**/*.html",
         ],
         options: {
           safelist: [],
         },
       },
     };
    
  9. Finally, there’s a /src/postcss.config.js file, which is a pretty common file to have in a Tailwind project. Honestly, at this point, even reading my own code, I don’t remember what explicitly calls it (or if there’s something implicit that will be looking for it there). But things work if I put it there. Man – 9 files, most of which I barely understand the interconnections of, 1 of which I can’t even find the interconnection to … I really ought to see if the new Tailwind v2.2 CLI can let me refactor to a simpler architecture.
     module.exports = {
       plugins: [
         require(`@tailwindcss/jit`)(`./src/tailwind/tailwind.config.js`),
         require(`autoprefixer`),
       ],
     };
    

6. Gotchas

The data cascade! Cassey covers the data cascade under “gotchas.”

I love doing weird stuff with the data cascade in 11ty, because I love feeling like I don’t have to worry too much about matching the folder names in which I store various files to the URLs that end up in my site.

It makes me feel more prepared for redeveloping the site in another site generator, or for flipping the source of data between filesystem-based data and headless-API-based data.

Markdown folder

I keep all of my .md files that are intended to be transformed into 1 URL per file in sub-folders of folder called /src/markdown, like advanced and basic. I then use 2 directory data files apiece within those sub-folders to control exactly how the .md files turn into URLs:

  1. File: /src/markdown/advanced/advanced.11tydata.js ensures there’s no /markdown/advanced/ in the URL for “advanced” pages:
     module.exports = {
       eleventyComputed: {
         documentData: (data) => {
           return data;
         },
         permalink: (data) => {
           return `${( data.page.filePathStem.endsWith('/index') ? '' : '/')}/index.html`;
         },
       },
     };
    
  2. File: /src/markdown/advanced/advanced.11tydata.json forces “advanced” pages to use my “advanced template” as a layout, and ensures that they get added to the “pages” collection so my standard footer can easily link to them as a sort of sitemap.
     {
       "layout": "layouts/advanced_page.liquid",
       "tags": ["pages"]
     }
    
  3. File: /src/markdown/basic/basic.11tydata.js ensures there’s no /markdown/basic/ in the URL for “basic” pages:
     module.exports = {
       eleventyComputed: {
         documentData: (data) => {
           return data;
         },
         permalink: (data) => {
           return `${( data.page.filePathStem.endsWith('/index') ? '' : '/')}/index.html`;
         },
       },
     };
    
  4. File: /src/markdown/basic/basic.11tydata.json forces “basic” pages to use my “basic template” as a layout, and ensures that they get added to the “pages” collection so my standard footer can easily link to them as a sort of sitemap.
     {
       "layout": "layouts/basic_page.liquid",
       "tags": ["pages"]
     }
    

I do an even fancier variation on this in the “web-site-11ty-05-markdown-pets” repository, where “cats” and “dogs” share some characteristics as “pets”.

Deep data merging

Don’t forget – *.11tydata.* files usually rely upon deep data merging, so remember that this will probably be in the .eleventy.js configuration file of any project that gets really fancy with template/directory data files.

// Get proper data into .11tydata.json files
eleventyConfig.setDataDeepMerge(true);

Computed pseudo-pages folder

Then there’s my /src/computed_pseudo_pages/ folder, which you can see several examples of:

  1. Singletons: All files that directly set a permalink value in their front matter use a filename that starts with singleton_.
  2. Loops: All files that leverage pagination to set a permalink value for iterations over the data in a /_data/ file or an 11ty collection use a filename that starts with loop_.

Further reading

--- ---