Migrating from Eleventy

Eleventy is a static site generator; Bascik is a build tool for HTML components. Both produce plain HTML with no client-side framework runtime. The main conceptual shift is that Bascik uses its own component format, plain HTML files resolved by tag name, instead of Nunjucks, Liquid, or Handlebars templates. Front matter and template inheritance map to build scripts and slot-based layout components.

Template Languages → HTML Component Files

Eleventy supports many template languages. In Bascik, all components are plain .html files. There is no template syntax, values are either hardcoded, injected as data-bascik-prop-* text props, passed as slot content, or generated by a <script data-bascik-build> block.

text
Before (Eleventy)           After (Bascik)
src/                        src/components/
  _includes/                  site-layout/
    base.njk                    site-layout.html
    nav.njk                   site-nav/
    footer.njk                  site-nav.html
  posts/                    src/pages/
    my-post.md                index.html
  index.njk                   about.html
  about.njk                   blog/
                                my-post.html

{% include %} → Bascik Component Tags

An Eleventy {% include 'nav.njk' %} becomes a self-contained Bascik component tag. The file name (hyphenated, without extension) is the tag name.

njk
{# src/_includes/nav.njk (Eleventy - before) #}
<nav class="nav">
  <a href="/">Home</a>
  <a href="/about">About</a>
  <a href="/blog">Blog</a>
</nav>

{# Used in a page with: #}
{% include "nav.njk" %}
html
<!-- src/components/site-nav/site-nav.html (Bascik - after) -->
<nav class="nav">
  <a href="/">Home</a>
  <a href="/about">About</a>
  <a href="/blog">Blog</a>
</nav>

<!-- Used in a page with: -->
<site-nav></site-nav>

{% extends %} / Template Inheritance → Slot-based Layout Components

Eleventy's {% extends 'base.njk' %} and {% block content %} pattern maps to a Bascik layout component that uses data-bascik-slot for the page content area and named slots for additional regions like the page title.

njk
{# src/_includes/base.njk (Eleventy - before) #}
<!DOCTYPE html>
<html lang="en">
<head>
  <title>{{ title }} - Acme</title>
  <link rel="stylesheet" href="/css/styles.css" />
</head>
<body>
  {% include "nav.njk" %}
  <main>{% block content %}{% endblock %}</main>
  {% include "footer.njk" %}
</body>
</html>
njk
{# src/about.njk (Eleventy - before) #}
---
title: About
---
{% extends "base.njk" %}
{% block content %}
<h1>About</h1>
<p>We build things.</p>
{% endblock %}

In Bascik, the layout component wraps only the repeated inner structure. The <html>, <head>, and <body> live in the page file.

html
<!-- src/components/site-layout/site-layout.html (Bascik - after) -->
<site-nav></site-nav>
<main class="content">
  <div data-bascik-slot></div>
</main>
<site-footer></site-footer>
html
<!-- src/pages/about.html (Bascik - after) -->
<!DOCTYPE html>
<html lang="en">
<head>
  <title>About - Acme</title>
  <link rel="stylesheet" href="/css/styles.css" />
</head>
<body>
  <site-layout>
    <h1>About</h1>
    <p>We build things.</p>
  </site-layout>
</body>
</html>

Front Matter Data → Hardcoded HTML or Build Scripts

Eleventy front matter (YAML between --- delimiters) injects variables into the template. Bascik has no front matter. Values that differ per page are either written directly into the HTML or supplied via data-bascik-prop-* props. Use a <script data-bascik-build> block when you want to read values from an external data file at build time.

njk
{# src/about.njk (Eleventy - before) #}
---
title: About
description: Learn about Acme Corp.
---
<h1>{{ title }}</h1>
<meta name="description" content="{{ description }}" />
html
<!-- src/pages/about.html (Bascik - after) -->
<!DOCTYPE html>
<html lang="en">
<head>
  <title>About - Acme</title>
  <meta name="description" content="Learn about Acme Corp." />
  <link rel="stylesheet" href="/css/styles.css" />
</head>
<body>
  <site-layout>
    <h1>About</h1>
  </site-layout>
</body>
</html>

For many pages sharing a data-driven title pattern, extract the title into a data-bascik-prop-title prop on the layout component, or generate the pages entirely from a build script.

Collections → <script data-bascik-build>

Eleventy's Collections API reads a directory of Markdown files and makes them available as a sorted array in templates. In Bascik, use a <script data-bascik-build> block that reads the same directory and outputs a list of HTML links directly into the page.

njk
{# src/blog/index.njk (Eleventy - before) #}
<ul>
  {%- for post in collections.post | reverse -%}
  <li>
    <a href="{{ post.url }}">{{ post.data.title }}</a>
    <time>{{ post.date | dateFilter }}</time>
  </li>
  {%- endfor -%}
</ul>
html
<!-- src/pages/blog/index.html (Bascik - after) -->
<ul>
  <script data-bascik-build>
    import { readdir, readFile } from 'node:fs/promises';
    import matter from 'gray-matter';

    const files = (await readdir('./content/posts'))
      .filter(f => f.endsWith('.md'))
      .sort()
      .reverse();

    const items = await Promise.all(files.map(async file => {
      const raw = await readFile(`./content/posts/${file}`, 'utf8');
      const { data } = matter(raw);
      const slug = file.replace('.md', '');
      return `<li><a href="/blog/${slug}">${data.title}</a></li>`;
    }));

    console.log(items.join('\n'));
  </script>
</ul>

Build scripts run first: A build script's output can itself contain Bascik component tags. They are resolved in the next pass, so you can output <post-card> tags from a build script and Bascik will expand them.

Passthrough File Copy → Automatic

In Eleventy, non-template files (CSS, images, fonts) must be explicitly added to the passthrough copy list in .eleventy.js. In Bascik, all non-.html files in src/pages/ are copied to dist/ automatically at build time with no configuration needed.

text
src/pages/
  index.html     ← transpiled
  about.html     ← transpiled
  css/
    styles.css   ← copied as-is
  img/
    hero.jpg     ← copied as-is
  fonts/
    inter.woff2  ← copied as-is

eleventy.config.js Plugins → Not Needed

Many Eleventy plugins exist to scope CSS, handle image optimization, or add syntax highlighting. Bascik handles CSS and JS scoping natively. For other transformations, use standard build tooling in your package.json build scripts (for example, PostCSS for global styles, or a syntax highlighter library in a build script).

Before and After: Blog Index Page

njk
{# src/index.njk (Eleventy - before) #}
---
layout: base.njk
title: Home
---
<section class="hero">
  <h1>Welcome to Acme Blog</h1>
  <p>The latest from our team.</p>
</section>

<section class="posts">
  <h2>Recent Posts</h2>
  <ul>
    {%- for post in collections.post | reverse | limit(5) -%}
    <li><a href="{{ post.url }}">{{ post.data.title }}</a></li>
    {%- endfor -%}
  </ul>
</section>
html
<!-- src/pages/index.html (Bascik - after) -->
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Home - Acme Blog</title>
  <link rel="stylesheet" href="/css/styles.css" />
</head>
<body>
  <site-nav></site-nav>
  <section class="hero">
    <h1>Welcome to Acme Blog</h1>
    <p>The latest from our team.</p>
  </section>
  <section class="posts">
    <h2>Recent Posts</h2>
    <ul>
      <script data-bascik-build>
        import { readdir, readFile } from 'node:fs/promises';
        import matter from 'gray-matter';
        const files = (await readdir('./content/posts'))
          .filter(f => f.endsWith('.md'))
          .sort().reverse().slice(0, 5);
        const items = await Promise.all(files.map(async f => {
          const { data } = matter(await readFile(`./content/posts/${f}`, 'utf8'));
          return `<li><a href="/blog/${f.replace('.md','')}">${data.title}</a></li>`;
        }));
        console.log(items.join('\n'));
      </script>
    </ul>
  </section>
  <site-footer></site-footer>
</body>
</html>