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

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

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 (Coming soon)

  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 will look at the following:

  1. Where to find blocks page code in the shadcn-ui/ui repository?

  2. getAllBlockIds function

  3. _getAllBlocks function

These function further call other utility functions that will be explained in the other parts.

Where to find blocks page code in the shadcn-ui/ui repository?

blocks/page.tsx is where you will find Blocks page related code in the shadcn-ui/ui repository

Just because it has only 10 lines of code does not mean it is a simple page, there is a lot going on behind these lines, especially in the lib/blocks.ts, but don’t worry, we will understand the utility functions used in depth later in this article and other parts as well.

BlocksPage gets the blocks from a function named getAllBlockIds() which is imported from lib/blocks and these blocks are mapped with a BlockDisplay component that shows blocks on the Blocks page. Let’s find out what is in getAllBlockIds()

getAllBlockIds function

The below code snippet is picked from lib/blocks.ts

export async function getAllBlockIds(
  style: Style["name"] = DEFAULT_BLOCKS_STYLE
) {
  const blocks = await _getAllBlocks(style)
  return blocks.map((block) => block.name)
}

This code snippet is self explanatory, style parameter gets a default value DEFAULT_BLOCKS_STYLE because in the Blocks page, we call getAllBlockIds without any params as shown below:

const blocks = await getAllBlockIds()

But wait, what is the value in DEFAULT_BLOCKS_STYLE?

At line 14 in lib/blocks, you will find this below code:

const DEFAULT_BLOCKS_STYLE = "default" satisfies Style["name"]

“default” satisfies Style[“name”], Style is from register/styles. I just admire the quality of Typescript written in this shadcn-ui/ui. So, getAllBlocks gets called with a param named style that is initiated to “default”. So far, the code is straight forward. Let’s now understand what is in getAllBlocks

_getAllBlocks function

The below code snippet is picked from lib/blocks.ts

async function _getAllBlocks(style: Style["name"] = DEFAULT_BLOCKS_STYLE) {
  const index = z.record(registryEntrySchema).parse(Index[style])

  return Object.values(index).filter(
    (block) => block.type === "components:block"
  )
}

Even though, getAllBlockIds from above calls this function with a parameter, this function still has a default value set to the style parameter.

const index = z.record(registryEntrySchema).parse(Index[style])

Code above has the following:

z.record

Record schema in Zod are used to validate types such as Record<string, number>. This is particularly useful for storing or caching items by ID.

registryEntrySchema

registryEntrySchema defines a schema for the blocks

export const registryEntrySchema = z.object({
  name: z.string(),
  description: z.string().optional(),
  dependencies: z.array(z.string()).optional(),
  devDependencies: z.array(z.string()).optional(),
  registryDependencies: z.array(z.string()).optional(),
  files: z.array(z.string()),
  source: z.string().optional(),
  type: z.enum([
    "components:ui",
    "components:component",
    "components:example",
    "components:block",
  ]),
  category: z.string().optional(),
  subcategory: z.string().optional(),
  chunks: z.array(blockChunkSchema).optional(),
})

parse(Index[style])

parse is a schema method to check data is valid. If it is, a value is returned with full type information! Otherwise, an error is thrown.

Example:

const stringSchema = z.string();

stringSchema.parse("fish"); // => returns "fish"
stringSchema.parse(12); // throws error

Index is imported from registryfolder and contains all the components used in shadcn-ui/ui.

Looks like this file gets auto generated by scripts/build-registry.ts and this is also used in CLI package to add shadcn components into your project, more on this in the upcominhg articles.

Basically, we validate Index[“default”] against the registry schema to ensure the auto generated code is valid and is ready for further processing such as showing in blocks page.

_getAllBlocks filters the blocks based on the block type as shown below:

return Object.values(index).filter(
    (block) => block.type === "components:block"
  )

This is how you are able to see components that are specific to Blocks page.

Conclusion:

We looked at two important module functions named getAllBlockIds and _getAllBlocks. I find this code to be pretty self explanatory, I do admire the way zod’s Record schema validations are used on the auto generated registry index json.

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/app/(app)/blocks/page.tsx

  2. https://github.com/shadcn-ui/ui/blob/main/apps/www/lib/blocks.ts#L20

  3. https://github.com/shadcn-ui/ui/blob/main/apps/www/lib/blocks.ts#L75

  4. https://github.com/shadcn-ui/ui/blob/main/apps/www/registry/schema.ts#L16