DEV Community

Cover image for shadcn-ui/ui codebase analysis: How does shadcn-ui CLI work? — Part 2.3
Ramu Narasinga
Ramu Narasinga

Posted on

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

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.2, I followed along the call stack when the function getProjectConfig is called and discussed functions named getConfig, getRawConfig. A detailed explanation is provided about getRawConfig that is called from getConfig. In this article, we will analyse few more lines of code from getConfig function.

resolveConfigPaths

export async function resolveConfigPaths(cwd: string, config: RawConfig) {
  // Read tsconfig.json.
  const tsConfig = await loadConfig(cwd)

  if (tsConfig.resultType === "failed") {
    throw new Error(
      \`Failed to load ${config.tsx ? "tsconfig" : "jsconfig"}.json. ${
        tsConfig.message ?? ""
      }\`.trim()
    )
  }

  return configSchema.parse({
    ...config,
    resolvedPaths: {
      tailwindConfig: path.resolve(cwd, config.tailwind.config),
      tailwindCss: path.resolve(cwd, config.tailwind.css),
      utils: await resolveImport(config.aliases\["utils"\], tsConfig),
      components: await resolveImport(config.aliases\["components"\], tsConfig),
      ui: config.aliases\["ui"\]
        ? await resolveImport(config.aliases\["ui"\], tsConfig)
        : await resolveImport(config.aliases\["components"\], tsConfig),
    },
  })
}
Enter fullscreen mode Exit fullscreen mode

Let’s break this function down.

loadConfig

// Read tsconfig.json.
const tsConfig = await loadConfig(cwd)

loadConfig is imported from [tsconfig-paths](https://www.npmjs.com/package/tsconfig-paths).

This function loads the tsconfig.json or jsconfig.json. It will start searching from the specified cwd directory.

if (tsConfig.resultType === "failed") {
    throw new Error(
      \`Failed to load ${config.tsx ? "tsconfig" : "jsconfig"}.json. ${
        tsConfig.message ?? ""
      }\`.trim()
    )
  }
Enter fullscreen mode Exit fullscreen mode

This is an error check that throws an error when tsConfig or jsConfig fails to load.

return configSchema.parse({
    ...config,
    resolvedPaths: {
      tailwindConfig: path.resolve(cwd, config.tailwind.config),
      tailwindCss: path.resolve(cwd, config.tailwind.css),
      utils: await resolveImport(config.aliases\["utils"\], tsConfig),
      components: await resolveImport(config.aliases\["components"\], tsConfig),
      ui: config.aliases\["ui"\]
        ? await resolveImport(config.aliases\["ui"\], tsConfig)
        : await resolveImport(config.aliases\["components"\], tsConfig),
    },
  })
Enter fullscreen mode Exit fullscreen mode

configSchema is returned by the resolveConfigPaths. This code snippet uses path.resolve and resolveImport.

resolveImport

import { createMatchPath, type ConfigLoaderSuccessResult } from "tsconfig-paths"

export async function resolveImport(
  importPath: string,
  config: Pick<ConfigLoaderSuccessResult, "absoluteBaseUrl" | "paths">
) {
  return createMatchPath(config.absoluteBaseUrl, config.paths)(
    importPath,
    undefined,
    () => true,
    \[".ts", ".tsx"\]
  )
}
Enter fullscreen mode Exit fullscreen mode

You can read more about createMatchPath.

Conclusion:

I updated the commanderjs-usage-in-shadcnui with shadcn-ui CLI package code to understand the getConfig call in getProjectConfig. Turns out, this little detour to understand the series of function calls following getConfig are to check if there is existing component config.

// Check for existing component config.
const existingConfig = await getConfig(cwd)
if (existingConfig) {
  return existingConfig
}
Enter fullscreen mode Exit fullscreen mode

Just to recap, the call stack is like this: getConfig calls getRawConfig, getRawConfig uses explorer.search (from cosmicconfig) and then if there is an existing component config, resolveConfigPaths is returned that uses some helpers function such as createMatchPath provided by tsconfig-paths package. All this trouble just to check if there’s an existingConfig. Why tho?

The answer lies in the different schema that you get when there’s an existing component config available. (configSchema and rawConfigSchema). There’s something unique about the way these functions are organised!

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/utils/get-config.ts#L55
  2. https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/get-config.ts#L4
  3. https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/get-config.ts#L2
  4. https://www.npmjs.com/package/tsconfig-paths
  5. https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/resolve-import.ts#L3
  6. https://www.npmjs.com/package/tsconfig-paths#creatematchpath

Top comments (0)