# 脚手架工具开发
# 写着前面
目前已经开发了一款自己的脚手架 —— karl-cli
NPM平台地址:https://www.npmjs.com/package/karl-cli (opens new window)
也可以查看karl-cli使用文档查看教程,里面包括了安装步骤、命令集合以及最重要的可能遇到的问题集😇
# 准备工作
# 使用到的NPM库
- commander
- download-git-repo
- ejs
- Inquirer
- ora
- chalk
- log-symbols
TIP
每个NPM库的作用在接下来的文档中有说明,这里就不再赘述了
# 基础构建
- 新建index.js文件作为入口文件
- 运行
npm init -y
命令创建package.json文件 - 使index.js自动运行,需要在index.js文件的第一行加上如下代码:
#!/usr/bin/env node
TIP
这行代码叫做shebang或者hashbang,虽然可以写绝对路径但因为兼容性问题还是推荐上述这种写法
- 为了让终端能够识别自己创建的指令,如
karl
,需要在package.json中加入如下语句:
{
...
"bin": {
"karl": "index.js"
}
...
}
- 然后运行
npm link
指令
解释作用
这样会让这个bin和真正环境变量那个地方做一个链接,就能使能将karl这个作为终端命令配置到那个环境变量里去
- 如果在index.js里写入如下代码:
console.log("my cli");
然后输入命令karl
后,终端中就能出现如下结果:
karl@KarldeMacBook-Pro Karl-cli % karl
my cli
自行检查
完成上述操作后,文件夹内只有index.js和package.json两个文件,并且能在终端打印出结果🎉
# 自定义指令集合
要完成这个功能需要下载第三方库——commander
需要在终端运行指令npm install commander
,然后在相应页面导入:
const program = require('commander');
# 命令输出版本号使用示例
#! /usr/bin/env node
const program = require('commander');
program.version("1.1.1"); // [1]
program.parse(process.argv); // [2]
语句解释
[1] 这个语句的功能是在终端输入karl --version
或者karl -V
后输出当前版本号
[2] 这个语句的功能是将指令后的字符串进行解析,也就是再输入karl --version
后能解析--version
这个命令
# 运行结果
karl@KarldeMacBook-Pro Karl-cli % karl --version
1.1.1
karl@KarldeMacBook-Pro Karl-cli % karl -V
1.1.1
以及可以输入karl --help
karl@KarldeMacBook-Pro Karl-cli % karl --help
Usage: karl [options]
Options:
-V, --version output the version number
-h, --help display help for command
# 选项处理功能的使用
# 增加自己的选项处理
代码如下:
program.option("-p --paper <paper>", "This is a option description");
然后在终端输入karl --help
,得到以下结果:
karl@KarldeMacBook-Pro Karl-cli % karl --help
Usage: karl [options]
Options:
-V, --version output the version number
-p --paper This is a option description
-h, --help display help for command
# 获取可选参数的值
根据官方文档的示例,具体操作如下:
const options = program.opts();
program.option("-p --paper <paper>", "This is a option description");
program.parse(process.argv);
console.log(options.paper);
首先需要在终端运行命令:
karl --paper mypaper
终端打印结果如下:
karl@KarldeMacBook-Pro Karl-cli % karl --paper mypaper
mypaper
注意事项
一定要在--paper
后加上参数😖,否则会报如下错误:
karl@KarldeMacBook-Pro Karl-cli % karl --paper
error: option '-p --paper <paper>' argument missing
# 添加指令
示例如下:
const program = require('commander');
program
.command("create <project>")
.description("Create your own project. For example: karl create demo")
.action(project => console.log(project));
在终端运行指令后会输出结果:
karl@KarldeMacBook-Pro Karl-cli % karl create demo
demo
官方文档
GitHub平台地址:https://github.com/tj/commander.js
NPM平台地址:https://www.npmjs.com/package/commander
这两个文档说明都是一样的,但是都是英文的文档,如果想查看中文文档,可以点击这里 (opens new window)!
# 克隆模板到本地
首先需要用到的库是——download-git-repo
需要在终端运行指令npm install download-git-repo
,然后在相应页面导入:
const download = require('download-git-repo');
TIP
因为download函数的是回调函数的形式,容易形成回调地狱,因此也可以将其转换为Promise的形式,方式如下:
const { promisify } = require('util');
const download = promisify(require('download-git-repo'));
# 克隆GitHub仓库
# 使用http的url下载
download('direct:https://gitlab.com/flippidippi/download-git-repo-fixture/repository/archive.zip', 'test/tmp', function (err) {
console.log(err ? 'Error' : 'Success')
})
# 使用直接url的git克隆
download('direct:https://gitlab.com/flippidippi/download-git-repo-fixture.git#vue', 'test/tmp', { clone: true }, function (err) {
console.log(err ? 'Error' : 'Success')
})
然后在想要下载的文件夹下运行指令karl create demo
,就能创建一个名字为demo的项目文件了。
官方文档
GitLab平台地址:https://gitlab.com/flippidippi/download-git-repo
NPM平台地址:https://www.npmjs.com/package/download-git-repo
# 指令创建vue模板
首先需要用到的库是——ejs
需要在终端运行指令npm install ejs
,然后在相应页面导入:
const ejs = require('ejs');
然后传入参数渲染文件得到字符串:
ejs.renderFile(absolutPath,{ name, lowerName: name.at(0).toLowerCase() + name.slice(1) }, {},
(err, str) => {
if (err) {
console.log(err);
reject(err);
return;
}
resolve(str);
})
注意事项
函数参数的absolutPath要用模板文件(也就是.ejs
文件)的绝对路径,所以可以使用path模块的resolve
函数进行路径拼接,渲染完用fs模块的promises.writeFile
写入即可
同时也可以用ejs来进行NPM包的安装,使用如下,直接在ejs文件里用相应的语法进行更新package.json
文件:
{
"name": "<%= name %>",
"version": "<%= answers.version %>",
"description": "<%= answers.description %>",
"main": "index.js",
"scripts": {
"dev": "vuepress dev docs",
"build": "vuepress build docs",
"deploy": "bash ./deploy.sh"
},
"author": "<%= answers.author %>",
"license": "ISC",
"dependencies": {
"vue": "^2.7.0",
"vuepress": "^1.9.7",
"vue-template-compiler": "^2.6.10"<% if(answers.plugins.length) { %><%= "," %><% } %>
<%_ answers.plugins.forEach(function(item, index, arr){ -%>
<% if(item === "backToTop") { %><%- '"@vuepress/plugin-back-to-top": "^1.9.7"' %><% if(arr.at(-1) !== 'backToTop') { %><%= "," %> <% } %><% } -%>
<%_ if(item === "codeCopy") { %><%- '"vuepress-plugin-code-copy": "^1.0.6"' %><% if(arr.at(-1) !== 'codeCopy') { %><%= "," %> <% } %><% } -%>
<%_ if(item === "readingProgress") { %><%- '"vuepress-plugin-reading-progress": "^1.0.10"' %><% if(arr.at(-1) !== 'readingProgress') { %><%= "," %> <% } %><% } -%>
<%_ if(item === "cutePet") { %><%- '"@vuepress-reco/vuepress-plugin-kan-ban-niang": "^1.0.5"' %><% } %>
<%_ }); -%>
}
}
官方文档
GitHub平台地址:https://github.com/mde/ejs
NPM平台地址:https://www.npmjs.com/package/ejs
# 终端提示功能
首先需要用到的库是——Inquirer
需要在终端运行指令npm install inquirer
,然后在相应页面导入:
const inquirer = require('inquirer');
简单的使用如下:
inquirer
.prompt([{
type: 'list',
name: 'choice',
message: 'your choice:',
default: 0,
choices: [
{ value: 'hjy', name: 'hjy' },
{ value: 'lio', name: 'lio' }
]
}])
.then(answers => {
console.log('answers', answers.choice);
})
.catch(error => {
if(error.isTtyError) {
// Prompt couldn't be rendered in the current environment
} else {
// Something else went wrong
}
});
prompt类型
Defaults: input
Possible values: input
, number
, confirm
, list
, rawlist
, expand
, checkbox
, password
, editor
官方文档
GitHub平台地址:https://github.com/SBoudrias/Inquirer.js
NPM平台地址:https://www.npmjs.com/package/inquirer
# 其他的库
ora (opens new window):下载过程久的话可以用于显示加载动画
const ora = require('ora');
const spinner = ora('Loading unicorns').start();
setTimeout(() => {
spinner.color = 'yellow';
spinner.text = 'Loading rainbows';
}, 1000);
chalk (opens new window):给终端字体加上颜色
console.log(chalk.blue('Hello world!'));
log-symbols (opens new window):提供了一些符号
const ls = require('log-symbols');
console.log(ls.success, 'success');
console.log(ls.info, 'info');
console.log(ls.error, 'error');
console.log(ls.warning, 'warning');
结果如下:
✔ success
ℹ info
✖ error
⚠ warning
友情提醒
可能在终端上会更好看一点,这里效果不太好
# 打印安装node_modules的信息
具体思路就是开辟一个子进程,然后把子进程的打印内容通过管道(pipe
)传输到主进程中即可,具体如下:
const { spawn } = require('child_process');
const commandSpawn = (...args) => {
return new Promise((resolve, reject) => {
const child_process = spawn(...args);
child_process.stdout.pipe(process.stdout);
child_process.stderr.pipe(process.stderr);
child_process.on("close", () => {
// 子进程完成操作
resolve();
});
})
}
# 遇到的问题
# 文件夹的名字
最开始将文件夹的名字设置为我的脚手架后运行npm init -y
,出现如下报错信息:
karl@KarldeMacBook-Pro 我的脚手架 % npm init -y
npm ERR! Invalid name: "我的脚手架"
npm ERR! A complete log of this run can be found in:
npm ERR! /Users/karl/.npm/_logs/2022-07-12T08_26_15_839Z-debug.log
解决方法
提示中显示Invalid name,所以将文件名改为英文,然后重新输入命令npm init -y
即可
# 输入自定义指令无效
再运行npm link
后,然后在终端输入karl
会出现如下报错:
karl@KarldeMacBook-Pro Karl-cli % karl
zsh: /usr/local/bin/karl: bad interpreter: /user/bin/env: no such file or directory
解决方法
这是因为第一次写shebang的时候把usr写为了user,虽然usr是user的缩写,但是不能写错了,否则会出错,改过来后终端就能运行karl
这个自定义指令了🤡
# 克隆模板时报错
# 问题1 git clone失败
报错时使用的是如下的代码:
download('direct:https://github.com/ox4f5da2/karl-cli-template.git#main', project, { clone: true });
运行后就有如下报错:
karl@KarldeMacBook-Pro Karl-cli % karl create demo
/Users/karl/Documents/我的脚手架/Karl-cli/node_modules/git-clone/index.js:33
cb && cb(new Error("'git clone' failed with status " + status));
^
Error: 'git clone' failed with status 128
at ChildProcess.<anonymous> (/Users/karl/Documents/我的脚手架/Karl-cli/node_modules/git-clone/index.js:33:22)
at ChildProcess.emit (node:events:390:28)
at maybeClose (node:internal/child_process:1064:16)
at Socket.<anonymous> (node:internal/child_process:450:11)
at Socket.emit (node:events:390:28)
at Pipe.<anonymous> (node:net:672:12)
解决方法
这是因为没有在URL前加上direct:
这串标识符导致的,加上后运行又报错了😭
# 问题2 克隆无效
上述代码运行后只能产生空文件,然后等待很长时间也没有什么反应,终端也不停止运行,然后就知道又出问题了🤕,然后发现默认下载的是master分支上的代码,如果在其他分支上的话需要在最后加上#main
,也就是如下代码:
download('direct:https://github.com/ox4f5da2/karl-cli-template.git#main', project, { clone: true });
这样子就能在想要的文件夹下克隆模板啦😏
# 安装inquirer包使用失败
直接运行npm install inquirer
后,package.json中会显示安装的版本如下:
{
...
"dependencies": {
...
"inquirer": "^9.0.0"
}
}
接下来require并使用prompt函数的时候会报如下的错误信息[心累啊😞]:
/Users/karl/Documents/我的脚手架/Karl-cli/lib/core/prompt.js:1
const inquirer = require('inquirer');
^
Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/karl/Documents/我的脚手架/Karl-cli/node_modules/inquirer/lib/inquirer.js from /Users/karl/Documents/我的脚手架/Karl-cli/lib/core/prompt.js not supported.
Instead change the require of inquirer.js in /Users/karl/Documents/我的脚手架/Karl-cli/lib/core/prompt.js to a dynamic import() which is available in all CommonJS modules.
at Object.<anonymous> (/Users/karl/Documents/我的脚手架/Karl-cli/lib/core/prompt.js:1:18)
at Object.<anonymous> (/Users/karl/Documents/我的脚手架/Karl-cli/lib/core/myCommandActions.js:9:18)
at Object.<anonymous> (/Users/karl/Documents/我的脚手架/Karl-cli/lib/core/create.js:5:5)
at Object.<anonymous> (/Users/karl/Documents/我的脚手架/Karl-cli/index.js:9:23) {
code: 'ERR_REQUIRE_ESM'
}
看报错信息写着代替require
而使用import
,那么就简单了,在package.json中加入"type": "module"
就解决了,事情真有这么简单嘛?[我看未必,不出意外的话要出意外了]果然,虽然inquirer可以正常使用了,但是其他require的其他包不能用了,因为只能用import导入,太麻烦了😞,然后给报了如下错误信息:
file:///Users/karl/Documents/%E6%88%91%E7%9A%84%E8%84%9A%E6%89%8B%E6%9E%B6/Karl-cli/index.js:5
const program = require('commander');
^
ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension and '/Users/karl/Documents/我的脚手架/Karl-cli/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
at file:///Users/karl/Documents/%E6%88%91%E7%9A%84%E8%84%9A%E6%89%8B%E6%9E%B6/Karl-cli/index.js:5:17
at ModuleJob.run (node:internal/modules/esm/module_job:183:25)
at async Loader.import (node:internal/modules/esm/loader:178:24)
at async Object.loadESM (node:internal/process/esm_loader:68:5)
at async handleMainPromise (node:internal/modules/run_main:63:12)
最终的解决方法
这是由于依赖版本的问题导致的,所以只要降低版本即可,然后打开NPM平台,搜索inquirer,查看历史Versions,选择了8.2.4
的版本,然后运行npm install inquirer@8.2.4
,完美解决问题😉
# 自动执行npm install命令
原本自执行函数用的如下方式:
await commandSpawn('npm', ['install']);
这个在Mac电脑上可以正常执行,但是在windows电脑上会报错,具体原因是因为在Windows电脑上,执行npm
命令的时候其实执行的是npm.cmd
,在Mac上执行的是npm
。所以可以用process.platform
判断平台然后执行,更改后如下:
// 自动安装node_modules文件夹
const command = process.platform === 'win32' ? 'npm.cmd' : 'npm';
await commandSpawn(command, ['install']);
不出意外的话还是出了意外😯,node_modules文件夹没有生成成功,原因是需要填写cwd
参数,那加上就好了,最后版本如下:
// 自动安装node_modules文件夹
const command = process.platform === 'win32' ? 'npm.cmd' : 'npm';
await commandSpawn(command, ['install'], { cwd: `./${project}` });
← Chrome插件 vuepress快速上手 →