记录-开发一个脚手架

功能:

  • 类似vue-cli,通过一个命令, 比如vue create hello-world,然后根据一步一步的提示,最终拉取到对应的内容

开发原因:

  • 前端组内部有符合自己业务逻辑的模板项目,为了方便组员快速拉取,需要一个脚手架。
  • 脚手架搭起来后,适应不同需求的模板也可以往脚手架里面加
  • 不同的模板可以用git管理起来,很方便

使用效果:

依然用到了工具平台:内部前端工具平台搭建, 安装:npm i -g @myCompany/feTools

  1. 终端输入: fe cli tips:启动cli
  2. 打印:一个提示:? 请输入您想创建的文件名 (会创建在当前目录下): (cli-project) tips:此时可以输入 想创建的文件名,或者用默认的名字cli-project
  3. 按回车往下走:一个提示:? 请选择您想拉取的模板: (Use arrow keys) tips:此时可以上下选择想要拉取的模板)
  4. vue2
  5. test
  6. 按回车结束(文件已经拉到了当前目录)

开发原理解析:

一些第三方工具包的组合

  1. commander: 识别命令,例如 fe cli -h
  2. inquirer: 终端交互,可以输入文件名,选择模板
  3. download-git-repo: 拉取gitlab的库。类似git clone
  4. 其他,比如执行npm install

记录-开发一个脚手架 - 图1

源码:

entry.js : 入口文件

  1. #! /usr/bin/env node
  2. // 上面是配置当前的环境变量地址: mac上是固定的
  3. // 引入依赖
  4. const commander = require('commander') // 完整的 node.js 命令行解决方案: https://www.npmjs.com/package/commander
  5. const createProject = require('./create-project') // 下一步要看的
  6. const inquirer = require('inquirer') // 命令行交互工具: https://www.npmjs.com/package/inquirer
  7. const pkgJson = require('../../package.json')
  8. // 定义版本号以及命令选项
  9. commander
  10. .version(pkgJson.version, '-v -V --version') // 不写后面这个的话,只能用大写的 -V
  11. .option('-i --init [name]', 'init a project')
  12. .parse(process.argv) // let commander can get process.argv
  13. const promptList = [{
  14. type: 'input',
  15. message: '请输入您想创建的文件名 (会创建在当前目录下):',
  16. name: 'name',
  17. default: 'cli-project' // 默认值
  18. }]
  19. let projectName = ''
  20. if (commander.init) { // 如果 cli -i xxName, 就走这里
  21. projectName = commander.init
  22. createProject(projectName)
  23. } else { // 直接 cli 走这里
  24. inquirer.prompt(promptList).then(answers => {
  25. projectName = answers.name ? answers.name : 'cli-project'
  26. createProject(projectName) // 拿到文件名
  27. })
  28. }

create-project.js : 配置用户的选项交互

  1. const chalk = require('chalk') // 彩色console.log
  2. const path = require('path') // 获取路径
  3. const inquirer = require('inquirer')
  4. const GitClone = require('./git-clone.js') // 下一步要看的
  5. const npmInstall = require('./npm-install.js') // 扩展项, 命令行执行lnpm i, 不影响主功能
  6. const promptList = [{
  7. type: 'list',
  8. message: '请选择您想拉取的模板:',
  9. name: 'key',
  10. choices: [],
  11. filter: function (val) { // filter result
  12. return val
  13. }
  14. }]
  15. GitClone.tplObj.map(e => {
  16. promptList[0].choices.push(e.key) // 把git模板放入 promptList 中, 最后由inquirer执行
  17. })
  18. module.exports = function createProject (name) {
  19. inquirer.prompt(promptList).then(answers => {
  20. // 获取将要构建的项目根目录
  21. const projectPath = path.resolve(name) // return absolute path
  22. console.log(`Start to init a project in ${chalk.green(projectPath)}`)
  23. const key = answers.key
  24. // 执行git clone
  25. GitClone.gitCloneFn(key, name)
  26. .then(msg => {
  27. console.log(chalk.green(msg))
  28. // 由于大一点的模板 npm install后, 编辑器很容易卡死, 编辑器执行可以注释掉此处 npm install的功能
  29. npmInstall(projectPath)
  30. })
  31. .catch(err => {
  32. if (err.includes('128')) {
  33. console.log(chalk.red(`失败! ${name}已经存在`))
  34. } else {
  35. console.log(chalk.red(err))
  36. }
  37. })
  38. })
  39. }

git-clone.js : 利用git clone把项目拉下来,拉取的模板可以在这里配置

  1. const download = require('download-git-repo') // 执行git clone拉代码: https://www.npmjs.com/package/download-git-repo
  2. const tplObj = [
  3. {
  4. key: 'vue2',
  5. git: 'https://gitlab.xxx.cn/xxxx/xxxx.git#vue2' // #vue2表示vue2分支
  6. },
  7. {
  8. key: 'test',
  9. git: 'https://gitlab.xxx.cn/xxxx/test.git'
  10. }
  11. ]
  12. const gitCloneFn = (key, tplName) => {
  13. return new Promise((resolve, reject) => {
  14. const obj = tplObj.find(e => e.key === key)
  15. download('direct:' + obj.git, tplName, { clone: true }, function (err) {
  16. const msg = `拉取最新${obj.git}: `
  17. if (err) {
  18. reject(`${msg}: Error: ${err}`)
  19. } else {
  20. resolve(`${msg}: Success`)
  21. }
  22. })
  23. })
  24. }
  25. module.exports = {
  26. tplObj,
  27. gitCloneFn
  28. }

npm-install.js : 执行 npm install

  1. // 引入依赖
  2. const which = require('which') // 当前环境(电脑环境)是否存在 某 全局变量: 用处: 找是否存在lnpm, 如果不存在则用npm
  3. const chalk = require('chalk')
  4. const { spawn } = require('child_process') // 执行命令行 如: lnpm i
  5. // 开启子进程来执行npm install命令
  6. function runCommand (command, args, fn) {
  7. args = args || []
  8. const runner = spawn(command, args, {
  9. stdio: 'inherit' // (继承父进程的stdio输出) 输出 npm install 的信息
  10. })
  11. runner.on('close', function (code) {
  12. if (fn) {
  13. fn(code)
  14. }
  15. })
  16. }
  17. // 查找系统中用于安装依赖包的命令
  18. function findNpm () {
  19. const npms = ['lnpm', 'cnpm', 'npm']
  20. for (let i = 0; i < npms.length; i++) {
  21. try {
  22. // 查找环境变量下指定的可执行文件的第一个实例
  23. which.sync(npms[i])
  24. console.log('use npm: ' + npms[i])
  25. return npms[i]
  26. } catch (e) {
  27. }
  28. }
  29. throw new Error(chalk.red('please install lnpm'))
  30. }
  31. module.exports = function npmInstall (projectPath) {
  32. console.log('Installing packages...')
  33. // 将node工作目录更改成构建的项目根目录下
  34. process.chdir(projectPath)
  35. setTimeout(() => {
  36. const npm = findNpm()
  37. runCommand(which.sync(npm), ['install'], function () {
  38. console.log(npm + ' install end')
  39. })
  40. }, 1000)
  41. }

码字不易,点点小赞鼓励