@augeo/tabularasa
v1.2.0
Published
Library + CLI for building Shopify themes from a colocated component tree
Readme
TabulaRasa
Build Shopify themes from colocated components. Author your button/ directory
once — button.liquid, button.ts, button.css, button.test.ts,
button.schema.ts — and TabulaRasa compiles it into the flat, namespaced files
Shopify expects.
Why
- Colocation. Every component lives in one directory. Liquid markup,
TypeScript behavior, CSS, tests, and schema are authored side-by-side instead
of scattered across
sections/,snippets/, andassets/. - Layering. TabulaRasa ships with a baseline component library; your theme
shadows any file. Drop in a
button.cssto restyle without touching markup, TS, or schema. Same pattern as Nuxt Layers or Gatsby shadowing. - Typed schemas. Author
{% schema %}blocks in TypeScript with autocomplete derived from Shopify's authoritative JSON Schemas. The build executes the file and injects the result. - Private blocks for free. Nest a
blocks/directory under a section or block and its children become private theme blocks — emitted with Shopify's_prefix and auto-merged into the parent's schema. No manual filename mangling. - Additive build. Only files prefixed
built--(or_built--for private blocks) are written or cleaned. Hand-written theme files are preserved, so you can adopt incrementally.
Contents
Example
A consumer theme writes one directory per component:
src/
├── sections/
│ └── hero/
│ ├── hero.liquid
│ ├── hero.css
│ └── hero.schema.ts
└── components/
└── card/
├── card.liquid
└── card.csssrc/sections/hero/hero.liquid:
<section class="tr-hero">
<h2>{{ section.settings.heading }}</h2>
{% render '@/components/card', title: 'Hello', body: 'World' %}
</section>src/sections/hero/hero.schema.ts:
import { defineSchemaSection } from "@augeo/tabularasa/schema";
export const schema = defineSchemaSection({
name: "Hero",
settings: [
{ type: "text", id: "heading", label: "Heading", default: "Welcome" },
],
presets: [{ name: "Hero" }],
});Compile:
tabularasa buildOutputs sections/built--sections--hero.liquid,
snippets/built--components--card.liquid, etc. — flat and namespaced, the way
Shopify wants.
See the 📄 example/ directory for a working consumer theme.
Install
npm install -D @augeo/tabularasaThis installs the tabularasa CLI and the @augeo/tabularasa/schema import for
schema authoring.
Authoring
Each component is a directory under src/<type>/<name>/ where <type> is
sections, blocks, or components. Files inside share the directory name:
| File | Role |
| ------------------ | -------------------------------------------- |
| <name>.liquid | Markup (required — anchors the component) |
| <name>.ts | Bundled and injected into {% javascript %} |
| <name>.css | Injected into {% stylesheet %} |
| <name>.schema.ts | Typed schema (sections + blocks only) |
| <name>.test.ts | Colocated test file, picked up by vitest |
Components reference other components with the @/ alias:
{% render '@/components/button', label: 'Continue' %}@/ resolves through the merged layer tree, so a consumer's button.liquid
automatically shadows the baseline.
Schemas
Author schemas in TypeScript with full type inference:
import { defineSchemaSection } from "@augeo/tabularasa/schema";
export const schema = defineSchemaSection({
name: "Hero",
settings: [
{ type: "text", id: "heading", label: "Heading" },
{
type: "select",
id: "alignment",
label: "Alignment",
options: [
{ value: "left", label: "Left" },
{ value: "center", label: "Center" },
],
default: "center",
},
],
});Types are codegen'd from Shopify's authoritative schemas in
theme-liquid-docs. Update via
npm run docs:update.
Inline {% schema %} blocks in .liquid files are a build error — single
source of truth.
Private blocks
Shopify treats theme block files prefixed with _ as private — hidden from
the merchant's block picker, renderable only via a parent's
{% content_for "blocks" %}. TabulaRasa expresses this structurally: a
blocks/ directory nested under a section or block emits its children with the
_ prefix automatically.
src/sections/hero/
├── hero.liquid
├── hero.schema.ts
└── blocks/
└── feature/
├── feature.liquid
└── feature.schema.tsCompiles to:
sections/built--sections--hero.liquid
blocks/_built--sections--hero--blocks--feature.liquidThe parent's schema auto-merges discovered children into its blocks: [] array
— sorted by directory name and prepended; any explicit entries you list (e.g.
globally-shared block types) appended after. Nesting is recursive: a private
block can have its own blocks/ subdir.
Top-level src/blocks/* files remain public (no _ prefix).
Layering
TabulaRasa walks an ordered list of layers and merges them per-file. The default is:
- Consumer — your theme's
src/(process.cwd()). @augeo/tabularasa— the package's baselinesrc/.
For each component slot (liquid, ts, css, schema), the first layer that
has the file wins. Drop just a button.css in your consumer to override styles;
the baseline's button.liquid and button.ts are inherited.
Build
tabularasa buildRun from the theme root. Outputs go to sections/built--*.liquid,
blocks/built--*.liquid, and snippets/built--*.liquid — prefixed so they
coexist with hand-written files in the same directories.
Watch mode
tabularasa devRuns an initial build, then watches each layer's src/ and rebuilds on file
changes (add, edit, delete). Build failures log and keep the watcher alive. Pair
with shopify theme dev (in another terminal or via
concurrently) — tabularasa dev
writes the built files; shopify theme dev uploads them.
Committing the output
Shopify imports themes from git, so built--* files (and _built--* for
private blocks) need to be committed alongside source. Two configs keep the
noise down:
.gitattributes — marks output as generated so GitHub collapses it in PR diffs
and excludes it from language stats:
sections/built--*.liquid linguist-generated=true
snippets/built--*.liquid linguist-generated=true
blocks/built--*.liquid linguist-generated=true
blocks/_built--*.liquid linguist-generated=true.prettierignore — skips the output so Prettier doesn't reformat it between
builds:
sections/built--*.liquid
snippets/built--*.liquid
blocks/built--*.liquid
blocks/_built--*.liquidLearn More
- 📄 Build Spec — pipeline, alias rules, conventions
- 📄 Testing Conventions — how to write tests against the built output
- 📄 Library Conversion Plan — phased plan for publishing
@augeo/assay— the test runner TabulaRasa uses
Future Plans
tabularasa.config.ts— consumer-defined layer list, enabling N-layer composition (e.g., a community component pack between consumer and baseline).- More baseline components — currently just
buttondemo. Expanding to a real default set.
