前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >靓仔快来!!用typescript带你搭建一个自己的脚手架

靓仔快来!!用typescript带你搭建一个自己的脚手架

作者头像
南山种子外卖跑手
发布2022-06-12 13:40:41
1.6K0
发布2022-06-12 13:40:41
举报
文章被收录于专栏:南山种子外卖跑手的专栏

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

本文为原创文章,引用请注明出处,欢迎大家收藏和分享💐💐

开场

哈喽大咖好,我是Johnny,这次给大家重新缕一缕如何用typescript配合周边插件做一个易用的脚手架管理工具。

想到写这篇文章的原因有俩:一是最近业务上有类似需求,作为这领域的探索和整理,给有需要的小伙伴做个参考;二是从前端个人或团队的技术储备角度出发,更抽象和统一的开发者工具,能使开发效率有效提升,省去大量的代码copy和update的冗余工作。

场景演示

为了直观给大家展示关键流程,本文实现的脚手架创建步骤为:

命令输入 → 检查目录合法 → 选择github工程模板 → 选择版本 → 填入必要信息 → 模板下载

效果图

https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c36c726e85934991966cb0c15b26f74c~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?

必备插件

欲善其功必先利其器,讲实现前,我们先了解下各插件的功能吧🐶🐶。。。

插件一览

  • chalk:命令行彩色文字
  • commander:完整的 node.js 命令行解决方案
  • figlet:花里胡哨的命令行艺术字
  • fs:nodejs的文件系统,多文件curd
  • fs-extra:fs升级版,提供更便利的API和编码方式
  • inquirer:命令行输入交互,提供多种问答方式
  • module-alias:nodejs别名路径转换器
  • ora:loading效果
  • shelljs:支持nodejs中执行shell命令神器

chalk

chalk是一个文字变色器,它可以在命令行实现以下文字效果:

在代码执行过程中往往需要把一些重要信息高亮输出,这个插件便恰到好处。举个例子:

代码语言:javascript
复制
import { cyan, bgYellow } from 'chalk';

console.log('这是正常的文字...');
console.log(cyan('这是用了cyan的文字...'));
console.log(bgYellow('这是用了bgYellow的文字...'));
复制代码

效果:

commander

commander node.js命令行界面的完整解决方案,受 Ruby Commander启发。简单演示下commander的用法:

代码语言:javascript
复制
import { program } from 'commander';

program
  .command('show <message>')
  .description('展示你输入的消息')
  .option('-t, --tip <tips>', '消息提示', '默认值')
  .action((message, cmd) => {
    console.log(message, cmd);
  });
复制代码

上面定义了一条交互命令,功能就是让用户执行show命令,并输入“展示的消息”“消息提示”2个参数后,命令面板就会打印用户的消息。

  • commandoption分别代表执行的命令和命令后面可选参数,<>,[]包裹的参数被认为是强制、可选输入项,强制项缺失系统会直接报错。
  • action便是用户按回车后要执行的操作,(message, cmd)分别代表commandoption紧跟的参数内容。

上面代码执行效果:

figlet

figlet能把你输入的文字通过字符组合变化出各种效果,这里就不细述了,大家可以看官方样例

fs 和 fs-extra

这2个库主要用于nodejs环境下对文件的操作,fs-extra是fs的拓展,让更少代码可以实现同样的操作。

inquirer

inquirer能满足你在命令行的各种输入交互,大概的使用规则就是通过async/await函数包裹交互式命令,等待用户输入后再获取结果执行后续逻辑,例如:

代码语言:javascript
复制
import { green } from 'chalk';
import { prompt } from 'inquirer';

const choseQuestion = async () => {
  const { question } = await prompt([
    {
      name: 'question',
      type: 'list',
      message: '请选择您的问题',
      choices: ['如何上热搜', '如何财富自由', '我要回家躺平'],
    },
  ]);
  console.log(`您选的问题是:${green(question)}`);
};

choseQuestion().then();
复制代码

效果

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/44297641603d4b6eb0e45caec0c4f219~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?

module-alias

module-alias主要兼容tsc编译的引用路径问题,下面会细述。

ora

命令行的loading效果,举个🌰:

代码语言:javascript
复制
import ora from 'ora';

export const loadingDemo = async <T>(message: T): Promise<T> => {
  const spinner = ora(message);
  spinner.start(); // 开启加载
  try {
    const result: T = await new Promise(resolve => {
      setTimeout(() => {
        resolve(message);
      }, 1000);
    });
    spinner.succeed();
    return Promise.resolve(result);
  } catch (err: any) {
    spinner.fail(err);
    return Promise.reject(err);
  }
};

loadingDemo<string>('我要loading 1秒').then(res => console.log(res));
复制代码

效果

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/37d2516fef5741d2b24a0fe4c1415e44~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?

shelljs

shelljsnodejs下的脚本语言解析器,具有丰富且强大的底层操作(Windows/Linux/OS X)权限。

本项目中shelljs主要作用是clone git仓库等。可能大家会有疑问,为什么对仓库的操作不用rest api?例如github有相对完善的rest api库,gitlab也有自己的api,而且网上也有很多插件封装了这些api。

关于这个灵魂拷问,笔者的想法是:api一般配套系列的鉴权流程,假如是一个public的仓库其实没必要做那么多额外的安全操作;其次项目也是想尽量减少三方制约的规则,方便以后作为一个纯净版项目移植到其他地方,可以到shelljs满足不了的情景再考虑加入api模块。

尽管如此,项目也保留了api目录方便以后拓展。

功能实现

前储备知识介绍完了,可以开始逐步实现我们的逻辑。这部分会讲关键步骤和思路,源码有兴趣的同学可以去github上看。

项目结构

代码语言:javascript
复制
.
├── .eslintrc.js          # eslint配置
├── bin                   # tsc转换后的js源码
├── config                # 环境配置
├── .gitignore
├── .prettierrc           # prettier配置
├── README.md
├── package-lock.json     # 依赖锁
├── package.json          # 项目配置
├── src
│   ├── tools             # 工具包源码
│   │   ├── cliCreator    # 脚手架创建
│   │   ├── cliUpdater    # 脚手架更新「待建」
│   │   └── proxy         # 开发服务代理小工具「待建」
│   └── utils             # 基础方法
└── tsconfig.json         # ts配置
复制代码

上面是轻量化版本,原项目是基于nestjs打造,因为在满足脚手架下载功能之外,还要启动本地服务来做其他开发提效工作。但是本文只叙述创建脚手架这一部分,方便大家理解就把项目简化了。

其中src/tools包含脚手架创建和更新功能,src/utils保存全局方法,eslintrc.js,prettierrc,tsconfig.json分别是代码规整文件,bin则是tsc编译后的js文件目录。

能力实现

注册全局命令

众所周知要直接在命令行使用自定义的命令,必须要先安装好Nodejs环境,然后再把命令注册到全局中去。下面举个例子:

代码语言:javascript
复制
mkdir hello
cd hello
npm init -y
touch helloWorld.js
echo '#!/usr/bin/env node \n console.log("hello world")' > helloWorld.js
复制代码

假如你用的是mac电脑,安装好nodejs后随便找个目录执行上面一系列命令后,会得到这样项目结构

接下来打开package.json,给项目加一条执行命令,例如:

代码语言:javascript
复制
{
  "name": "hello",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "bin": {
    "hello": "./helloWorld.js"
  },
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
复制代码

"hello": "./helloWorld.js"的意思是假如执行hello命令,nodejs就会选择同文件夹下helloWorld.js执行。

到这里我们缺最后一步就是把hello命令挂到全局中去,要实现这个很简单,本地挂载直接在package.json所在目录执行npm link

注册完后随便在电脑找个目录执行hello,控制台就会输出hello world了;而远程npm只需要在安装时加-g参数即可,这就是全局命令注册方法。

另附:npm软链常用命令

创建命令入口

我们确定src/tools/cliCreator/bin/demo.ts作为创建脚手架项目的入口文件,内容如下:

代码语言:javascript
复制
#!/usr/bin/env node

import { cyan } from 'chalk';
import { program } from 'commander';
import figlet from 'figlet';
import { create } from '../lib';
import pkg from '@root/package.json';

program
  .command('create <project-name>')
  .description('创建项目')
  .option('-f, --force', '是否强制覆盖')
  .action((projectName, cmd) => {
    create(projectName, cmd).then();
  });

program.on('--help', () => {
  console.log(
    cyan(
      figlet.textSync('dc', {
        font: '3D-ASCII',
        horizontalLayout: 'default',
        verticalLayout: 'default',
        width: 80,
        whitespaceBreak: true,
      }),
    ),
  );
});

program.name('dc').usage(`<command> [option]`).version(`dc ${pkg.version}`);
program.parse(process.argv);
复制代码

代码简单定义了一个create命令,并且强制带上项目名作为参数。另提供-f可选参数,是否在存在路径情况下强制覆盖。

创建命令选项流

良好的编程习惯下,在到达核心创建脚手架逻辑前,应该在外面还有一层封装,对每个输入参数做容错处理。

create中,我们有-f --force参数要处理,所以选项流程函数src/tools/cliCreator/lib/index.ts可以这样写:

代码语言:javascript
复制
import path from 'path';
import fse from 'fs-extra';
import { clearDirectory } from './filesHandler';
import { mainLine } from './creator';
import { red } from 'chalk';

export const create = async (projectName: string, options: Record<string, unknown>) => {
  try {
    // 获取当前工作目录
    const targetDirectory = path.resolve(process.cwd(), projectName);
    let result = '';

    // 检查是否有文件覆盖
    if (fse.existsSync(targetDirectory)) {
      result = await clearDirectory(targetDirectory, options);
    }

    if (result === 'Cancel') return;

    // 创建项目总线
    await mainLine(projectName);
  } catch (err) {
    console.log(red('❌ Error: ' + err));
  }
};
复制代码

在create方法中,所有步骤的错误都会被catch捕获,在catch我们可以设计统一的出错处理,例如可以上报logger。

创建核心流程

通过上述流程后,我们基本可以确保所有输入选项都处理好了,接下来就可以到核心的创建流程了。创建流程在src/tools/cliCreator/lib/creator.ts路径里,完整代码

代码语言:javascript
复制
import { prompt } from 'inquirer';
import shell from 'shelljs';
import fse from 'fs-extra';
import fs from 'fs';
import { cyan, red, green } from 'chalk';
import { loading } from '@root/src/utils/global';
import { template, TTemplate } from '../constants/repo';

type TInfo = {
  repo: string;
  name: string;
  version: string;
  author: string;
  description: string;
};

/**
 * 创建项目主线程
 */
export const mainLine = async (projectName: string) => {
  try {
    const info: TInfo = {
      repo: '',
      name: projectName,
      version: '',
      author: '',
      description: '',
    };

    // 仓库信息 —— 模板信息
    info.repo = await getRepoInfo();

    // 标签信息 —— 版本信息
    info.version = await getTagInfo(info.repo);

    // 作者
    info.author = await getAuthor();

    // 描述
    info.description = await getDescription();

    // 下载模板
    await loading(`下载模板 ${info.repo}`, download, info);

    console.log(green(`成功创建 ${cyan(info.name)}`));
  } catch (err) {
    console.log(red('❌ Error: ' + err));
  }
};

/**
 * 选取模板
 */
const getRepoInfo = async () => {
    // ...
};

/**
 * 选取版本
 * @param repo
 */
const getTagInfo = async (repo: string): Promise<string> => {
    // ...
};

/**
 * 输入作者
 */
const getAuthor = async () => {
    // ...
};

/**
 * 输入项目描述
 */
const getDescription = async () => {
    // ...
};

/**
 * 获取模板版本
 * @param repo
 */
const getTagInfoList = (repo: string): Promise<string[]> => {
    // ...
};

/**
 * 下载模板
 * @param info
 */
const download = async (info: TInfo) => {
    // ...
};
复制代码

套路一样的,mainLine负责把控创建步骤每个环节,任意一步出错会走catch里处理;另外每个步骤的处理就是用到了我们上面介绍的插件能力。

解决typescript compile的路径问题

由于这个项目是nestjs拆出来的简单版,没有用框架的构建能力,假如在项目中用了路径别名「path alias」,并且直接用tsc编译,那么输出的js包会有路径引用不到的问题,举个简单例子:

tsconfig.json

在某个文件(src/tools/cliCreator/lib/creator.ts)调用,本地开发是没问题的:

代码语言:javascript
复制
import { loading } from '@root/src/utils/global';
复制代码

但是在tsc编译后再运行就会出错,原因是无法识别@root

再追查下原因,我们去到编译后文件已排查,发现路径根本没转换,这不是芭比Q了嘛。。。

为了解决这个问题,要么就使用webpack、nest这些打包工具,要么就找些三方插件支持。对比下前者肯定不是最优选,只会使得项目越来越重,在后者这里推荐module-alias插件,使用起来方便,只需要在package.json注册,然后在总入口引入就可以了。

落幕

到这里,一个简单易用的脚手架就做好了,逻辑不复杂,小伙伴们可以尝试下。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-06-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开场
  • 场景演示
    • 效果图
    • 必备插件
      • 插件一览
        • chalk
          • commander
            • figlet
              • fs 和 fs-extra
                • inquirer
                  • module-alias
                    • ora
                      • shelljs
                      • 功能实现
                        • 项目结构
                        • 能力实现
                          • 注册全局命令
                            • 创建命令入口
                              • 创建命令选项流
                                • 创建核心流程
                                  • 解决typescript compile的路径问题
                                  • 落幕
                                  相关产品与服务
                                  云开发 CLI 工具
                                  云开发 CLI 工具(Cloudbase CLI Devtools,CCLID)是云开发官方指定的 CLI 工具,可以帮助开发者快速构建 Serverless 应用。CLI 工具提供能力包括文件储存的管理、云函数的部署、模板项目的创建、HTTP Service、静态网站托管等,您可以专注于编码,无需在平台中切换各类配置。
                                  领券
                                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档