Files
shadcn-ui/apps/www/lib/rehype-component.ts
shadcn 2f869a2590 feat(www): code blocks package manager (#6075)
* feat(www): code blocks

* fix: code style
2024-12-14 19:13:06 +04:00

283 lines
8.3 KiB
TypeScript

import fs from "fs"
import path from "path"
import { UnistNode, UnistTree } from "types/unist"
import { u } from "unist-builder"
import { visit } from "unist-util-visit"
import { Index } from "../__registry__"
import { styles } from "../registry/registry-styles"
export function rehypeComponent() {
return async (tree: UnistTree) => {
visit(tree, (node: UnistNode) => {
// src prop overrides both name and fileName.
const { value: srcPath } =
(getNodeAttributeByName(node, "src") as {
name: string
value?: string
type?: string
}) || {}
if (node.name === "ComponentSource") {
const name = getNodeAttributeByName(node, "name")?.value as string
const fileName = getNodeAttributeByName(node, "fileName")?.value as
| string
| undefined
if (!name && !srcPath) {
return null
}
try {
for (const style of styles) {
let src: string
if (srcPath) {
src = path.join(process.cwd(), srcPath)
} else {
const component = Index[style.name][name]
src = fileName
? component.files.find((file: unknown) => {
if (typeof file === "string") {
return (
file.endsWith(`${fileName}.tsx`) ||
file.endsWith(`${fileName}.ts`)
)
}
return false
}) || component.files[0]?.path
: component.files[0]?.path
}
// Read the source file.
const filePath = src
let source = fs.readFileSync(filePath, "utf8")
// Replace imports.
// TODO: Use @swc/core and a visitor to replace this.
// For now a simple regex should do.
source = source.replaceAll(
`@/registry/${style.name}/`,
"@/components/"
)
source = source.replaceAll("export default", "export")
// Add code as children so that rehype can take over at build time.
node.children?.push(
u("element", {
tagName: "pre",
properties: {
__src__: src,
__style__: style.name,
},
attributes: [
{
name: "styleName",
type: "mdxJsxAttribute",
value: style.name,
},
],
children: [
u("element", {
tagName: "code",
properties: {
className: ["language-tsx"],
},
children: [
{
type: "text",
value: source,
},
],
}),
],
})
)
}
} catch (error) {
console.error(error)
}
}
if (node.name === "ComponentPreview") {
const name = getNodeAttributeByName(node, "name")?.value as string
if (!name) {
return null
}
try {
for (const style of styles) {
const component = Index[style.name][name]
const src = component.files[0]?.path
// Read the source file.
const filePath = src
let source = fs.readFileSync(filePath, "utf8")
// Replace imports.
// TODO: Use @swc/core and a visitor to replace this.
// For now a simple regex should do.
source = source.replaceAll(
`@/registry/${style.name}/`,
"@/components/"
)
source = source.replaceAll("export default", "export")
// Add code as children so that rehype can take over at build time.
node.children?.push(
u("element", {
tagName: "pre",
properties: {
__src__: src,
},
children: [
u("element", {
tagName: "code",
properties: {
className: ["language-tsx"],
},
children: [
{
type: "text",
value: source,
},
],
}),
],
})
)
}
} catch (error) {
console.error(error)
}
}
// if (node.name === "ComponentExample") {
// const source = getComponentSourceFileContent(node)
// if (!source) {
// return
// }
// // Replace the Example component with a pre element.
// node.children?.push(
// u("element", {
// tagName: "pre",
// properties: {
// __src__: src,
// },
// children: [
// u("element", {
// tagName: "code",
// properties: {
// className: ["language-tsx"],
// },
// children: [
// {
// type: "text",
// value: source,
// },
// ],
// }),
// ],
// })
// )
// const extractClassname = getNodeAttributeByName(
// node,
// "extractClassname"
// )
// if (
// extractClassname &&
// typeof extractClassname.value !== "undefined" &&
// extractClassname.value !== "false"
// ) {
// // Extract className from string
// // TODO: Use @swc/core and a visitor to extract this.
// // For now, a simple regex should do.
// const values = source.match(/className="(.*)"/)
// const className = values ? values[1] : ""
// // Add the className as a jsx prop so we can pass it to the copy button.
// node.attributes?.push({
// name: "extractedClassNames",
// type: "mdxJsxAttribute",
// value: className,
// })
// // Add a pre element with the className only.
// node.children?.push(
// u("element", {
// tagName: "pre",
// properties: {},
// children: [
// u("element", {
// tagName: "code",
// properties: {
// className: ["language-tsx"],
// },
// children: [
// {
// type: "text",
// value: className,
// },
// ],
// }),
// ],
// })
// )
// }
// }
// if (node.name === "ComponentSource") {
// const source = getComponentSourceFileContent(node)
// if (!source) {
// return
// }
// // Replace the Source component with a pre element.
// node.children?.push(
// u("element", {
// tagName: "pre",
// properties: {
// __src__: src,
// },
// children: [
// u("element", {
// tagName: "code",
// properties: {
// className: ["language-tsx"],
// },
// children: [
// {
// type: "text",
// value: source,
// },
// ],
// }),
// ],
// })
// )
// }
})
}
}
function getNodeAttributeByName(node: UnistNode, name: string) {
return node.attributes?.find((attribute) => attribute.name === name)
}
function getComponentSourceFileContent(node: UnistNode) {
const src = getNodeAttributeByName(node, "src")?.value as string
if (!src) {
return null
}
// Read the source file.
const filePath = path.join(process.cwd(), src)
const source = fs.readFileSync(filePath, "utf8")
return source
}