脚手架的基本原理

点击查看脚手架系列文章总览【正在更新】
个人网站:www.dengzhanyong.com
关注公众号【前端筱园】,不错过每一篇文章

初始化项目

1. 创建项目文件

mkdir steamed-cli
cd steamed-cli

2. 使用lerna初始化项目

lerna init

项目默认结构如下:

C:.
|  .git
│  lerna.json
│  package.json
│
└─packages

在此基础上需要进行一些改动:

  • 删除 packages 目录, packages 是用来放置所有包的目录,包较多的情况下,最好多建几个文件夹将他们进行分类

  • 新建文件夹 commands (命令) 、core (核心包)、models (类)、utils (工具方法)

  • 修改 lerna.json 配置中的 packages 属性,此属性指定包的位置

    {"packages": ["core/*","commands/*","models/*","utils/*"],"version": "1.0.0"
    }
    
  • 新建 .gitignore 文件,添加 git 忽略文件配置 **/node_modules

  • 新建 README.md 文件,对脚手架进行介绍及使用说明

整体流程

整个脚手架的开发分成三个阶段进行:准备阶段、注册阶段、执行阶段

  • 准备阶段:在执行命令前需要做的一些事,如:检查node版本,检查脚手架更新,检查用户目录、权限等
  • 注册阶段:解析用户输入的命令(command)和参数(options),这里的参数主要针对于全局参数。
  • 执行阶段:安装/更新执行相应的package,也可直接执行本地文件。

准备阶段

创建入口 package

现在就正式开始脚手架的开发,首先需要一个入口。创建一个名叫 @steamed/cli 的包

lerna create @steamed/cli ./core
# 在最后可以加上一个路径,会将此包创建到指定的目录下

一个包的目录结构如下:

cli:.
│  package.json
│  README.md
│
├─lib
│      cli.js
│
└─__tests__cli.test.js

为了保持统一的规范,最好将 lib 下的 cli.js 改为 index.js

@steamed/cli 这个包是整个脚手架的入口,需要创建一个 /bin/index.js 文件作为入口,并在 packages.json 中配置 bin 属性。

{"name": "@steamed/cli","version": "1.0.0","bin": {"st": "./bin/index.js"}
}

测试流程

core/cli 下执行 npm link ,创建其软链接,使得我们可以在本地使用设置的命令进行调试。

core/cli/bin/index.js 下可以先写一句打印 console.log('hello steamed-cli'),然后在命令行中执行 st 命令,如果正常输入,则说明到目前一切正常。

如何获取输入的命令参数?

需要根据用户输入不同的命令来做不同的事,使用 process.argv 可以获取到输入的所有内容,其值为一个数组。

st init procejectName['C:\\Program Files\\nodejs\\node.exe','C:\\Users\\DZY26\\AppData\\Roaming\\npm\\node_modules\\@steamed\\cli\\bin\\index.js','init','myname'
]
  • 第一项是 node 的安装目录
  • 第二项是所执行的文件路径
  • 后面的内容就是所传入的参数

bin/index.js 只做一件事

对于输入命令后需要做的事情我们都放到 lib/index.js 中去做,在 bin/index.js 只做一件事。

  • import-local:允许全局安装的包使用其自身的本地安装版本(如果可用)

如果存在本地版本,则执行本地脚手架。如果不存在,则执行 lib/index.js 中的内容

#! /usr/bin/env nodeconst importLocal = require('import-local');if (importLocal(__filename)) {    console.log('执行本地脚手架');} else {    require('../lib/index')(process.argv.slice(2))}

功能开发

本阶段的开发需要用到以下的 npm 包,

  • semver:用户版本号规范校验,版本号比较等
  • colors:对打印的日志设置颜色
  • userHome:获取用户主目录
  • npmlog:用户输入日志信息
  • root-check:尝试降级具有root权限的进程的权限,如果失败,则阻止访问
  • dotenv:可以将本地 .env 文件中的环境变量配置到全局环境变量中。

工具包开发

@steamed/log

功能:对 npmlog 进行封装,自定义打印级别和样式

位置:/utils/log

** npmlog 打印级别**

在不同的场景下, 可以使用 npmlog 打印出不同类型的信息,包括:sillyverboseinfotiminghttpnoticewarnerrorsilent

默认打印级别为 info ,即大于等于 info 级别的信息才会被真正的打印。

主要作用

debug 信息一般使用 verbose 级别打印,因此默认情况下是不会展示debug信息的。为了更好的调试,定位问题,可以通过传入 --debug 参数使得可以看到debug信息,实现原理就是将 npmlog 的打印级别设置为 verbose

完整代码:

'use strict';const log = require('npmlog');log.level = process.env.STEAMED_CLI_LOG_LEVEL || 'info';   // 设置log 的打印级别,默认为info,可以在环境变量中拿到自定义级别设置log.heading = 'Steamed';   // 自定义log头部信息,一般为脚手架的名字log.headingStyle = { bg: 'white', fg: 'black' }  // 自定义头部样式module.exports = log;

@steamed/get-npm-info

功能:获取 npm 信息、版本等功能

位置:/utils/get-npm-info

完整代码:

'use strict';const axios = require('axios');const semver = require('semver');// 获取默认的npm源function getDefaultRegistry(origin = true) {    return origin ? 'https://registry.npmjs.org/' : 'https://registry.npm.taobao.org/'}// 获取包的所有历史版本号async function getNpmVersions(npmName, registry) {    const npmRegistry = registry || getDefaultRegistry();    return axios.get(`${npmRegistry}${npmName}`)        .then((res) => {            if (res.status === 200) {                return Object.keys(res.data.versions);            }            return [];        })        .catch(() => {            return [];        })}// 获取最新版本async function getLatestVersion(npmName, registry) {    const versions = (await getNpmVersions(npmName, registry))        .sort((a, b) => semver.gte(a, b) ? -1 : 1);    if (versions[0]) {        return versions[0]    } else {        throw new Error('检查更新失败');    }}module.exports = {    getDefaultRegistry,    getNpmVersions,    getLatestVersion};

准备阶段功能实现

在准备阶段需要按照下面的顺序依次完成每个功能。

检查当前版本

这里指的是脚手架当前版本,此版本号就是 package.json 中的 version 的值。

const pkg = require('../package.json'); // 检查当前版本function checkPkgVersion() {    log.info('cli-version', pkg.version);}

检查node版本

需要检查用户当前使用的 node 版本是否满足脚手架的最低版本要求,以保证脚手架的所有功能可以正常使用。

新建一个 constant.js 文件,用户配置一些常量配置信息。

const LOWEST_NODE_VERSION = '12.0.0';   // 最低node版本const STEAMED_CLI_HOME_PATH = '.steamed';  // 脚手架主目录名称const STEAMED_CLI_LOG_LEVEL = 'info';   // log 的打印等级module.exports = {    LOWEST_NODE_VERSION,    STEAMED_CLI_HOME_PATH,    STEAMED_CLI_LOG_LEVEL}
const semver = require('semver');const { LOWEST_NODE_VERSION } = require('./constant');// 检查node版本function checkNodeVersion() {    const currentVersion = process.version;  // 获取当前使用的版本    const lowestVersion = LOWEST_NODE_VERSION;    if (!semver.gte(currentVersion, lowestVersion)) {        throw new Error(`steamed-cli 需要node的最低版本为${lowestVersion},当前node.js版本为${currentVersion}`);    }}

检查root权限

需要检查用户是否有root权限,如果没有,则会进行降级处理,此功能在 root-check 中已实现。

import rootCheck from 'root-check';// 检查root权限function checkRoot() {   rootCheck();}

检查用户主目录

检查用户主目录是否存在

const userHome = require('user-home');// 检查用户主目录function checkUserHome() {    if (!userHome || !fs.existsSync(userHome)) {        throw new Error(colors.red('当前用户主目录不存在!'));    } else {        process.env.STEAMED_CLI_USER_HOME = userHome;    }}

检查入参

  • 用户需要执行命令,则至少需要输入一个命令或参数。
  • 检查用户输入的参数是否包含 -d--debug ,如果存在,则表示开启 debug 模式,需要将 log 的打印等级设置为 verbose
const log = require('@steamed/log');const { STEAMED_CLI_LOG_LEVEL } = require('./constant');// 检查用户主目录function checkArgv(argv) {    if (argv.length < 0) {        throw new Error('请输入命令')    }    if (argv.includes('-d') || argv.includes('--debug')) {        log.level = 'verbose';    } else {        log.level = STEAMED_CLI_LOG_LEVEL;    }    process.env.LOG_LEVEL = log.level; // 将log等级存到环境变量中,以便其他地方使用}

检查环境变量

将主目录下的 .env 文件中环境变量会被注入到 process.env 中

const { STEAMED_CLI_HOME_PATH } = require('./constant');const userHome = require('user-home');// 检查环境变量function checkEnv() {    const dotnev = require('dotenv');    const envPath = path.resolve(userHome, '.env');  // 获取主目录下的 .env 文件    dotnev.config({        path: envPath    });  // .env 中的环境变量会被注入到 process.env 中    process.env.STEAMED_CLI_HOME_PATH = path.resolve(userHome, STEAMED_CLI_HOME_PATH);}

检查版本更新

检查本地脚手架版本是否是最新版本,如果不是,则提示用户及时更新脚手架

const { getLatestVersion } = require('@steamed/get-npm-info');// 检查更新async function checkCliUpdate() {    const lastVersion = await getLatestVersion(pkg.name);    if (lastVersion && !semver.gte(pkg.version, lastVersion)) {        log.warn('steamed更新', `发现新版本${lastVersion},当前版本${pkg.version},请及时更新!`);    }}

打印报错信息

在上面的流程中,每一步都有可能会出错,可能是第三方组件内部报错,也可能是我们自己写的 throw new Error(),这对这些错误信息,我们可以在最外层通过 try catch 处理。

async function index() {    try {        checkPkgVersion();        checkNodeVersion();        checkRoot();        checkUserHome();        checkEnv();        await checkCliUpdate();    } catch (e) {        log.error(e.message);  // 只打印关键信息        if (log.level === 'verbose') {  // 如果是debug模式,则打印出完整的错误信息栈,以便于定位问题            console.log(e);        }    }}

完整代码地址 Github:https://github.com/DengZhanyong/steamed

点击查看脚手架系列文章总览

个人网站:www.dengzhanyong.com

关注公众号【前端筱园】,不错过每一篇文章

脚手架开发(1)-准备阶段相关推荐

  1. Python中str()与repr()函数的区别——repr() 的输出追求明确性,除了对象内容,还需要展示出对象的数据类型信息,适合开发和调试阶段使用...

    Python中str()与repr()函数的区别 from:https://www.jianshu.com/p/2a41315ca47e 在 Python 中要将某一类型的变量或者常量转换为字符串对象 ...

  2. 软件开发之计划阶段: ”声控打鼓”游戏的”用户/场景”分析

    "用户/场景"分析(a.k.a user scenarios)对于软件开发的计划阶段是十分重要的.只有明确了软件的用户群,以及软件所应用的场合,才能真正了解到所要开发的软件是否有价 ...

  3. 脚手架(一)——脚手架开发入门

    脚手架 1. 脚手架是什么 2. 为什么要开发脚手架 3. 脚手架实现原理 4. 脚手架开发 4.1 开发流程 4.2 安装脚手架 4.3 脚手架开发难点 1. 脚手架是什么 脚手架本质是一个操作系统 ...

  4. Vue教程03-Vue脚手架开发环境

    Vue脚手架开发环境 1.Vue开发环境的安装 1.1安装Node JS 1.2全局安装Vue脚手架 1.3安装HBuilderX 1.4强烈推荐安装以下工具软件 2.HBuilderX创建Vue项目 ...

  5. 软件开发的六大阶段 (指针经典原创)

     软件开发的六大阶段      第一阶段:调研阶段 本阶段我们将组成企业项目调研组到企业进行现场调研,企业也部分需组织相应人员进行配合.整个调研工作将历时三星期到一个月左右时间.调研内容按以下方面进行 ...

  6. 前端脚手架开发(1)

    脚手架实现原理 当我们在终端输入vue create xx的时候 终端会去全局环境变量中,找到vue指令的方法 查看create-react-app的位置,所有通过npm -g安装的包都会放入该目录下 ...

  7. 脚手架开发(2)-注册阶段

    点击查看脚手架系列文章总览[正在更新] 个人网站:www.dengzhanyong.com 关注公众号[前端筱园],不错过每一篇文章 在上篇文章,已经完成了第一个阶段:准备阶段,在准备阶段做了许多基础 ...

  8. 项目管理_软件开发的六大阶段

    转载 2013年05月01日 19:47:08 http://blog.csdn.net/northplayboy/article/details/601443 第一阶段:调研阶段 本阶段我们将组成企 ...

  9. 利用Vue 脚手架 开发chrome 插件,太方便了

    前言 一个 Chrome 插件,核心就是 manifest.json 文件,manifest.json 下的属性 content_scripts 指定inject的脚本列表 js,css [ inje ...

  10. 巨蟒python全栈开发-第11阶段 ansible_project2

    一个NB的网站: https://www.toolfk.com/ CDN:将用户的需求送到最近的节点:内容分发网络 有些是专门做CDN的工具 常用的markdown是需要知道的,短信有字数限制. we ...

最新文章

  1. IDEA使用log4j
  2. css盒子子类继承父类哪些,css不继承父类的属性有哪些
  3. Django_ORM数据表查询总结
  4. 怎样定义网页里的关键字关键词
  5. qml mousearea 点击其他地方_Qml 快速使用
  6. android assets文件夹资源的访问
  7. 使用Visual Studio 创建新的Web Part项目
  8. java 过滤xss脚本_Java Web应用程序的反跨站点脚本(XSS)过滤器
  9. 莫比乌斯带catia建模_用sw2018制作莫比乌斯环图文教程
  10. arraylist线程安全吗_Java面试复习-IO和多线程
  11. ##CSP 201803-2 碰撞的小球(C语言)100分
  12. mysql的压缩包,mysql 压缩包安装
  13. 什么是面向对象对象,什么是面向过程,什么是面向对象思想。
  14. 什么是CSS网页切图
  15. pdfFactory2020最新7许可密钥-激活码版虚拟打印软件 中文特别授权版下载
  16. oracle英文日期转换为中文,excel中文日期与英文日期如何转换
  17. 编程语言介绍以及特点
  18. 一名程序员的内心独白:我很忙,但我的代码还是很糟糕
  19. java 模板 word转pdf 可分页 带图片
  20. Unit2 附加问句

热门文章

  1. 导航栏背景色、标题颜色以及返回键自定义
  2. Qt系列文章之 QAbstractItemModel(下)
  3. Speedoffice(word)如何添加超链接
  4. html网页如何限制ip访问量,Nginx 限制IP访问频率
  5. 2008年最吸引眼球的10只股票
  6. VINS-Mono 代码解析六、边缘化(3)
  7. win10怎么更新显卡驱动_win10系统AMD显卡驱动安装失败的解决方法
  8. 第120章 SQL函数 ROUND
  9. java题目青蛙跳杯子_蓝桥杯 历届试题 青蛙跳杯子
  10. Hive3第五章:函数