【源码解析】taro-cli(1)- 创建、诊断、info、更新

1,202 阅读5分钟

taro-cli解析

前言

taro 脚手架分别有:

  • init // 初始化项目
  • create // 创建页面
  • doctor // 诊断项目
  • info // 查看系统信息和版本信息等
  • update // 更新依赖
  • convert // 小程序转taro
  • build // 编译taro项目

本章主要讲解前面五个的功能和实现,而buildconvert则会放在taro-cli原理系列的二和三,敬请期待~ 由于 官方也在维护,代码可能会更新,因此文中的代码难免会与最新源码有出入, 本文所解析taro-cliversion1.3.21,未来如果某些功能的分析文章与源码如果出入过大,则会更新对应的文章。

cli目录

首先我们看一下taro-cli的目录,该工程位于taro的packages目录下。当我们安装taro-cli后,会生成taro 命令。

用到的核心库
  • tj/commander.js Node.js - 命令行接口全面的解决方案,灵感来自于 Ruby's commander。可以自动的解析命令和参数,合并多选项,处理短参等等,功能强大,上手简单。
  • jprichardson/node-fs-extra - 在 Node.js 的 fs 基础上增加了一些新的方法,更好用,还可以拷贝模板。
  • chalk/chalk - 可以用于控制终端输出字符串的样式。
  • SBoudrias/Inquirer.js - Node.js 命令行交互工具,通用的命令行用户界面集合,可以和用户进行交互。
  • sindresorhus/ora - 实现加载中的状态是一个 Loading 加前面转起来的小圈圈,成功了是一个 Success 加前面一个小钩钩。
  • SBoudrias/mem-fs-editor - 提供一系列 API,方便操作模板文件。
  • shelljs/shelljs - ShellJS 是 Node.js 扩展,用于实现 Unix shell 命令执行。
  • Node.js child_process - 模块用于新建子进程。子进程的运行结果储存在系统缓存之中(最大 200KB),等到子进程运行结束以后,主进程再用回调函数读取子进程的运行结果。

如何在命令行中使用Taro

当我们在命令行中输入Taro时,会有以下的输出:

�👽 Taro v1.2.13

Usage: taro <command> [options]                                                                                            ource\\todoList-Redux\\

Options:                                                                                                                   de
  -V, --version       output the version number
  -h, --help          output usage information

Commands:                                                                                                                   postinstall || echo \"
  init [projectName]  Init a project with default templete
  build               Build a project with options
  update              Update packages of taro
  convert             Convert weapp to taro
  info                Diagnostics Taro env info
  help [cmd]          display help for [cmd]

那么,像@vue/cli@wepy/cli等,是如何让我们可以直接在命令行中使用呢?
原因在于, @taro/taro-clipackage.json中的bin字段:

  "bin": {
    "taro": "bin/taro"
  }

上面代码指定,taro 命令对应的可执行文件为 bin 子目录下的 taro.js。Npm会寻找这个文件,在node_modules/.bin/目录下建立符号链接。在上面的例子中,taro.js会建立符号链接npm_modules/.bin/taro。由于node_modules/.bin/目录会在运行时加入系统的PATH变量,因此在运行npm时,就可以不带路径,直接通过命令来调用这些脚本。如果只有一个命令,也可以直接简写成string,前提是命令名称和模块名称一样

{ 
    "name": "taro",
    "version": "1.2.5",
    "bin": "bin/taro"
}

Taro Init

使用命令创建模板项目

taro init myApp
// npx @tarojs/cli init myApp

其中用到了commander这个库,可以自动的解析命令和参数,合并多选项,处理短参等等。让我们来看一下它在init命令中的用法:

// taro-cli/bin/taro-init
const program = require('commander')
program
  .option('--name [name]', '项目名称')
  .option('--description [description]', '项目介绍')
  .option('--typescript', '使用TypeScript')
  .option('--no-typescript', '不使用TypeScript')
  .option('--template-source [templateSource]', '项目模板源')
  .option('--clone [clone]', '拉取远程模板时使用git clone')
  .option('--template [template]', '项目模板')
  .option('--css [css]', 'CSS预处理器(sass/less/stylus/none)')
  .parse(process.argv)
const { template, templateSource, clone, description, name, css } = program;

const project = new Project({
  projectName,
  projectDir: process.cwd(),
  templateSource,
  clone,
  template,
  description,
  typescript,
  css
})

代码较短,相信大家看了也是一目了然。这里就不详细说明。 重点讲一下,new Projectproject.create()所做的事情。

// taro-cli/src/create/project.ts
class Project extends Creator {
    constructor (options: IProjectConf) {
        super(options.sourceRoot)
        this.conf = Object.assign({
            projectName: '',
            projectDir: '',
            template: '',
            description: ''
        }, options)
    }
}

new Project时,在构造函数中传进我们通过命令行输入的参数,比如 -- description。紧接着我们看一下create做了什么事情。

create () {
    this.fetchTemplates()
      .then((templateChoices: string[]) => this.ask(templateChoices))
      .then(answers => {
        const date = new Date()
        this.conf = Object.assign(this.conf, answers)
        this.conf.date = `${date.getFullYear()}-${(date.getMonth() + 1)}-${date.getDate()}`
        this.write()
      })
      .catch(err => console.log(chalk.red('创建项目失败: ', err)))
  }
  1. 获取模板。先从taroconfig中查看是否有模板的地址(templateSource),如果没有的话则使用默认的模板组,并通过git clone的方式下载(这里是使用了download-git-repo这个库)

1.3.9 开始 Taro 会在用户根目录下创建 .taro 文件夹,其中 .taro/index.json 用于存放 CLI 相关配置。 开发者可以使用 taro config 命令对配置项进行一系列操作。说明

  1. ask(询问),即是通过inquirer模块,创造命令行交互,确定(项目名,描述,是否用ts, css预处理器,模板)
  2. 确定所需的选项后,使用mem-fs-editorcopyTpl拷贝模板,模板使用ejs,并将所需要的选项传递进去。比如修改package.json中的name和description。

4. 在创建完项目后,是使用child_process来执行git init初始化仓库和npm install来安装依赖(当然,也可能是使用yarn源或者cnpm来安装),以下是判断使用哪个registry的代码

if (shouldUseYarn) {
    command = 'yarn install'
  } else if (helper.shouldUseCnpm()) {
    command = 'cnpm install'
  } else {
    command = 'npm install'
  }
export function shouldUseYarn (): boolean {
  try {
    execSync('yarn --version', { stdio: 'ignore' })
    return true
  } catch (e) {
    return false
  }
}

Taro create

快速创建新页面

Taro create --name [页面名称]

能够在当前项目的pages目录下快速生成新的页面文件,并填充基础代码,是一个提高开发效率的利器。

create的过程与init类似,这里就不做过多的阐述.

Taro doctor

在taro中,提供了5个Validator,分别是:

  • configValidator,
  • packageValidator,
  • recommandValidator,
  • eslintValidator,
  • abilityXMLValidator (这个是用来检查原子化服务规范) 下面,我们分别讲讲各个validator的功能与实现:

1. configValidator

使用joi(The most powerful data validation library for JS),对config目录下的配置做校验。

/**
 *projectConfig: config文件夹下的配置
 *configSchema: joi配置
 */
export default async function ({ configPath, projectConfig }) {
  const { error } = Joi.validate(projectConfig, configSchema, { abortEarly: false })
  return buildReport(configPath, error)
}

这里我们也看一下JOI的简单用法。以下两个字段示例,定义了类型,以及是否必填

const schema = Joi.object().keys({
  'projectName': Joi.string().required(),
  'date': Joi.date()
})

2. packageValidator

依赖检查主要有三个方面:

  • 包是否已经安装
  • 检测是否有新版本
  • 检测cli版本是否一致
  errorLines = _.concat(errorLines, pkgsNotInstalled(pkgs))
  errorLines = _.concat(errorLines, taroShouldUpdate(taroPkgs))
  errorLines = _.concat(errorLines, taroCliVersionNotMatch(taroPkgs))

3. recommandValidator

recommandValidator 检查的是一些常见的推荐内容。主要有:

  • .editconfig配置
  • .gitignore 配置
  • Readme
  • ESLint
  • 测试依赖

前三项文件配置的检查,是通过fs.readdirSync('./')获取项目文件,并匹配。而eslint和测试的检查,则是通过检查是否有安装了相关的依赖包。

4. eslintValidator

检查 ESLint,该Validator是对代码做了一遍eslint的检查。使用eslint的 CLIEngine模块

import { CLIEngine } from 'eslint'
const eslintCli = new CLIEngine({
    cwd: process.cwd(),
    useEslintrc: false,
    configFile
})
...
const report = eslintCli.executeOnFiles([sourceFiles])
const formatter = eslintCli.getFormatter()

5. abilityXMLValidator

这个validator是用于对华为原子服务定义文件的校验。快应用这一块小编并不了解,想详细学习的可以去developer.huawei.com看一下。
检查是否有project.quickapp.json文件,如果有的话则对ability.xml做检验。以下是abilityXMLValidator.ts中的注释,小编就直接CV过来啦~~~

Taro Info

Taro 提供了命令来一键检测 Taro 环境及依赖的版本等信息,方便大家查看项目的环境及依赖,排查环境问题。在提 issue 的时候,请附上 taro info 打印的信息,帮助开发人员快速定位问题。

以下是taro info的核心代码,主要是使用到了envinfo这个库

async function info (options) {
  const info = await envinfo.run(Object.assign({}, {
    System: ['OS', 'Shell'],
    Binaries: ['Node', 'Yarn', 'npm'],
    npmPackages,
    npmGlobalPackages: ['typescript']
  }, options), {
    title: `Taro CLI ${getPkgVersion()} environment info`
  })
  console.log(info)
}

envinfo.run这个方法接受需要的env Info的参数,返回具体的参数值。

Taro update

更新项目中 Taro 相关的依赖

taro update project

1. 获取 Taro 的最新版本

const getLatestVersion = require('latest-version')

2.更新项目 package.json 里面的 Taro 依赖信息到最新版本

// 写入package.json
  try {
    await fs.writeJson(pkgPath, packageMap, {spaces: '\t'})
    console.log(chalk.green('更新项目 package.json 成功!'))
    console.log()
  } catch (err) {
    console.error(err)
  }

3. 运行 npm install

  let command
  if (shouldUseYarn()) {
    command = 'yarn'
  } else if (shouldUseCnpm()) {
    command = 'cnpm install'
  } else {
    command = 'npm install'
  }

  const child = exec(command)

  const spinner = ora('即将将项目所有 Taro 相关依赖更新到最新版本...').start()

  child.stdout.on('data', function (data) {
    spinner.stop()
    console.log(data)
  })
  child.stderr.on('data', function (data) {
    spinner.stop()
    console.log(data)
  })

这里的exec使用了child_process模块,exce也提供了回调,因此,也可以这样子写:

  exec(command, (error, stdout, stderr) => {
    if (error) {
      console.log(error)
    } else {
      console.log(`${stderr}${stdout}`)
    }
  })

总结

关于taro-cli的实现:
我们借助nodejs,通过用户输入的命令和命令行与用户的交互,来创建文件创建页面项目诊断编译更新等等。其中,由commanderinquire模块来处理输入参数。
taro-cli的亮点,是多了一个doctor的东西,按照以往的经验,根据一个推荐化的配置来检查项目,减少我们的犯错机会,这是十分值得称赞的。这其中很多技术细节值得我们去深入挖掘~

下一篇预告: 【源码解析】taro-cli(2) - build