Table of Contents
Page 9 of 10

Eleventy Excellent

Where the call sites go

The starter declares its assets where they are used, with a {% css %} or {% js %} block wrapping an include. Baseline works one bundle per directory instead, so you will not treat those blocks the same way. Delete the CSS ones, translate the JS ones, and move two things to the config.

Delete the eleven CSS blocks

They all look like this, and they all go:

{% css "local" %}
  {% include "css/custom-card.css" %}
{% endcss %}

Delete the block, not the stylesheet. The file it included is one of the ten now listed in local/index.css, so the CSS still ships, it just ships on every page instead of the pages that use it. That is the per-page scoping you are giving up, and it is the one trade here you might not want to make.

Two of those files are Markdown rather than templates, which is worth knowing before you go looking: a {% css %} block can sit in a post.

View the eleven blocks and the file each one included
file included
_includes/partials/card-blog.njk custom-card.css
_includes/partials/card-tag.njk custom-card.css
_includes/partials/details.njk details.css
_includes/partials/gallery.njk gallery.css
_includes/partials/main-nav.njk nav-main-drawer-cls.css
_includes/partials/pagination.njk pagination.css
_layouts/post.njk post.css, footnotes.css
content/pages/built-with.njk custom-card.css
content/pages/get-started.md custom-card.css
content/pages/styleguide.njk styleguide.css, table.css
content/posts/2022/2022-11-02-markdown.md table.css

Four of them include the same custom-card.css, which is the clearest argument for the bundle: the old arrangement was already shipping it four ways.

Two things worth knowing about the bundled CSS

Inline or linked, measured. The starter inlines its whole stylesheet into every page in production. Baseline links it. Linked costs about 9.6KB on a cold first visit and saves about 34KB on every page after that, so it breaks even on the second page and removes 1.16MB of duplicated CSS across the site. That is bytes rather than perceived speed: a single-page visit does genuinely make more round trips than it did.

@import-glob only fires in the entry file. Do not put a thin entry point above a stylesheet that uses it. Globs one level down are never processed, silently drop everything they matched, and survive into the output as dead text. global/index.css globs three directories safely, because it is the entry itself. Had it been reached through a wrapper, those three lines would have shipped to the browser as text and taken every composition, block and utility with them. local/index.css lists its ten imports by hand for that reason. It could glob today, but it would break quietly if anything were ever put above it.

Translate the five JS blocks to inlineESbuild

The JavaScript keeps the scoping the CSS gave up, because these blocks are genuinely page-scoped. Swap each block for the filter, in place:

{% js "defer" %}
  {% include "scripts/nav-drawer.js" %}
{% endjs %}
{% set jsNavDrawerPath = _baseline.paths.assets ~ "js/bundle/nav-drawer.js" %}
{{ jsNavDrawerPath | inlineESbuild({ minify: true }) | safe }}

_baseline.paths.assets is a Baseline global, so the path is not hard-coded against the directory conventions. The five sites are details.njk, gallery.njk, main-nav.njk twice, and base.njk for the theme toggle.

Remove two registrations, then add a passthrough

Take the pre-build events and the CSS bundle out of the config. The plugin does both jobs now, and leaving them in means two things writing to the same place:

// eleventy.config.js, all of this goes
eleventyConfig.on('eleventy.before', async () => {
  await events.buildAllCss();
  await events.buildAllJs();
});

eleventyConfig.addBundle('css', {hoist: true});

Then add one entry back. The custom elements are loaded with <script src> rather than imported, so no entry point reaches them and nothing compiles them. They need copying across as they are:

// eleventy.config.js, the last entry is the new one
eleventyConfig.addPassthroughCopy({
  'src/assets/images/favicon/*': '/',
  'node_modules/lite-youtube-embed/src/lite-yt-embed.{css,js}': `assets/components/`,

  // Custom elements referenced by <script src> rather than bundled.
  'src/assets/js/components/': `assets/js/components/`
});

While you are in there, repoint the two tags that load them. The directory they name moved from src/assets/scripts/ to src/assets/js/ at the top of this chapter, so both are one word out:

{# src/_includes/webc/custom-masonry.webc #}
<script src="/assets/js/components/custom-masonry.js" type="module"></script>

{# src/_layouts/base.njk #}
<script type="module" src="/assets/js/components/custom-easteregg.js"></script>

Neither of those would have failed the build. They would have 404'd in the browser, on the two pages that use them.

Watch for template syntax in your asset sources

Your asset sources cannot contain template syntax any more. Under the old pipeline the scripts are pulled in through an include, so Nunjucks substitutes the {{ … }} inside them by accident of the mechanism. Baseline compiles the file directly, so those braces ship to the browser verbatim. Nothing fails, the build is green, and the JavaScript is broken.

Grep every asset source for {{ and {% before you move it. Two scripts have it here, theme-toggle.js reading two colours and nav-sub.js reading a breakpoint. Hand those values in from the template instead, in base.njk, as a sibling below <baseline-head> and above the theme toggle you inlined a moment ago:

View the block in place in `base.njk`
<baseline-head></baseline-head>
<!-- the theme-color metas and the share card sit here -->

<script>
  window.__site = {
    themeDark: '{{ meta.themeDark }}',
    themeLight: '{{ meta.themeLight }}',
    navBreakpoint: '{{ designTokens.viewports.navigation }}'
  };
</script>

{% set jsThemeTogglePath = _baseline.paths.assets ~ "js/bundle/theme-toggle.js" %}
{{ jsThemeTogglePath | inlineESbuild({ minify: true }) | safe }}

The comment stands in for what the head step already put there. Nothing above the placeholder changes.

Order matters here, because the object has to exist before anything reads it. The head is the earliest place it can go, and it covers nav-sub.js too, which is inlined further down in main-nav.njk. Read the values defensively on the other side, window.__site?.themeDark || '#2e2e2e', so a page that somehow renders without the block still runs.