脚手架核心开发流程

痛点分析

  • 创建项目组件时,存在大量重复代码拷贝,快速复用已有沉淀
  • 协同开发时,由于git操作不规范导致分支混乱,操作耗时,制定标准的操作规范,并集成到脚手架
  • 发布上线耗时,容易出现各种错误,制定标准的上线流程和规范并集成到脚手

需求分析

  • 通用到研发脚手架
  • 通用到项目/组件创建能力
    • 模版支持定制,定制后能够发布生效
    • 模版支持快速接入,极地到接入成本
  • 通用到项目/组件发布能力
    • 发布过程自动完成标准的git操作
    • 发布成功后自动删除开发分支并创建tag
    • 发布后自动完成云构建,CDN,域名绑定
    • 发布过程支持测试/正式两种模式

脚手架设计图

脚手架拆包策略

  • 核心流程
  • 命令
    • 初始化
    • 发布
  • 模型层
    • command命令
    • project项目
    • component组件
    • npm模型
    • git仓库
  • 支撑模块
    • git操作
    • 云构建
    • 工具方法
    • api请求
    • git api

脚手架拆包原则

  • 核型模块 core
  • 命令模块 command
  • 模型模块 models
  • 工具模块 utils

core模块技术方案

  • 准备阶段
  • 命令注册
  • 命令执行

命令执行流程

  • 准备阶段
  • 命令注册
  • 命令执行

    设计技术点

    核型库

  • import-local
  • commander

工具库

  • npmlogs
  • fs-extra
  • semver
  • colors
  • user-home
  • dotenv
  • root-check

开始写代码

  • 1.首先创建一个lerna项目: learn init intest-cli-dev
  • 2.根目录下创建coreutilsmodelscommands文件夹,并修改lerna.json配置如下:
    {
    "packages": [
      "core/*",
      "utils/*",
      "models/*",
      "commands/*"
    ],
    "version": "1.0.1"
    }
    
  • 3.将原项目packages下的core移入到创建的core目录下,同时将packages下的utils下,修改core项目为cli,如下图:

  • 4.在cli/bin/index.js代码如下:
#!/usr/bin/env node
const utils = require('intest-cli-dev-utils')
// console.log('111')
// console.log(utils)

const importLocal = require('import-local')
const npmlog = require('npmlog')
if(importLocal(__filename)) {
    npmlog.info('cli', '正在使用本地版本')
} else {
    require('../lib')(process.argv.slice(2))
}

import-local的作用当全局node_modules和本地node_modules中,存在相同的库,则优先加载本地node_modules中的库 npmlog打印工具

  • 5.检查版本号开发core/cli/lib/index.js下写入代码如下:

      const pkg = require('../package.json')
      function checkPkgVersion() {
          const { version } = pkg
          // log.notice('cli', version)
      }
    

此时我们可以获取到配置文件package.json的版本号,那么现在我们需要考虑require加载资源的方式

require可加载的资源文件有.js, .json, .node

  • 加载.js时需要我们必须在js文件中,使用module.exports=any或者exports.输出
  • 加载.json时会使用JSON.parse进行转译从而得到一个json对象
  • .node是一个c++插件

如果我们加一个txt文件,暂且命名为test.txt,其内容如下:

 module.exports = function test() {
     console.log('11')
 }

我们使用require('./test.txt')(),这个时候代码依然会执行,这是为什么呢?原来我们是使用require的时候,如果加载的文件不是上述三种格式的文件,会默认当成js文件进行解析

  • npmlog的使用
    • 1.使用lerna创建lerna create intest-cli-dev-log,项目会创建在core下,移入到utils下并进入当前目录,安装npmlognpm i npmlog -S,代码如下:
        'use strict';
        const log = require('npmlog')
        log.addLevel('success', 2000, { fg: 'green' })
        // 降级处理 log
        log.level = process.env.LOG_LEVEL || 'info'
        //c 添加前缀
        log.heading = 'intest' 
        module.exports = log
      
    • 2.如何在项目使用intest-cli-dev-log模块呢?
      • 首先在core/cli/package.json下,自动引入,注意file:../../utils/log是其相对路径目录,如下:
        {
        ...
        "dependencies": {
            ...
            "intest-cli-dev-log": "file:../../utils/log",
        }
        }
        
      • 然后在core/clinpm link,此时就可以在core/cli中使用了。
  • 6.检查Node版本 依然在core/cli/lib/index.js下创建方法checkNodeVersion方法,通过semver库(用于检测版本号的库),通过process.version获取node版本,以及代码中设置的最对版本进行判断,代码如下:

    const LOW_NODE_VERSION = '8.0.0'
    function checkNodeVersion() {
      // 1.获取当前node版本好
      const currentVersion = process.version
      // 比对最低版本
      const result = semver.gt(currentVersion, LOW_NODE_VERSION)
      // console.log(chalk.red(result))
      if (!result) {
          throw new Error(chalk.red('当前版本过低,请升级node版本。 ') + chalk.blue(`最低版本: ${LOW_NODE_VERSION}`))
      }
    }
    
  • 6.检查root启动 我们可以使用process.getuid()来获取uid,如果返回值是0则说明说超级管理员,否则是一般用户。开发中一般是使用一般用户进行开发,如果需要降级,可以使用root-check这个库进行开发,使用也很简单,代码如下:

    function checkRoot() {
      const rootCheck = require('root-check') // 使用之后可降级为用户权限
      rootCheck()
    }
    
  • 7.用户主目录检查功能开发

    const chalk = require('chalk)
    const pathExists = reuqire('path-exists').sync // 判读目录是否存在
    const userHome = require('user-home') // 获取当前用户主目录
    function checkUserHome() {
      if (!userHome || !pathExists(userHome)) {
          throw new Error(chalk.red('当前登陆用户主目录不存在!'))
      }
    }
    
  • 8.入参数检查和debug模式开发 入口参数就是我们在终端上输入的指令例如:vue create project,我们可以得到createproject参数

我们如何得到入口参数呢?这里我们使用minimist这个库来获取入口参数,代码如下:

const args = require('minimist')(process.argv.slice(2))
const log = require('intest-cli-dev-log')
function checkInputArgs() {
    checkArgs()
}
// 设置debug
function checkArgs() {
    if(args.debug) {
        process.env.LOG_LEVEL = 'verbose'
    } else {
        process.env.LOG_LEVEL = 'info'
    }
    log.level = process.env.LOG_LEVEL
}
  • 9.环境变量检查功能开发和npm全局更新功能开发 我们可以使用dotenv进行读取.env的配置内容,具体方法可以参考dotenv官方文档 具体代码如下:

    const dotEnv = require('dotenv)
    function checkEnv() {
      const dotEnvPath = path.resolve(userHome, '.env')// 获取本地.env文件内容
    
      if (pathExists(dotEnvPath)) {
          config = dotEnv.config({
              path: dotEnvPath
          })
      } else {
          log.error('找不到.env')
      }
      createDefaultConfig() // 设置默认配置
    }
    // 设置默认的环境变量
    function createDefaultConfig() {
      const cliConfig = {
          home: userHome,
    
      }
      if(process.env.CLI_HOME) {
          cliConfig['cli_home'] = path.join(userHome, process.env.CLI_HOME)
      } else {
          cliConfig['cli_home'] = path.join(userHome, DEFAULT_CLI_HOME)
      }
      // console.log(cliConfig)
      process.env['CLI_HOME_PATH'] = cliConfig['cli_home']
    }
    
  • 9.通用npm API封装 实现这个功能需要如下步骤:

    • 1.获取当前版本号和模块名
    • 2.调用npm API获取说有的版本号和模块名
    • 3.提取所有版本版本号,比对那些版本号是大于当前版本号的

      • 使用lerna创建get-npm-info模块 lerna create intest-cli-dev-get-npm-info,并移入到utils/目录下
      • core/cli/package.json下配置 如下,并进行软连接 npm link
          {
          ...
          "dependencies": {
              ...
              "intest-cli-dev-log": "file:../../utils/log",
              "get-npm-info": "file:../../utils/get-npm-info",
          }
          }
        
      • get-npm-info模块下lib/index.js写入如下代码:
      'use strict';
      const axios = require('axios')
      const urlJoin = require('url-join')
      const semver = require('semver')
      
      function getNpmInfo(npmName, registry) {
          // TODO
          if (!npmName) return
          const registryUrl = registry || getDefaultRegistry()
          const res = urlJoin(registryUrl, npmName)
          return axios.get(res).then(resp => {
              if (resp.status === 200) {
                  return resp.data
              } else {
                  return null
              }
          })
              .catch(err => {
                  return Promise.reject(err)
              })
      }
      
      function getDefaultRegistry(isOrigin = false) {
          return isOrigin ? 'http://registry.npmjs.org/' : 'http://registry.npm.taobao.org/'
      }
      
      async function getNpmVersion(npmName, registry) {
          const data = await getNpmInfo(npmName, registry);
          if (data) {
              // console.log(Object.keys(data.versions))
              const r = Object.keys(data.versions)
              return r
          } else {
              return []
          }
      }
      
      function getSemverVersion(baseVersion, versionList) {
          return versionList
          .filter(version => semver.satisfies(version,`^${baseVersion}`))
          .sort((a,b)=>semver.gt(b,a))
      }
      
      async function getNpmSemverVersion(baseVersion, npmName, registry) {
          const versionList = await getNpmVersion(npmName, registry)
          const newVersions = getSemverVersion(baseVersion, versionList)
          if(newVersions && newVersions.length) return newVersions[0]
          return 
      }
      
      async function getNpmLatest(npmName, registry) {
          const versions = await getNpmVersion(npmName, registry)
          if(versions) {
          return versions.sort((a,b)=>semver.gt(a,b))[0]
          }
          return null
      }
      
      module.exports = {
          getNpmInfo,
          getNpmVersion,
          getNpmSemverVersion,
          getDefaultRegistry,
          getNpmLatest
      };
      
    • 4.获取最新的版本号,提示用户更新到该版本号

    core/cli/lib/index.js下写入如下代码:

    const { getNpmSemverVersion } = require('get-npm-info')
    async function checkGlobalUpdate() {
      // 1.获取当前版本号和模块名
      const { version, name } = pkg
      // 2.调用npm API获取说有的版本号和模块名
      const lastVersion = await getNpmSemverVersion(version, name)
      // 3.提取所有版本版本号,比对那些版本号是大于当前版本号的
      // 4.获取最新的版本号,提示用户更新到该版本号
      if(lastVersion && semver.gt(lastVersion,version)) log.warn(chalk.yellow(`请手动更新${name} 当前版本:${version} 最新版本:${lastVersion}`))
    }
    

commander到使用

快速实现一个commander脚手架,代码如下:

function registryCommand() {
    program
    .name(Object.keys(pkg.bin)[0])
    .usage('<command> [options]')
    .version(pkg.version)
    .option('-d, --debug', '是否开启调试模式', false)
    .option('-tp, --targerPath <targetPath>', '是否指定本地调试文件路径', '')

    program
    .command('init [projectName]')
    .option('-f,--force', '是否强制初始化项目')
    .action(exec)

    // 开启debug模式
    program.on('option:debug', function(){
        if (program.debug) {
            process.env.LOG_LEVEL = 'verbose'
        } else {
            process.env.LOG_LEVEL = 'info'
        }
        log.level = process.env.LOG_LEVEL
        log.verbose('test')
    })

    // 监听targetPath,并设置环境变量
    program.on('option:targerPath', function() {
        // console.log(this.targerPath)
        process.env.CLI_TARGET_PATH = this.targerPath
    })

    // 对未知命令进行监听
    program.on('command:*', function(obj) {
        // 获取所有注册的命令
        const allRegistryCommand = program.commands.map(cmd=>cmd.name())
        console.log(chalk.red('未知命令:' + obj[0]))
    })

    program.parse(process.argv)
    // 后面调用
    // console.log(program.args)
    if(program.args && !program.args.length){
        program.outputHelp()
    }
}
Copyright © imooc-lego (2020 - present) all right reserved,powered by GitbookFile Modify: 2021-06-27 08:04:56

results matching ""

    No results matching ""