This is the entire idea behind Liquid Loom:
src/theme/sections/home/hero.liquid
↓
dist/theme/sections/hero.liquid
The source stays where a developer would look for it. The output lands where Shopify requires it.
That small mapping resolves a persistent tension in Shopify theme development. Shopify themes use a prescribed directory structure, and most deployable directories do not support arbitrary subfolders. A growing codebase, however, is easier to reason about when related sections, snippets, styles, and behavior can live together by feature.
Liquid Loom is an open-source, source-first framework built around that boundary. It lets developers author an Online Store 2.0 theme in an organized workspace, compile modern frontend assets through Vite and Tailwind CSS, and generate a conventional dist/theme/ directory that Shopify CLI can preview or push directly.
It does not replace Liquid. It does not add a storefront runtime. It makes the build between authored source and Shopify-ready output explicit, deterministic, and testable.
Why source-first theme development matters
Shopify's runtime contract is useful precisely because it is conventional. A deployable theme has recognizable directories such as layout, sections, snippets, templates, config, locales, and assets. The platform, theme editor, Theme Check, and Shopify CLI all understand that shape.
The problem begins when the deployment format also becomes the authoring format.
Imagine a theme with dozens of sections. A homepage hero, featured collection, product gallery, product recommendations, cart drawer, search interface, and global navigation all sit beside one another in a flat sections/ directory. Their related snippets are separated into another flat directory. JavaScript and CSS often accumulate around them without a clear ownership boundary.
The theme is valid, but the source tree stops communicating how the product is organized.
Liquid Loom treats Shopify's directory structure as a build target, not a limit on how source must be arranged. Developers can group files by concern:
src/
├── entrypoints/
│ └── theme.js
├── public/
│ └── icons/
│ └── cart.svg
├── styles/
│ └── theme.css
└── theme/
├── sections/
│ ├── home/
│ │ └── hero.liquid
│ └── products/
│ └── main-product.liquid
└── snippets/
└── product/
└── price.liquid
The build then produces Shopify's expected structure:
dist/theme/
├── assets/
│ ├── cart.svg
│ ├── style.css
│ └── theme.js
├── sections/
│ ├── hero.liquid
│ └── main-product.liquid
└── snippets/
└── price.liquid
This is not abstraction for its own sake. The source tree tells developers where a feature belongs, while the generated tree remains boring enough for Shopify to understand.
The mapping contract is the framework
Liquid Loom deliberately separates two jobs.
The static mapper owns Liquid, JSON, and public files. Vite owns JavaScript and CSS. Both write into the same generated theme, but neither pretends the other kind of file works the same way.
| Authored source | Generated output | Behavior |
|---|---|---|
src/theme/sections/home/hero.liquid |
dist/theme/sections/hero.liquid |
Flattens the feature folder |
src/theme/snippets/product/price.liquid |
dist/theme/snippets/price.liquid |
Flattens the feature folder |
src/theme/templates/customers/account.json |
dist/theme/templates/customers/account.json |
Preserves a supported nested template path |
src/public/icons/cart.svg |
dist/theme/assets/cart.svg |
Copies a public asset into Shopify assets |
src/entrypoints/theme.js |
dist/theme/assets/theme.js |
Bundles and minifies with Vite |
src/styles/theme.css |
dist/theme/assets/style.css |
Compiles Tailwind and authored CSS |
Every supported source path has one predictable destination. Unsupported paths fail. Missing required theme files fail. Invalid theme JSON fails. The generated directory is disposable and should never be edited by hand.
This contract also fits Shopify CLI's own guidance. Shopify notes that theme commands need to run against the standard theme directory structure, and that projects using build tools may need to run those commands from their generated output. Liquid Loom does exactly that: shopify theme dev --path dist/theme.
Flattening needs a collision strategy
Feature folders create one important risk.
src/theme/sections/home/hero.liquid
src/theme/sections/campaigns/hero.liquid
Both files would map to:
dist/theme/sections/hero.liquid
A careless copier would let the last file win. The build would succeed, one section would disappear, and the result could depend on filesystem ordering.
Liquid Loom plans the complete copy operation first. If two source files resolve to one destination, it raises a BuildCollisionError before either file is copied. The error names the destination and both competing sources.
That behavior is more important than the nested folders themselves. Organization is only useful when the process that flattens it is safe.
Incremental builds without stale output
Liquid Loom keeps a manifest under .cache/manifest.json. For each static source file, the manifest stores a SHA-256 content fingerprint, its output path, and its byte size.
On the next build, a file is skipped only when three conditions are true:
-
its content fingerprint is unchanged
-
its mapped destination is unchanged
-
the expected output file still exists
Changed files are copied again. Deleted source files remove their stale generated output. A renamed file cannot leave an old deployable artifact behind.
The manifest is written to a process-specific temporary file and renamed only after the static build succeeds. An interrupted build therefore cannot leave a half-written cache that claims the output is current.
A typical unchanged build reports useful work directly:
LIQUID LOOM · production build
✓ 27 theme files · 0 copied · 27 cached · 0 removed · 21.4 KiB · 1.85s
output dist/theme
The goal is not a theatrical terminal. It is a build report that answers what changed, what was reused, what was removed, and where the deployable theme now lives.
Clean commands should be defensive
Build tools eventually delete generated files. That makes path validation part of the framework's safety model.
Before Liquid Loom runs a clean build or removes its cache, it resolves the project root, source root, and output root. It rejects an output target when that target is:
-
the project root itself
-
the source directory or one of its children
-
anywhere outside the repository
It applies the same principle when reading stale output paths from the cache manifest. A malformed or tampered cache entry cannot point cleanup outside dist/theme/.
These checks are covered by tests because a clean command should be convenient, not courageous.
A real OS 2.0 starter, without pretending to be a store
The repository includes a merchant-neutral Online Store 2.0 reference theme. It covers the surfaces needed to prove the workflow:
-
home, product, collection, cart, page, search, and 404 templates
-
JSON templates and editable header and footer section groups
-
theme settings exposed as CSS custom properties
-
semantic navigation, visible focus states, skip links, and reduced-motion support
-
standard Shopify product and cart forms that continue to work without JavaScript
-
small progressive enhancements for mobile navigation and quantity controls
This matters because a framework is easier to evaluate against real theme behavior than against an empty folder.
The starter is intentionally not a production storefront. It includes no merchant data, private integrations, analytics provider, store identifiers, customer workflow, or vertical-specific visual system. Its job is to demonstrate the build contract and sensible defaults. The presentation is meant to be replaced.
The quality gate is one command
pnpm validate reproduces the repository's CI gate locally. It runs:
test coverage
→ formatting check
→ clean production build
→ project validation
→ Shopify Theme Check
→ public-readiness scan
At the time of this release, the repository reports 15 passing tests, 96.92% line coverage, 91.67% branch coverage, 100% function coverage, and zero Shopify Theme Check offenses in the generated starter. CI runs the same validation command on Node 24.
The public-readiness scan adds a less common release check. It examines repository filenames and public text files for excluded legacy product or brand terms while avoiding dependencies, generated output, Git internals, and cache data. It reports the location of a finding without repeating the excluded value into CI logs.
That check reflects a broader principle: an open-source repository should prove that it is portable, not merely claim that private details were removed.
Start building with Liquid Loom
Liquid Loom is public under the MIT License. It requires Node.js 22.12 or newer, Corepack, and pnpm. A Shopify development store is needed only for live theme preview and deployment.
git clone https://github.com/yotamon/Liquid-Loom.git liquid-loom
cd liquid-loom
corepack enable
pnpm install
pnpm build
Authenticate once, then launch the build watcher and Shopify development theme together:
pnpm exec shopify auth login
pnpm dev
For local compilation without Shopify preview, use pnpm watch. Before opening a pull request, use pnpm validate.
What Liquid Loom is for
Liquid Loom is a good fit when a Shopify theme team wants:
-
native Liquid and Online Store 2.0 behavior
-
feature-oriented source folders without giving up standard Shopify output
-
modern CSS and JavaScript compilation through Tailwind and Vite
-
deterministic builds with explicit failure modes
-
a small starter that demonstrates commerce surfaces without imposing a brand or app stack
It is not a headless storefront, an application framework, or a replacement for Shopify CLI. It is the workshop around a theme: organized inputs, guarded transformations, and a deployable result that remains native to the platform.
The most useful open-source tools often do not erase constraints. They turn constraints into contracts developers can see, test, and trust.
Explore the Liquid Loom repository on GitHub, run the validation suite, and open a focused issue or pull request when you find a way to make Shopify theme development more predictable.



