脚手架构建

1. 引言

背景介绍

前端脚手架的目的在于提高前端开发的效率和一致性 ,节约一些重复性工作,例如:编译、打包、代码检查,避免了从零开始搭建项目框架的繁琐工作。

需求分析: 

  • 项目创建与初始化
    • 创建新的项目目录结构。
    • 初始化基本的项目文件,如index.htmlpackage.json等。
  • 依赖安装
    • 自动安装项目所需的核心依赖。
    • 支持选择性安装额外的依赖或插件。
  • 用户交互
    • 提供命令行界面,让用户能够输入项目配置信息。
    • 根据用户输入生成定制化的项目配置。
  • 模板生成
    • 根据用户的选择生成基础的项目模板。
    • 支持多种预设模板以适应不同的项目类型。
  • 架构设计
    • 包括命令行解析模块、模板管理模块、文件生成模块等,并解释每个模块的作用和它们之间的交互。

2. 相关工具介绍

(📗代表核心工具,📒代表辅助工具)

命令行交互

  • 📗 commander:Commander.js 中文网
  • 📗 inquirer:选项命令行交互工具
  • 📒 chalk:美化命令行输出
  • 📒 figlet:创建 ASCII 艺术字
  • 📒 ora:加载指示器

下载模板

  • 📗 git-clone:用于创建任何可通过网络访问的 Git 仓库的副本
  • 📗 npminstall: 可以用来安装依赖包,将安装的依赖包名称及版本信息添加到项目的 package.json 文件中

文件操作

  • 📗 fs-extra:扩展 Node.js 原生 fs 模块功能的库,支持 Promise、防止 EMFILE 错误,并包含复制、移动、确保目录存在等常用操作

4. 实现脚手架

以下是一个简单的脚手架结构示例:

scaffold/
├── package.json
├── jsconfig.json
├── babel.config.js
└── bin/
    ├── index.js
└── src/
    ├── commands
        ├── create.js
        └── ...
    ├── lib
        ├── consts.js
        ├── index.js
        ├── logger.js
        ├── spinner.js
        └── ...

创建 package.json

package.json 文件中,你需要定义脚手架的元数据和依赖项:

{
  "name": "demo-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "bin": "./bin/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "chalk": "^4.0.0",
    "commander": "^12.1.0",
    "figlet": "^1.7.0",
    "fs-extra": "^11.2.0",
    "git-clone": "^0.2.0",
    "inquirer": "^8.0.0",
    "npminstall": "^7.6.0",
    "ora": "^5.0.0"
  },
  "devDependencies": {
    "@babel/cli": "^7.23.4",
    "@babel/core": "^7.25.2",
    "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
    "@babel/preset-env": "^7.24.7"
  }
}

实现 index.js

index.js 是脚手架的入口文件,它将处理用户输入和脚手架的逻辑流程:

#!/usr/bin/env node

const { program } = require('commander');
const pkg = require('../package.json');
const chalk = require('chalk');
const inquirer = require('inquirer');
const ora = require('ora');
const figlet = require('figlet');
const { info, warn, error } = require('../src/lib/logger');
const { startSpinner, succeedSpiner, failSpinner } = require('../src/lib/spinner');
const createAction = require('../src/commands/create');

// figlet('KFC Crazy Thursday V Me 50 !!!', (err, data) => {
//   if (err) {
//     console.log('Something went wrong...');
//     console.dir(err);
//     return;
//   }
//   console.log(chalk.green(data));
// });

// const spinner = ora('loading ...').start();

program.name('demo-cli').usage('<command> [options]');
program.version(`v${pkg.version}`);
program.on('--help', () => {
  console.log();
  console.log('  Examples:');
  console.log();
  console.log('    $ demo-cli create <project-name>');
  console.log('    $ demo-cli <command> [options]');
});
program
  .command('create <name>')
  .description('create a new project')
  .action((name) => {
    createAction(name);
  });

program.parse(process.argv);

// const options = program.opts();
// console.log(options);

// console.log('肯德基疯狂星期四,V我50!');

实现 create.js

create.js 它将处理用户输入和脚手架的逻辑流程:

const path = require('path');
const fs = require('fs-extra');
const inquirer = require('inquirer');
const chalk = require('chalk');
const { startSpinner, succeedSpiner, failSpinner } = require('../lib/spinner');
const { info, warn, error } = require('../lib/logger');
const { cwd, reactRepo, vueRepo } = require('../lib/consts');
const gitCLone = require('git-clone');

const checkProjectExist = async (targetDir) => {
  if (fs.existsSync(targetDir)) {
    const answer = await inquirer.prompt({
      type: 'list',
      name: 'checkExist',
      message: `\n仓库路径${targetDir}已存在,请选择`,
      choices: ['覆盖', '取消'],
    });
    if (answer.checkExist === '覆盖') {
      warn(`删除${targetDir}...`);
      fs.removeSync(targetDir);
    } else {
      return true;
    }
  }
  return false;
};

const getQuestions = async (projectName) => {
  return await inquirer.prompt([
    {
      type: 'input',
      name: 'name',
      message: `project name: (${projectName})`,
      default: projectName,
    },
    {
      type: 'rawlist',
      name: 'frame',
      message: '选择所用框架:',
      choices: ['React', 'Vue'],
      default: 'React',
    },
    {
      type: 'checkbox',
      name: 'enosDependencies',
      message: '请选择所用依赖:',
      choices: [
        { name: 'antd', value: 'antd' },
        { name: '***SDK', value: '***SDK' },
      ],
    },
    // {
    //   type: 'confirm',
    //   name: 'antd',
    //   message: '是否集成antd:',
    //   default: false,
    // },
  ]);
};

const createProject = async (targetDir, projectInfo) => {
  console.log(projectInfo);
  const repo = projectInfo.frame === 'React' ? reactRepo : vueRepo;
  startSpinner('下载react仓库...');
  gitCLone(repo, targetDir, { checkout: 'main' }, (err) => {
    if (err) {
      failSpinner(err);
      return;
    }
    succeedSpiner(`项目创建完成 ${chalk.yellow(projectInfo.name)}\n👉 输入以下命令进入项目:`);
    info(`$ cd ${projectInfo.name}\n `);
  });
};

const action = async (projectName, cmdArgs) => {
  try {
    const targetDir = path.join((cmdArgs && cmdArgs.context) || cwd, projectName);
    if (!(await checkProjectExist(targetDir))) {
      const projectInfo = await getQuestions(projectName);
      await createProject(targetDir, projectInfo);
    }
  } catch (err) {
    failSpinner(err);
    return;
  }
};

module.exports = action;

 

5. 测试脚手架

  • 本地测试
    • 使用npm link进行本地测试,以便在全局环境中测试你的脚手架。
    • 然后,你可以使用以下命令来创建新项目:
demo-cli create test-project

posted @ 2024-12-19 23:32  Yang9710  阅读(3)  评论(0编辑  收藏  举报