shadcn-ui/ui codebase analysis: How does shadcn-ui CLI work? — Part 2.12

Ramu NarasingaRamu Narasinga
4 min read

I wanted to find out how shadcn-ui CLI works. In this article, I discuss the code used to build the shadcn-ui/ui CLI.

In part 2.11, we looked at runInit function and how shadcn-ui/ui ensures directories provided in resolvedPaths in config exist.

The following operations are performed in runInit function:

  1. Ensure all resolved paths directories exist.

  2. Write tailwind config.

  3. Write css file.

  4. Write cn file.

  5. Install dependencies.

Let’s understand how shadcn-ui/ui CLI writes to tailwind config in runInit function.

Write tailwind config

After checking the directories exist, there are few more operations performed before writing tailwind config as shown in the below code.

This code is picked from cli/src/commands/init.ts.

const extension = config.tsx ? "ts" : "js"

const tailwindConfigExtension = path.extname(
  config.resolvedPaths.tailwindConfig
)

let tailwindConfigTemplate: string
if (tailwindConfigExtension === ".ts") {
  tailwindConfigTemplate = config.tailwind.cssVariables
    ? templates.TAILWIND_CONFIG_TS_WITH_VARIABLES
    : templates.TAILWIND_CONFIG_TS
} else {
  tailwindConfigTemplate = config.tailwind.cssVariables
    ? templates.TAILWIND_CONFIG_WITH_VARIABLES
    : templates.TAILWIND_CONFIG
}

// Write tailwind config.
await fs.writeFile(
  config.resolvedPaths.tailwindConfig,
  template(tailwindConfigTemplate)({
    extension,
    prefix: config.tailwind.prefix,
  }),
  "utf8"
)

Let’s understand this code snippet

Extension

const extension = config.tsx ? "ts" : "js"

This extension is used in later parts of code that deal with writing cn file.

tailwindConfigExtension

const tailwindConfigExtension = path.extname(
  config.resolvedPaths.tailwindConfig
)

The path.extname() method returns the extension of the path, from the last occurrence of the . (period) character to end of string in the last portion of the path. If there is no . in the last portion of the path, or if there are no . characters other than the first character of the basename of path (see path.basename()) , an empty string is returned.

let tailwindConfigTemplate: string
if (tailwindConfigExtension === ".ts") {
  tailwindConfigTemplate = config.tailwind.cssVariables
    ? templates.TAILWIND_CONFIG_TS_WITH_VARIABLES
    : templates.TAILWIND_CONFIG_TS
} else {
  tailwindConfigTemplate = config.tailwind.cssVariables
    ? templates.TAILWIND_CONFIG_WITH_VARIABLES
    : templates.TAILWIND_CONFIG
}

Depending on the tailwindConfigExtension and config.tailwind.cssVariables, tailwindConfigTemplate is set to a value using templates. Templates is imported from utils/templates and contains some variables initialised with values related to tailwind config

Write to tailwind config file

await fs.writeFile(
  config.resolvedPaths.tailwindConfig,
  template(tailwindConfigTemplate)({
    extension,
    prefix: config.tailwind.prefix,
  }),
  "utf8"
)

template used here in the above snippet is different from utils/templates. Different how? this template is imported from lodash.template

Read more about lodash.template

An example using lodash.template:

// Use the "interpolate" delimiter to create a compiled template.
var compiled = _.template('hello <%= user %>!');
compiled({ 'user': 'fred' });
// => 'hello fred!'

This explains why there is %extension% in utils/templates.ts

It gets replaced with w/e is passed. Interesting…

Conclusion:

After checking the directories exist (explained in the part 2.11), there are few more operations performed before writing tailwind config as shown in the below code.

const extension = config.tsx ? "ts" : "js"

const tailwindConfigExtension = path.extname(
  config.resolvedPaths.tailwindConfig
)

let tailwindConfigTemplate: string
if (tailwindConfigExtension === ".ts") {
  tailwindConfigTemplate = config.tailwind.cssVariables
    ? templates.TAILWIND_CONFIG_TS_WITH_VARIABLES
    : templates.TAILWIND_CONFIG_TS
} else {
  tailwindConfigTemplate = config.tailwind.cssVariables
    ? templates.TAILWIND_CONFIG_WITH_VARIABLES
    : templates.TAILWIND_CONFIG
}

// Write tailwind config.
await fs.writeFile(
  config.resolvedPaths.tailwindConfig,
  template(tailwindConfigTemplate)({
    extension,
    prefix: config.tailwind.prefix,
  }),
  "utf8"
)

extension — This extension is used in later parts of code that deal with writing cn file.

tailwindConfigExtension — The path.extname() method returns the extension of the path, from the last occurrence of the . (period) character to end of string in the last portion of the path.

Depending on the tailwindConfigExtension and config.tailwind.cssVariables, tailwindConfigTemplate is set to a value using templates. Templates is imported from utils/templates and contains some variables initialised with values related to tailwind config

Write to tailwind config file — using fs.writeFile, tailwindConfig is updated but there is a catch. template from lodash.template is used to perform some replacements before writing to the file.

An example usage of lodash.template from the docs:

// Use the "interpolate" delimiter to create a compiled template.
var compiled = _.template('hello <%= user %>!');
compiled({ 'user': 'fred' });
// => 'hello fred!'

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

Build shadcn-ui/ui from scratch

References:

  1. https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/init.ts#L331C3-L356C4

  2. https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/get-config.ts#L53

  3. https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/get-config.ts#L43

  4. https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/get-config.ts#L20

  5. https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/templates.ts

  6. https://lodash.com/docs#template

0
Subscribe to my newsletter

Read articles from Ramu Narasinga directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ramu Narasinga
Ramu Narasinga