DEV Community

Cover image for shadcn-ui/ui codebase analysis: How is “Blocks” page built — Part 3
Ramu Narasinga
Ramu Narasinga

Posted on

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.

  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,
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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,
})
Enter fullscreen mode Exit fullscreen mode

Hang on a minute, what is project object?

const project = new Project({
  compilerOptions: {},
})
Enter fullscreen mode Exit fullscreen mode

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,
})
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)