一路繁花似锦绣前程
失败的越多,成功才越有价值

导航

 

三、脚手架核心流程开发

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")
})()
posted on 2022-08-09 23:25  一路繁花似锦绣前程  阅读(90)  评论(0编辑  收藏  举报