持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情
node前置知识
require能加载什么类型的文件
require(.js/.json/.node)
- 如果是js文件,就用module.exports 或者exports导出
- 如果是json文件,就用JSON.parse解析成一个对象返回
- 如果是node文件,说明是一个c++的插件
如果不是js/json/node等文件,都会当成js文件来解析,比如一个file.txt,里面的js代码,当require的时候,也能正常解析,如果file.txt里面不是js代码就会报错。
也就是如果后缀不是.js/.json/.node的文件,但是里面是js代码,也能被正常解析和执行。
核心流程
脚手架在运行之前需要做很多检查,如下图:
打印功能
在脚手架运行过程中,在命令行上需要打印很多信息方便用户查看,如果用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')
检查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的可能性更少,同时节约开发时间。
- 功能划分清楚,一个函数只做一个功能,代码非常清晰