前端脚手架开发之准备阶段

148 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

node前置知识

require能加载什么类型的文件

require(.js/.json/.node)
  1. 如果是js文件,就用module.exports 或者exports导出
  2. 如果是json文件,就用JSON.parse解析成一个对象返回
  3. 如果是node文件,说明是一个c++的插件

如果不是js/json/node等文件,都会当成js文件来解析,比如一个file.txt,里面的js代码,当require的时候,也能正常解析,如果file.txt里面不是js代码就会报错。

也就是如果后缀不是.js/.json/.node的文件,但是里面是js代码,也能被正常解析和执行。

核心流程

脚手架在运行之前需要做很多检查,如下图:

image.png

打印功能

在脚手架运行过程中,在命令行上需要打印很多信息方便用户查看,如果用console.log是完全不行的,因此需要封装一个log包。

创建log包

// 在utils目录下创建log包
npx lerna create @yijiang-cli-dev/log  utils

首先安装npmlog

// 安装npmlog库
const log = require('npmlog')

// 修改日志显示等级
log.level = process.env.LOG_LEVEL ? process.env.LOG_LEVEL : 'info'
// 增加打印日志命令,使用时直接log.success()
log.addLevel('success', 2000, { fg: 'green', bold: true })

// 设置前缀
log.heading = 'yijing'
// 前缀的样式
log.headingStyle = { fg: 'red', bg: 'white' }

module.exports = log

使用log包:

const log = require('@yijiang-cli-dev/log')

image.png

检查node版本号

检查node版本号是因为脚手架可能用到了新版本node的API,所以使用之前要先指定node版本号。

const pkg = require('../package.json')
const semver = require('semver')
const colors = require('colors/safe')
const LOWEST_NODE_VERSION = '12.0.0'

// 检查脚手架的版本号
function checkPkgVersion() {
  // 使用log方法打印到命令行
  log.info('cli', pkg.version)
}

// 检查node的版本号
function checkNodeVersion() {
  // 因为脚手架执行时就是一个node进程,所以可以从process拿到node版本号
  const currentVersion = process.version
  const lowestVersion = LOWEST_NODE_VERSION
  // 通过semver这个库来比对版本号的大小
  if (!semver.gte(currentVersion, lowestVersion)) {
    throw new Error(
      // 使用colors库来设置打印信息的颜色 
      colors.red(`yijiang-cli-dev需要使用${lowestVersion}版本的nodejs`)
    )
  }
}

检查root启动

检查是否是root用户是非常必要的,如果你是root用户利用脚手架创建了工程,那么其他用户就没有读写权限,所以如果是root用户需要给它降级

在mac电脑下,root用户的uid是0,一般用户是501,通过process.geteuid()可以获取,通过process.seteuid()进行降级。

这里使用root-check第三方库帮我们检测是否是root用户,如果是就帮我们降级。

function checkRoot() {
  // 这个库只在这个函数使用,所以在这里写就可以了
  const rootCheck = require('root-check')
  rootCheck()
}

检查用户主目录

管理员用户目录是/root,普通用户被创建后,一登录的就是用户主目录,通常是/home/用户名

检查用户主目录这里使用库user-home

const userHome = require('user-home')
const pathExists = require('path-exists').sync

function checkUserHome() {
  if (!userHome || !pathExists(userHome)) {
    throw new Error(colors.red('当前用户主目录不存在'))
  }
}

目前node已经提供了原生方法获取用户主目录:

import {homedir} from os

检查入参

当用户输入test-cli --debug时,表示用户要开启debug模式,此时我们需要对打印日志做一些设置,把打印日志显示出来,一般没有输入--debug时有些日志是不会显示出来的。

// 这个库用来解析用户输入的options
const minimist = require('minimist')

function checkInputArgs() {
  args = minimist(process.argv.slice(2))
  checkArgs()
}

function checkArgs() {
  if (args.debug) {
    // 在环境变量中设置log的级别
    process.env.LOG_LEVEL = 'verbose'
  } else {
    process.env.LOG_LEVEL = 'info'
  }
  // 设置level的级别
  log.level = process.env.LOG_LEVEL
}

检查环境变量

所谓环境变量就是一个全局变量,不管你在哪个目录下打开,都能获取到这个值。

对于某些敏感信息如密码,一般是不能放在代码中的,而是写在某个文件中读取,然后写入到环境变量中,这样就能获取到密码等信息。

const DEFAULT_CLI_CONFIG = '.yijiang'

function checkEnv() {
  // 使用dotenv这个库来文件中的信息,并设置到process.env中
  const dotenv = require('dotenv')
  // 用户主目录下如果存在.env文件
  const dotenvPath = path.resolve(userHome, '.env')
  if (pathExists(dotenvPath)) {
    // 它会把.env文件里面的变量设置到process.env中
    dotenv.config({
      path: dotenvPath
    })
  }
  // 如果用户没有环境变量文件.env,那么设置一个默认的环境变量
  createDefaultConfig()
}

function createDefaultConfig() {
  const cliConfig = {
    home: userHome
  }
  // 如果本地存在.env文件,且里面有CLI_HOME这个属性
  if (process.env.CLI_HOME) {
    cliConfig['cliHome'] = path.join(cliConfig.home, process.env.CLI_HOME)
  } else {
    cliConfig['cliHome'] = path.join(
      cliConfig.home,
      DEFAULT_CLI_CONFIG
    )
  }
  // cliConfig.cliHome这个目录作为缓存主目录
  process.env.CLI_HOME_PATH = cliConfig['cliHome']
}

最后process.env.CLI_HOME_PATH的值为/Users/用户/.yijiang,这个目录以后会作为缓存目录使用。

检查是否是最新版本

关于版本的信息较多,专门设置一个get-npm-info包:

npx lerna create @***/get-npm-info utils

1. 获取npm上脚手架的信息

// axios即可用于web端,也可以用于node端
const axios = require('axios')
// 用于拼接url
const urlJoin = require('url-join')
// 版本信息的操作
const semver = require('semver')


function getNpmInfo(npmName, registry) {
  if (!npmName) {
    return null
  }
  const registryUrl = registry || getDefaultRegistry()
  const npmInfoUrl = urlJoin(registryUrl, npmName)
  return axios
    .get(npmInfoUrl)
    .then(res => {
      if (res.status === 200) {
        return res.data
      } else {
        return null
      }
    })
    .catch(err => {
      throw new Error(err)
    })
}

function getDefaultRegistry(isOriginal = false) {
  // 默认是从淘宝源的仓库获取
  return isOriginal
    ? 'https://registry.npmjs.org'
    : 'https://registry.npmmirror.com'
}

2. 获取版本信息

// 全部采用async/await的写法
async function getNpmVersions(npmName, registry) {
  const data = await getNpmInfo(npmName, registry)
  if (data) {
    return Object.keys(data.versions)
  } else {
    return []
  }
}

3. 获取比当前脚手架版本大的版本信息

function getSemverVersions(baseVersion, versions) {
  return versions
    .filter(version =>
      // semver.satisfies函数表示获取大于^1.0.4的版本,^有大于等于的意思
      semver.satisfies(version, `^${baseVersion}`)
    )
    // 有可能从npm上获取的版本不是排序之后的
    .sort((a, b) => {
      if (semver.gt(b, a)) {
        return 1
      } else {
        return -1
      }
    })
}

4. 获取最新版本

async function getNpmSemverVersions(baseVersion, npmName, registry) {
  const versions = await getNpmVersions(npmName, registry)
  const newVersions = getSemverVersions(baseVersion, versions)
  if (newVersions && newVersions.length > 0) {
    return newVersions[0]
  }
}

5. 比对是否要更新

async function checkGlobalUpdate() {
  const currentVersion = pkg.version
  const npmName = pkg.name
  const { getNpmSemverVersions } = require('@yijiang-cli-dev/get-npm-info')
  const lastVersion = await getNpmSemverVersions(currentVersion, npmName)
  if (lastVersion && semver.gt(lastVersion, currentVersion)) {
    log.warn(
      colors.yellow(
        '更新提示',
        `请手动更新${npmName},当前版本是${currentVersion},最新版本是${lastVersion},更新命令:npm install -g ${npmName}`
      )
    )
  }
}

总结

从上面的代码可以看出需要学习的几点:

  • 多使用第三方库,优秀的第三方库经过多人使用,出bug的可能性更少,同时节约开发时间。
  • 功能划分清楚,一个函数只做一个功能,代码非常清晰