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

Ramu Narasinga
5 min readJun 18, 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 (Coming soon)
  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 1, we looked at two important modular functions named `getAllBlockIds` and `_getAllBlocks`.

In part 2, we will look at the following:

  1. Where is BlockDisplay used?
  2. Where to find BlockDisplay component?
  3. BlockDisplay component explained
  4. getBlock function

Where is BlockDisplay used?

BlockDisplay is used in blocks/page.tsx. In part 1, I explained the code behind fetching blocks; When you visit blocks page on ui.shadcn.com, you will find a lot of blocks rendered, it is in fact from blocks array above. These blocks so far do not contain the code that translates to a components used in an individual block. BlockDisplay has it own logic to deal with rendering components for each block. Important concept here to remember is to pass the minimal information required.

Where to find BlockDisplay component?

You can find BlockDisplay component exported from components/block-display.tsx.

Has about 29 lines of code.

BlockDisplay component explained

Let’s try to understand the BlockDisplay code at a high level.

import { getBlock } from "@/lib/blocks"
import { BlockPreview } from "@/components/block-preview"
import { styles } from "@/registry/styles"

export async function BlockDisplay({ name }: { name: string }) {
const blocks = await Promise.all(
styles.map(async (style) => {
const block = await getBlock(name, style.name)
const hasLiftMode = block?.chunks ? block?.chunks?.length > 0 : false

// Cannot (and don't need to) pass to the client.
delete block?.component
delete block?.chunks

return {
...block,
hasLiftMode,
}
})
)

if (!blocks?.length) {
return null
}

return blocks.map((block) => (
<BlockPreview key={`${block.style}-${block.name}`} block={block} />
))
}

There is a blocks array that is populated after Promise.all resolves. Promise.all has an array of styles imported from registry/styles. These styles are mapped over and each style is further processed.

`getBlock` is a utility function that accepts two parameters, name and style.name and there is also a flag named `hasLiftMode` that is based on block.chunks.length. block.components and block.chunks are deleted as they are not required on the client side.

These blocks are then mapped over and each block is used in BlockPreview.

getBlock function

`getBlock` function is imported lib/blocks.ts and contains the code below:

export async function getBlock(
name: string,
style: Style["name"] = DEFAULT_BLOCKS_STYLE
) {
const entry = Index[style][name]

const content = await _getBlockContent(name, style)

const chunks = await Promise.all(
entry.chunks?.map(async (chunk: BlockChunk) => {
const code = await readFile(chunk.file)

const tempFile = await createTempSourceFile(`${chunk.name}.tsx`)
const sourceFile = project.createSourceFile(tempFile, code, {
scriptKind: ScriptKind.TSX,
})

sourceFile
.getDescendantsOfKind(SyntaxKind.JsxOpeningElement)
.filter((node) => {
return node.getAttribute("x-chunk") !== undefined
})
?.map((component) => {
component
.getAttribute("x-chunk")
?.asKind(SyntaxKind.JsxAttribute)
?.remove()
})

return {
...chunk,
code: sourceFile
.getText()
.replaceAll(`@/registry/${style}/`, "@/components/"),
}
})
)

return blockSchema.parse({
style,
highlightedCode: content.code ? await highlightCode(content.code) : "",
...entry,
...content,
chunks,
type: "components:block",
})
}

As you can see, there is a lot going on here. We will cover some part of this code in this article and the rest in the coming articles.

const entry = Index[style][name]
const content = await _getBlockContent(name, style)

Index is imported from `_registry_` and is autogenerated by scripts/build-registry.ts (more on this later).

_getBlockContent returns an object that is assigned to content. The below code is picked from _getBlockContent.

async function _getBlockContent(name: string, style: Style["name"]) {
const raw = await _getBlockCode(name, style)

as you can this, in turn, calls `_getBlockCode`.

_getBlockCode has the code

async function _getBlockCode(
name: string,
style: Style["name"] = DEFAULT_BLOCKS_STYLE
) {
const entry = Index[style][name]
const block = registryEntrySchema.parse(entry)

if (!block.source) {
return ""
}

return await readFile(block.source)
}

I have talked about Index, registryEntrySchema and parse in great detail in part 1. we did not understand the readFile function yet.

readFile

readFile function is used to read file content from block.source

async function readFile(source: string) {
const filepath = path.join(process.cwd(), source)
return await fs.readFile(filepath, "utf-8")
}

Index contains blocks that do not have source, but at the end, you will find some blocks with source as shown below

For example, for authentication-04, code is available at _registry_/new-york/block/authentication-04.tsx.

What you see above as one of the blocks shown on blocks page on ui.shadcn.com is the code from _registry_/new-york/block/authentication-04.tsx. Isn’t this awesome!?

Let’s take a step back now and get back on track with out function call stack. We got to `readFile` from `_getBlockCode`. With this so far, we have understood the `_getBlockCode`. We got to `_getBlockCode` from `__getBlockContent` and only covered the first line as shown below

_getBlockContent

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 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 . Did you notice the chain of function calls here? functions following single responsibility principle and self explanatory and modular.

Conclusion

In part 2, I discuss the BlockDisplay component. This component has functions that are chained together so well that it respects the SRP and is quite modular.

I will highlight the key function in this chain. It goes like this:

BlockDisplay => getBlock => _getBlockContent => readFile

readFile is where you will find the code that reads the source code available at _registry_/ that loads the “blocks” and is rendered via an iframe. Jesus! I wasn’t expecting magic of this sort.

So far, this only completes _getBlockContent explanation, I still need to get back to `getBlock` and BlockDisplay which has its own complex functions such as `createTempSourceFile`, `createSourceFile` and `_extractVariable`. I have 0 clue as how they work, but I will make a good attempt to understand and explain them in an easy way in the next article.

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/components/block-display.tsx#L5
  2. https://github.com/shadcn-ui/ui/blob/main/apps/www/lib/blocks.ts#L27

--

--