@augeo/smelt 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.json +11 -0
- package/.github/workflows/verify.yml +64 -0
- package/.gitmodules +3 -0
- package/.prettierignore +25 -0
- package/.prettierrc.cjs +9 -0
- package/.zed/settings.json +21 -0
- package/AGENTS.md +232 -0
- package/LICENSE +21 -0
- package/README.md +266 -0
- package/biome.json +58 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +350 -0
- package/dist/schema.d.mts +265 -0
- package/dist/schema.mjs +21 -0
- package/docs/TESTING.md +293 -0
- package/docs/assets-plan.md +197 -0
- package/docs/build-spec.md +466 -0
- package/docs/library-conversion-plan.md +419 -0
- package/example/.gitattributes +7 -0
- package/example/.shopifyignore +28 -0
- package/example/.theme-check.yml +7 -0
- package/example/blocks/_built--sections--hero--blocks--feature.liquid +52 -0
- package/example/config/settings_schema.json +10 -0
- package/example/layout/theme.liquid +25 -0
- package/example/locales/en.default.json +1 -0
- package/example/package-lock.json +51 -0
- package/example/package.json +20 -0
- package/example/sections/built--sections--hero.liquid +83 -0
- package/example/snippets/built--components--button.liquid +38 -0
- package/example/snippets/built--components--card.liquid +33 -0
- package/example/src/components/button/button.css +13 -0
- package/example/src/components/card/card.css +16 -0
- package/example/src/components/card/card.liquid +9 -0
- package/example/src/sections/hero/blocks/feature/feature.css +11 -0
- package/example/src/sections/hero/blocks/feature/feature.liquid +9 -0
- package/example/src/sections/hero/blocks/feature/feature.schema.ts +14 -0
- package/example/src/sections/hero/hero.css +15 -0
- package/example/src/sections/hero/hero.liquid +16 -0
- package/example/src/sections/hero/hero.schema.ts +26 -0
- package/example/src/sections/hero/hero.test.ts +43 -0
- package/example/src/utilities/labels.ts +5 -0
- package/example/templates/index.liquid +1 -0
- package/example/tsconfig.json +10 -0
- package/example/vitest.config.ts +6 -0
- package/lib/build/build.test.ts +475 -0
- package/lib/build/build.ts +314 -0
- package/lib/build/command.ts +27 -0
- package/lib/build/index.ts +1 -0
- package/lib/cli.ts +17 -0
- package/lib/dev/command.ts +25 -0
- package/lib/dev/index.ts +1 -0
- package/lib/dev/watch.ts +52 -0
- package/lib/resolver.test.ts +275 -0
- package/lib/resolver.ts +156 -0
- package/lib/schema.ts +37 -0
- package/package.json +59 -0
- package/scripts/codegen-schema.ts +66 -0
- package/src/components/button/button.css +13 -0
- package/src/components/button/button.liquid +5 -0
- package/src/components/button/button.ts +5 -0
- package/src/tsconfig.json +10 -0
- package/tests/example.test.ts +101 -0
- package/tsconfig.json +20 -0
- package/tsdown.config.ts +14 -0
- package/vendor/theme-liquid-docs/.gitattributes +10 -0
- package/vendor/theme-liquid-docs/.github/CODEOWNERS +1 -0
- package/vendor/theme-liquid-docs/.github/CODE_OF_CONDUCT.md +73 -0
- package/vendor/theme-liquid-docs/.github/ISSUE_TEMPLATE/bug_report.md +17 -0
- package/vendor/theme-liquid-docs/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
- package/vendor/theme-liquid-docs/.github/dependabot.yaml +6 -0
- package/vendor/theme-liquid-docs/.github/workflows/ci.yml +33 -0
- package/vendor/theme-liquid-docs/.github/workflows/cla.yml +27 -0
- package/vendor/theme-liquid-docs/.github/workflows/shopify-dev-preview-automation.yml +86 -0
- package/vendor/theme-liquid-docs/.github/workflows/update-latest.yml +56 -0
- package/vendor/theme-liquid-docs/.prettierrc.json +16 -0
- package/vendor/theme-liquid-docs/.vscode/settings.json +28 -0
- package/vendor/theme-liquid-docs/LICENSE.md +7 -0
- package/vendor/theme-liquid-docs/README.md +48 -0
- package/vendor/theme-liquid-docs/ai/claude/CLAUDE.md +1485 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/assets.mdc +15 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/blocks.mdc +339 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/examples/block-example-group.mdc +103 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/examples/block-example-text.mdc +59 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/examples/section-example.mdc +61 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/examples/snippet-example.mdc +72 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/liquid.mdc +837 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/locales.mdc +100 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/localization.mdc +67 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/mcp.mdc +2 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/schemas.mdc +184 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/sections.mdc +84 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/settings-schema.mdc +51 -0
- package/vendor/theme-liquid-docs/ai/cursor/rules/snippets.mdc +119 -0
- package/vendor/theme-liquid-docs/ai/github/copilot-instructions.md +1485 -0
- package/vendor/theme-liquid-docs/ai/liquid.mdc +638 -0
- package/vendor/theme-liquid-docs/data/filters.json +6148 -0
- package/vendor/theme-liquid-docs/data/latest.json +2 -0
- package/vendor/theme-liquid-docs/data/objects.json +20594 -0
- package/vendor/theme-liquid-docs/data/shopify_system_translations.json +2586 -0
- package/vendor/theme-liquid-docs/data/tags.json +1276 -0
- package/vendor/theme-liquid-docs/package.json +20 -0
- package/vendor/theme-liquid-docs/schemas/manifest_schema.json +31 -0
- package/vendor/theme-liquid-docs/schemas/manifest_theme.json +19 -0
- package/vendor/theme-liquid-docs/schemas/manifest_theme_app_extension.json +10 -0
- package/vendor/theme-liquid-docs/schemas/theme/app_block_entry.json +13 -0
- package/vendor/theme-liquid-docs/schemas/theme/default_setting_values.json +24 -0
- package/vendor/theme-liquid-docs/schemas/theme/local_block_entry.json +25 -0
- package/vendor/theme-liquid-docs/schemas/theme/preset.json +72 -0
- package/vendor/theme-liquid-docs/schemas/theme/preset_blocks.json +91 -0
- package/vendor/theme-liquid-docs/schemas/theme/section.json +208 -0
- package/vendor/theme-liquid-docs/schemas/theme/setting.json +1413 -0
- package/vendor/theme-liquid-docs/schemas/theme/settings.json +10 -0
- package/vendor/theme-liquid-docs/schemas/theme/targetted_block_entry.json +15 -0
- package/vendor/theme-liquid-docs/schemas/theme/theme_block.json +91 -0
- package/vendor/theme-liquid-docs/schemas/theme/theme_block_entry.json +14 -0
- package/vendor/theme-liquid-docs/schemas/theme/theme_settings.json +83 -0
- package/vendor/theme-liquid-docs/schemas/theme/translations.json +63 -0
- package/vendor/theme-liquid-docs/schemas/update/update_extension_schema_v1.json +186 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-nested-blocks.json +18 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-1.json +90 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-2.json +201 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-3.json +29 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-4.json +315 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-5.json +114 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-6.json +63 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-conditional-settings.json +145 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-preset-blocks-as-hash.json +60 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-schema-static-block-preset.json +76 -0
- package/vendor/theme-liquid-docs/tests/fixtures/section-settings.json +34 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-block-1.json +234 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-block-2.json +253 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-block-basics.json +48 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-block-conditional-settings.json +202 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-block-presets-as-hash.json +50 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-block-settings.json +34 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-settings-all-settings.json +313 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-settings-dawn.json +1469 -0
- package/vendor/theme-liquid-docs/tests/fixtures/theme-settings-metadata.json +10 -0
- package/vendor/theme-liquid-docs/tests/fixtures/translations-1.json +14 -0
- package/vendor/theme-liquid-docs/tests/section.spec.ts +367 -0
- package/vendor/theme-liquid-docs/tests/test-constants.ts +58 -0
- package/vendor/theme-liquid-docs/tests/test-helpers.ts +104 -0
- package/vendor/theme-liquid-docs/tests/theme-settings/color_palette.spec.ts +184 -0
- package/vendor/theme-liquid-docs/tests/theme-settings/color_scheme_group.spec.ts +143 -0
- package/vendor/theme-liquid-docs/tests/theme-settings/general.spec.ts +192 -0
- package/vendor/theme-liquid-docs/tests/theme-settings/metaobject.spec.ts +94 -0
- package/vendor/theme-liquid-docs/tests/theme-settings/resource_list.spec.ts +58 -0
- package/vendor/theme-liquid-docs/tests/theme-settings/theme-metadata.spec.ts +59 -0
- package/vendor/theme-liquid-docs/tests/theme_block.spec.ts +266 -0
- package/vendor/theme-liquid-docs/tests/translations_schema.spec.ts +31 -0
- package/vendor/theme-liquid-docs/yarn.lock +543 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
---
|
|
2
|
+
status: draft
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Smelt Build Pipeline
|
|
6
|
+
|
|
7
|
+
Spec for the source-to-theme compiler that turns the `src/` authoring tree into
|
|
8
|
+
the flat, namespaced files Shopify expects.
|
|
9
|
+
|
|
10
|
+
The build is the only thing this document describes. Theme-wide concerns
|
|
11
|
+
(initial scaffolding choices, deployment workflow, store configuration) live
|
|
12
|
+
elsewhere or are out of scope.
|
|
13
|
+
|
|
14
|
+
## Goal
|
|
15
|
+
|
|
16
|
+
Let theme authors work in colocated components (Liquid + TS + CSS + tests in one
|
|
17
|
+
directory) and have a build step emit the flat, namespaced output Shopify
|
|
18
|
+
expects. The build is **additive**: hand-written files in `sections/`,
|
|
19
|
+
`snippets/`, etc. are never touched — the compiler only writes files prefixed
|
|
20
|
+
`built--` (or `_built--` for private theme blocks).
|
|
21
|
+
|
|
22
|
+
## Inputs and Outputs
|
|
23
|
+
|
|
24
|
+
The build runs in a **consumer theme's** repo. It reads from an ordered list of
|
|
25
|
+
**layers** — directories that each contain a
|
|
26
|
+
`src/{sections,blocks,components, utilities}/` tree — and writes the merged
|
|
27
|
+
output into Shopify's flat directories at the consumer's repo root.
|
|
28
|
+
|
|
29
|
+
In this repository the consumer is `example/`, so paths shown below as `src/…`
|
|
30
|
+
and `sections/…` are physically at `example/src/…` and `example/sections/…`. The
|
|
31
|
+
pipeline itself is identical either way.
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
<layer>/src/ # one such tree per layer
|
|
35
|
+
├── sections/ # → sections/built--*.liquid
|
|
36
|
+
├── blocks/ # → blocks/built--*.liquid
|
|
37
|
+
├── components/ # → snippets/built--*.liquid
|
|
38
|
+
└── utilities/ # no Liquid output; bundled into importers
|
|
39
|
+
|
|
40
|
+
sections/ # outputs (mixed with hand-written) — at the
|
|
41
|
+
snippets/ # consumer's repo root, written by the build
|
|
42
|
+
blocks/
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The default layer list is `[<consumer>, "@augeo/smelt"]` — the consumer's own
|
|
46
|
+
tree first, then Smelt's baseline. See [Layer Resolution](#layer-resolution)
|
|
47
|
+
below for how files merge across layers.
|
|
48
|
+
|
|
49
|
+
Directories the build does **not** read or write:
|
|
50
|
+
|
|
51
|
+
- `config/`, `layout/`, `templates/`, `locales/`, `assets/` — entirely
|
|
52
|
+
hand-written in v1.
|
|
53
|
+
- Any file in an output directory that lacks the `built--` prefix.
|
|
54
|
+
|
|
55
|
+
## Component Model
|
|
56
|
+
|
|
57
|
+
A **component** is a directory with at minimum a `.liquid` file matching the
|
|
58
|
+
directory name. Optional sibling files extend it:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
src/components/button/
|
|
62
|
+
├── button.liquid # required — Liquid template
|
|
63
|
+
├── button.ts # optional — inlined as {% javascript %}
|
|
64
|
+
├── button.css # optional — inlined as {% stylesheet %}
|
|
65
|
+
├── button.test.ts # optional — vitest test (via @augeo/assay)
|
|
66
|
+
└── props.ts # optional — typed schema (future)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Component types differ only by their source directory and output target:
|
|
70
|
+
|
|
71
|
+
| Source dir | Output dir | Notes |
|
|
72
|
+
| ----------------- | ----------- | ---------------------------------------- |
|
|
73
|
+
| `src/sections/` | `sections/` | Renderable as Shopify sections |
|
|
74
|
+
| `src/blocks/` | `blocks/` | Theme blocks |
|
|
75
|
+
| `src/components/` | `snippets/` | Reusable across sections/blocks/snippets |
|
|
76
|
+
| `src/utilities/` | (none) | TS/CSS-only; bundled into importers |
|
|
77
|
+
|
|
78
|
+
Nested components are supported and namespaced into the output name (see
|
|
79
|
+
[Output Naming](#output-naming)).
|
|
80
|
+
|
|
81
|
+
## Layer Resolution
|
|
82
|
+
|
|
83
|
+
The build accepts an ordered list of **layers**; each layer is a directory
|
|
84
|
+
containing its own `src/{sections,blocks,components,utilities}/` tree. The
|
|
85
|
+
default layers, in order, are:
|
|
86
|
+
|
|
87
|
+
1. **The consumer's layer** — `process.cwd()`. The consumer's own `src/`.
|
|
88
|
+
2. **`@augeo/smelt`** — the published package's `src/`, the baseline.
|
|
89
|
+
|
|
90
|
+
For every component (keyed by `type` + path segments), each of the five file
|
|
91
|
+
slots — `liquid`, `ts`, `css`, `test.ts`, `schema.ts` — is resolved
|
|
92
|
+
**independently**: the first layer that has the file wins, the later layers are
|
|
93
|
+
shadowed.
|
|
94
|
+
|
|
95
|
+
A consumer who drops just `src/components/button/button.css` into their tree
|
|
96
|
+
inherits the baseline's `button.liquid` and `button.ts` and overrides only the
|
|
97
|
+
styles. The same shadowing rule applies to all five slots.
|
|
98
|
+
|
|
99
|
+
A component is included in the build if **any** layer has its `liquid` slot
|
|
100
|
+
filled. A consumer-only `.css` for a component that doesn't exist in any layer
|
|
101
|
+
is dead code (no liquid anchor) and is silently ignored.
|
|
102
|
+
|
|
103
|
+
## Authoring Conventions
|
|
104
|
+
|
|
105
|
+
### Liquid imports — `@/` and `./` aliases
|
|
106
|
+
|
|
107
|
+
Inside `.liquid` source files, reference other source components via one of two
|
|
108
|
+
aliases (modeled on Next.js / TypeScript path conventions):
|
|
109
|
+
|
|
110
|
+
- **`@/...`** — rooted at `src/`. Use for anything outside the current
|
|
111
|
+
component's subtree.
|
|
112
|
+
- **`./...`** — relative to the current source file's directory. Use for
|
|
113
|
+
references into the current component's nested children. **Descending only —
|
|
114
|
+
`../` is not supported.** If you need to reach a sibling-of-parent, use `@/`;
|
|
115
|
+
if you find yourself wanting `../` repeatedly, the nested component should
|
|
116
|
+
probably be hoisted to a shared location.
|
|
117
|
+
|
|
118
|
+
<!-- prettier-ignore -->
|
|
119
|
+
```liquid
|
|
120
|
+
{# inside src/sections/hero/hero.liquid: #}
|
|
121
|
+
|
|
122
|
+
{% render './components/foo' %} {# nested child of hero #}
|
|
123
|
+
{% render '@/components/button' %} {# top-level shared component #}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Both rewrite to namespaced output names:
|
|
127
|
+
|
|
128
|
+
```liquid
|
|
129
|
+
{% render 'built--sections--hero--components--foo' %}
|
|
130
|
+
{% render 'built--components--button' %}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Hand-written snippets remain referenceable by their bare name as usual:
|
|
134
|
+
|
|
135
|
+
```liquid
|
|
136
|
+
{% render 'icon-cart' %} {# unchanged — resolves to snippets/icon-cart.liquid #}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### TS / CSS — colocated and implicit
|
|
140
|
+
|
|
141
|
+
If `<name>.ts` exists alongside `<name>.liquid`, it is the component's
|
|
142
|
+
JavaScript entry point. The bundled output is inlined into a `{% javascript %}`
|
|
143
|
+
tag at the end of the built `.liquid` file. Same for `<name>.css` →
|
|
144
|
+
`{% stylesheet %}`.
|
|
145
|
+
|
|
146
|
+
Each component owns its own `{% javascript %}` / `{% stylesheet %}` block.
|
|
147
|
+
Shopify supports these tags in sections, blocks, and snippets (one per file) and
|
|
148
|
+
handles per-file asset deduplication at runtime.
|
|
149
|
+
|
|
150
|
+
### Cross-component imports — utilities only
|
|
151
|
+
|
|
152
|
+
A component's TS may import from `src/utilities/` and those will be bundled into
|
|
153
|
+
the component's IIFE. Importing another _component's_ TS
|
|
154
|
+
(`src/components/foo/foo.ts`) is **not supported** — foo is rendered as its own
|
|
155
|
+
snippet with its own script tag; the DOM already gets foo's behavior when
|
|
156
|
+
`{% render 'foo' %}` runs.
|
|
157
|
+
|
|
158
|
+
### Section / block schemas — `<name>.schema.ts`
|
|
159
|
+
|
|
160
|
+
Sections and theme blocks can author their schema as a TypeScript file alongside
|
|
161
|
+
the component:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
// example/src/sections/hero/hero.schema.ts
|
|
165
|
+
import { defineSchemaSection } from "@augeo/smelt/schema";
|
|
166
|
+
|
|
167
|
+
export const schema = defineSchemaSection({
|
|
168
|
+
name: "Hero",
|
|
169
|
+
settings: [
|
|
170
|
+
{ type: "text", id: "heading", label: "Heading", default: "Welcome" },
|
|
171
|
+
],
|
|
172
|
+
presets: [{ name: "Hero" }],
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Conventions:
|
|
177
|
+
|
|
178
|
+
- **Filename:** `<name>.schema.ts`.
|
|
179
|
+
- **Required export:** a named `schema` constant. Other named exports are fine
|
|
180
|
+
and ignored.
|
|
181
|
+
- **Helper per component kind:** `defineSchemaSection` for `src/sections/*`,
|
|
182
|
+
`defineSchemaBlock` for `src/blocks/*`. Each is an identity function whose
|
|
183
|
+
generic preserves literal types so the schema author gets autocomplete for
|
|
184
|
+
`type` / `id` / setting shapes.
|
|
185
|
+
- **Types are codegen'd** from Shopify's authoritative JSON Schemas in
|
|
186
|
+
`vendor/theme-liquid-docs`. Update via
|
|
187
|
+
`npm run docs:update && npm run schema:codegen`.
|
|
188
|
+
|
|
189
|
+
At build time the engine executes the schema file (esbuild bundle → `import()`),
|
|
190
|
+
takes the `schema` export, `JSON.stringify`s it, and injects a
|
|
191
|
+
`{% schema %} … {% endschema %}` block into the built `.liquid`.
|
|
192
|
+
|
|
193
|
+
**Schemas may only live in `<name>.schema.ts` files.** Inline `{% schema %}`
|
|
194
|
+
blocks in `.liquid` source files are a build error — the engine refuses to emit
|
|
195
|
+
and points at the expected sibling filename. This keeps a single source of truth
|
|
196
|
+
and avoids surprises when shadowing across layers.
|
|
197
|
+
|
|
198
|
+
Shared settings (padding, alignment, etc.) compose through plain TS imports:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { paddingSettings } from "@/utilities/schemas/padding";
|
|
202
|
+
|
|
203
|
+
export const schema = defineSchemaSection({
|
|
204
|
+
name: "Hero",
|
|
205
|
+
settings: [
|
|
206
|
+
{ type: "text", id: "heading", label: "Heading" },
|
|
207
|
+
...paddingSettings,
|
|
208
|
+
],
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Private blocks — nested `blocks/`
|
|
213
|
+
|
|
214
|
+
Shopify treats theme block files prefixed with `_` as **private**: hidden from
|
|
215
|
+
the merchant's block picker, renderable only via the parent's
|
|
216
|
+
`{% content_for "blocks" %}`. Smelt expresses this structurally — a `blocks/`
|
|
217
|
+
directory nested under a section or block emits its children with the `_` prefix
|
|
218
|
+
on the built filename, mirroring the parent/child relationship that private
|
|
219
|
+
blocks already imply in Shopify.
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
src/sections/hero/
|
|
223
|
+
├── hero.liquid
|
|
224
|
+
├── hero.schema.ts
|
|
225
|
+
└── blocks/
|
|
226
|
+
└── headline/
|
|
227
|
+
├── headline.liquid
|
|
228
|
+
└── headline.schema.ts
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Compiles to:
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
sections/built--sections--hero.liquid
|
|
235
|
+
blocks/_built--sections--hero--blocks--headline.liquid
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Top-level `src/blocks/*` files remain public (no `_` prefix). The trigger is
|
|
239
|
+
"this block was reached through a nested `blocks/` type marker" — i.e.
|
|
240
|
+
`component.type === "blocks"` AND a `"blocks"` segment appears at any position
|
|
241
|
+
after the root (`component.segments.slice(1).includes("blocks")`). That covers
|
|
242
|
+
both a section/component owning private blocks (`sections/hero/blocks/headline`)
|
|
243
|
+
and a public block owning its own private blocks (`blocks/promo/blocks/item`).
|
|
244
|
+
The rule applies recursively: a private block can have its own `blocks/` subdir,
|
|
245
|
+
producing privately-scoped grandchildren that walk the same way.
|
|
246
|
+
|
|
247
|
+
The parent section or block's schema auto-merges these private children into its
|
|
248
|
+
`blocks: [...]` array — discovered children sorted by directory name and
|
|
249
|
+
prepended, any blocks the author explicitly lists (e.g. globally-shared block
|
|
250
|
+
types) appended after. No helper call required.
|
|
251
|
+
|
|
252
|
+
## Build Pipeline
|
|
253
|
+
|
|
254
|
+
The engine lives in `lib/build/build.ts` and is invoked by `lib/cli.ts` (a citty
|
|
255
|
+
CLI bundled by tsdown to `dist/cli.mjs` — the published `smelt` bin). From a
|
|
256
|
+
consumer's repo the commands are `smelt build` (one-shot compile) and
|
|
257
|
+
`smelt dev` (build then watch — see below). From this repo, `npm run build`
|
|
258
|
+
rebuilds the bin and `npm run test:all` runs the full integration chain against
|
|
259
|
+
`example/`. The engine itself:
|
|
260
|
+
|
|
261
|
+
1. **Resolve layers** — walk each layer's `src/` tree and produce a merged
|
|
262
|
+
`Map<segments, ResolvedComponent>` where each component's `liquid` / `ts` /
|
|
263
|
+
`css` / `test` / `schema` slots are independently first-wins (see
|
|
264
|
+
[Layer Resolution](#layer-resolution)).
|
|
265
|
+
2. **Clean previous output** — sweep any file matching `/^_?built--/` from
|
|
266
|
+
`sections/`, `snippets/`, and `blocks/`. Output is regenerated from scratch
|
|
267
|
+
on every build, so removed source components disappear cleanly. Hand-written
|
|
268
|
+
files without the prefix are untouched.
|
|
269
|
+
3. **For each component** (anything with a `.liquid`):
|
|
270
|
+
- Prepend a `{%- comment -%}` banner naming the source file, for tracing.
|
|
271
|
+
- Bundle `<name>.ts` (esbuild, IIFE, utilities inlined) → string.
|
|
272
|
+
- Read `<name>.css` as-is (no transform today — see TODO).
|
|
273
|
+
- Execute `<name>.schema.ts` — esbuild → `data:` URL → dynamic `import()`,
|
|
274
|
+
capturing the named `schema` export. esbuild auto-discovers the consumer's
|
|
275
|
+
`tsconfig.json` so `@/...` (and any custom path aliases) resolve the way
|
|
276
|
+
`tsc` sees them.
|
|
277
|
+
- Auto-merge any immediate private `blocks/` children into the loaded
|
|
278
|
+
schema's `blocks: [...]` array (see
|
|
279
|
+
[Private blocks](#private-blocks--nested-blocks)).
|
|
280
|
+
- Transform `<name>.liquid`:
|
|
281
|
+
- **Error** if the source `.liquid` contains an inline `{% schema %}` block
|
|
282
|
+
— schemas must live in `<name>.schema.ts`.
|
|
283
|
+
- Rewrite `{% render '@/...' %}` and `{% render './...' %}` to namespaced
|
|
284
|
+
output names (private blocks get the `_built--` form).
|
|
285
|
+
- Append `{% schema %}…{% endschema %}` block if schema present.
|
|
286
|
+
- Append `{% stylesheet %}…{% endstylesheet %}` block if CSS present.
|
|
287
|
+
- Append `{% javascript %}…{% endjavascript %}` block if TS present.
|
|
288
|
+
- Emit to the matching output directory under the namespaced name.
|
|
289
|
+
|
|
290
|
+
The build never touches files in output directories that lack the `built--` /
|
|
291
|
+
`_built--` prefix.
|
|
292
|
+
|
|
293
|
+
**Watch mode (`smelt dev`)** re-runs the whole pipeline on any source file
|
|
294
|
+
change, debounced ~100ms. Build failures log and keep the watcher alive. Today
|
|
295
|
+
that's a full rebuild every time — see TODO for the dependency-graph-aware
|
|
296
|
+
version.
|
|
297
|
+
|
|
298
|
+
## Output Naming
|
|
299
|
+
|
|
300
|
+
Source path → output name:
|
|
301
|
+
|
|
302
|
+
```
|
|
303
|
+
src/components/button/button.liquid
|
|
304
|
+
→ snippets/built--components--button.liquid
|
|
305
|
+
|
|
306
|
+
src/sections/hero/hero.liquid
|
|
307
|
+
→ sections/built--sections--hero.liquid
|
|
308
|
+
|
|
309
|
+
src/sections/hero/components/foo/foo.liquid
|
|
310
|
+
→ snippets/built--sections--hero--components--foo.liquid
|
|
311
|
+
|
|
312
|
+
src/blocks/promo/promo.liquid
|
|
313
|
+
→ blocks/built--blocks--promo.liquid
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Rules:
|
|
317
|
+
|
|
318
|
+
- Prefix `built--` marks compiler output: anything matching `/^_?built--/` in
|
|
319
|
+
`sections/`, `snippets/`, or `blocks/` is the build's territory — cleaned and
|
|
320
|
+
rewritten every build. Hand-written files in those directories are untouched.
|
|
321
|
+
- Path segments under `src/` are joined with `--`.
|
|
322
|
+
- Final segment (directory name) appears once; the matching `.liquid` filename
|
|
323
|
+
is not duplicated.
|
|
324
|
+
- Sections, blocks, and components-of-components all flatten into the same set
|
|
325
|
+
of Shopify-flat directories.
|
|
326
|
+
- Private blocks (reached through a nested `blocks/` marker) get an additional
|
|
327
|
+
`_` prefix on the filename — see
|
|
328
|
+
[Private blocks](#private-blocks--nested-blocks).
|
|
329
|
+
|
|
330
|
+
## Worked Example
|
|
331
|
+
|
|
332
|
+
A fuller picture showing source files, nested components, and the compiled
|
|
333
|
+
output sitting alongside hand-written files.
|
|
334
|
+
|
|
335
|
+
### Source (`src/`)
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
src/
|
|
339
|
+
├── sections/
|
|
340
|
+
│ └── hero/
|
|
341
|
+
│ ├── hero.liquid
|
|
342
|
+
│ ├── hero.css
|
|
343
|
+
│ ├── hero.ts
|
|
344
|
+
│ ├── hero.test.ts
|
|
345
|
+
│ ├── components/
|
|
346
|
+
│ │ └── foo/
|
|
347
|
+
│ │ ├── foo.liquid
|
|
348
|
+
│ │ ├── foo.css
|
|
349
|
+
│ │ ├── foo.ts
|
|
350
|
+
│ │ └── foo.test.ts
|
|
351
|
+
│ └── blocks/
|
|
352
|
+
│ └── headline/ # private — see Private blocks section
|
|
353
|
+
│ ├── headline.liquid
|
|
354
|
+
│ └── headline.schema.ts
|
|
355
|
+
├── blocks/
|
|
356
|
+
├── components/
|
|
357
|
+
│ └── button/
|
|
358
|
+
│ ├── props.ts # typed prop schema (future — see Open Questions)
|
|
359
|
+
│ ├── button.liquid
|
|
360
|
+
│ ├── button.css
|
|
361
|
+
│ ├── button.ts
|
|
362
|
+
│ └── button.test.ts
|
|
363
|
+
└── utilities/
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Compiled output (Shopify-flat dirs)
|
|
367
|
+
|
|
368
|
+
```
|
|
369
|
+
sections/
|
|
370
|
+
├── built--sections--hero.liquid # ← src/sections/hero/
|
|
371
|
+
└── old_legacy_section.liquid # hand-written, preserved
|
|
372
|
+
|
|
373
|
+
snippets/
|
|
374
|
+
├── built--sections--hero--components--foo.liquid # nested under hero
|
|
375
|
+
├── built--components--button.liquid # top-level component
|
|
376
|
+
└── icon-cart.liquid # hand-written, preserved
|
|
377
|
+
|
|
378
|
+
blocks/
|
|
379
|
+
└── _built--sections--hero--blocks--headline.liquid # private to hero
|
|
380
|
+
|
|
381
|
+
assets/
|
|
382
|
+
└── (hand-written static files; build does not write here in v1)
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Notes:
|
|
386
|
+
|
|
387
|
+
- Co-located `hero.ts` / `hero.css` are inlined into
|
|
388
|
+
`built--sections--hero.liquid` as `{% javascript %}` / `{% stylesheet %}`
|
|
389
|
+
blocks — no separate files in `assets/`.
|
|
390
|
+
- `built--` is the only namespace the compiler writes. Files without the prefix
|
|
391
|
+
(`old_legacy_section.liquid`, `icon-cart.liquid`) are treated as hand-written
|
|
392
|
+
and survive rebuilds untouched.
|
|
393
|
+
- The nested `hero/components/foo/` directory illustrates that components scoped
|
|
394
|
+
to a single section live inside it and flatten into a path-encoded snippet
|
|
395
|
+
name on output.
|
|
396
|
+
|
|
397
|
+
## Testing
|
|
398
|
+
|
|
399
|
+
Tests use [`@augeo/assay`](https://www.npmjs.com/package/@augeo/assay) — Vitest
|
|
400
|
+
browser mode + liquidjs + Playwright.
|
|
401
|
+
|
|
402
|
+
- `<name>.test.ts` lives beside `<name>.liquid`.
|
|
403
|
+
- Tests run against the **built** theme directories (Liquid in `sections/`,
|
|
404
|
+
`snippets/`, `blocks/`), not against `src/` directly, since assay / liquidjs
|
|
405
|
+
cannot resolve the `@/...` alias.
|
|
406
|
+
- `npm test` runs `build` first, then `vitest`.
|
|
407
|
+
|
|
408
|
+
Vitest config uses the assay preset:
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
import { assayPreset } from "@augeo/assay/preset";
|
|
412
|
+
|
|
413
|
+
export default assayPreset({
|
|
414
|
+
liquidPaths: ["./sections", "./snippets", "./blocks"],
|
|
415
|
+
assetsPath: "./assets",
|
|
416
|
+
});
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## Decisions Made (and Why)
|
|
420
|
+
|
|
421
|
+
- **`{% javascript %}` / `{% stylesheet %}` inlining over standalone
|
|
422
|
+
`assets/section-*.js` files.** Compiled output is inlined into the
|
|
423
|
+
per-component tag; Shopify owns the asset generation and runtime dedup. Less
|
|
424
|
+
file proliferation in `assets/`. An earlier objection — that these tags were
|
|
425
|
+
section-only — was based on outdated docs; current Shopify supports them in
|
|
426
|
+
sections, blocks, and snippets (one tag of each kind per file).
|
|
427
|
+
- **No cross-component TS imports.** Each component is its own bundle. Sharing
|
|
428
|
+
happens through `src/utilities/`, which has no Liquid output and is purely a
|
|
429
|
+
code-sharing layer.
|
|
430
|
+
- **Tests run against built output.** Avoids reimplementing the `@/...` resolver
|
|
431
|
+
inside the test runner. `npm test` builds first, then runs vitest.
|
|
432
|
+
- **Build is additive, not destructive.** The compiler only writes files
|
|
433
|
+
prefixed `built--`. Hand-written files in output directories are preserved
|
|
434
|
+
across rebuilds, allowing gradual migration into `src/`.
|
|
435
|
+
|
|
436
|
+
## Open Questions
|
|
437
|
+
|
|
438
|
+
- `src/components/<x>/props.ts` — typed prop schema for non-section/block
|
|
439
|
+
components (snippets), declaring the variables a `{% render %}` call must
|
|
440
|
+
pass. Distinct from `<name>.schema.ts`, which describes Shopify's
|
|
441
|
+
merchant-facing settings for sections and theme blocks. Possibly with runtime
|
|
442
|
+
validation injected into the built snippet for dev mode. Defer.
|
|
443
|
+
- LSP / editor support for the `@/...` alias (go-to-definition). Defer.
|
|
444
|
+
|
|
445
|
+
## TODO
|
|
446
|
+
|
|
447
|
+
Deferred work — things we've decided we want to address eventually but aren't
|
|
448
|
+
scoped or designed yet.
|
|
449
|
+
|
|
450
|
+
- **Utility deduplication across component bundles.** Currently each component's
|
|
451
|
+
IIFE inlines its imported utilities, so a utility used by N components ships N
|
|
452
|
+
times. Acceptable for a small theme; revisit when measured page weight
|
|
453
|
+
justifies the design work. Candidate approaches include a single shared
|
|
454
|
+
`assets/built--utilities.js` bundle exposed on a global, ES modules via an
|
|
455
|
+
importmap, or per-utility snippet guards.
|
|
456
|
+
|
|
457
|
+
- **CSS processing.** Today `<name>.css` is inlined as-is. A real pipeline would
|
|
458
|
+
run something like [lightningcss](https://lightningcss.dev/) or
|
|
459
|
+
[postcss](https://postcss.org/) for autoprefixing, nesting, custom-property
|
|
460
|
+
fallbacks, and source-of-truth normalization across components. Defer until
|
|
461
|
+
someone authoring components asks for a feature that needs it.
|
|
462
|
+
|
|
463
|
+
- **Dependency-graph-aware watch.** `smelt dev` currently re-runs the whole
|
|
464
|
+
pipeline on any source change. A smarter loop would track which components
|
|
465
|
+
import which utilities and rebuild only the affected set — important once
|
|
466
|
+
builds get slow enough that full-rebuild latency hurts the authoring loop.
|