目录
三、脚手架核心流程开发
1、脚手架架构设计
* 脚手架核心框架:执行准备、命令注册、命令执行
- 本地缓存体系:本地文件&环境变量
* 标准git操作体系:仓库创建、开发模式、发布模式
- 代码仓库:github、gitee
* 初始化体系:项目初始化、组件初始化、初始化模式
- 模板库:项目模板、组件模板
* 发布体系:项目发布、组件发布、发布模式
* open api:项目或组件模板、静态资源查询、配置信息
- 数据体系:mysql、mongodb
* websocket服务:云构建、云发布、发布模式
- 资源体系:oss、cdn
- 远程缓存体系:redis、服务端缓存
2、脚手架框架代码拆包+importLocal
- 拆包
* 项目下新建四个目录:core、models、commands、utils
* core包改名cli,移动至core目录下。utils包移动至utils目录下
* lerna.json:{"package":["core/*","commands/*","models/*","utils/*"]}
或package.json:{"workspaces":["core/*","commands/*","models/*","utils/*"]}
- import-local
#!/usr/bin/env node
// npm i -S import-local(cli包)
const importLocal = require("import-local")
/**
* 1、importLocal(__filename)执行逻辑
* - 全局和本地分别安装命令包,__filename为执行文件路径,命令首先执行的是全局的命令包
* - 沿着__filename向上找,找不到package.json,则全局importLocal(__filename)为false
* - 找到package.json,package.json后面的路径为相对路径,并取出package.json的name,
* - 拼出本地执行文件路径:命令执行处向上有node_models的路径 + node_models + package.json的name + 相对路径
* - 判断(本地执行文件是否存在 并且 本地执行文件与__filename是否相等)。
* 存在且不相等,则全局会require(本地执行文件),且全局importLocal(__filename)为true。
* 否则,全局importLocal(__filename)为false
* - 本地执行文件的importLocal逻辑与全局一致
*/
if (importLocal(__filename)) {
require("npmlog").info("cli", "正在使用linding-cli本地版本")
} else {
require("../lib")(process.argv.slice(2))
}
3、检查项目版本号+npmlog
- core
'use strict';
module.exports = core;
/**
* 1、require加载文件(.js/.json/.node)
* - .js:module.exports/exports
* - .json:JSON.parse
* - .node:process.dlopen
* - 其他:.js
*/
const pkg = require("../package.json")
const log = require("@linding-cli/log")
function core() {
checkPkgVersion()
}
function checkPkgVersion() {
log.success("cli", pkg.version)
}
- npmlog
'use strict';
/**
* 1、log包
* - lerna create log(项目下)
* - npm i -D npmlog(log包)
* - lerna clean(项目下)
* - lerna bootstrap(项目下)
* - npm i(core包)
* - linding(测试)
* 2、自定义log
* - 参数1是方法名
* - 参数2是等级,等级高于log.level都会打印
* - 用法:log.success("cli", pkg.version)
*/
const log = require("npmlog")
log.level = process.env.LOG_LEVEL || 'info'
log.heading = "linding"
// log.headingStyle = {fg: 'red', bg: 'black'}
log.addLevel("success", 2000, {fg: 'green', bold: true})
module.exports = log;
4、检查node版本号
'use strict';
module.exports = core;
/**
* 1、semver版本号比对
* - 安装:npm i -S semver(cli包)
* 2、colors
* - 安装:npm i -S colors(cli包)
*/
const semver = require("semver")
const colors = require("colors")
const log = require("@linding-cli/log")
const LOWEST_NODE_VERSION = "15.0.0"
function core() {
try {
checkNodeVersion()
} catch (e) {
log.error(e.message)
}
}
function checkNodeVersion() {
const currentVersion = process.version
if (semver.gte(LOWEST_NODE_VERSION, currentVersion)) {
throw new Error(colors.yellow(`linding-cli需要安装v${LOWEST_NODE_VERSION}以上版本的node.js`))
}
}
5、检查root启动
'use strict';
module.exports = core;
function core() {
checkRoot()
}
/**
* 1、root账号启动检查和自动降级功能开发
* - 因为使用root账号执行脚手架命令创建的文件,其他账号可能就没有操作权限
* - 所以在使用脚手架命令之前需要进行降级
*/
function checkRoot() {
// console.log(process.geteuid()) // windows平台不可用
// npm i -S root-check(cli包)
const rootCheck = require("root-check")
rootCheck()
}
6、检查用户主目录
'use strict';
module.exports = core;
// npm i -S user-home(cli包)
const userHome = require("user-home")
// npm i -S path-exists(cli包)
const pathExists = require("path-exists").sync
const colors = require("colors")
function core() {
checkUserHome()
}
function checkUserHome() {
if (!userHome || !pathExists(userHome)) {
throw new Error(colors.red("当前登录用户主目录不存在"))
}
}
7、检查入参
'use strict';
module.exports = core;
const log = require("npmlog")
// npm i -S minimist(cli包)
const minimist = require("minimist");
function core() {
checkInputArgs()
log.verbose("打印verbose日志")
log.info("打印info日志")
}
function checkInputArgs() {
const args = minimist(process.argv.slice(2));
console.log(args)
checkArgs(args)
}
function checkArgs(args) {
// cmd测试:linding --debug
if (args.debug) {
process.env.LOG_LEVEL = "verbose"
} else {
process.env.LOG_LEVEL = "info"
}
log.level = process.env.LOG_LEVEL
}
8、检查环境变量
'use strict';
module.exports = core;
// npm i -S dotenv(cli包)
const dotenv = require("dotenv")
const path = require("path")
const userHome = require("user-home")
const pathExists = require("path-exists").sync
function core() {
checkEnv()
}
function checkEnv() {
const dotenvPath = path.resolve(userHome, ".env")
if (pathExists(dotenvPath)) {
// 会从.env文件读取key和value,并添加到process.env
dotenv.config({
path: dotenvPath
})
}
createDefaultConfig()
console.log(`环境变量:${process.env.CLI_HOME_PATH}`)
}
function createDefaultConfig() {
const cliConfig = {
home: userHome
}
if (process.env.CLI_HOME) {
cliConfig['cliHome'] = path.join(userHome, process.env.CLI_HOME)
} else {
cliConfig['cliHome'] = path.join(userHome, "")
}
process.env.CLI_HOME_PATH = cliConfig.cliHome
return cliConfig
}
9、检查是否为最新版本
- cli包
'use strict';
module.exports = core;
const semver = require("semver")
const colors = require("colors")
async function core() {
await checkGlobalUpdate()
}
async function checkGlobalUpdate() {
// 1、获取当前版本号和模块名
const pkg = require("../package.json")
const currentVersion = pkg.version
const npmName = pkg.name
// 2、调用npm api,获取所有版本号
const {getNpmSemverVersion} = require("@linding-cli/get-npm-info")
const lastVersion = await getNpmSemverVersion(currentVersion, npmName)
// 3、提取所有版本号,比对哪些版本号是大于当前版本号
// 4、获取最新的版本号,提示用户更新到该版本
if (lastVersion && semver.gt(lastVersion, currentVersion)) {
console.log(colors.yellow(`请手动更新:${npmName}
当前版本:${currentVersion}
最新版本:${lastVersion}
更新命令:npm i -g ${npmName}`))
}
}
- get-npm-info包
'use strict';
const axios = require("axios")
const semver = require("semver")
// npm i -S url-join(get-npm-info包)
const urlJoin = require("url-join")
module.exports = {
getNpmInfo,
getNpmVersions,
getNpmSemverVersion
};
function getNpmInfo(npmName, registry) {
if (!npmName) {
return null
}
const registryUrl = registry || getDefaultRegistry()
const npmInfoUrl = urlJoin(registryUrl, npmName)
return axios.get(npmInfoUrl).then(response => {
if (response.status === 200) {
return response.data
}
return null
}).catch(err => {
return Promise.reject(err)
})
}
function getDefaultRegistry(isOriginal = false) {
return isOriginal ? "https://registry.npmjs.org/" : "https://registry.npm.taobao.org/"
}
async function getNpmVersions(npmName, registry) {
const data = await getNpmInfo(npmName, registry)
if (data) {
return Object.keys(data.versions)
} else {
return []
}
}
function getSemverVersions(baseVersion, versions) {
return versions.filter(version => semver.satisfies(version, `^${baseVersion}`))
.sort((a, b) => semver.gt(b, a))
}
async function getNpmSemverVersion(baseVersion, npmName, registry) {
const versions = await getNpmVersions(npmName, registry)
const newVersions = getSemverVersions(baseVersion, versions)
if (newVersions && newVersions.length > 0) {
return newVersions[0]
}
return null
}
10、commander脚手架
'use strict';
module.exports = core;
function core() {
// npm i -S commander(cli包)
const commander = require("commander")
const pkg = require("../package.json")
// 获取commander的单例
// const {program} = commander
// 实例化一个command示例
const program = new commander.Command()
program
.name(Object.keys(pkg.bin)[0])
.usage("<command> [options]")
.version(pkg.version)
.option('-d, --debug', '是否开启调试模式', false)
.option('-e, --envName <envName>', '获取环境变量名称');
// console.log(program.opts()) // 查看所有选项
// command注册命令
const clone = program.command('clone <source> [destination]')
clone
.description('clone a repository')
.option('-f, --force', '是否强制克隆')
.action((source, destination, cmdObj) => {
console.log("do clone", source, destination, cmdObj.force)
})
// addCommand注册命令
const service = new commander.Command('service')
service
.command('start [port]')
.description('start service at some port')
.action((port) => {
console.log('do service start', port)
})
service
.command('stop')
.description('stop service')
.action(() => {
console.log('stop service')
})
program.addCommand(service)
/*
program
.arguments('<cmd> [options]')
.description('test command', {
cmd: 'command to run',
options: 'options for command'
})
.action(function (cmd, options) {
console.log(cmd, options)
})*/
/*
// linding vue-init create -> vue create
program
.command('vue-init [name]', 'create vue project', {
executableFile: 'vue',
isDefault: true,
hidden: true
})
.alias('vi')*/
/*
// 高级定制1:自定义help信息
program.helpInformation = function () {
return ""
}
program.on('--help', function () {
console.log('your help information')
})*/
// 高级定制2:实现debug模式
program.on('option:debug', function () {
if (program.debug) {
process.env.LOG_LEVEL = 'verbose'
}
console.log(process.env.LOG_LEVEL)
})
// 高级定制3:对未知命令监听
program.on('command:*', function (obj) {
// console.log(obj)
console.error('未知的命令:' + obj[0])
const availableCommands = program.commands.map(cmd => cmd.name())
// console.log(availableCommands)
console.log('可用命令:' + availableCommands.join(','))
})
program
.parse(process.argv)
}
11、node项目如何支持es module(webpack构建方式)
- core/cli/bin/index.js
#!/usr/bin/env node
require("../dist/core")
- core/cli/lib/core.js
'use strict';
import path from "path"
import {exists} from "@linding-cli/utils"
/**
* 1、npm i -D webpack webpack-cli
* 2、新建配置文件webpack.config.js
* 3、package.json:
* - {"scripts": {"build": "webpack","dev": "webpack -w"}}
* 4、bin文件无法作为webpack入口文件,所以将lib文件作为webpack入口文件。
* bin文件调用的是dist文件,dist文件是由lib文件构建出来的
*/
console.log(path.resolve("."));
console.log(exists(path.resolve(".")));
(async function () {
await new Promise(resolve => setTimeout(resolve, 1000))
console.log("ok")
})()
- core/cli/webpack.config.js
const path = require("path")
module.exports = {
entry: "./lib/core.js",
output: {
path: path.join(__dirname, "/dist"),
filename: "core.js"
},
mode: "production",
target: "node",
// npm i -D babel-loader @babel/core @babel/preset-env
module: {
rules: [{
test: /\.js$/,
// exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
/*
// npm i -D @babel/plugin-transform-runtime
plugins: [[
"@babel/plugin-transform-runtime",
{
// npm i -D @babel/runtime-corejs3
corejs: 3,
regenerator: true,
useESModules: true,
helpers: true
}
]]*/
}
}
}]
}
}
- utils/utils/lib/utils.js
'use strict';
import fs from "fs"
export function exists(p) {
return fs.existsSync(p)
}
12、node项目如何支持es module(node原生支持)
- core/cli/bin/index.mjs
#!/usr/bin/env node
/**
* 1、命令
* - 低版本:node --experimental-modules core/cli/bin/index.mjs
* - 高版本:node core/cli/bin/index.mjs
* 2、模块文件必须以.mjs结尾
*/
import "../lib/core.mjs"
- core/cli/lib/core.mjs
'use strict';
import path from "path"
import fs from "fs"
console.log(path.resolve("."));
console.log(fs.existsSync(path.resolve(".")));
(async function () {
await new Promise(resolve => setTimeout(resolve, 1000))
console.log("ok")
})()