背景
- 新建一个项目的时候要从零开始,重新配置一大堆东西,过于重复冗杂;
- 实际开发中有很多业务功能,文件是固定要建的,比如页面的layout布局,路由权限校验等……
- 当前已经把公共部分抽离生成了一个git项目,每次新建项目都需要创建两个远程仓库去拉取模板代码,再推送到origin远程仓库,过程过于繁琐。
基于解放生产力,减少重复工作,提高效率的原则。如果我们把模板做成一个脚手架,只要运行一下如vue-cli这样的语句就能帮我们下载模板 安装需要的依赖,这样就会省去很多繁琐的工作。
准备工作
- 将公共部分抽离出来,建一个git项目单独维护
- 知识储备
- commander 命令编写
- chalk 让输出带颜色
- download-git-repo 下载git模板
- inquirer 命令行交互,获取用户输入
- ora 进度条
- fs-extra 文件相关操作工具库
- mem-fs 操作模板文件工具库
- mem-fs-editor 操作模板文件工具库
效果
- 执行init命令
- 输入项目名称与描述
- 选择模板
- 下载模板
- 初始化git仓库
- 安装项目依赖
实现
创建项目
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 模块链接到对应的运行项目中去,方便地对模块进行调试和测试