@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,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>template-kit dev</title>
|
|
7
|
+
<script type="module" crossorigin src="/dev-client/assets/index-bpMP0b0W.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/dev-client/assets/index-DfEfrp6P.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div id="root"></div>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ResponsiveImage, ResponsiveSource } from "./contracts/image-descriptor.js";
|
|
2
2
|
import { Coordinates, FloorplanAssignment, PropertyFacts, UnitRow } from "./schema/property-facts.js";
|
|
3
|
+
import "./asset-modules.js";
|
|
3
4
|
import { COLLECTIONS, CONTENT_SLOT_TYPES, CollectionField, CollectionIdKey, CollectionOwnership, ContentSlotType, DECISION_TYPES, DERIVED_INPUT_FIELDS, DecisionType, DerivedInputName, FORMATTER_NAMES, FormatterName, REFERRAL_FIELDS, REFERRAL_IMAGE_FIELDS, REF_SLOT_TYPES, RefSlotType, ReferralField, ReferralImageField, SLOT_TYPES, SOURCE_FIELDS, SlotType, SourceFieldContainer, SourceFieldDescriptor, SourceFieldName, SourceFieldValueType, WritebackTarget, collectionForSlot } from "./contracts/slot-catalog.js";
|
|
4
5
|
import { AddressParts, TRANSFORMS, TRANSFORM_NAMES, TransformDescriptor, TransformFn, TransformInput, TransformName, TransformUnit } from "./contracts/transforms.js";
|
|
5
6
|
import { VariantSelectorFn, VariantsMeta, makeResolveVariants } from "./schema/resolve-variants.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { hasClientDirective } from "../eslint/is-client-file.js";
|
|
2
|
+
//#region src/islands/detect.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* True iff `file` is a client component — an on-disk source file whose directive
|
|
5
|
+
* prologue is `"use client"`. Returns false for an unreadable or absent path.
|
|
6
|
+
*/
|
|
7
|
+
declare function isClientComponentFile(file: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Compose an island key: `<sectionKey>:<componentName>` (e.g.
|
|
10
|
+
* `spw-essential/floorplan:UnitFilter`). The key is what a marker in the DOM
|
|
11
|
+
* carries and what the browser loader resolves the client module by, so the
|
|
12
|
+
* emitter and the loader must derive it identically.
|
|
13
|
+
*/
|
|
14
|
+
declare function islandKey(sectionKey: string, componentName: string): string;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { hasClientDirective, isClientComponentFile, islandKey };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { hasClientDirective, isClientFile } from "../eslint/is-client-file.js";
|
|
2
|
+
|
|
3
|
+
//#region src/islands/detect.ts
|
|
4
|
+
/**
|
|
5
|
+
* True iff `file` is a client component — an on-disk source file whose directive
|
|
6
|
+
* prologue is `"use client"`. Returns false for an unreadable or absent path.
|
|
7
|
+
*/
|
|
8
|
+
function isClientComponentFile(file) {
|
|
9
|
+
return isClientFile(file);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Compose an island key: `<sectionKey>:<componentName>` (e.g.
|
|
13
|
+
* `spw-essential/floorplan:UnitFilter`). The key is what a marker in the DOM
|
|
14
|
+
* carries and what the browser loader resolves the client module by, so the
|
|
15
|
+
* emitter and the loader must derive it identically.
|
|
16
|
+
*/
|
|
17
|
+
function islandKey(sectionKey, componentName) {
|
|
18
|
+
return `${sectionKey}:${componentName}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
export { hasClientDirective, isClientComponentFile, islandKey };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { isClientComponentFile } from "./detect.js";
|
|
2
|
+
import { extname, join } from "node:path";
|
|
3
|
+
import { readdir } from "node:fs/promises";
|
|
4
|
+
|
|
5
|
+
//#region src/islands/discover.ts
|
|
6
|
+
const COMPONENT_EXTS = /* @__PURE__ */ new Set([".tsx", ".jsx"]);
|
|
7
|
+
/** Every `"use client"` component file anywhere under a section directory (recursive). */
|
|
8
|
+
async function findSectionIslands(sectionDir) {
|
|
9
|
+
const out = [];
|
|
10
|
+
for (const entry of await readdir(sectionDir, { withFileTypes: true })) {
|
|
11
|
+
const full = join(sectionDir, entry.name);
|
|
12
|
+
if (entry.isDirectory()) out.push(...await findSectionIslands(full));
|
|
13
|
+
else if (COMPONENT_EXTS.has(extname(entry.name)) && isClientComponentFile(full)) out.push(full);
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { findSectionIslands };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Plugin } from "esbuild";
|
|
2
|
+
//#region src/islands/esbuild-plugin.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Wrap every `"use client"` `.tsx`/`.jsx` in the build graph so it renders through
|
|
5
|
+
* `IslandMarker`, keyed `islandKey(sectionKey, <file-basename>)`. `sectionKey` is
|
|
6
|
+
* fixed per bundle (one section per renderer build). Non-client modules and other
|
|
7
|
+
* extensions fall through to esbuild untouched.
|
|
8
|
+
*/
|
|
9
|
+
declare function islandTransformEsbuildPlugin(opts: {
|
|
10
|
+
sectionKey: string;
|
|
11
|
+
}): Plugin;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { islandTransformEsbuildPlugin };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { islandKey } from "./detect.js";
|
|
2
|
+
import { wrapClientModuleSource } from "./wrap.js";
|
|
3
|
+
import { basename, extname } from "node:path";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
|
|
6
|
+
//#region src/islands/esbuild-plugin.ts
|
|
7
|
+
const LOADER = {
|
|
8
|
+
".tsx": "tsx",
|
|
9
|
+
".jsx": "jsx"
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Wrap every `"use client"` `.tsx`/`.jsx` in the build graph so it renders through
|
|
13
|
+
* `IslandMarker`, keyed `islandKey(sectionKey, <file-basename>)`. `sectionKey` is
|
|
14
|
+
* fixed per bundle (one section per renderer build). Non-client modules and other
|
|
15
|
+
* extensions fall through to esbuild untouched.
|
|
16
|
+
*/
|
|
17
|
+
function islandTransformEsbuildPlugin(opts) {
|
|
18
|
+
return {
|
|
19
|
+
name: "template-kit:island-transform-esbuild",
|
|
20
|
+
setup(b) {
|
|
21
|
+
b.onLoad({ filter: /\.(tsx|jsx)$/ }, async (args) => {
|
|
22
|
+
const code = await readFile(args.path, "utf8");
|
|
23
|
+
const wrapped = wrapClientModuleSource(code, islandKey(opts.sectionKey, basename(args.path, extname(args.path))));
|
|
24
|
+
const ext = extname(args.path);
|
|
25
|
+
if (wrapped === null) return {
|
|
26
|
+
contents: code,
|
|
27
|
+
loader: LOADER[ext]
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
contents: wrapped,
|
|
31
|
+
loader: LOADER[ext]
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { islandTransformEsbuildPlugin };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import MagicString from "magic-string";
|
|
2
|
+
//#region src/islands/wrap.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Rewrite a client module's source so its default export renders through
|
|
5
|
+
* `IslandMarker` with `islandKey`. Returns null when `code` is not a client
|
|
6
|
+
* component (`hasClientDirective` false) or has no default export — the caller
|
|
7
|
+
* then leaves the module untouched. The rebound original is `__trIslandReal`;
|
|
8
|
+
* it still flows through the consumer's JSX/TS transform unchanged.
|
|
9
|
+
*/
|
|
10
|
+
declare function wrapClientModuleSource(code: string, islandKey: string): string | null;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { wrapClientModuleSource };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { hasClientDirective } from "../eslint/is-client-file.js";
|
|
2
|
+
import MagicString from "magic-string";
|
|
3
|
+
|
|
4
|
+
//#region src/islands/wrap.ts
|
|
5
|
+
const DEFAULT_EXPORT = /export\s+default\s+/;
|
|
6
|
+
/**
|
|
7
|
+
* The codemod as a `MagicString`, so a caller (the Vite plugin) can emit a
|
|
8
|
+
* hi-res sourcemap alongside the rewritten code. Returns null on the same terms
|
|
9
|
+
* as `wrapClientModuleSource` — a non-client module or one with no default
|
|
10
|
+
* export. The rebound original is `__trIslandReal`; it still flows through the
|
|
11
|
+
* consumer's JSX/TS transform unchanged.
|
|
12
|
+
*/
|
|
13
|
+
function wrapClientModuleMagic(code, islandKey) {
|
|
14
|
+
if (!hasClientDirective(code)) return null;
|
|
15
|
+
if (!DEFAULT_EXPORT.test(code)) return null;
|
|
16
|
+
const s = new MagicString(code);
|
|
17
|
+
s.replace(DEFAULT_EXPORT, "const __trIslandReal = ");
|
|
18
|
+
s.append(wrapperSource(islandKey));
|
|
19
|
+
return s;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Rewrite a client module's source so its default export renders through
|
|
23
|
+
* `IslandMarker` with `islandKey`. Returns null when `code` is not a client
|
|
24
|
+
* component (`hasClientDirective` false) or has no default export — the caller
|
|
25
|
+
* then leaves the module untouched. The rebound original is `__trIslandReal`;
|
|
26
|
+
* it still flows through the consumer's JSX/TS transform unchanged.
|
|
27
|
+
*/
|
|
28
|
+
function wrapClientModuleSource(code, islandKey) {
|
|
29
|
+
return wrapClientModuleMagic(code, islandKey)?.toString() ?? null;
|
|
30
|
+
}
|
|
31
|
+
function wrapperSource(islandKey) {
|
|
32
|
+
return `
|
|
33
|
+
;
|
|
34
|
+
import { createElement as __trCreateElement, useId as __trUseId } from "react";
|
|
35
|
+
import { IslandMarker as __TrIslandMarker } from "@homepages/template-kit";
|
|
36
|
+
export default function __trIslandWrapper(__trProps) {
|
|
37
|
+
const __trIslandId = __trUseId();
|
|
38
|
+
return __trCreateElement(
|
|
39
|
+
__TrIslandMarker,
|
|
40
|
+
{ islandKey: ${JSON.stringify(islandKey)}, islandId: __trIslandId, props: __trProps },
|
|
41
|
+
__trCreateElement(__trIslandReal, __trProps),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
export { wrapClientModuleMagic, wrapClientModuleSource };
|
package/dist/package.js
CHANGED
package/dist/ssr.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ResolvedFixture } from "./schema/fixture-schema.js";
|
|
2
|
+
import "./schema/index.js";
|
|
3
|
+
import { hasClientDirective } from "./eslint/is-client-file.js";
|
|
4
|
+
import { isClientComponentFile, islandKey } from "./islands/detect.js";
|
|
5
|
+
import { wrapClientModuleSource } from "./islands/wrap.js";
|
|
6
|
+
import { findSectionIslands } from "./islands/discover.js";
|
|
7
|
+
import { islandTransformEsbuildPlugin } from "./islands/esbuild-plugin.js";
|
|
8
|
+
import { ComponentType } from "react";
|
|
9
|
+
//#region src/ssr/index.d.ts
|
|
10
|
+
type LoadedSection = {
|
|
11
|
+
Renderer: ComponentType<Record<string, unknown>>;
|
|
12
|
+
snapshotFixtures: ResolvedFixture[];
|
|
13
|
+
invariantFixtures: ResolvedFixture[];
|
|
14
|
+
schemaSlots: Record<string, {
|
|
15
|
+
type: string;
|
|
16
|
+
editable_by_user?: boolean;
|
|
17
|
+
required?: boolean;
|
|
18
|
+
}>;
|
|
19
|
+
};
|
|
20
|
+
type SectionModuleLoader = (dir: string, file: string) => Promise<unknown>;
|
|
21
|
+
declare const nativeSectionLoader: SectionModuleLoader;
|
|
22
|
+
/**
|
|
23
|
+
* Load a bundle's Renderer plus its resolved snapshot and invariant fixture
|
|
24
|
+
* sets from the bundle directory `dir`. `load` resolves each of the four
|
|
25
|
+
* contract files; it defaults to native dynamic import.
|
|
26
|
+
*/
|
|
27
|
+
declare function loadSectionBundle(dir: string, load?: SectionModuleLoader): Promise<LoadedSection>;
|
|
28
|
+
/** Render a resolved fixture's props to static markup — the byte-for-byte page render. */
|
|
29
|
+
declare function renderSectionHtml(renderer: ComponentType<Record<string, unknown>>, props: Record<string, unknown>): string;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { LoadedSection, SectionModuleLoader, findSectionIslands, hasClientDirective, isClientComponentFile, islandKey, islandTransformEsbuildPlugin, loadSectionBundle, nativeSectionLoader, renderSectionHtml, wrapClientModuleSource };
|
package/dist/ssr.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { FixtureModuleShape, buildFixtureSet, buildInvariantFixtures } from "./schema/fixture-schema.js";
|
|
2
|
+
import { hasClientDirective } from "./eslint/is-client-file.js";
|
|
3
|
+
import { isClientComponentFile, islandKey } from "./islands/detect.js";
|
|
4
|
+
import { findSectionIslands } from "./islands/discover.js";
|
|
5
|
+
import { wrapClientModuleSource } from "./islands/wrap.js";
|
|
6
|
+
import { islandTransformEsbuildPlugin } from "./islands/esbuild-plugin.js";
|
|
7
|
+
import { createElement } from "react";
|
|
8
|
+
import { basename, join } from "node:path";
|
|
9
|
+
import { pathToFileURL } from "node:url";
|
|
10
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
11
|
+
|
|
12
|
+
//#region src/ssr/index.ts
|
|
13
|
+
const nativeSectionLoader = (dir, file) => import(pathToFileURL(join(dir, file)).href);
|
|
14
|
+
/**
|
|
15
|
+
* Load a bundle's Renderer plus its resolved snapshot and invariant fixture
|
|
16
|
+
* sets from the bundle directory `dir`. `load` resolves each of the four
|
|
17
|
+
* contract files; it defaults to native dynamic import.
|
|
18
|
+
*/
|
|
19
|
+
async function loadSectionBundle(dir, load = nativeSectionLoader) {
|
|
20
|
+
const rendererMod = await load(dir, "Renderer.tsx");
|
|
21
|
+
const fixturesMod = await load(dir, "fixtures.ts");
|
|
22
|
+
const schemaMod = await load(dir, "schema.ts");
|
|
23
|
+
const parsed = FixtureModuleShape.parse(fixturesMod.default);
|
|
24
|
+
const selfAnchor = schemaMod.schema.meta?.anchor ?? basename(dir);
|
|
25
|
+
return {
|
|
26
|
+
Renderer: rendererMod.default,
|
|
27
|
+
snapshotFixtures: buildFixtureSet({
|
|
28
|
+
schema: schemaMod.schema,
|
|
29
|
+
module: parsed,
|
|
30
|
+
selfAnchor
|
|
31
|
+
}),
|
|
32
|
+
invariantFixtures: buildInvariantFixtures({
|
|
33
|
+
schema: schemaMod.schema,
|
|
34
|
+
module: parsed,
|
|
35
|
+
selfAnchor
|
|
36
|
+
}),
|
|
37
|
+
schemaSlots: schemaMod.schema.slots
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Render a resolved fixture's props to static markup — the byte-for-byte page render. */
|
|
41
|
+
function renderSectionHtml(renderer, props) {
|
|
42
|
+
return renderToStaticMarkup(createElement(renderer, props));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { findSectionIslands, hasClientDirective, isClientComponentFile, islandKey, islandTransformEsbuildPlugin, loadSectionBundle, nativeSectionLoader, renderSectionHtml, wrapClientModuleSource };
|
package/docs/INDEX.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
purpose: Router for the docs corpus that ships inside the @homepages/template-kit npm package.
|
|
3
3
|
status: living
|
|
4
4
|
related: []
|
|
5
|
-
updated: 2026-07-
|
|
5
|
+
updated: 2026-07-16
|
|
6
6
|
---
|
|
7
7
|
# template-kit docs — router
|
|
8
8
|
|
|
@@ -13,11 +13,19 @@ detail.
|
|
|
13
13
|
|
|
14
14
|
| Doc | What it covers | Read when you're touching... |
|
|
15
15
|
|---|---|---|
|
|
16
|
+
| `quickstart.md` | The cold-start tutorial: create a workspace, preview the starter's `hero` section, read its four files, make one edit, and check it — owns the author-loop anchor every recipe links to | your first section, end to end |
|
|
17
|
+
| `overview.md` | The one-page mental model: workspace → templates → sections, the four reserved files, and the server-vs-client mapping | orienting yourself before your first section |
|
|
16
18
|
| `llms.txt` | Machine-readable index of this corpus for an agent working inside an installed kit | orienting an agent in a consuming workspace |
|
|
17
19
|
| `schema-system.md` | The section authoring contract: `schema.ts` / `fill-spec.ts` / `fixtures.ts` / `Renderer.tsx`, `SectionProps` inference, the closed source/transform vocabulary, and the golden property fixtures | authoring or changing any section |
|
|
20
|
+
| `vocabulary.md` | The closed authoring vocabulary — the 9 slot types, the `direct()` source fields, and the `derived()` transforms, with selection semantics | choosing which slot type / source / transform to use |
|
|
18
21
|
| `primitives.md` | The five contract primitives (`Section`, `Slot`, `SlotItem`, `SlotGroup`, `Image`) and the marker attributes they emit | writing a section's markup |
|
|
22
|
+
| `assets.md` | Static assets (svg/images/fonts): where they live (template level or a section folder, if referenced), referencing them from a section's CSS with `url()`/`@font-face` or importing them into a component, and the asset-vs-content-image distinction | shipping a logo, texture, icon, or brand font with a template |
|
|
19
23
|
| `theme-and-css.md` | The two CSS entries, the one-Tailwind-import rule, `TokenTheme` (your own token names → your own Tailwind utilities), the `template-kit theme` command that generates `theme.css` from `theme.ts`, the breakpoint ladder, and the opt-in `content-visibility` recipe for long, image-heavy pages | a template's theme, its CSS entry, or a long page with a live map or full-screen overlay |
|
|
20
24
|
| `islands.md` | Interactivity: `"use client"` components, the serializable-props rule, editor options, the loader | anything interactive in a section |
|
|
21
25
|
| `eslint.md` | The `@homepages/template-kit/eslint` preset: the two-line config, the ESLint + parser install it expects, and its glob scope | wiring up lint in a template workspace |
|
|
22
26
|
| `check.md` | The `template-kit check` CLI command: the toolchain it resolves from your workspace, its stages, `--all`/`--json`, output shape, and exit code | running `check` locally or wiring it into CI |
|
|
27
|
+
| `dev.md` | The `template-kit dev` CLI command: the local preview server, its home launcher and two tool cards (full template viewer, canvas mirror), the canvas mirror's selection overlay and per-slot edit panel, live reload, the content-override protocol, and the render/inspect/screenshot endpoints | previewing sections as you author them |
|
|
28
|
+
| `new.md` | The `template-kit new` / `new-section` CLI commands: scaffolding a new template with a working starter section, or adding a bare section to an existing template | adding a template or a section to a workspace |
|
|
29
|
+
| `pack.md` | The `template-kit pack` CLI command: builds a template's submission zip — template source, the workspace lockfile, and a stamped `pack-manifest.json`; refuses on a failing `check`, a missing lockfile, or a local-protocol dependency | packaging a template for submission |
|
|
23
30
|
| `rules/` | One page per rule-id (`template-kit/*`), across both venues — the id printed by a lint or `check` failure resolves here. `rules/INDEX.md` routes them; `rules/server-vs-client.md` is the concept the server-rendering rules assume | any lint or `check` failure |
|
|
31
|
+
| `recipes/` | Task recipes: how do I add a slot, bind a fact, add an island, package a template… Each is a minimal delta from the starter's `hero` section, ending in the author loop. `recipes/INDEX.md` routes them by task | doing something specific and not sure which file to touch |
|
package/docs/assets.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
purpose: Authoring static assets (svg, images, fonts) at the template level or in a section folder, and how they differ from content images.
|
|
3
|
+
status: living
|
|
4
|
+
related: [INDEX.md, primitives.md, theme-and-css.md, rules/size-assets.md, rules/bundle-binary-asset.md, rules/no-raw-element.md]
|
|
5
|
+
updated: 2026-07-16
|
|
6
|
+
---
|
|
7
|
+
# Static assets
|
|
8
|
+
|
|
9
|
+
A **static asset** is a file that is part of the template itself — a logo, a
|
|
10
|
+
background texture, an icon, a brand webfont. It is authored once, is the
|
|
11
|
+
**same for every deliverable** rendered from the template, and is referenced
|
|
12
|
+
either from your CSS or from a component.
|
|
13
|
+
|
|
14
|
+
## Where an asset lives
|
|
15
|
+
|
|
16
|
+
An asset lives in one of two places:
|
|
17
|
+
|
|
18
|
+
- **Template level** — e.g. `templates/<template>/assets/` — shared across every
|
|
19
|
+
section in the template. Reference it from a section's CSS with a `../../assets/…`
|
|
20
|
+
path (below).
|
|
21
|
+
- **Section folder** — e.g. `sections/<section>/assets/` — local to that one
|
|
22
|
+
section. A section-folder asset must be *referenced*: imported in `Renderer.tsx`
|
|
23
|
+
or `url()`-referenced from one of the section's CSS files. An unreferenced binary
|
|
24
|
+
in a section folder is flagged by [`bundle-binary-asset`](rules/bundle-binary-asset.md) —
|
|
25
|
+
it has no build meaning and can never be served.
|
|
26
|
+
|
|
27
|
+
Keep a logo, texture, or font that several sections share in the template-level
|
|
28
|
+
folder; keep one that only a single section uses alongside that section.
|
|
29
|
+
|
|
30
|
+
## Supported files
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
.svg .png .jpg .jpeg .gif .webp .avif .ico images
|
|
34
|
+
.woff .woff2 .ttf .otf .eot fonts
|
|
35
|
+
.mp4 .webm .mov .mp3 .wav audio / video
|
|
36
|
+
.pdf .zip documents
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Referencing an asset — from CSS with `url()`
|
|
40
|
+
|
|
41
|
+
Reference a template-level asset from a section's `styles.css` with `url()` —
|
|
42
|
+
a `@font-face` `src` or a `background-image` resolves to the asset's built URL,
|
|
43
|
+
identical in the `template-kit dev` preview, in the editor, and on the published
|
|
44
|
+
page. Hand-CSS opens with a `css-reason:` comment (see
|
|
45
|
+
[`css-reason`](rules/css-reason.md)):
|
|
46
|
+
|
|
47
|
+
```css
|
|
48
|
+
/* css-reason: brand webfont + crest watermark, both template assets */
|
|
49
|
+
@font-face {
|
|
50
|
+
font-family: "Brand Serif";
|
|
51
|
+
src: url("../../assets/fonts/brand.woff2") format("woff2");
|
|
52
|
+
font-display: swap;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.hero-crest {
|
|
56
|
+
background-image: url("../../assets/crest.svg");
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The `../../assets/…` path climbs out of the section folder to the template-level
|
|
61
|
+
`assets/` directory. A font you declare here is available to the whole section.
|
|
62
|
+
If the family you want is one the template's theme already loads, reference that
|
|
63
|
+
instead — see [Theme and CSS](theme-and-css.md); a font the page already has
|
|
64
|
+
costs nothing to reuse.
|
|
65
|
+
|
|
66
|
+
A **section-folder** asset is referenced the same way, with a section-relative
|
|
67
|
+
path instead — no climbing out of the folder:
|
|
68
|
+
|
|
69
|
+
```css
|
|
70
|
+
/* css-reason: hero watermark, local to this section */
|
|
71
|
+
.hero-crest {
|
|
72
|
+
background-image: url("./assets/crest.svg");
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Referencing an asset — from a component with `import`
|
|
77
|
+
|
|
78
|
+
An asset you render as an element — a logo, a crest — can be imported in the component and
|
|
79
|
+
handed to the `Image` primitive, whose `src` accepts a URL string:
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { Image } from "@homepages/template-kit";
|
|
83
|
+
import crest from "./assets/crest.svg";
|
|
84
|
+
|
|
85
|
+
export default function Renderer() {
|
|
86
|
+
return <Image src={crest} alt="" />;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The import resolves to the asset's built `/section-assets/…` URL — the same URL in the
|
|
91
|
+
`dev` preview, the editor, and the published page. The kit ships an ambient TypeScript
|
|
92
|
+
declaration that types every supported asset import as that URL string, so the import
|
|
93
|
+
typechecks with no per-workspace setup. Use `alt=""` for a purely decorative
|
|
94
|
+
asset. (Never a raw `<img>` — `Image` carries the responsive/fallback contract; a raw
|
|
95
|
+
`<img>` is rejected by [`no-raw-element`](rules/no-raw-element.md).)
|
|
96
|
+
|
|
97
|
+
## Which to use
|
|
98
|
+
|
|
99
|
+
- CSS decoration (backgrounds, `@font-face`, masks) → `url()`.
|
|
100
|
+
- An asset rendered as an element → `import` + `<Image>`.
|
|
101
|
+
- A tiny glyph or icon → inline `<svg>` markup — no file at all.
|
|
102
|
+
|
|
103
|
+
## Assets vs. content images
|
|
104
|
+
|
|
105
|
+
These are two different things; keep them apart.
|
|
106
|
+
|
|
107
|
+
- A **static asset** is authored template decoration — a logo, a texture, a brand
|
|
108
|
+
font. It ships inside the template, is the same on every deliverable, and you
|
|
109
|
+
reference it from your CSS with `url()` or by importing it into a component (above).
|
|
110
|
+
- A **content image** is the property's own content — listing photos, floor
|
|
111
|
+
plans, a headshot. It differs on every deliverable and flows through the
|
|
112
|
+
section's slots via the [`Image`](primitives.md) descriptor, never as a file in
|
|
113
|
+
your template. Declare an image slot in your schema and render it with the
|
|
114
|
+
`Image` primitive; the platform supplies the actual photo per deliverable.
|
|
115
|
+
|
|
116
|
+
Rule of thumb: if the picture is the same for every home the template ever
|
|
117
|
+
renders, it's an asset — place it at the template level or in the section folder
|
|
118
|
+
and reference it from CSS or a component `import`. If it changes with the
|
|
119
|
+
property, it's content — give it a slot. Never hard-code a listing photo as an
|
|
120
|
+
asset.
|
|
121
|
+
|
|
122
|
+
## Keep them lean
|
|
123
|
+
|
|
124
|
+
Every asset a section pulls in counts toward its asset budget — see
|
|
125
|
+
[`size-assets`](rules/size-assets.md). Fonts and images are already compressed, so
|
|
126
|
+
the budget is raw bytes: subset a webfont to the weights and characters you use,
|
|
127
|
+
export images at the size they render, and drop anything a section doesn't
|
|
128
|
+
actually reference.
|
package/docs/check.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
purpose: The `template-kit check` command — what it runs, its flags, its output shape, and its exit code.
|
|
3
3
|
status: living
|
|
4
|
-
related: [INDEX.md, rules/INDEX.md, eslint.md, theme-and-css.md]
|
|
4
|
+
related: [INDEX.md, rules/INDEX.md, eslint.md, theme-and-css.md, dev.md]
|
|
5
5
|
updated: 2026-07-15
|
|
6
6
|
---
|
|
7
7
|
# template-kit check
|
|
@@ -62,8 +62,9 @@ one class of fix at a time:
|
|
|
62
62
|
stray `[object Object]`, a bare `null`/`undefined`/`NaN` leaf, a `src`-less `<img>` —
|
|
63
63
|
plus, for the `typical` fixture, that the editor's sidebar order matches the page's
|
|
64
64
|
real render order.
|
|
65
|
-
6. **size** — every section's renderer bundle
|
|
66
|
-
fixed byte budget
|
|
65
|
+
6. **size** — every section's renderer bundle, compiled stylesheet, and largest island
|
|
66
|
+
(browser) bundle, gzipped, against a fixed byte budget; and its static assets
|
|
67
|
+
(svg/images/fonts), budgeted by raw bytes since they are already compressed.
|
|
67
68
|
|
|
68
69
|
**The dependency gates** run once per invocation, never once per template — they are
|
|
69
70
|
properties of the workspace's one lockfile and installed tree, not of any one template:
|
|
@@ -119,3 +120,5 @@ submission.
|
|
|
119
120
|
each, Rule / Reason / Fix.
|
|
120
121
|
- [ESLint preset](eslint.md) — the one-file-at-a-time half of the same rule set, run
|
|
121
122
|
directly by your editor, not only through `check`.
|
|
123
|
+
- [Dev](dev.md) — the other workspace command: preview sections in a browser as you
|
|
124
|
+
author, before running `check` as the acceptance gate.
|