shadcn-ui/ui codebase analysis: How is “Blocks” page built — Part 3

Ramu Narasinga
4 min readJun 19, 2024

--

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.

  1. shadcn-ui/ui codebase analysis: How is “Blocks” page built — Part 1
  2. shadcn-ui/ui codebase analysis: How is “Blocks” page built — Part 2
  3. shadcn-ui/ui codebase analysis: How is “Blocks” page built — Part 3
  4. shadcn-ui/ui codebase analysis: How is “Blocks” page built — Part 4 (Coming soon)
  5. 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

References:

  1. https://github.com/shadcn-ui/ui/blob/main/apps/www/lib/blocks.ts#L107
  2. https://github.com/shadcn-ui/ui/blob/main/apps/www/lib/blocks.ts#L102
  3. https://github.com/shadcn-ui/ui/blob/main/apps/www/lib/blocks.ts#L16
  4. https://github.com/shadcn-ui/ui/blob/main/apps/www/lib/blocks.ts#L135

--

--