本文目标 该文章将带你使用rust和clap库实现一个创建前端vite项目的脚手架工具
git仓库 本文基于我的开源项目rust-init-cli改编
第一步: 初始化项目 安装 1 2 3 4 cargo init rust-vite-cli cd rust-vite-cli #  如果你用vscode, 可以用这个命令打开 code .  
 
依赖安装 1 2 3 cargo add clap #  or  cargo add clap -F derive  
 
第二步: clap读取命令行 
Args结构体 创建一个结构体来接收命令行参数
1 2 3 4 5 6 #[derive(Parser)] #[command()] struct  Args  {    #[command(subcommand)]      command: Option <Commands>, } 
 
关于subcommand, 大概就是可以用在结构体和枚举等类型上
1 2 3 4 5 6 7 8 9 10 11 /// 从源代码找的注释 /// Parse a sub-command into a user-defined enum. /// /// Implementing this trait lets a parent container delegate subcommand behavior to `Self`. /// with: /// - `#[command(subcommand)] field: SubCmd`: Attribute can be used with either struct fields or enum ///   variants that impl `Subcommand`. /// - `#[command(flatten)] Variant(SubCmd)`: Attribute can only be used with enum variants that impl ///   `Subcommand`. /// /// **NOTE:** Deriving requires the `derive` feature flag 
 
Commands结构体 我们实现Commands结构体, 结构体的属性会对应命令
1 2 3 4 5 6 #[derive(Subcommand)] enum  Commands  {         CreateVite } 
 
接下来, 在main函数中读取
1 2 3 4 5 6 7 8 9 10 fn  main () {    let  cli  = Args::parse ();          match  cli.command {         Some (Commands::CreateVite) => println! ("create vite!" ),         None  => {             println! ("Run with --help to see instructions." )         }     } } 
 
运行!
1 2 3 cargo run -- create-vite #  or  cargo run --release create-vite 
 
第三步: 从vite脚手架中复制项目模板 我们并没有采用从网络获取前端项目模板的实现方法, 而是使用将前端项目模板保存到目录, 当创建项目时再复制到目标目录的实现方式, 这种方式当然有坏处, 比如会让项目过大, 要手动将目录放到build后的文件夹等, 但是好处是实现起来比较简单方便
使用vite脚手架创建项目 我们先用vite脚手架创建项目, 然后再将项目复制到我们项目的public文件夹中
我们只创建vue+js/vue+ts/react+js/react+ts这4个项目
创建完后, 复制到项目的public文件夹下
第四步: 编写需要用到的方法 创建common.rs, 我们在其中存放通用的工具类方法
1 2 3 src/     common.rs     main.rs 
 
将common模块引入main.rs
读取命令行输入的方法 1 2 3 4 5 6 7 8 9 10 11 12 pub  fn  read_line () ->  String  {         let  mut  input  = String ::new ();          std::io::stdin ()         .read_line (&mut  input)         .expect ("Stdin not working" );     input.trim ().to_string () } 
 
复制文件到指定目录的方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 use  std::{fs, io, path::Path};pub  fn  copy_dir_all (src: impl  AsRef <Path>, dst: impl  AsRef <Path>) ->  io::Result <()> {         fs::create_dir_all (&dst).expect ("创建dst目录失败" );          fs::read_dir (&src).expect ("找不到src目录" );     for  entry  in  fs::read_dir (src)? {         let  entry  = entry?;         let  ty  = entry.file_type ()?;         if  ty.is_dir () {             copy_dir_all (entry.path (), dst.as_ref ().join (entry.file_name ()))?;         } else  {             fs::copy (entry.path (), dst.as_ref ().join (entry.file_name ()))?;         }     }          Ok (()) } 
 
获取运行时目录位置的方法 我原本希望能让程序在运行时找到src下的public文件夹, 但是发现不行, 后面找到可以获取运行时目录的方法, 这样就可以把public文件复制到target下的debug或release里, 让程序不管在什么地方运行都可以找到public里的文件, 并开始复制
1 2 3 4 5 6 7 8 9 src/     main.rs target/     cargo run运行时的current_exe目录     debug/         public/...     cargo build之后运行exe时的current_exe目录     release/         public/... 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 use  std::{fs, path::Path, env::current_exe};pub  fn  current_exe_pkg () ->  String  {    let  pkg_name  = env! ("CARGO_PKG_NAME" );          let  pkg_name  = pkg_name.to_string () + ".exe" ;          let  current_exe  = current_exe ().unwrap ();     current_exe.display ().to_string ().replace (&pkg_name, "" ) } 
 
第五步: 创建项目的逻辑 在做完了前面的准备工作后, 终于要开始编写了创建项目的逻辑了. 这一步的内容都放在vite.rs里
 
保存用户输入的结构体 为了保存用户输入, 以便后续根据输入的选项创建不同的项目, 我们需要一个结构体来保存这些数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #[derive(Debug)] enum  FrameworkType  {    React,     Vue, } #[derive(Debug)] enum  VariantType  {    Javascript,     Typescript, } #[derive(Debug)] struct  UserSelected  {    framework_type: FrameworkType,     variant_type: VariantType,     project_name: String ,  } 
 
为结构体添加方法 我们需要两个方法, 一个是创建结构体的方法, 一个是创建项目的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 use  std::path::Path;use  crate::common::{copy_dir_all, current_exe_pkg, read_line};impl  UserSelected  {    fn  new (project_name: &str , framework: &str , variant: &str ) ->  Self  {         let  framework_type  = match  framework {             "react"  => {                 FrameworkType::React             }             "vue"  => {                 FrameworkType::Vue             }             _ => {                                                   FrameworkType::React             }         };         let  variant_type  = match  variant {             "javascript"  => {                 VariantType::Javascript             }             "typescript"  => {                 VariantType::Typescript             }             "js"  => {                 VariantType::Javascript             }             "ts"  => {                 VariantType::Typescript             }             _ => {                                  VariantType::Typescript             }         };         let  project_name  = if  project_name.is_empty () {             "vite-project"          } else  { project_name };         UserSelected {             project_name: project_name.to_string (),             framework_type: framework_type,             variant_type: variant_type,                      }     }          fn  init (&self ) {         let  mut  path  = "public/vite/" .to_string ();         match  self .framework_type {             FrameworkType::React => {                 path += "react" ;             }             FrameworkType::Vue => {                 path += "vue" ;             }         }         path += "-" ;         match  self .variant_type {             VariantType::Javascript => {                 path += "js" ;             }             VariantType::Typescript => {                 path += "ts" ;             }         }         println! ("复制 {}" , &(current_exe_pkg () + &path));                  copy_dir_all (             Path::new (                 &(current_exe_pkg () + &path)             ),             Path::new (&self .project_name),         ).unwrap ();     } } 
 
询问并接收用户输入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 pub  fn  create_vite_project () {         println! ("your project name? {}" , "vite-project" );     let  project_name  = read_line ();     let  project_name  = if  project_name.is_empty () {         "vite-project"      } else  {         &project_name     };     println! ("{}" , (&project_name));               println! ("select a framework: (default: react)" );     println! ("react" );     println! ("vue" );     let  mut  framework  = read_line ().to_lowercase ();     framework = match  framework.as_str () {         "r"  => "react" .to_string (),         "react"  => "react" .to_string (),         "v"  => "vue" .to_string (),         "vue"  => "vue" .to_string (),         _ => "react" .to_string (),     };     println! ("{}" , (&framework));               println! ("select a variant: (default: ts)" );     println! ("typescript(ts)" );     println! ("javascript(js)" );     let  mut  variant  = read_line ().to_lowercase ();     variant = match  variant.as_str () {         "ts"  => "typescript" .to_string (),         "typescript"  => "typescript" .to_string (),         "js"  => "javascript" .to_string (),         "javascript"  => "javascript" .to_string (),         _ => "typescript" .to_string (),     };     println! ("{}" , (&variant));     let  user_select  = UserSelected::new (&project_name, &framework, &variant);     user_select.init (); } 
 
第六步: 测试 修改main.rs 1 2 3 4 5 6 7 8 9 10 11 12 fn  main () {    let  cli  = Args::parse ();     match  cli.command {         Some (Commands::CreateVite) => create_vite_project (),         None  => {             println! ("Run with --help to see instructions." )         }     } } 
 
测试cargo run 将public目录移动到target/debug目录
测试cargo run时能否成功
1 cargo run -- create-vite 
 
可以看到在项目根目录成功创建了vite-project目录
测试cargo build后的exe 先复制public目录到target/release目录
1 2 cargo build --release .\target\release\rust-vite-cli.exe create-vite 
 
end 接下来, 你可以将release目录的文件复制到系统的任意目录, 然后将其配置到系统环境变量, 就可以随时随地运行 rust-vite-cli create-vite 来创建前端项目了.
感谢观看