自己搭个脚手架

时间:2021-1-8 作者:admin

背景

  • 新建一个项目的时候要从零开始,重新配置一大堆东西,过于重复冗杂;
  • 实际开发中有很多业务功能,文件是固定要建的,比如页面的layout布局,路由权限校验等……
  • 当前已经把公共部分抽离生成了一个git项目,每次新建项目都需要创建两个远程仓库去拉取模板代码,再推送到origin远程仓库,过程过于繁琐。

基于解放生产力,减少重复工作,提高效率的原则。如果我们把模板做成一个脚手架,只要运行一下如vue-cli这样的语句就能帮我们下载模板 安装需要的依赖,这样就会省去很多繁琐的工作。

准备工作

  1. 将公共部分抽离出来,建一个git项目单独维护
  2. 知识储备
    • commander 命令编写
    • chalk 让输出带颜色
    • download-git-repo 下载git模板
    • inquirer 命令行交互,获取用户输入
    • ora 进度条
    • fs-extra 文件相关操作工具库
    • mem-fs 操作模板文件工具库
    • mem-fs-editor 操作模板文件工具库

效果

  1. 执行init命令
  2. 输入项目名称与描述
  3. 选择模板
  4. 下载模板
  5. 初始化git仓库
  6. 安装项目依赖

实现

创建项目

npm init 创建 package.json,添加 bin 命令

  "bin": {
    "project": "bin/project",
    "project-init": "bin/project-init"
  }
主体流程

创建一个bin目录,在目录里面创建project

#! /usr/bin/env node

const program = require('commander');

program
  .usage('<command> [options]') // 用户使用提示
  .command('init [name]', 'init a project') // 如果没有action 会在同目录下找x-init文件执行
  .parse(process.argv)

处理init命令

在bin目录中创建project-init

#! /usr/bin/env node

const program = require('commander');
const version = require('../package.json').version

program
  .version(version, "-v, --version")
  .option('--name [name]', 'project-name')
  .parse(process.argv)
const { name } = program
const args = program.args // project init [name] 带过来的name参数

const projectName = args[0] || name

主体流程

1. 简单问询
const inquirer = require('inquirer')
const fse = require('fs-extra')
let prompts = []

if (typeof projectName !== 'string') {
  prompts.push({
    type: 'input',
    name: 'projectName',
    message: '请输入项目名称:',
    validate(input) {
      if (!input) {
        return '项目名称不能为空'
      }
      if (fse.existsSync(input)) {
        return '已经存在同名项目,请重新输入项目名'
      }
      return true
    }
  })
} else if (fse.existsSync(projectName)) {
  prompts.push({
    type: 'input',
    name: 'projectName',
    message: '请输入项目名称:',
    validate(input) {
      if (!input) {
        return '项目名称不能为空'
      }
      if (fse.existsSync(input)) {
        return '已经存在同名项目,请重新输入项目名'
      }
      return true
    }
  })
}
// 项目描述
prompts.push({
  type: 'input',
  name: 'description',
  message: '请输入项目描述'
})
// 选择模板
prompts.push({
  type: 'list',
  message: '请选择要下载的模板',
  name: 'template',
  choices: [
    {
      name: "PC",
      value: "PC-master"
    }, {
      name: "mobile",
      value: "mobile-master"
    }
  ]
})
inquirer.prompt(prompts).then(answer => {
    // answer 为用户输入的内容
    // 问询之后的操作,一般为下载模板
})
2. 下载模板
const path = require('path');
const ora = require('ora')
const projectPath = path.join(process.cwd(), projectName)
const downloadPath = path.join(projectPath, '__download__')

const spinner = ora('正在从gitlab下载template')
spinner.start()

const sourcePath = `${模板的git地址}#${ answer.template}`
download(sourcePath, downloadPath, { clone: true }, err => {
  if (err) {
    // 可以输出一些项目失败的信息
    spinner.color = 'red';
    spinner.fail(err);
    console.log(err)
    return
  }
  spinner.color = 'green';
  spinner.succeed('下载成功');
})

此时下载的文件是在内存中的临时文件,还不能对其进行操作。我们还需要把临时文件复制下来,提交到磁盘中,真正的保存下来。

3. 保存文件
const memFs = require('mem-fs');
const editor = require('mem-fs-editor');
const INJECT_FILES = ['package.json']
//复制文件
function getDirFileName (dir) {
  try {
    const files = fse.readdirSync(dir)
    const fileToCopy = []
    files. forEach(file => {
      if (file.indexOf(INJECT_FILES) > -1) return
      fileToCopy.push(file)
    })
    return fileToCopy
  } catch (e) {
    return []
  }
}
const store = memFs.create()
let memFsEditor = editor.create(store)
function injectTemplate(source, dest, data) {
   memFsEditor.copyTpl( source, dest, data )
}
const copyFiles = getDirFileName(downloadPath) // 要复制的文件

copyFiles.forEach((file) => {
    fse.copySync(path.join(downloadPath, file), path.join(projectPath, file))
    console.log(`${chalk.green('✔ ')}${chalk.green(`创建${projectName}/${file}`)}`)
})

INJECT_FILES.forEach(file => {
    injectTemplate(path.join(downloadPath, file), path.join(projectName, file), {
      projectName,
      description
    })
})

// 将内存中的文件操作,全部提交到磁盘
memFsEditor.commit(() => {
    INJECT_FILES.forEach(file => {
      console.log(`${chalk.green('✔ ')}${chalk.green(`创建${projectName}/${file}`)}`)
})

// 删除临时文件
fse.remove(downloadPath)
process.chdir(projectPath)
4. git init && 安装依赖
const { exec } = require('child_process')
const path = require('path');
    // git 初始化
const gitInitSpinner = ora(`cd ${chalk.green.bold(projectName)} 目录, 执行 ${chalk.green.bold('git init')}`)
gitInitSpinner.start()

const gitInit = exec('git init')

gitInit.on('close', code => {
  if (code === 0) {
    gitInitSpinner.color = 'red'
    gitInitSpinner.succeed(gitInit.stdout.read())
  } else {
    gitInitSpinner.color = 'red'
    gitInitSpinner.fail(gitInit.stderr.read())
  }
  // 安装依赖
  const installSpinner =  ora(`安装项目依赖 ${chalk.green.bold('npm install')}, 请稍后...`)
  installSpinner.start()
  exec('npm install', (error, stdout, stderr) => {
    if (error) {
      installSpinner.color = 'red'
      installSpinner.fail(chalk.red('安装项目依赖失败,请自行重新安装!'))
      console.log(error)
    } else {
      installSpinner.color = 'green'
      installSpinner.succeed('安装成功')
      console.log(`${stderr}${stdout}`)
      console.log(chalk.green('创建项目成功!'))
      console.log(chalk.green('输入npm run serve启动项目'))
    }
  })
})

本地调试

添加了bin命令之后,需要执行npm link将npm 模块链接到对应的运行项目中去,方便地对模块进行调试和测试

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。