@homepages/template-kit 0.3.0 → 0.4.1
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/CHANGELOG.md +84 -0
- package/README.md +39 -4
- package/dist/asset-modules.d.ts +81 -0
- package/dist/cli/check/config.js +5 -1
- package/dist/cli/check/css.js +27 -13
- package/dist/cli/check/loader.js +4 -0
- package/dist/cli/check/section-assets.js +58 -0
- package/dist/cli/check/stages/size.js +124 -8
- package/dist/cli/check/stages/tree.js +26 -27
- package/dist/cli/dev/build-css.js +91 -0
- package/dist/cli/dev/catalog.js +35 -0
- package/dist/cli/dev/compose-template.js +38 -0
- package/dist/cli/dev/content-override.js +58 -0
- package/dist/cli/dev/discover.js +35 -0
- package/dist/cli/dev/fill-state.js +75 -0
- package/dist/cli/dev/index.js +32 -0
- package/dist/cli/dev/inspect.js +38 -0
- package/dist/cli/dev/island-bootstrap.js +38 -0
- package/dist/cli/dev/island-map.js +35 -0
- package/dist/cli/dev/island-transform.js +31 -0
- package/dist/cli/dev/manifest-instances.js +31 -0
- package/dist/cli/dev/render-section.js +24 -0
- package/dist/cli/dev/screenshot-target.js +24 -0
- package/dist/cli/dev/section-page.js +54 -0
- package/dist/cli/dev/server.js +480 -0
- package/dist/cli/dev/slot-schema.js +12 -0
- package/dist/cli/dev/structure-summary.js +118 -0
- package/dist/cli/dev/tailwind.js +32 -0
- package/dist/cli/dev/vite-server.js +33 -0
- package/dist/cli/dev/workspace.js +63 -0
- package/dist/cli/link/index.js +74 -0
- package/dist/cli/link/overlay.js +59 -0
- package/dist/cli/link/watch.js +25 -0
- package/dist/cli/new/emit.js +48 -0
- package/dist/cli/new/index.js +69 -0
- package/dist/cli/new/scaffold/section/_Renderer.tsx +20 -0
- package/dist/cli/new/scaffold/section/_fill-spec.ts +18 -0
- package/dist/cli/new/scaffold/section/_fixtures.ts +13 -0
- package/dist/cli/new/scaffold/section/_schema.ts +24 -0
- package/dist/cli/new/scaffold/template/_manifest.json +9 -0
- package/dist/cli/new/scaffold/template/sections/hero/ExpandableText.tsx +45 -0
- package/dist/cli/new/scaffold/template/sections/hero/Renderer.tsx +40 -0
- package/dist/cli/new/scaffold/template/sections/hero/components/Feature.tsx +14 -0
- package/dist/cli/new/scaffold/template/sections/hero/fill-spec.ts +29 -0
- package/dist/cli/new/scaffold/template/sections/hero/fixtures.ts +41 -0
- package/dist/cli/new/scaffold/template/sections/hero/schema.ts +60 -0
- package/dist/cli/new/scaffold/template/theme.css +24 -0
- package/dist/cli/new/scaffold/template/theme.ts +27 -0
- package/dist/cli/new/scaffold-assets.js +20 -0
- package/dist/cli/pack/collect.js +44 -0
- package/dist/cli/pack/guards.js +57 -0
- package/dist/cli/pack/index.js +66 -0
- package/dist/cli/pack/manifest.js +15 -0
- package/dist/cli/pack/zip.js +86 -0
- package/dist/cli.js +80 -14
- package/dist/dev-client/assets/index-DfEfrp6P.css +1 -0
- package/dist/dev-client/assets/index-bpMP0b0W.js +50 -0
- package/dist/dev-client/index.html +13 -0
- package/dist/eslint/is-client-file.d.ts +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/islands/detect.d.ts +16 -0
- package/dist/islands/detect.js +22 -0
- package/dist/islands/discover.d.ts +5 -0
- package/dist/islands/discover.js +19 -0
- package/dist/islands/esbuild-plugin.d.ts +13 -0
- package/dist/islands/esbuild-plugin.js +39 -0
- package/dist/islands/wrap.d.ts +12 -0
- package/dist/islands/wrap.js +48 -0
- package/dist/package.js +1 -1
- package/dist/ssr.d.ts +31 -0
- package/dist/ssr.js +46 -0
- package/docs/INDEX.md +9 -1
- package/docs/assets.md +128 -0
- package/docs/check.md +6 -3
- package/docs/dev.md +187 -0
- package/docs/islands.md +5 -2
- package/docs/llms.txt +41 -3
- package/docs/new.md +27 -0
- package/docs/overview.md +57 -0
- package/docs/pack.md +46 -0
- package/docs/primitives.md +5 -6
- package/docs/quickstart.md +102 -0
- package/docs/recipes/INDEX.md +27 -0
- package/docs/recipes/bind-property-fact.md +58 -0
- package/docs/recipes/collection-slot.md +100 -0
- package/docs/recipes/fill-spec-decision.md +66 -0
- package/docs/recipes/fixture-states.md +68 -0
- package/docs/recipes/image-slot-crop.md +98 -0
- package/docs/recipes/interactive-island.md +100 -0
- package/docs/recipes/organize-section-folder.md +72 -0
- package/docs/recipes/prepare-submission.md +55 -0
- package/docs/recipes/second-template.md +49 -0
- package/docs/recipes/select-slot.md +59 -0
- package/docs/recipes/static-asset.md +103 -0
- package/docs/recipes/third-party-package.md +90 -0
- package/docs/rules/INDEX.md +4 -2
- package/docs/rules/bundle-binary-asset.md +33 -52
- package/docs/rules/bundle-incomplete.md +2 -2
- package/docs/rules/css-reason.md +3 -3
- package/docs/rules/no-bare-css-import.md +8 -8
- package/docs/rules/no-css-import-from-render-path.md +7 -8
- package/docs/rules/size-assets.md +88 -0
- package/docs/rules/size-island-bundle.md +123 -0
- package/docs/rules/size-section-css.md +19 -14
- package/docs/schema-system.md +28 -28
- package/docs/theme-and-css.md +2 -2
- package/docs/vocabulary.md +98 -0
- package/package.json +12 -3
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
purpose: Add a graphic — an icon, a divider, a photo, a font — to a section.
|
|
3
|
+
status: living
|
|
4
|
+
related: [../assets.md, ../check.md, ../rules/bundle-binary-asset.md, image-slot-crop.md, ../theme-and-css.md]
|
|
5
|
+
updated: 2026-07-16
|
|
6
|
+
---
|
|
7
|
+
# Add a static asset (image, icon, font)
|
|
8
|
+
|
|
9
|
+
## Goal
|
|
10
|
+
|
|
11
|
+
Add a graphic — an icon, a divider mark, a photo, a custom font — to a section.
|
|
12
|
+
|
|
13
|
+
## The delta
|
|
14
|
+
|
|
15
|
+
A binary asset — an image, a font, a video, an archive — may live in a section's own
|
|
16
|
+
`sections/<section>/assets/` folder, but only if the section references it: imported in
|
|
17
|
+
`Renderer.tsx` (`import crest from "./assets/crest.svg"`) or `url()`-referenced from one
|
|
18
|
+
of the section's CSS files. An unreferenced binary in the folder is flagged; see
|
|
19
|
+
[`bundle-binary-asset`](../rules/bundle-binary-asset.md) for the rule and why.
|
|
20
|
+
|
|
21
|
+
A file shared across sections — a logo, a brand webfont, a texture — lives at the
|
|
22
|
+
**template level** instead, referenced the same way with a `../../assets/…` path. See
|
|
23
|
+
[Static assets](../assets.md) for both paths and the full reference contract. What you
|
|
24
|
+
reach for depends on what the graphic is:
|
|
25
|
+
|
|
26
|
+
**A vector graphic — an icon, a divider, a decorative mark used only here.** Inline it as
|
|
27
|
+
SVG markup directly in the JSX that draws it. It is text, ships with the rest of your
|
|
28
|
+
component, and needs no file or import at all:
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
// sections/hero/components/ScrollArrow.tsx
|
|
32
|
+
export function ScrollArrow() {
|
|
33
|
+
return (
|
|
34
|
+
<svg viewBox="0 0 16 16" className="h-4 w-4" fill="none" aria-hidden="true">
|
|
35
|
+
<path d="M8 3v10M3 9l5 4 5-4" stroke="currentColor" strokeWidth="1.5" />
|
|
36
|
+
</svg>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
// sections/hero/Renderer.tsx
|
|
43
|
+
import { ScrollArrow } from "./components/ScrollArrow";
|
|
44
|
+
// …
|
|
45
|
+
<ScrollArrow />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
That markup's bytes ship inside the section's renderer bundle, so they count
|
|
49
|
+
toward the same byte budget as the rest of your JSX — see the size stage in
|
|
50
|
+
[Check](../check.md).
|
|
51
|
+
|
|
52
|
+
**A raster image or watermark used only in this section.** Add it under
|
|
53
|
+
`sections/<section>/assets/` and import it into the component:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
// sections/hero/Renderer.tsx
|
|
57
|
+
import { Image } from "@homepages/template-kit";
|
|
58
|
+
import crest from "./assets/crest.svg";
|
|
59
|
+
|
|
60
|
+
export default function Renderer() {
|
|
61
|
+
return <Image src={crest} alt="" />;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Or reference it from the section's CSS with `url()` instead of importing it — see
|
|
66
|
+
[Static assets](../assets.md) for both forms. Either way the file must be referenced:
|
|
67
|
+
an asset nothing imports or `url()`-references is flagged by
|
|
68
|
+
[`bundle-binary-asset`](../rules/bundle-binary-asset.md).
|
|
69
|
+
|
|
70
|
+
**A photo.** Not a section asset at all — a section never ships a photo file. A
|
|
71
|
+
photo arrives at runtime through an `image` slot: declare it in `schema.ts`,
|
|
72
|
+
produce it with an `image-assign` decision in `fill-spec.ts`, and render it
|
|
73
|
+
through the `<Image>` primitive, which reads `url`, `alt`, and `responsive` off
|
|
74
|
+
the slot's resolved value. See
|
|
75
|
+
[Image slot with a locked crop](image-slot-crop.md) for the full shape.
|
|
76
|
+
|
|
77
|
+
**A custom font shared across the template.** Declared once, for the whole
|
|
78
|
+
template, in the theme's `fonts` and optional `fontFaces` block. See
|
|
79
|
+
[Theme and CSS](../theme-and-css.md). A font only one section uses can instead
|
|
80
|
+
live in that section's `assets/` folder and be declared with a `@font-face` in
|
|
81
|
+
the section's own CSS — see [Static assets](../assets.md).
|
|
82
|
+
|
|
83
|
+
## Commands
|
|
84
|
+
|
|
85
|
+
None — inlining SVG markup and declaring a slot are content edits. No CLI
|
|
86
|
+
command beyond the author loop below.
|
|
87
|
+
|
|
88
|
+
## Verify
|
|
89
|
+
|
|
90
|
+
Verify with [the author loop](../quickstart.md#the-author-loop).
|
|
91
|
+
|
|
92
|
+
## See also
|
|
93
|
+
|
|
94
|
+
- [Static assets](../assets.md) — the full reference/import contract, template
|
|
95
|
+
level vs. section folder.
|
|
96
|
+
- [`bundle-binary-asset`](../rules/bundle-binary-asset.md) — the rule that
|
|
97
|
+
flags an unreferenced binary in a section folder, and the SVG-inlining fix.
|
|
98
|
+
- [Check](../check.md) — the size stage a large inlined SVG or a bloated
|
|
99
|
+
renderer bundle counts against.
|
|
100
|
+
- [Image slot with a locked crop](image-slot-crop.md) — the real channel for a
|
|
101
|
+
photo: an `image` slot, an `image-assign` decision, and the `<Image>`
|
|
102
|
+
primitive.
|
|
103
|
+
- [Theme and CSS](../theme-and-css.md) — where a template's fonts are declared.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
purpose: Use a third-party npm package in a section.
|
|
3
|
+
status: living
|
|
4
|
+
related: [../islands.md, ../pack.md, ../rules/single-react.md, ../rules/no-bare-css-import.md, ../rules/no-css-import-from-render-path.md]
|
|
5
|
+
updated: 2026-07-15
|
|
6
|
+
---
|
|
7
|
+
# Use a third-party npm package in a section
|
|
8
|
+
|
|
9
|
+
## Goal
|
|
10
|
+
|
|
11
|
+
Use an npm package in a section.
|
|
12
|
+
|
|
13
|
+
## The delta
|
|
14
|
+
|
|
15
|
+
Install the package like any npm dependency:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm i swiper
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Only a published version.** `pack` refuses to produce a submission zip when a
|
|
22
|
+
dependency uses a local `file:`/`link:` protocol — depend on a real published
|
|
23
|
+
version, never a path on your own machine.
|
|
24
|
+
|
|
25
|
+
**Where you import it from matters.** A client library — a carousel, a map SDK,
|
|
26
|
+
anything that touches `window` or the DOM — is imported inside a `"use client"`
|
|
27
|
+
island, the same as any other browser-only code:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
// sections/gallery/Carousel.tsx
|
|
31
|
+
"use client";
|
|
32
|
+
|
|
33
|
+
import Swiper from "swiper";
|
|
34
|
+
|
|
35
|
+
export default function Carousel({ slides }: { slides: { id: string; url: string }[] }) {
|
|
36
|
+
// …
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
A server-side import — one that lands in the Renderer or a plain component it
|
|
41
|
+
imports, not behind `"use client"` — must stay pure and deterministic: no reads
|
|
42
|
+
of the clock, randomness, or the network at render time. A package built for a
|
|
43
|
+
browser runtime (it touches `window`, schedules a timer, generates an id) does
|
|
44
|
+
not belong there; move the usage into an island.
|
|
45
|
+
|
|
46
|
+
**A package that ships its own stylesheet** is imported by its bare specifier,
|
|
47
|
+
from the module that renders it — the Renderer's import graph (the components
|
|
48
|
+
and islands it imports, transitively) is what the CSS build bundles and
|
|
49
|
+
harvests for the section's stylesheet:
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
// sections/gallery/Carousel.tsx
|
|
53
|
+
"use client";
|
|
54
|
+
|
|
55
|
+
import Swiper from "swiper";
|
|
56
|
+
import "swiper/css"; // ← the sanctioned channel: a bare specifier reaches this section's layer
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
A **local** `.css` import from that same render path is a different, banned case
|
|
60
|
+
— see [`no-css-import-from-render-path`](../rules/no-css-import-from-render-path.md)
|
|
61
|
+
for the full distinction between the two, and
|
|
62
|
+
[`no-bare-css-import`](../rules/no-bare-css-import.md) for why a hand-written
|
|
63
|
+
stylesheet can't reach the package's CSS with its own `@import` instead.
|
|
64
|
+
|
|
65
|
+
## Commands
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm i <package>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Verify
|
|
72
|
+
|
|
73
|
+
Verify with [the author loop](../quickstart.md#the-author-loop) — a green
|
|
74
|
+
`check` after the install confirms the package resolves, its CSS (if any)
|
|
75
|
+
landed in the section's layer, and the workspace still carries exactly one copy
|
|
76
|
+
of React.
|
|
77
|
+
|
|
78
|
+
## See also
|
|
79
|
+
|
|
80
|
+
- [Islands](../islands.md) — writing a `"use client"` island; a third-party
|
|
81
|
+
client library is fine inside one, never in a Renderer.
|
|
82
|
+
- [`template-kit pack`](../pack.md) — why a `file:`/`link:` dependency blocks a
|
|
83
|
+
submission.
|
|
84
|
+
- [`single-react`](../rules/single-react.md) — a package that pulls its own
|
|
85
|
+
copy of React breaks hydration; the installed tree may carry only one.
|
|
86
|
+
- [`no-css-import-from-render-path`](../rules/no-css-import-from-render-path.md) —
|
|
87
|
+
bare package specifier (legal) vs. a local `.css` import (banned) from a
|
|
88
|
+
section's render path.
|
|
89
|
+
- [`no-bare-css-import`](../rules/no-bare-css-import.md) — why a hand-written
|
|
90
|
+
stylesheet can't reach a package's CSS with its own `@import` either.
|
package/docs/rules/INDEX.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
purpose: Router for the rule corpus — every template-kit/<id> an author can hit, and the page that explains it.
|
|
3
3
|
status: living
|
|
4
4
|
related: [server-vs-client.md, ../eslint.md, ../check.md, ../INDEX.md]
|
|
5
|
-
updated: 2026-07-
|
|
5
|
+
updated: 2026-07-15
|
|
6
6
|
---
|
|
7
7
|
# Rules
|
|
8
8
|
|
|
@@ -34,7 +34,7 @@ and it is explained there rather than on each of them.
|
|
|
34
34
|
| [`no-hex`](no-hex.md) | eslint | No hard-coded colours — the one legal colour function is a `color-mix()` over theme-derived arguments. Islands are NOT exempt. |
|
|
35
35
|
| [`no-css-import-from-render-path`](no-css-import-from-render-path.md) | eslint | No relative/absolute .css import from a section's render path — a package's is the sanctioned channel. |
|
|
36
36
|
| [`bundle-incomplete`](bundle-incomplete.md) | check | A section folder is missing one of its four required files. |
|
|
37
|
-
| [`bundle-binary-asset`](bundle-binary-asset.md) | check |
|
|
37
|
+
| [`bundle-binary-asset`](bundle-binary-asset.md) | check | An unreferenced binary asset sits in a section folder. |
|
|
38
38
|
| [`schema-invalid`](schema-invalid.md) | check | A section's schema.ts or fill-spec.ts fails a contract assertion, or cannot be loaded. |
|
|
39
39
|
| [`fixtures-invalid`](fixtures-invalid.md) | check | A section's fixtures.ts does not satisfy the fixture-module shape. |
|
|
40
40
|
| [`manifest-invalid`](manifest-invalid.md) | check | A template's manifest.json is missing, unparseable, or violates the manifest contract. |
|
|
@@ -55,6 +55,8 @@ and it is explained there rather than on each of them.
|
|
|
55
55
|
| [`license-denied`](license-denied.md) | check | A production dependency's license is not on the allowlist. |
|
|
56
56
|
| [`size-renderer-bundle`](size-renderer-bundle.md) | check | A section's renderer bundle exceeds its byte budget. |
|
|
57
57
|
| [`size-section-css`](size-section-css.md) | check | A section's compiled CSS exceeds its byte budget. |
|
|
58
|
+
| [`size-island-bundle`](size-island-bundle.md) | check | A section's island (browser) bundle exceeds its byte budget. |
|
|
59
|
+
| [`size-assets`](size-assets.md) | check | A section's static assets exceed their byte budget. |
|
|
58
60
|
| [`no-templates`](no-templates.md) | check | The workspace has no template to check at all. |
|
|
59
61
|
|
|
60
62
|
## See also
|
|
@@ -1,44 +1,43 @@
|
|
|
1
1
|
---
|
|
2
2
|
purpose: The template-kit/bundle-binary-asset authoring rule — what it enforces, why, and how to fix it.
|
|
3
3
|
status: living
|
|
4
|
-
related: [INDEX.md, bundle-incomplete.md, ../primitives.md]
|
|
5
|
-
updated: 2026-07-
|
|
4
|
+
related: [INDEX.md, bundle-incomplete.md, ../primitives.md, ../assets.md]
|
|
5
|
+
updated: 2026-07-16
|
|
6
6
|
---
|
|
7
7
|
# template-kit/bundle-binary-asset
|
|
8
8
|
|
|
9
|
-
> A
|
|
10
|
-
>
|
|
9
|
+
> A static asset may live in a section folder — but only if something references it. An
|
|
10
|
+
> unreferenced binary is dead weight and can never be served.
|
|
11
11
|
|
|
12
12
|
## Rule
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
A binary asset inside a section folder is allowed **only when the section references it** —
|
|
15
|
+
imported in `Renderer.tsx` (`import crest from "./assets/crest.svg"`) or `url()`-referenced
|
|
16
|
+
from one of the section's CSS files. A binary that nothing references is flagged. The check
|
|
17
|
+
looks two ways to find candidates:
|
|
16
18
|
|
|
17
19
|
- **By extension** — `.svg`, `.png`, `.jpg`/`.jpeg`, `.gif`, `.webp`, `.avif`, `.ico`,
|
|
18
20
|
`.woff`/`.woff2`, `.ttf`, `.otf`, `.eot`, `.mp4`, `.webm`, `.mov`, `.mp3`, `.wav`,
|
|
19
21
|
`.pdf`, `.zip`.
|
|
20
|
-
- **By content** — any other file whose first bytes contain a NUL byte
|
|
21
|
-
asset type the extension list doesn't name.
|
|
22
|
-
|
|
23
|
-
This is scoped to the section folder itself (`Renderer.tsx`, `schema.ts`, `components/`,
|
|
24
|
-
everything under it) — not to the photos a listing supplies. Those arrive at runtime
|
|
25
|
-
through a slot's `src`, never as a file that ships inside your section.
|
|
22
|
+
- **By content** — any other file whose first bytes contain a NUL byte.
|
|
26
23
|
|
|
27
24
|
## Reason
|
|
28
25
|
|
|
29
|
-
A
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Everything a section needs to draw — an icon, a divider graphic, a background pattern —
|
|
35
|
-
is expressible as markup instead: an inline `<svg>`, or a shape built from CSS. That
|
|
36
|
-
markup is text, ships with the rest of your JSX, and needs no pipeline at all.
|
|
26
|
+
A referenced asset is first-class: the build content-hashes it to a stable
|
|
27
|
+
`/section-assets/` URL and the publish pipeline uploads it — identical in the `dev`
|
|
28
|
+
preview, the editor, and the published page. An *unreferenced* binary has no such URL: it
|
|
29
|
+
is never emitted or uploaded, yet it still counts toward the section's content hash (its
|
|
30
|
+
identity). So it is pure dead weight — a file that ships nowhere.
|
|
37
31
|
|
|
38
32
|
## Fix
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
Either reference the asset, or delete it.
|
|
35
|
+
|
|
36
|
+
- **Reference it from a component:** `import crest from "./assets/crest.svg"` and render it
|
|
37
|
+
with the `Image` primitive — `<Image src={crest} alt="…" />`. See [Static assets](../assets.md).
|
|
38
|
+
- **Reference it from CSS:** `background-image: url("./assets/crest.svg")` (or a
|
|
39
|
+
`@font-face` `src`) in the section's `styles.css`.
|
|
40
|
+
- **Delete it** if it is left over and unused.
|
|
42
41
|
|
|
43
42
|
### Before
|
|
44
43
|
|
|
@@ -49,32 +48,25 @@ sections/hero/
|
|
|
49
48
|
fill-spec.ts
|
|
50
49
|
fixtures.ts
|
|
51
50
|
assets/
|
|
52
|
-
|
|
51
|
+
crest.svg ← nothing imports or url()-references this file
|
|
53
52
|
```
|
|
54
53
|
|
|
54
|
+
### After
|
|
55
|
+
|
|
55
56
|
```tsx
|
|
56
57
|
// sections/hero/Renderer.tsx
|
|
57
|
-
import
|
|
58
|
+
import { Image } from "@homepages/template-kit";
|
|
59
|
+
import crest from "./assets/crest.svg";
|
|
58
60
|
|
|
59
|
-
export function Renderer() {
|
|
60
|
-
return <
|
|
61
|
+
export default function Renderer() {
|
|
62
|
+
return <Image src={crest} alt="" />;
|
|
61
63
|
}
|
|
62
64
|
```
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
```text
|
|
67
|
-
sections/hero/
|
|
68
|
-
Renderer.tsx
|
|
69
|
-
schema.ts
|
|
70
|
-
fill-spec.ts
|
|
71
|
-
fixtures.ts
|
|
72
|
-
components/
|
|
73
|
-
ScrollArrow.tsx ← markup, not a binary file
|
|
74
|
-
```
|
|
66
|
+
For a tiny glyph or divider, prefer inline `<svg>` markup over a file at all — it ships with
|
|
67
|
+
your JSX, needs no asset at all, and costs no extra request:
|
|
75
68
|
|
|
76
69
|
```tsx
|
|
77
|
-
// sections/hero/components/ScrollArrow.tsx
|
|
78
70
|
export function ScrollArrow() {
|
|
79
71
|
return (
|
|
80
72
|
<svg viewBox="0 0 16 16" className="h-4 w-4" fill="none" aria-hidden="true">
|
|
@@ -84,19 +76,8 @@ export function ScrollArrow() {
|
|
|
84
76
|
}
|
|
85
77
|
```
|
|
86
78
|
|
|
87
|
-
```tsx
|
|
88
|
-
// sections/hero/Renderer.tsx
|
|
89
|
-
import { ScrollArrow } from "./components/ScrollArrow.js";
|
|
90
|
-
|
|
91
|
-
export function Renderer() {
|
|
92
|
-
return <ScrollArrow />;
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
79
|
## See also
|
|
97
80
|
|
|
98
|
-
- [
|
|
99
|
-
|
|
100
|
-
- [Contract primitives](../primitives.md) — `Image`,
|
|
101
|
-
a listing supplies. It reads a `src` at runtime; it is not a way to ship a binary file
|
|
102
|
-
inside your section.
|
|
81
|
+
- [Static assets](../assets.md) — where an asset may live and how to reference it.
|
|
82
|
+
- [`bundle-incomplete`](bundle-incomplete.md) — the other rule on the folder's contents.
|
|
83
|
+
- [Contract primitives](../primitives.md) — `Image`, which renders an asset URL you import.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
purpose: The template-kit/bundle-incomplete authoring rule — what it enforces, why, and how to fix it.
|
|
3
3
|
status: living
|
|
4
4
|
related: [INDEX.md, ../schema-system.md]
|
|
5
|
-
updated: 2026-07-
|
|
5
|
+
updated: 2026-07-15
|
|
6
6
|
---
|
|
7
7
|
# template-kit/bundle-incomplete
|
|
8
8
|
|
|
@@ -30,7 +30,7 @@ Two near-misses do **not** satisfy it:
|
|
|
30
30
|
searched for down the tree.
|
|
31
31
|
|
|
32
32
|
Everything else in the folder is unconstrained. `components/`, `hooks/`, `utils/`, any
|
|
33
|
-
nesting, any number of support modules, islands, stylesheets
|
|
33
|
+
nesting, any number of support modules, islands, stylesheets: the check has no
|
|
34
34
|
opinion about them, and neither does the rest of the kit.
|
|
35
35
|
|
|
36
36
|
## Reason
|
package/docs/rules/css-reason.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
purpose: The template-kit/css-reason authoring rule — what it enforces, why, and how to fix it.
|
|
3
3
|
status: living
|
|
4
4
|
related: [INDEX.md, ../theme-and-css.md]
|
|
5
|
-
updated: 2026-07-
|
|
5
|
+
updated: 2026-07-15
|
|
6
6
|
---
|
|
7
7
|
# template-kit/css-reason
|
|
8
8
|
|
|
@@ -36,7 +36,7 @@ and cannot leak into another section on the same page. Hand-written CSS gives al
|
|
|
36
36
|
up.
|
|
37
37
|
|
|
38
38
|
There is exactly one thing utilities cannot do: style **DOM that Tailwind's scanner never
|
|
39
|
-
sees**. Markup an
|
|
39
|
+
sees**. Markup an island builds at runtime has no class in your source for the scanner
|
|
40
40
|
to find, and a third-party widget's own DOM was never yours to put classes on. Those are
|
|
41
41
|
the cases hand-CSS exists for.
|
|
42
42
|
|
|
@@ -67,7 +67,7 @@ classes on the markup.
|
|
|
67
67
|
### After
|
|
68
68
|
|
|
69
69
|
```css
|
|
70
|
-
/* css-reason: styles the marker DOM the neighborhood-map
|
|
70
|
+
/* css-reason: styles the marker DOM the neighborhood-map island builds at runtime —
|
|
71
71
|
the nodes are created by the map library after hydration, so Tailwind's scanner never
|
|
72
72
|
sees a class to compile. */
|
|
73
73
|
.tr-map-marker {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
purpose: The template-kit/no-bare-css-import authoring rule — what it enforces, why, and how to fix it.
|
|
3
3
|
status: living
|
|
4
4
|
related: [INDEX.md, ../theme-and-css.md]
|
|
5
|
-
updated: 2026-07-
|
|
5
|
+
updated: 2026-07-15
|
|
6
6
|
---
|
|
7
7
|
# template-kit/no-bare-css-import
|
|
8
8
|
|
|
@@ -59,11 +59,11 @@ in the whole kit: the only signal is that the styles are not there.
|
|
|
59
59
|
Delete the `@import`.
|
|
60
60
|
|
|
61
61
|
**A package's stylesheet is reached from the code that needs it**, not from CSS. Write
|
|
62
|
-
`import "swiper/css";` in the component
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
`import "swiper/css";` in the component or island that renders the thing — the CSS build
|
|
63
|
+
bundles your `Renderer.tsx` and everything it imports, transitively, precisely to harvest
|
|
64
|
+
those imports, and it compiles what they pull in into **this section's cascade layer**.
|
|
65
|
+
Package CSS goes in first and your own hand-CSS second, so at equal specificity your rules
|
|
66
|
+
win.
|
|
67
67
|
|
|
68
68
|
Two things authors get wrong immediately after:
|
|
69
69
|
|
|
@@ -79,7 +79,7 @@ Two things authors get wrong immediately after:
|
|
|
79
79
|
|
|
80
80
|
```css
|
|
81
81
|
/* sections/gallery/styles.css */
|
|
82
|
-
/* css-reason: styles the slide DOM the Swiper
|
|
82
|
+
/* css-reason: styles the slide DOM the Swiper carousel builds at runtime. */
|
|
83
83
|
@import "swiper/css"; /* template-kit/no-bare-css-import — silently dropped */
|
|
84
84
|
|
|
85
85
|
.tr-gallery .swiper-pagination-bullet-active {
|
|
@@ -95,7 +95,7 @@ The `@import` is gone from the stylesheet:
|
|
|
95
95
|
|
|
96
96
|
```css
|
|
97
97
|
/* sections/gallery/styles.css */
|
|
98
|
-
/* css-reason: overrides the Swiper pagination DOM the
|
|
98
|
+
/* css-reason: overrides the Swiper pagination DOM the carousel builds at runtime —
|
|
99
99
|
those nodes carry the library's own classes, which Tailwind's scanner never sees. */
|
|
100
100
|
.tr-gallery .swiper-pagination-bullet-active {
|
|
101
101
|
background: var(--tr-color-primary);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
purpose: The template-kit/no-css-import-from-render-path authoring rule — what it enforces, why, and how to fix it.
|
|
3
3
|
status: living
|
|
4
4
|
related: [INDEX.md, ../theme-and-css.md]
|
|
5
|
-
updated: 2026-07-
|
|
5
|
+
updated: 2026-07-15
|
|
6
6
|
---
|
|
7
7
|
# template-kit/no-css-import-from-render-path
|
|
8
8
|
|
|
@@ -31,8 +31,8 @@ server-rendered inline before it is hydrated.
|
|
|
31
31
|
## Reason
|
|
32
32
|
|
|
33
33
|
The section's CSS build works by **bundling the render path itself** — `Renderer.tsx` and
|
|
34
|
-
|
|
35
|
-
pull in. A bare specifier resolves into `node_modules`, which is exactly what that build
|
|
34
|
+
every module it imports, components and islands alike — and harvesting the CSS those
|
|
35
|
+
imports pull in. A bare specifier resolves into `node_modules`, which is exactly what that build
|
|
36
36
|
looks for: `import "swiper/css";` in `Renderer.tsx` is not a mistake, it is the mechanism.
|
|
37
37
|
|
|
38
38
|
A **local** stylesheet is a different problem. Every `.css` file that already lives in the
|
|
@@ -47,11 +47,10 @@ Delete the import. A local stylesheet already ships — the build picks up every
|
|
|
47
47
|
in the section folder on its own; importing it again only makes it double-emit.
|
|
48
48
|
|
|
49
49
|
A package's stylesheet is different: import it **by its bare specifier**, from the module
|
|
50
|
-
that needs it. The build bundles `Renderer.tsx`
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
layer.
|
|
50
|
+
that needs it. The build bundles `Renderer.tsx`'s import graph **transitively**, so a bare
|
|
51
|
+
specifier is equally legal in the Renderer itself or in any component or island it
|
|
52
|
+
imports — not only at the top of the tree. That import is how the build finds the
|
|
53
|
+
stylesheet and routes it into the section's cascade layer.
|
|
55
54
|
|
|
56
55
|
### Before
|
|
57
56
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
purpose: The template-kit/size-assets authoring rule — what it enforces, why, and how to fix it.
|
|
3
|
+
status: living
|
|
4
|
+
related: [INDEX.md, size-section-css.md, ../theme-and-css.md]
|
|
5
|
+
updated: 2026-07-15
|
|
6
|
+
---
|
|
7
|
+
# template-kit/size-assets
|
|
8
|
+
|
|
9
|
+
> A section's static assets — the svg, images, and fonts it imports or `url()`-references —
|
|
10
|
+
> must total **≤ 250 KB raw.** Raw, not gzip: images and fonts are already compressed
|
|
11
|
+
> formats, so gzipping them again buys nothing.
|
|
12
|
+
|
|
13
|
+
## Rule
|
|
14
|
+
|
|
15
|
+
A section's static assets are budgeted by **total raw bytes, 250 KB or less**. What is
|
|
16
|
+
measured is every asset the published section actually ships:
|
|
17
|
+
|
|
18
|
+
- an svg, image, or font `import`ed directly in `Renderer.tsx` (or an island), and
|
|
19
|
+
- an svg, image, or font `url()`-referenced from the section's own CSS or from a
|
|
20
|
+
package stylesheet its module graph pulls in — including a webfont `@font-face`s.
|
|
21
|
+
|
|
22
|
+
The same asset referenced twice (from both a stylesheet and a component, say) counts
|
|
23
|
+
once, not twice.
|
|
24
|
+
|
|
25
|
+
The budget is **visible in `template-kit check`'s configuration and is not
|
|
26
|
+
author-overridable**. A workspace cannot raise it, and there is no per-section exemption.
|
|
27
|
+
|
|
28
|
+
## Reason
|
|
29
|
+
|
|
30
|
+
An svg icon or a product photo a section imports, and a webfont a package's stylesheet
|
|
31
|
+
pulls in, both ship to the same page load as the section's own code and CSS — they are
|
|
32
|
+
not free just because they never appear in a byte-budget an author is used to watching.
|
|
33
|
+
|
|
34
|
+
Fonts are the sharpest case. A `url()`-referenced webfont used to inline as part of the
|
|
35
|
+
compiled stylesheet, which made a large font look like a CSS problem: trimming your own
|
|
36
|
+
rules never fixed it, because the bytes were never your CSS to begin with. This rule
|
|
37
|
+
separates that weight into its own budget — a font, an icon, or an image is measured as
|
|
38
|
+
what it is, and the stylesheet budget ([`size-section-css`](size-section-css.md)) is left
|
|
39
|
+
to measure only what it actually contains.
|
|
40
|
+
|
|
41
|
+
## Fix
|
|
42
|
+
|
|
43
|
+
Two moves, in order of how often they work:
|
|
44
|
+
|
|
45
|
+
1. **Shrink or subset the asset.** A full variable font family, shipped for three
|
|
46
|
+
weights actually used, is the usual cause — subset it to the characters and weights
|
|
47
|
+
the section renders. An unoptimized PNG is the other common case — recompress it, or
|
|
48
|
+
switch to a format built for the content (`webp`/`avif` for photos, `svg` for icons).
|
|
49
|
+
2. **Drop assets the section does not use.** A copied-in icon set or a leftover preview
|
|
50
|
+
image that never renders still ships its bytes — delete what the section does not
|
|
51
|
+
reference.
|
|
52
|
+
|
|
53
|
+
### Before
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
// sections/hero/Renderer.tsx
|
|
57
|
+
import "brandfont/css"; // ← ships the package's full variable font family
|
|
58
|
+
|
|
59
|
+
export default function Renderer() {
|
|
60
|
+
return <h1 style={{ fontFamily: "BrandFont" }}>Welcome home</h1>;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### After
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
// sections/hero/Renderer.tsx
|
|
68
|
+
import heroIcon from "./assets/hero-icon.svg"; // ← a small, single-purpose asset
|
|
69
|
+
|
|
70
|
+
export default function Renderer() {
|
|
71
|
+
// Uses a font the template's theme already declares (`--tr-font-<name>`),
|
|
72
|
+
// which costs nothing because it is already loaded — no package font import.
|
|
73
|
+
return (
|
|
74
|
+
<h1 className="tr-font-display">
|
|
75
|
+
<img src={heroIcon} alt="" /> Welcome home
|
|
76
|
+
</h1>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## See also
|
|
82
|
+
|
|
83
|
+
- [`size-section-css`](size-section-css.md) — the stylesheet budget this rule was split
|
|
84
|
+
out of; a `url()`-referenced font now counts here, not there.
|
|
85
|
+
- [`size-renderer-bundle`](size-renderer-bundle.md) — the same idea applied to the
|
|
86
|
+
section's JavaScript.
|
|
87
|
+
- [Theme and CSS](../theme-and-css.md) — the fonts and tokens the page already loads,
|
|
88
|
+
which cost you nothing to reuse.
|