DEV Community

Bao Huynh Lam
Bao Huynh Lam

Posted on

3 cool things about the `create-vite` CLI you might not have known

I wanted to learn how to make pleasant and interactive CLI like the creat-vite project scaffolding CLI, so yesterday I took a quick dive into the code of create-vite (note: not vite itself) to study what magical sauces they have and hopefully learn some cool new techniques along the way.

At a glance

Everything starts in the init function of the src/index.ts file

At a glance, we can see that the CLI progresses through 6 stages:

  1. Get (or ask for) project name and target directory
  2. Handle directory if exist and not empty
  3. Get package name (if the project name at step 1 is an invalid NPM package name)
  4. ASk users to choose a framework and variant
  5. Ask user if immediate install is desired
  6. Finally starts scaffolding folders and files

The main "stars" of this elegant experience includes:

  • mri library for working with CLI arguments
  • @clack/prompts library for displaying pretty interactive prompts
  • And picocolors for adding colors to the console log

Overall, the create-vite CLI is a pretty straightforward and simple tool. Diving into the code, I learned some interesting details.

Cool thing 1 - All the different CLI flags

On README, you might see that create-vite CLI supports the --template flag, but that's not the only one. Here are some more:

  • --overwrite / --no-overwrite: Do you want to overwrite if a non-empty directory already exists at your target location
  • --immediate / --no-immediate: Marks your preference for step (5) - i.e. do you want immediate install and starting the Dev server after scaffolding
  • --interactive (-i) / --no-interactive: Should Vite prompt you for answer, or assume default values? By default, the template is vanilla-ts if none is provided, and overwrite and immediate is false. The No-Interactive mode is useful when running create-vite as part of some unmonitored CI/CD pipeline, or if an AI agent is running some command.

Additionally, here are the full list of template names that you can pass into the --template argument:

// vanilla
"vanilla-ts",
"vanilla",

// vue
"vue-ts",
"vue",
"custom-create-vue",
"custom-nuxt",
"custom-vike-vue",

// react
"react-ts",
"react-compiler-ts",
"react-swc-ts",
"react",
"react-compiler",
"react-swc",
"rsc",
"custom-react-router",
"custom-tanstack-router-react",
"redwoodsdk-standard",
"custom-vike-react",

// preact
"preact-ts",
"preact",
"custom-create-preact",

// lit
"lit-ts",
"lit",

// svelte
"svelte-ts",
"svelte",
"custom-svelte-kit",

// solid
"solid-ts",
"solid",
"custom-tanstack-router-solid",
"custom-vike-solid",

// ember
"ember-app-ts",
"ember-app",

// qwik
"qwik-ts",
"qwik",
"custom-qwik-city",

// angular
"custom-angular",
"custom-analog",

// marko
"marko-run",

// others
"create-vite-extra",
"create-electron-vite"
Enter fullscreen mode Exit fullscreen mode

Cool thing 2 - "Support" for AI agent

This is a fun little detail, but create-vite uses @vercel/detect-agent to determine if an agent is running the CLI. And if isAgent and interactive mode is enabled, the CLI will log a helpful message

To create in one go, run: create-vite --no-interactive --template

Cool thing 3 - Some coding techniques

Here are some cool programming techniques I though were very interesting:

  1. Determining the package mananger used via npm_config_user_agent ENV

Ever wondered how CLI can determine what package manager you used, so they can continue using that in subsequent commands? It's all thanks to the npm_config_user_agent environment variable. Each package manager sets the variable accordingly (like how pnpm does here).

Example: You can run pnpm config get user-agent to get the full agent string:

pnpm/10.20.0 npm/? node/v20.11.1 linux x64
Enter fullscreen mode Exit fullscreen mode

Then you can split by space and then by slash to get the package manager name.

  1. Detect whether standard input is connected to a terminal or not via process.stdin.isTTY.

The terminal input can also be piped in (like cat data.txt | xargs pnpm create-vite), in which case interactivity won't be possible. As a result, the CLI only enables interactive mode if isTTY is true

  1. Handling Control-C gracefully

After every prompt, I noticed that there is always a check to see if user has cancelled the command so that we can gracefully display the message "Operation Cancelled".

if (prompts.isCancel(projectName)) return cancel()
Enter fullscreen mode Exit fullscreen mode

This technique feels so obvious in retrospect (and the @clack/prompts creator also recommends so), but seeing how it is employed in production-ready code base somehow really cements it for me.

In summary - lessons I learned

  • mri, @clack/prompts, and picocolors are a quick combo to create a very pleasant CLI
  • Use process.env.npm_config_user_agent to detect what package manager was used
  • If you're using @clack/prompts: Check prompts.isCancel to handle control-C gracefully

Top comments (0)