开发项目脚手架

902 阅读3分钟

什么是脚手架?

一般每个公司或者团队的前端项目,都会有固定的项目结构,打包配置方案,代码风格规范等。但是不同项目,项目名称,配置等又有所不同。

脚手架就是可以帮助我们生成特定的项目结构,打包配置方案,代码风格规范的新项目模板,同时又可以个性化配置一些选项。

脚手架的工作过程

1. 通过命令行交互询问用户问题

2. 根据用户回答的结果生成文件

为什么要自己写?

现在的确有很多主流的脚手架比如vue-cli、create-react-app等,还有专注开发脚手架yeoman等,主要是因为这些不符合公司项目结构,还有就是这个是公司内部的项目模,不能开源。

开发脚手架

项目模板

因为考虑到项目模板会更新,所以模板作为一个单独项目维护,托管在gitlab的仓库中,模板就是模板引擎。

基本流程如下:

  1. 将项目模板clone到当前运行目录的templates目录下

    - 判断是否已经存在templates,如果存在需要删除在重新clone

    - templates不存在,直接clone

  1. 使用inquirer进行控制台交互,获取用户自定义信息

  2. 递归遍历模板

    - 如果是目录,创建目录

    - 如果是文件,使用ejs包去替换模板引擎中用户自定义的信息,然后将处理后的结果文件写到目标目录下

使用到的工具如下

    - git项目克隆工具:download-git-repo

    - 删除文件工具包: rimraf

    - 控制台交互工具:inquirer

    - 控制台输出字符串的样式:chalk

    - js模板引擎: ejs

工具代码

  1. package.json中指定npm包命令的入口文件
{
  "name": "test-template",
  "version": "1.0.1",
  "description": "脚手架",
  "main": "index.js",
  "bin": "cli.js", // 指定npm包命令的入口文件
  "keywords": ["npm","node"],
  "author": "jill",
  "license": "ISC",
  "dependencies": {
    "chalk": "^4.1.0",
    "download-git-repo": "^3.0.2",
    "ejs": "^3.1.6",
    "inquirer": "^7.3.3",
    "rimraf": "^3.0.2"
  }
}
  1. 用户交互配置文件config/index.js
module.exports = [
  { // 初始化项目名称
    type: 'input',
    name: 'name',
    message: `what's this project name?`,
    // default: '11',
    validate: function (input) {
      // Declare function as asynchronous, and save the done callback
      let done = this.async();

      const reg = new RegExp('[\\\\/:*?\"<>|@]')
      if (reg.test(input)) {
        done('You cannot enter special characters');
        return;
      }
      // Pass the return value in the done callback
      done(null, true);
    }
  },
  { // 初始化项目的版本号
    type: 'input',
    name: 'version',
    message: `what's this project version?`,
    default: '1.0.0',
    validate: function (input) {
      // Declare function as asynchronous, and save the done callback
      let done = this.async();
      
      const reg = new RegExp('^[1-9]\d?(\.(0|[1-9]\d?)){2}$')
      if (!reg.test(input)) {
        done('Please enter the correct version number');
        return;
      }
      // Pass the return value in the done callback
      done(null, true);
    }
  },
  // 根据实际情况,自定义配置
  ......
]
  1. 主文件cli.js:
#!/usr/bin/env node
// Node CLI 应用入口文件必须要有这样的文件头

const path = require('path')
const fs = require('fs')

const inquirer = require('inquirer')
const download = require('download-git-repo')
const chalk = require('chalk');
const ejs = require('ejs')
const rimraf = require('rimraf');
const inquirerConfig = require('./config')

const log = console.log;

const project = {
  repository: 'xxxx/test-template.git',
  branch: 'develop'
}

const templateDir = path.join(__dirname, 'templates')

if (fs.existsSync(templateDir)) {
  // templates已经存在要删除重新下载
  rimraf(templateDir, function (err) {
    if (err) throw err;
    downloadTemplate()
  });
} else {
  downloadTemplate()
}

function downloadTemplate () {
  log(chalk.green.bold('loading...'));
  download(`direct:${project.repository}#${project.branch}`, templateDir, { clone: true }, function (err) {
    if (err) {
      throw err
    }
    // 下载成功, 用户交互:获取用户自定义信息
    inquirer.prompt(inquirerConfig).then(answers => {
      const templateDir = path.join(__dirname, 'templates')
      let targetDir = process.cwd()
      targetDir = path.join(targetDir, answers.name)
      // 创建项目目录
      fs.mkdirSync(targetDir)
      readTemplate(templateDir, targetDir, answers)
    })
  })
}

// 深度遍历读取文件内容,进行模板替换
function readTemplate (templateDir, destDir, answers) {
  fs.readdir(templateDir, (err, files) => {
    if (err) throw err
    files.forEach(file => {
      const crrentTemplate = path.join(templateDir, file)
      let state = fs.lstatSync(crrentTemplate);
      if (state.isDirectory()) {
        /***
         * 是文件夹
         * 在目标目录下创建文件夹
         * 递归遍历子文件
         * ***/
        const targetPath = path.join(destDir, file)
        fs.mkdirSync(targetPath)
        readTemplate(crrentTemplate, targetPath, answers)
      } else {
        /***
         * 是文件
         * 读取文件,通过模板引擎渲染文件
         * 将渲染结果写入目标文件
         * **/ 
        const targetPath = path.join(destDir, file)
        ejs.renderFile(crrentTemplate, answers, (err, result) => {
          if (err) throw err
          fs.writeFileSync(targetPath, result)
        })
      }
    })
  })
}

最后,把项目pushlish到公司npm镜像即可

如何使用

### hc-template
 前端项目脚手架工具
#### 如何安装

npm install -g test-template

#### 初始化命令

hc-template

注意点

在开发过程中可以通过添加软链对项目进行调试

在工具项目目录下运行命令:

npm link / yarn link // 添加
npm unlink / yarn unlink // 移除

即可在全局范围内使用 test-template命令,对项目进行调试

如果运行命令报错,可能是权限问题:

  • 如果是 Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755

  • 具体就是通过 chmod 755 cli.js 实现修改