「打造属于自己的前端项目生成器:现代 CLI 脚手架技术实现全解析」

6 阅读2分钟

前言

在前端工程化浪潮下,脚手架工具已成为提升开发效率、规范项目结构的利器。本文将深入剖析一个现代前端项目生成器的技术实现思路,包括 CLI 交互、模板引擎、配置管理、插件机制等核心技术点,帮助你理解并实现属于自己的脚手架工具。

一、项目架构设计

一个现代 CLI 脚手架的整体架构主要分为以下几个核心模块:

  1. 命令行交互(CLI):负责与用户交互,收集项目配置信息。
  1. 模板引擎:根据用户选择渲染项目模板,生成最终代码结构。
  1. 文件操作与依赖管理:自动化生成文件、安装依赖。

二、核心技术实现

1. 命令行交互

CLI 脚手架通常采用 prompts 实现命令行交互,动态收集用户输入

import prompts from 'prompts'

const main=()=>{
    //支持填充默认值
    prompts.override({
        overwrite: argvOverwrite
    })
    const result=await prompts([
          {
              name: 'projectName',
              type: argvProjectDir ? null : 'text',
              message: yellow('项目名称: '),
              initial: targetDir,
          },
          ...等配置
    ])

}

2. 模板引擎

项目模板通常采用 fs 等模板引擎进行渲染。CLI 脚手架支持变量替换、条件渲染等功能:

const write = (templateDir: string, root: string, { packageName, projectName }: { packageName: string, projectName: string }) => {
    const templateFiles = fs.readdirSync(templateDir)
    templateFiles.forEach(file => {
        const newFile = renameFiles[file] ?? file
        const originPath = path.resolve(templateDir, `./${file}`)
        const targetPath = path.resolve(root, `./${newFile}`)
        //特殊处理package.json
        if (newFile === 'package.json') {
            const content = getPkgFile(originPath, packageName)
            fs.writeFileSync(targetPath, content)
            return
        }
        //如果还有特殊逻辑可以在添加
        ...
        const fileState = fs.statSync(path.resolve(templateDir, `./${file}`))
        //递归创建
        if (fileState.isDirectory()) {
            fs.mkdirSync(targetPath)
            write(originPath, targetPath, { packageName, projectName })
        } else {
            fs.copyFileSync(originPath, targetPath)
        }

    })
}


//获取模版地址
const templateDir = path.resolve(fileURLToPath(import.meta.url), `../../template-${template}`)
//通过fs模块将文件写入指定位置
write(templateDir, root, {
   packageName: packageName ?? projectName ?? argvProjectDir,
   projectName: projectName
})

3. 依赖管理

使用 child_process 实现依赖自动安装:

import {execSync} from "child_process"

async function installDeps(projectPath) {
  await execSync('npm', ['install'], { cwd: projectPath, stdio: 'inherit' });

那么!为什么我们在终端执行特定的命令就可以执行这个脚手架呢,让我们去看package.json

{
    name:"cli",
    ...
    //注意这里是注册命令
    bin:{
        "key":"path"
    }
}

我们可以看到bin为注册命令,key为输入的命令,path为脚本执行路径,这样就可以完成cli脚手架啦。

如果喜欢麻烦大家给个关注哦