本文主要介绍如何开发一个脚手架的基本框架。

# 使用工具

  1. Commander.js (opens new window) 用来接收处理用户命令。

  2. inquirer.js (opens new window) 用来完成与用户的交互。

  3. download-git-repo (opens new window) 能够用来下载 github 项目。

  4. chalk (opens new window) 能使文字以某种颜色高亮显示。

  5. ora (opens new window) 用来实现 loading 效果。

  6. figlet (opens new window) 可以用来实现一些简单的字符画。

  7. @darkobits/lolcatjs (opens new window) 可以让字符显示随机的颜色。

  8. execa (opens new window) 能够用于传入命令,直接执行。可用于自动化测试和持续集成。

  9. semver (opens new window) 是一个专门分析 Semantic Version(语义化版本)的工具,通常用来处理、管理版本。

  10. multimatch (opens new window) 是一个用来匹配文件的库。

  11. user-home (opens new window) 用来获取用户的主目录路径。

  12. shelljs (opens new window) 能够让我们在 js 中编写 shell 脚本。

# 基本要求

  • 能够完成与用户的交互。

  • 能够根据用户的选择,从远端仓库下载 github 项目。

  • 下载下来的项目能够放到用户指定的目录。

  • 帮用户完善最终的操作。npm install / yarn install

注意

使用脚手架之前,要确保有一个模版项目可供下载,不然这个脚手架就发挥不了用处了。

# 基本步骤

# 初始化项目

新建一个 star-cli 项目,并新建一个 bin 文件夹。然后进入到这个项目中,执行 npm init -y 初始化项目。

在 star-cli 项目的 bin 目录下新建 starcli 文件。我们可以在项目目录下使用 Node.js 去执行这个文件:node ./bin/starcli。但是如果想全局去执行这个文件,就需要在 package.json 中加上 bin。

"bin": {
    "star": "bin/starcli"
}
1
2
3

然后执行 npm link,就会链接到全局的 node。可以使用 where node 命令查看全局的 node 路径。

cli

此时在全局就有一个 star 命令了。

cli

在 starcli 文件中输入以下内容:

#!/usr/bin/env node
console.log(123);
1
2

然后在终端执行 star 就能看到输出结果了。

cli

#!/usr/bin/env node 的作用

  • 首先,#! 其实是一个符号,这个符号在 Linux 或 Unix 中被称为 Shebang用于指明这个脚本文件的解释程序,因此加这一行的目的就是指定 Node.js 来执行脚本文件。

  • 其次,由于不同用户或者不同的脚本解释器有可能安装在不同的目录下,那么系统如何知道要去哪里找解释程序呢?/usr/bin/env 就是告诉系统可以去 PATH 目录中查找,这就解决了不同用户的 Node.js 路径不同的问题,可以让系统动态的去查找 Node.js 来执行你的脚本文件。有时候如果加了这一行,碰到了 No such file or directory 这样的错误,那应该是你的 Node.js 没有添加到系统的 PATH 中,配置下就好了。

  • 最后,这句命令一定要放在第一行才会生效!

注意

在网上有看到过这个说法:“如果是 windows 系统,就不需要加这一行了,也能执行,因为 windows 系统并不支持 Shebang,它是通过文件的扩展名来确定使用什么解释器来执行脚本”,经过实测发现,windows 系统下还是有必要加上这一句的,不加的话 npm link 之后还是无法全局使用。

全局执行自定义命令的原理

package.json 中的 bin 字段就是专门用来放置用户自定义命令的。比如在 vue-cli 中:

"bin": {
  "vue": "bin/vue",
  "vue-init": "bin/vue-init",
  "vue-list": "bin/vue-list"
}
1
2
3
4
5

如果我们想使用自定义的命令,就需要把它加到环境变量 PATH 中,加到 PATH 中的方法就是通过 bin 目录,bin 目录再通过 npm link 命令(命令的软链接,链接到全局或者某个空间下)进行添加。

全局命令之所以可以直接使用,就是因为添加到了 PATH 中。

而 node_modules/.bin/ 目录下的命令只是下载下来了,并没有添加到 PATH 中,所以不能直接使用,要想使用就需要添加 npm 脚本才能使用。比如:

"scripts": {
  "createUid": "uid"
}
1
2
3

然后执行 npm run createUid 就可以使用 uid 命令了。这是利用 npm 做了一个中转,因为执行 npm run xxx 之前,会先把对应的命令添加到 PATH 中,就可以使用了。运行完之后,再从 PATH 中删除。

执行 npm link 之后会做以下两步处理:

C:\Program Files\nodejs\suncli -> C:\Program Files\nodejs\node_modules\sun-cli\bin\suncli
C:\Program Files\nodejs\node_modules\sun-cli -> D:\project\custom\sun-cli
1
2

当我们在执行 npm install -g xxx 的时候,实际上是把包下载到了全局的 /usr/local/lib/node_modules 目录下,而 /usr/local/bin 这个目录就在 PATH 中,所以全局安装之后我们可以直接使用命令。

在 mac 中可以通过 echo $PATH 命令来查看环境变量 PATH 中有哪些值。

cli

在 windows 中可以通过 path 命令来查看环境变量 PATH 中有哪些值。

cli

# 使用字符画 figlet

接下来就可以开始整活了。首先我们可以弄一个好玩的字符画,这需要用到 figlet (opens new window)。直接 npm install figlet 安装就行了。

在 starcli 中编辑以下代码:

#!/usr/bin/env node
const figlet = require("figlet");
const fontStr = figlet.textSync("linnan");
console.log(fontStr);
1
2
3
4

然后执行 star 命令,就可以看到终端输出字符画了。不过这个字符画看起来有点丑,我们是可以自己设置它的字体、大小什么的,具体参考 figlet 的用法。

cli

# 字符画添加颜色 lolcatjs

我们可以给这个字符画加一些颜色,需要用到 lolcatjs (opens new window)一定要全局安装它,然后补充 starcli 的内容。

这里需要注意的一点是,lolcatjs 官方的用法是需要用 import 来使用的,但是我们这里暂时不支持 import,只能用 require 的方式,可是使用 require 方法引入包会有问题,会报 “Printer.fromString is not a function” 的错误。解决方法就是通过 Printer.default.fromString 的方式调用。

#!/usr/bin/env node
const figlet = require("figlet");
const fontStr = figlet.textSync("linnan");
const Printer = require("@darkobits/lolcatjs");
const transformed = Printer.default.fromString(fontStr);
console.log(transformed);
1
2
3
4
5
6

执行后就会看到以下效果。

cli

注意

在 window 上还会报找不到 @darkobits/lolcatjs 这个包的错误,原因是这个包是全局安装的,而 Node.js 默认不在全局找包。我们需要配置环境变量才能解决这个问题,那么如何配置呢?

  • 首先,通过 where node 命令查看自己本机的 Node.js 的安装路径,比如我自己的是在:C:\Program Files\nodejs\node.exe。

  • 然后,打开文件夹,输入 控制面板\系统和安全\系统 打开系统设置页面,再点击 高级系统设置 -> 环境变量

  • 最后,在当前用户的环境变量里或者是系统变量里增加一个环境变量 NODE_PATH,值是 C:\Program Files\nodejs\node_modules。配置好之后重新打开终端,执行命令就没问题了。

# 接收用户输入的命令 Commander.js

弄好字符画之后,接下来看看怎么实现接受用户的命令执行不同的操作。

这块功能需要用到两个经典的库:shelljs (opens new window)commander (opens new window)。shelljs 是一个在 js 文件中编写 shell 脚本的库,而 commander 就是用来接收处理用户命令的库。

在 starcli 文件中补充以下代码:

#!/usr/bin/env node
const figlet = require("figlet");
const fontStr = figlet.textSync("linnan");
const Printer = require("@darkobits/lolcatjs");
const transformed = Printer.default.fromString(fontStr);
const { program } = require("commander");

program.version(transformed);

program
  .option("-u, --update", "Get lastest version")
  .option("-d, --download", "Download a project")
  .option("-c, --create", "Create a project");

program.parse(process.argv);

// console.log(transformed);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

然后执行 star --help 或者 star -h 就可以看到我们创建的那些命令了。

cli

执行 star -V 或者 star --version 就可以看到字符画。

cli

我们也可以直接读取 package.json 里面的 version,然后输出出来。

#!/usr/bin/env node
const figlet = require("figlet");
const fontStr = figlet.textSync("linnan");
const version = require("../package").version;
const Printer = require("@darkobits/lolcatjs");
const transformed = Printer.default.fromString(
  ` 星际开发脚手架:${version} \n ${fontStr}`
);
const { program } = require("commander");

program.version(transformed);

program
  .option("-u, --update", "Get lastest version")
  .option("-d, --download", "Download a project")
  .option("-c, --create", "Create a project");

program.parse(process.argv);

// console.log(transformed);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

执行效果如下:

cli

# 让文字高亮显示 chalk

quicktype (opens new window) 是一个能将 JSON 转换为任何语言的包。需要全局安装。

chalk (opens new window) 是一个能使文字以某种颜色高亮显示的包。

#!/usr/bin/env node
const figlet = require("figlet");
const fontStr = figlet.textSync("linnan");
const version = require("../package").version;
const Printer = require("@darkobits/lolcatjs");
const transformed = Printer.default.fromString(
  ` 星际开发脚手架:${version} \n ${fontStr}`
);
const chalk = require("chalk");
const shell = require("shelljs");
const { program } = require("commander");

program.version(transformed);

program
  .option("-u, --update", "Get lastest version")
  .option("-d, --download", "Download a project")
  .option("-c, --create", "Create a project")
  .option("-j, json", "Convert JSON to any language");

const handlers = {
  json(dataURL) {
    shell.exec(
      `quicktype ${dataURL} -o ${
        shell.pwd().stdout
      }/Weather.ts --runtime-typecheck`
    );
    // shell.exec(`quicktype ${dataURL} -o Weather.ts --runtime-typecheck`);
  }
};

program
  .usage("[cmd] <options>")
  .arguments("<cmd> [env]")
  .action((cmd, otherParams) => {
    const handler = handlers[cmd];
    if (typeof handler == "undefined") {
      console.log(chalk.blue(`${cmd}`) + chalk.red("暂未支持"));
    } else {
      handler(otherParams);
    }
  });

program.parse(process.argv);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 发布到 npm

# 开发一个 CSS CLI

这个 CLI 的功能是扫描团队的 CSS 代码并转换成 AST,然后使⽤ CSS matrix() 代替全部基础 API,从而增强渲染性能。

这个 CLI 的实现主要包含以下几个步骤:

# 1. 创建 CLI 应⽤

可以使⽤ Node.js 创建 CLI 应⽤。⾸先,确保已经安装了 Node.js 和 npm。创建⼀个新的项⽬⽬录,并初始化 npm:

mkdir css-matrix-transformer
cd css-matrix-transformer
npm init -y
1
2
3

安装必要的库:

npm install postcss postcss-safe-parser yargs fs
1
  • postcss 和 postcss-safe-parser ⽤于解析和处理 CSS。

  • yargs ⽤于处理命令⾏参数。

  • fs(Node.js 内置模块)⽤于⽂件读写。

# 2. 解析 CSS ⽂件

创建⼀个⽂件 transform.js ,并添加基本的 CSS 解析逻辑:

const fs = require("fs");
const postcss = require("postcss");
const safeParser = require("postcss-safe-parser");
const yargs = require("yargs/yargs");
const { hideBin } = require("yargs/helpers");
async function transformCss(file) {
  const css = fs.readFileSync(file, "utf8");
  const root = postcss.parse(css, { parser: safeParser });
  // 遍历并转换 CSS 规则...
  // ...
  // 写回⽂件
  fs.writeFileSync(file, root.toString());
}
const argv = yargs(hideBin(process.argv)).argv;
if (argv.file) {
  transformCss(argv.file);
} else {
  console.error("Please specify a CSS file.");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 3. 转换 AST

在 transformCss 函数中添加逻辑以转换 CSS 属性。以下是⼀个示例,需要根据实际情况编写适当的转换逻辑:

root.walkRules((rule) => {
  rule.walkDecls((decl) => {
    if (shouldBeTransformed(decl)) {
      decl.value = convertToMatrix(decl.value);
    }
  });
});
function shouldBeTransformed(decl) {
  // 根据需要检查声明是否应该转换
  // 例如,检查是否使⽤了 transform 属性
  return decl.prop === "transform";
}
function convertToMatrix(value) {
  // 将 CSS 转换为 matrix() 形式
  // 这需要你编写具体的转换逻辑
  return `matrix(${value})`;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 4. 使⽤ CLI

现在,可以使⽤这个 CLI ⼯具来转换 CSS ⽂件。例如:

node transform.js --file path/to/your/style.css
1

后续步骤

  • 完善转换逻辑:根据需求完善 shouldBeTransformed 和 convertToMatrix 函数。

  • 错误处理:增加适当的错误处理逻辑。

  • 测试:在不同的 CSS ⽂件上进⾏测试,确保转换逻辑正确⽆误。

上次更新时间: 2024年04月22日 21:45:09