脚手架学习-项目流程设计与开发

既上篇脚手架学习
本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

一、准备阶段

1.判断当前目录是否为空

接之前的代码,我们在commands/init/lib目录
项目的流程设计主要有准备阶段、下载模板、安装模板三个阶段,现在准备阶段。修改exec方法

exec() {
    try {
      // 准备阶段
      this.prepare()
      // 下载模板
      // 安装模板
    } catch (e) {
      log.error(e.message)
    }
}
复制代码

这里我们写了准备方法,首先要判断当前目录是否为空,这里写了一个isCwdEmpty判断目录是否为空

isCwdEmpty() {
    const localPath = process.cwd()
    let fileList = fs.readdirSync(localPath)
    // 文件过滤的逻辑
    fileList = fileList.filter(file => (
      !file.startsWith('.') && ['node_modules'].indexOf(file) < 0)
    );
    return !fileList || fileList.length <= 0
}
复制代码

在prepare方法中,我们要对目录为空做是否继续创建操作,不为空,那么我们要做什么呢

prepare() {
    // 判断当前目录是否为空
    if(!this.isCwdEmpty()) {
      // 1.1 询问是否继续创建
    }
    console.log('ret', ret)
    // fs.readdirSync()
    // 是否启动强制更新
    // 选择创建项目或组件
    // 获取项目的基本信息
  }
复制代码

2.是否继续创建

当得到项目文件是否为空之后,我们通inquirer插件,做用户进行询问,这里我们要导入

lerna add inquirer commands/init/
 lerna add fs-extra  commands/init/
复制代码

导入fs-extra,可在确认清当前文件目录时,使用emptyDirSync,清空文件夹

async prepare() {
    // 判断当前目录是否为空
    const localPath = process.cwd()
    if(!this.isDirEmpty(localPath)) {
      let ifContinue = false
      if (!this.force) {
        // 1.1 询问是否继续创建
        ifContinue = (await inquirer.prompt({
          type: 'confirm',
          name: 'ifContinue',
          default: false,
          message: '当前文件夹不为空, 是否继续创建项目?'
        })).ifContinue
        if(!ifContinue) {
          return
        }
      }
      // 是否启动强制更新
      if (ifContinue || this.force) {
        // 给用户做二次确认
        const { confirmDelete } = await inquirer.prompt({
          type: 'confirm',
          name: 'confirmDelete',
          default: false,
          message: '是否确认清空当前目录下的文件'
        })
        if(confirmDelete) {
          // 清空当前目录
          fse.emptyDirSync(localPath)
        }
        
      }
    }
  }
复制代码

在这里,我们把isCwdEmpty修改为isDirEmpty,并将地址的获取放在了perpare中,

isDirEmpty(localPath) {
    let fileList = fs.readdirSync(localPath)
    // 文件过滤的逻辑
    fileList = fileList.filter(file => (
      !file.startsWith('.') && ['node_modules'].indexOf(file) < 0)
    );
    return !fileList || fileList.length <= 0
  }
复制代码

之后我们可以获取用户信息,选择创建组件还是项目

  async getProjectInfo() {
    const projectInfo = {}
    // 选择创建项目或组件
    const type = await inquirer.prompt({
      type: 'list',
      name: 'type',
      message: '请选择初始化类型',
      default: TYPE_PROJECT,
      choices: [{
        name: '项目',
        value: TYPE_PROJECT,
      }, {
        name: '组件',
        value: TYPE_COMPONENT
      }]
    })
    log.verbose('type', type)
    
    if (type === TYPE_PROJECT) {
      // 获取项目的基本信息
    } else if (type === TYPE_COMPONENT){
      
    }
    return projectInfo
    // return 项目的基本信息 (object)
  }
复制代码

在prepare最后回调这个方法

return this.getProjectInfo()
复制代码

如果选择项目的话

if (type === TYPE_PROJECT) {
      // 获取项目的基本信息
      const o = await inquirer.prompt({
        type: 'input',
        name: 'projectName',
        message: '请输入项目名称',
        default: '', 
        validate: function(v) {
          return typeof v === 'string'
        },
        filter: function(v) {
          return v
        }
      }, {
        type: 'input',
        name: 'projectVersion',
        message: '请输入项目版本号',
        default: '',
        validate: function(v) {
          return typeof v === 'string'
        },
        filter: function(v) {
          return v
        }
      })
      console.log(o)
    } else if (type === TYPE_COMPONENT){
      
    }
复制代码

在这里可以加载semver依赖,使用valid对版本号和项目名称进行判断

项目名称

validate: function(v) {
  let done = this.async();
  setTimeout(function() {
       // 1. 首字符必须为英文字符
       // 2. 尾字符必须为英文或数字,不能为字符
       // 3. 字符仅允许"-_"
      if(!/^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]*|[_][a-zA-Z][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(v)) {
      done('请输入合法的项目名称')
      return
  }
  done(null, true)
  }, 0)
},
复制代码

版本号

validate: function(v) {
   let done = this.async();
   setTimeout(function() {
      if(!(!!semver.valid(v))) {
         done('请输入合法的项目版本号')
         return
       }
       done(null, true)
    }, 0)
   },
   filter: function(v) {
      if (!!semver.valid(v)) {
           return semver.valid(v)
      } else {
          return v
   }
}
复制代码

创建成功后返回项目信息

projectInfo = {
  type,
  ...project,
}
复制代码

二、下载模板

1.下载模板的数据获取

1.通过项目模板api获取项目模板信息

1.1 通过egg.js搭建一套后端系统

1.2 通过npm 存储项目模板(vue-cli/vue-element-admin)

1.3 将项目模板信息存储到mongodb数据库中

1.4 通过egg.js获取mongodb中的数据并通过api返回

在utils目录下创建request文件,加载axios依赖

lerna create @zl-cli-dev/request
lerna add axios .\utils\request\
复制代码

在request文件中配置请求

const axios = require('axios')

const BASE_URL = process.env.ZL_CLI_BASE_URL ? process.env.ZL_CLI_BASE_URL : 'xxxxxxxxxxx'
const request = axios.create({
  baseURL: BASE_URL,
  timeout: 5000,
})
request.interceptors.response.use(
  response => {
    return response.data;
  },
  error => {
    return Promise.reject(error)
  }
)

module.exports = request;
复制代码

在commands文件创建getProjectTemplate文件,调用服务

const request = require('@zl-cli-dev/request')

module.exports = function() {
  return request({
    url: '/project/template'
  })
}
复制代码

在index中使用,判断当前是否有模板

const getProjectTemplate = require('./getProjectTemplate')
// 判断项目模板是否存在
const template = await getProjectTemplate()
if (!template || template.length === 0) {
   throw new Error('项目模板不存在')
}
this.template = template
复制代码

2.下载模板的选择

在之前的交互方法中新加对项目模板选择的交互

{
   type: 'list',
   name: 'projectTemplate',
   message: '请选择项目模板',
   choices: this.createTemplateChoices()
 }
复制代码

createTemplateChoices这个方法把数据都遍历出来,让用户选择

createTemplateChoices() {
    return this.template.map(item => ({
      value: item.npmName,
      name: item.name
    }))
  }
复制代码

3.下载

加载package,userHome, path的依赖

lerna add user-home commands/init/
复制代码

1.获取项目信息

const { projectTemplate } = this.projectInfo
const templateInfo = this.template.find(item => item.npmName === projectTemplate)
复制代码

通过user-home获取本地目录,创建文件

const templateInfo = this.template.find(item => item.npmName === projectTemplate)
const targetPath = path.resolve(userHome, '.zl-cli-dev', 'template')
const storeDir = path.resolve(userHome, '.zl-cli-dev', 'template', 'node_modules')
复制代码

初始化package包,并下载包,如果已经下载了,就更新包

const templateNpm = new Package({
      targetPath,
      storeDir,
      packageName: npmName,
      packageVersion: version
    })
    if (!await templateNpm.exists()) {
      await templateNpm.install()
    } else {
      await templateNpm.update()
    }
复制代码

4.友好提示
使用cli-spinner,加载依赖到utils下的utils文件中

function spinnerStart (msg, spinnerString = '|/-\\') {
  const Spinner = require('cli-spinner').Spinner

  const spinner = new Spinner(msg + ' %s')
  spinner.setSpinnerString(spinnerString);
  spinner.start()
  return spinner
}

function sleep (timeout = 1000) {
  return new Promise((resolve, reject) => setTimeout(resolve, timeout))
}
复制代码

将方法导入到commands/init中,

const { spinnerStart, sleep } = require('@zl-cli-dev/utils')
if (!await templateNpm.exists()) {
      const spinner = spinnerStart('正在下载模板...')
      await sleep()
      try {
        await templateNpm.install()
        log.success('下载模板成功')
      } catch (e) {
        throw e
      } finally {
        spinner.stop(true)
      }
    } else {
      const spinner = spinnerStart('正在更新模板...')
      await sleep()
      try {
        await templateNpm.update()
        log.success('更新模板成功')
      } catch (e) {
        throw e
      } finally {
        spinner.stop(true)
      }
    }
复制代码

对模板下载和更新进行优化提示

上一篇:脚手架学习-基于node多进程

下一篇:未完待续。。。。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享