shadcn-ui/ui codebase analysis: How is “Blocks” page built — Part 3
In this article, I discuss how Blocks page is built on ui.shadcn.com. Blocks page has a lot of utilities used, hence I broke down this Blocks page analysis into 5 parts.
- shadcn-ui/ui codebase analysis: How is “Blocks” page built — Part 1
- shadcn-ui/ui codebase analysis: How is “Blocks” page built — Part 2
- shadcn-ui/ui codebase analysis: How is “Blocks” page built — Part 3
- shadcn-ui/ui codebase analysis: How is “Blocks” page built — Part 4 (Coming soon)
- shadcn-ui/ui codebase analysis: How is “Blocks” page built — Part 5 (Coming soon)
In part 3, I will explain how createTempSourceFile, createSourceFile and _extractVariable work in order to understand `_getBlockCode` completely. Keep in mind, we still need to get back to `getBlock` since this is used in BlockDisplay
.
_getBlockCode function code:
async function _getBlockContent(name: string, style: Style["name"]) {
const raw = await _getBlockCode(name, style)
const tempFile = await createTempSourceFile(`${name}.tsx`)
const sourceFile = project.createSourceFile(tempFile, raw, {
scriptKind: ScriptKind.TSX,
})
// Extract meta.
const description = _extractVariable(sourceFile, "description")
const iframeHeight = _extractVariable(sourceFile, "iframeHeight")
const containerClassName = _extractVariable(sourceFile, "containerClassName")
// Format the code.
let code = sourceFile.getText()
code = code.replaceAll(`@/registry/${style}/`, "@/components/")
code = code.replaceAll("export default", "export")
return {
description,
code,
container: {
height: iframeHeight,
className: containerClassName,
},
}
}
In part 2, we looked at `_getBlockCode` in great detail. Let’s understand createTempSourceFile.
createTempSourceFile
createTempSourceFile function creates a unique temporary directory.
async function createTempSourceFile(filename: string) {
const dir = await fs.mkdtemp(path.join(tmpdir(), "codex-"))
return path.join(dir, filename)
}
`tempdir` is imported from “os”, since shadcn-ui/ui uses Next.js, all this code is executed on server, hence it has access to “os” node.js package. lib/blocks.ts has “use server” at the top of file.
Basically, what this code means is that a temporary file with block file name is temporarily placed in a temporary folder.
createSourceFile
createSourceFile is a function called using `project` object
const sourceFile = project.createSourceFile(tempFile, raw, {
scriptKind: ScriptKind.TSX,
})
Hang on a minute, what is project object?
const project = new Project({
compilerOptions: {},
})
At the top of the file, this project variable is initiated with a Project instance. Now what is Project? Project is imported from ts-morph? What is ts-morph? Let’s find out.
ts-morph
ts-morph is a library that wraps the TypeScript compiler API to simplify setup, navigation and manipulation of the Typescript AST. This article has a good explanation what Typescript AST means.
When the TypeScript compiler compiles your code, it creates an Abstract Syntax Tree (AST) of it. Essentially, an AST can be thought of as a tree representation of the syntax of your source code, with each node being a data structure representing a construct in the relating source code. The tree is complete with nodes of all the elements of the source code.
I definitely need to more research on this ts-morph concept and provide some examples in the best practices module and also talk about this a bit more in part 4.
const sourceFile = project.createSourceFile(tempFile, raw, {
scriptKind: ScriptKind.TSX,
})
ts-morph.com documentation provides an example that uses project.createSourceFile, just like how shadcn-ui/ui does it.
We know how tempFile is created, raw is variable containing the file code returning by _getBlockCode, for example, code sitting at https://github.com/shadcn-ui/ui/blob/main/apps/www/__registry__/new-york/block/authentication-04.tsx
I will provide an example project that uses ts-morph and performs the similar operations that shadcn-ui/ui does to understand createSourceFile and extractVariable. getBlock also uses these similar functions in part 4.
Conclusion:
shadcn-ui/ui performs some additional operations using ts-morph after reading the code from _registry_ folder’s blocks file. Why not just read the file directly? this is an interesing question. I will find out why there is a need to use ts-morph to extract the code further from the read file in part 4.
This is the beauty of reading/studying OSS code, you are exposed to a lot of new technical concepts. I do not know what ts-morph is yet… but I will find out soon and provide an example project after running some experiments. I will keep this example limited to the similar operations used in shadcn-ui/ui.
This part 3 does provide insights into creating a temporary folder using “os” package since lib/block.ts has “use server” at the top of this file.
Want to learn how to build shadcn-ui/ui from scratch? Check out build-from-scratch
About me:
Website: https://ramunarasinga.com/
Linkedin: https://www.linkedin.com/in/ramu-narasinga-189361128/
Github: https://github.com/Ramu-Narasinga
Email: ramu.narasinga@gmail.com