Shadcn CLI borks subpath imports and how to work around it
I recently released a pnpm workspace (monorepo) template that includes support for the shadcn CLI along with a post about it:
Announcing a New Vite + React + shadcn/ui Monorepo Project
This post dives into the details about a challenge I faced when integrating shadcn CLI with the workspace related to its lack of support for subpath imports and how I worked around the problem.
It serves to document the solution and provide details for an issue report to the shadcn team.
Code: https://github.com/firxworx/vite-shadcn-workspace
Preview: https://vite-shadcn-workspace.pages.dev/
The workaround is simply to correct the borked paths of subpath imports. The bash script that performs this task lives at packages/react-ui/fix-shadcn-import-paths.sh
.
It is automatically be executed any time the shadcn
script is run to invoke the shadcn CLI. This is accomplished by including it in the postshadcn
target defined in the root package.json
.
Problem Description
The current version of shadcn CLI does not support subpath imports: it will consistently bork import paths with an erroneous /
character.
The issue exists because the CLI determines where to write files and the paths to use for the import statements inside those files based on magic logic that considers input from the paths
definition in tsconfig.json
as well as its own config file components.json
.
This is an unfortunate design decision because TypeScript path aliases are on the way out and would otherwise be an optional feature. They are a frequent source of tooling and path resolution problems in monorepos.
This issue would not exist if the shadcn CLI config format were to support an explicit definition of the paths to output generated files and the import path format to use.
Explicit and declarative configuration files are an important consideration in software design that would have prevented this issue and would be more resilient to future changes in the TypeScript ecosystem.
Workaround
The script at packages/react-ui/fix-shadcn-import-paths.sh
works around the issue by removing the erroneous /
from generated paths.
It uses git
to determine which files have been added/changed then uses sed
with a regex to fix the paths.
The script is automatically run by the postshadcn
script target in the root package.json
following the shadcn
script.
Subpath Imports
Node subpath imports were first introduced in Node 12.7.x. They are now supported by the majority of relevant tooling in the JS/TS ecosystem.
TypeScript introduced support in version 5.4 and Vite also supports the feature.
Why subpath imports are important
Subpath imports are huge because they apply to the package itself whereas TypeScript paths in tsconfig.json
exist only for the TypeScript compiler within a given project.
They are defined via the imports
property in package.json
so they “live” with every package in both source and distribution form.
Subpath imports help unlock the full potential of monorepos because they enable path resolution across multiple projects in a workspace.
The popular turborepo officially recommends using subpath imports for any workspace running TypeScript 5.4+.
VSCode mostly supports subpath imports and full support is expected in the near future.
At the time of writing for CTRL/CONTROL + click navigation to work consistently as expected in a monorepo one must define a redundant paths
definition in tsconfig.json
.
Enabling type safety across the workspace
Subpath imports are what enable tsc --noEmit
to check types within in the apps/client
project.
This is a particularly important step because Vite and its vite build
command does not perform any type checking on its own.
You can run pnpm typecheck
from the workspace root to run this command on every project in the workspace.
vite build
is only able to resolve path aliases in tsconfig.json
files thanks to the vite-tsconfig-paths
plugin in vite.config.ts
. While the approach works it is still more limited and brittle than subpath imports.
While there are a couple plugins in the Vite ecosystem to type check during build when I looked into them they actually depend on tools like tsc
and thus run into path resolution issues in monorepos even with the vite-tsconfig-paths
plugin.
Without a fix it would be impossible to support the particular combination project layout + configuration + developer experience in play.
My goal was to deliver the ability to add a component via the shadcn CLI then immediately turn around and use it in any app in the workspace without any further steps and without compromising important features like type safety.
Alternative solution paths do exist such as prebuilding the react-ui
package however they did not offer the same developer experience or efficiencies that I was aiming for.
The obvious option to avoid the issue entirely by not using TypeScript path aliases at all is blocked by shadcn’s reliance on them.
Conclusion
I hope this helps developers in the community work with the awesome shadcn/ui in their workspace/monorepo projects.
shadcn/ui has personally saved me hundreds of development hours. Being able to effectively use it in pnpm workspaces with Vite and the huge number of frameworks and tools that use it under the hood was a win that was worth the effort for me.
The only truly feasible workspace examples that I could find for shadcn CLI in a workspace were sponsored by Vercel and thus were specific to NextJS + turborepo. Plus they lacked support for subpath imports though I did come across unresolved discussions on the topic.