1 脚手架命令动态加载功能架构设计
1.1 指定本地调试文件路径 targetPath
- core>cli>bin>index.js
function registerCommand() {
program
.name(Object.keys(pkg.bin)[0])
.usage('<command> [options]')
.version(pkg.version)
.option('-d, --debug', '是否开启调试模式', false)
.option('-tp, --targetPath <targetPath>', '是否指定本地调试文件路径', '')
program
.command('init [projectName]')
.option('-f, --force', '是否强制初始化项目')
.action(exec)
// 指定targetPath
program.on('option:targetPath', function() {
process.env.CLI_TARGET_PATH = program._optionValues.targetPath
})
program.parse(process.argv)
if(program.args && program.args.length < 1) {
program.outputHelp()
}
}
- commands>init>lib>index.js
function init(projectName, cmdObj) {
console.log('init', projectName, cmdObj.force, process.env.CLI_TARGET_PATH);
}
1.2 动态执行库 exec 模块创建
lerna create exec
function exec() {
console.log('targetPath', targetPath)
console.log('homePath', homePath)
}
module.exports = exec;
1.3 创建 npm 模块通用类 Package
- models>package>lib>index.js
'use strict';
class Package {
constructor(options) {
}
}
module.exports = Package;
- core>exec>lib>index.js
const Package = require('@zmoon-cli-dev/package')
const log = require('@zmoon-cli-dev/log')
function exec() {
const targetPath = process.env.CLI_TARGET_PATH
const homePath = process.env.CLI_HOME_PATH
log.verbose('targetPath', targetPath)
log.verbose('homePath', homePath)
const pkg = new Package({
targetPath
})
console.log(pkg);
}
module.exports = exec;
1.4 Package 类的属性、方法定义及构造函数
1. Package 类的属性、方法定义
class Package {
constructor(options) {
// package的路径
this.targetPath = options.targetPath
// package的存储路径
this.storePath = options.storePath
// package的name
this.packageName = options.name
// package的version
this.packageVersion = options.version
}
// 判断当前Package是否存在
exists() {}
// 安装Package
install() {}
// 更新Package
update() {}
// 获取入口文件的路径
getRootFilePath() {}
}
2. Package 构造函数逻辑开发
- models>package>lib>index.js
const { isObject } = require('@zmoon-cli-dev/utils')
class Package {
constructor(options) {
if(!options) {
throw new Error('Package类的options参数不能为空!')
}
if(!isObject(options)) {
throw new Error('Package类的options参数必须为对象!')
}
// package的路径
this.targetPath = options.targetPath
// package的存储路径
this.storePath = options.storePath
// package的name
this.packageName = options.packageName
// package的version
this.packageVersion = options.packageVersion
}
}
- utils>utils>lib>index.js
function isObject(o) {
return Object.prototype.toString.call(o) === '[object Object]'
}
- core>exec>lib>index.js
const SETTINGS = {
init: '@zmoon-cli-dev/init'
}
function exec() {
const targetPath = process.env.CLI_TARGET_PATH
const homePath = process.env.CLI_HOME_PATH
const cmdObj = arguments[arguments.length-1]
const cmdName = cmdObj.name()
const packageName = SETTINGS[cmdName]
const packageVersion = 'latest'
const pkg = new Package({
targetPath,
packageName,
packageVersion
})
}
1.5 Package 类获取文件入口路径
pkg-dir 应用+解决不同操作系统路径兼容问题
1. 获取package.json所在目录 -- pkg-dir
const pkgDir = require('pkg-dir').sync
getRootFilePath() {
const dir = pkgDir(this.targetPath)
}
2. 读取package.json -- require() js/json/node
const pkgFile = require(path.resolve(dir, 'package.json'))
3. 寻找main/lib -- path
if(pkgFile && pkgFile.main) {}
4. 路径的兼容(macOS/windows)
if(pkgFile && pkgFile.main) {
return formatPath(path.resolve(dir, pkgFile.main))
}
- utils>format-path>lib>index.js
const path = require('path')
function formatPath(p) {
if(p && typeof p === 'string') {
// 分隔符
const sep = path.sep
if(sep === '/') {
return p.replace(/\//g, '\\')
} else {
return p
}
}
return p
}
1.6 利用 npminstall 库安装 npm 模块
- models>package>lib>index.js
const npminstall = require('npminstall')
const { getDefaultRegistry } = require('@zmoon-cli-dev/get-npm-info')
// 安装Package
install() {
return npminstall({
root: this.targetPath, // 模块路径
storeDir: this.storeDir,
registry: getDefaultRegistry(),
pkgs: [{
name: this.packageName,
version: this.packageVersion
}]
})
}
- utils>get-npm-info>lib>index.js
function getDefaultRegistry(isOriginal = false) {
return isOriginal ? 'https://registry.npmjs.org' : 'https://registry.npm.taobao.org'
}
- core>exec>lib>index.js
const SETTINGS = {
init: '@zmoon-cli-dev/init'
}
const CACHE_DIR = 'dependencies'
function exec() {
let targetPath = process.env.CLI_TARGET_PATH
const homePath = process.env.CLI_HOME_PATH
let storeDir = ''
let pkg
const cmdObj = arguments[arguments.length-1]
const cmdName = cmdObj.name()
const packageName = SETTINGS[cmdName]
const packageVersion = 'latest'
if(!targetPath) {
targetPath = path.resolve(homePath, CACHE_DIR) // 生成缓存路径
storeDir = path.resolve(targetPath, 'node_modules')
pkg = new Package({
targetPath,
storeDir,
packageName,
packageVersion
})
if(pkg.exists()) {
// 更新Package
} else {
// 安装Package
await pkg.install()
}
} else {
pkg = new Package({
targetPath,
packageName,
packageVersion
})
}
const rootFile = pkg.getRootFilePath()
if(rootFile) {
require(rootFile).apply(null, arguments) // [] -> 参数列表
}
}
1.7 Package 类判断模块是否存在
- models>package>lib>index.js
// package的缓存目录前缀
// this.cacheFilePathPrefix = this.packageName.replace('/', '_')
async exists() {
if(this.storeDir) {
await this.prepare()
return pathExists(this.cacheFilePath)
} else {
return pathExists(this.targetPath)
}
}
async prepare() {
if(this.packageVersion === 'latest') {
this.packageVersion = await getNpmLatestVersion(this.packageName)
}
}
// @imooc-cli/init 1.1.2 -> _@[email protected]@@imooc-cli
get cacheFilePath() {
return path.resolve(this.storeDir, `_${this.cacheFilePathPrefix}@${this.packageVersion}@${this.packageName}`)
}
- utils>get-npm-info>lib>index.js
async function getNpmLatestVersion(npmName, registry) {
const version = await getNpmVersions(npmName, registry)
if(version) {
// return version.sort((a, b) => semver.gt(b, a))[0]
return version[version.length-1]
}
return null
}
1.8 Package 类更新模块
- models>package>lib>index.js
// 最新版本存在则不需要更新
async update() {
await this.prepare()
// 1. 获取最新的npm模块版本号
const latestPackageVersion = await getNpmLatestVersion(this.packageName)
// 2. 查询最新版本号对应的路径是否存在
const latestFilePath = this.getSpecificCacheFilePath(latestPackageVersion)
// 3. 如果不存在,则直接安装最新版本
if(!pathExists(latestFilePath)) {
return npminstall({
root: this.targetPath, // 模块路径
storeDir: this.storeDir,
registry: getDefaultRegistry(),
pkgs: [{
name: this.packageName,
version: latestPackageVersion
}]
})
}
return latestFilePath
}
async prepare() {
if(this.storeDir && !pathExists(this.storeDir)) {
fse.mkdirSync(this.storeDir) // 将当前路径不存在的文件都创建
}
if(this.packageVersion === 'latest') {
this.packageVersion = await getNpmLatestVersion(this.packageName)
}
}
getSpecificCacheFilePath(packageVersion) {
return path.resolve(this.storeDir, `_${this.cacheFilePathPrefix}@${packageVersion}@${this.packageName}`)
}
1.9 Package 类获取缓存模块入口文件功能改造
- models>package>lib>index.js
// 获取入口文件的路径
getRootFilePath() {
function _getRootFile(targetPath) {
// 1. 获取package.json所在目录 -- pkg-dir
const dir = pkgDir(targetPath)
if(dir) {
// 2. 读取package.json -- require() js/json/node
const pkgFile = require(path.resolve(dir, 'package.json'))
// 3. 寻找main/lib -- path
if(pkgFile && pkgFile.main) {
// 4. 路径的兼容(macOS/windows)
return formatPath(path.resolve(dir, pkgFile.main))
}
}
return null
}
if(this.storeDir) {
return _getRootFile(this.cacheFilePath)
} else {
return _getRootFile(this.targetPath)
}
}
2 通用脚手架命令 Command 类封装
2.1 commander 脚手架初始化
- models>command>lib>index.js
class Command {
constructor() {}
init() {} // 准备阶段
exec() {} // 执行阶段
}
module.exports = Command
2.2 动态加载 initCommand + new initCommand
- commands>init>lib>index.js
const Command = require('@zmoon-cli-dev/command')
class InitCommand extends Command {}
function init() {
return new InitCommand()
}
module.exports = init
module.exports.InitCommand = InitCommand
2.3 Command constructor
- commands>init>lib>index.js
const Command = require('@zmoon-cli-dev/command')
class InitCommand extends Command {}
function init(argv) {
return new InitCommand(argv)
}
module.exports = init
module.exports.InitCommand = InitCommand
- models>command>lib>index.js
class Command {
constructor(argv) {
console.log('Commond constructor', argv);
this._argv = argv
let runner = new Promise((resolve, reject) => {
let chain = Promise.resolve()
chain = chain.then(() => {})
})
}
init() {
throw new Error('init 必须实现')
}
exec() {
throw new Error('init 必须实现')
}
}
2.4 命令的准备阶段 -- 检查 node 版本
- models>command>lib>index.js
const semver = require('semver')
const colors = require('colors/safe')
const log = require('@zmoon-cli-dev/log')
const LOWEST_NODE_VERSION = '12.0.0'
class Command {
constructor(argv) {
this._argv = argv
let runner = new Promise((resolve, reject) => {
let chain = Promise.resolve()
chain = chain.then(() => this.checkNodeVersion())
chain.catch(err => {
log.error(err.message);
})
})
}
// 检查node版本
checkNodeVersion() {
// 1. 获取当前 node 版本号
const currentVersion = process.version
// 2. 比对最低版本号
const lowestNodeVersion = LOWEST_NODE_VERSION
if (!semver.gte(currentVersion, lowestNodeVersion)) {
throw new Error(colors.red(`imooc-cli 需要安装v${lowestNodeVersion}以上版本的node.js`))
}
}
}
2.5 命令的准备阶段 -- 参数初始化
通过异步执行的命令都需要单独进行错误捕获
即每次新建promise
都需要有单独的catch
- models>command>lib>index.js
class Command {
constructor(argv) {
// console.log('Commond constructor', argv);
if(!argv) {
throw new Error('参数不能为空!')
}
if(!Array.isArray(argv)) {
throw new Error('参数必须为数组!')
}
if(argv.length < 1) {
throw new Error('参数列表为空!')
}
this._argv = argv
let runner = new Promise((resolve, reject) => {
let chain = Promise.resolve()
chain = chain.then(() => this.checkNodeVersion())
chain = chain.then(() => this.initArgs())
chain = chain.then(() => this.init())
chain = chain.then(() => this.exec())
chain.catch(err => {
log.error(err.message);
})
})
}
initArgs() {
this.cmd = this._argv[this._argv.length-1]
this._argv = this._argv.slice(0, this._argv.length-1)
}
// ...
init() {
throw new Error('init 必须实现!')
}
exec() {
throw new Error('exec 必须实现!')
}
}
- commands>init>lib>index.js
class InitCommand extends Command {
init() {
this.projectName = this._argv[0] || ''
this.force = !!this.cmd._optionValues.force
log.verbose('projectName', this.projectName);
log.verbose('force', this.force);
}
exec() {}
}
标签:const,Package,--,argv,js,init,Commond,targetPath
From: https://www.cnblogs.com/pleaseAnswer/p/16925191.html