Skip to content

Commit 343e193

Browse files
feat: block chunks (#997)
Co-authored-by: AdrianGonz97 <[email protected]>
1 parent f662636 commit 343e193

File tree

100 files changed

+4006
-446
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+4006
-446
lines changed

.prettierignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ CHANGELOG.md
1919

2020

2121
# docs site specific
22-
apps/www/src/__registry__/index.js
22+
apps/www/src/__registry__/*
2323
apps/www/other/themes/dark.json
2424
apps/www/other/themes/light.json
2525
apps/www/static

apps/www/scripts/build-registry.ts

+55-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { themes } from "../src/lib/registry/themes";
1111
import { buildRegistry } from "./registry";
1212
import { transformContent } from "./transformers";
1313
import { BASE_STYLES, BASE_STYLES_WITH_VARIABLES, THEME_STYLES_WITH_VARIABLES } from "./templates";
14+
import { getChunks } from "./transform-chunks.js";
1415

1516
const REGISTRY_PATH = path.resolve("static", "registry");
1617
const REGISTRY_IGNORE = ["super-form"];
@@ -24,6 +25,54 @@ async function main() {
2425
process.exit(1);
2526
}
2627

28+
// ----------------------------------------------------------------------------
29+
// Build blocks registry (__registry__/blocks.js) and block chunks (__registry__/chunks/[style]/[block]-[chunk].svelte)
30+
// ----------------------------------------------------------------------------
31+
const registryChunksDirPath = path.resolve("src", "__registry__", "chunks");
32+
const libPath = path.resolve("src", "lib", "registry");
33+
rimraf.sync(registryChunksDirPath);
34+
let blocksIndex = `
35+
// This file is autogenerated by scripts/build-registry.ts
36+
// Do not edit this file directly.
37+
export const Blocks = {
38+
`;
39+
// Create the __registry__/chunks/[style] dir
40+
for (const style of styles) {
41+
blocksIndex += `\t"${style.name}": {`;
42+
const chunkStyleDir = path.resolve(registryChunksDirPath, style.name);
43+
// Create directory if it doesn't exist.
44+
if (!fs.existsSync(chunkStyleDir)) {
45+
fs.mkdirSync(chunkStyleDir, { recursive: true });
46+
}
47+
// Creates chunk files
48+
for (const block of result.data) {
49+
if (block.type !== "components:block" || block.style !== style.name) continue;
50+
const file = block.files[0];
51+
const blockPath = path.resolve(libPath, block.style, "block", file.name);
52+
const chunkDir = path.resolve(registryChunksDirPath, block.style);
53+
54+
const chunks = getChunks(file.content, blockPath);
55+
for (const chunk of chunks) {
56+
const chunkPath = path.resolve(chunkDir, `${chunk.name}.svelte`);
57+
fs.writeFileSync(chunkPath, chunk.content, { encoding: "utf8" });
58+
}
59+
60+
blocksIndex += `
61+
"${block.name}": {
62+
name: "${block.name}",
63+
type: "${block.type}",
64+
chunks: [${chunks.map((chunk) => ` { name: "${chunk.name}", description: "${chunk.description}", container: { className: "${chunk.container.className}" }, raw: () => import("./chunks/${style.name}/${chunk.name}.svelte?raw").then((m) => m.default), component: () => import("./chunks/${style.name}/${chunk.name}.svelte").then((m) => m.default) }`)}],
65+
component: () => import("../lib/registry/${style.name}/block/${block.name}.svelte").then((m) => m.default),
66+
raw: () => import("../lib/registry/${style.name}/block/${block.name}.svelte?raw").then((m) => m.default),
67+
},`;
68+
}
69+
// end of style
70+
blocksIndex += `\n\t},`;
71+
}
72+
blocksIndex += "\n};\n";
73+
const blocksPath = path.resolve("src", "__registry__", "blocks.js");
74+
fs.writeFileSync(blocksPath, blocksIndex);
75+
2776
// ----------------------------------------------------------------------------
2877
// Build __registry__/index.js.
2978
// ----------------------------------------------------------------------------
@@ -38,15 +87,19 @@ export const Index = {
3887

3988
// Build style index.
4089
for (const item of result.data) {
41-
if (item.type === "components:ui" || item.style !== "default") {
90+
if (
91+
item.type === "components:ui" ||
92+
item.type === "components:block" ||
93+
item.style !== "default"
94+
) {
4295
continue;
4396
}
97+
const type = item.type.split(":")[1];
4498

4599
const resolveFiles = item.files.map(
46100
(file) => `../lib/registry/${style.name}/${file.path}`
47101
);
48102

49-
const type = item.type.split(":")[1];
50103
index += `
51104
"${item.name}": {
52105
name: "${item.name}",

apps/www/scripts/transform-chunks.ts

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { parse, walk } from "svelte/compiler";
2+
import prettier from "@prettier/sync";
3+
import type { Attribute, TemplateNode } from "svelte/types/compiler/interfaces";
4+
import { codeBlockPrettierConfig } from "../other/code-block-prettier.js";
5+
6+
type Chunk = {
7+
name: string;
8+
dependencies: string[];
9+
start: number;
10+
end: number;
11+
content: string;
12+
description: string;
13+
container: { className: string };
14+
};
15+
export function getChunks(source: string, filename: string) {
16+
const ast = parse(source, { filename });
17+
const chunks: Chunk[] = [];
18+
19+
walk(ast as any, {
20+
enter(n) {
21+
const chunkNode = n as TemplateNode;
22+
if (chunkNode.type !== "Element" && chunkNode.type !== "InlineComponent") return;
23+
24+
const attrs: Attribute[] = chunkNode.attributes;
25+
const nameNode = attrs.find((a) => a.name === "data-x-chunk-name");
26+
const descriptionNode = attrs.find((a) => a.name === "data-x-chunk-description");
27+
if (descriptionNode === undefined || nameNode === undefined) return;
28+
29+
const containerNode = attrs.find((a) => a.name === "data-x-chunk-container");
30+
31+
const name: string = nameNode.value[0].data;
32+
const description: string = descriptionNode.value[0].data;
33+
const containerClassName: string = containerNode?.value[0].data ?? "";
34+
const dependencies = new Set<string>();
35+
36+
// discard any prop members
37+
const [componentName] = chunkNode.name.split(".") as string[];
38+
dependencies.add(componentName);
39+
40+
// walk the chunk to acquire all component dependencies
41+
walk(chunkNode as any, {
42+
enter(n) {
43+
const node = n as TemplateNode;
44+
if (node.type === "InlineComponent") {
45+
const [componentName] = node.name.split(".") as string[];
46+
dependencies.add(componentName);
47+
}
48+
},
49+
});
50+
51+
const chunk: Chunk = {
52+
name,
53+
description,
54+
dependencies: [...dependencies],
55+
start: chunkNode.start,
56+
end: chunkNode.end,
57+
content: "",
58+
container: {
59+
className: containerClassName,
60+
},
61+
};
62+
chunks.push({ ...chunk, content: transformChunk(source, chunk) });
63+
// don't traverse the rest of this node
64+
this.skip();
65+
},
66+
});
67+
68+
return chunks;
69+
}
70+
71+
export function transformChunk(source: string, chunk: Chunk): string {
72+
const html = source.substring(chunk.start, chunk.end);
73+
const lines = source.split("\n");
74+
const scriptEndIdx = lines.indexOf("</script>");
75+
const imports = lines
76+
// we only want to look at the script tag...
77+
.slice(0, scriptEndIdx)
78+
// spaced on the edges to prevent false positives (e.g. `CreditCard` could be falsely triggered by `Card`)
79+
.filter((line) => chunk.dependencies.some((dep) => line.includes(` ${dep} `)));
80+
81+
let template = `<script lang="ts">\n`;
82+
template += imports.join("\n");
83+
template += `\n</script>\n\n${html}`;
84+
85+
return prettier.format(template, {
86+
...codeBlockPrettierConfig,
87+
useTabs: true,
88+
tabWidth: undefined,
89+
});
90+
}

0 commit comments

Comments
 (0)