nodejs是个非常好用的工具,同时对我们前端同学来说学习成本低,非常友善,可以使用js来开发服务端,同时兼顾前端,实现了语言统一化,这里我不展开说了,主要展开说一下脚手架是怎么实现的。前端的同学想必都使用过vue脚手架(vue-cli),一条简单的命令vue init 就可以将一个简单的单页面应用包括webpack的简单配置全部搭建好并且你只用关注开发层面的东西(如果没有什么特殊的要求的话),他的实现原理其实也不难。先上vue脚手架的原理图:
整个vue init大致流程如我上图所示,应该还是比较好理解的。这里我大致阐述一下大致的流程。
- vue-cli会先判断你的模板在远程github仓库上还是在你的本地某个文件里面,若是本地文件夹则会立即跳到第3步,反之则走第2步。
- 第2步会判断是否为官方模板,官方模板则会从官方github仓库中下载模板到本地的默认仓库下,即根目录下.vue-templates文件夹下。
- 第3步则读取模板目录下meta.js或者meta.json文件,根据里面的内容会询问开发者,根据开发者的回答,确定一些修改。
- 根据模板内容以及开发者的回答,渲染出项目结构并生成到指定目录。
引用:https://blog.csdn.net/sinat_17775997/article/details/84099731
脚手架的优势: 减少重复性的工作,不再需要复制其他项目再删除无关代码,或者从零创建一个项目和文件。你可以将常用的组件、工具类、样式等全部抽离出来放在git或者其他的模板库里,再用脚手架进行拉取,这样开发类似风格的新业务时候就不需要复制其他的代码。
开始搭建我们的node脚手架
首先新建文件夹,cd进该文件夹并且npm init 初始化一个node项目包括项目名称、版本、作者、依赖等相关信息。他会在当前目录下生成一个package.json文件。
代码语言:javascript复制bin文件的作用: 很多包都有一个或多个可执行的文件,希望放在PATH中,(实际上,就是这个功能让npm可执行的)。 当你要用这个功能时,需要给package.json中的bin字段添加一个命令名,并指向需要执行的文件(即后文的入口文件)。初始化的时候npm会将他链接到prefix/bin(全局初始化)或者./node_modules/.bin/(本地初始化)。 引用:https://blog.csdn.net/weixin_34357436/article/details/88811435 比如:我们在bin目录下新建一个index.js文件(当前目录也可以,随便你)
"bin": {
"cli": "./bin/index.js"
},
实际上就是相当于一个入口文件,这个入口文件就是他的可执行文件,你可以将其他js引入该文件中然后通过入口文件暴露出去,上面代码指定,cli命令对应的可执行文件为 bin 子目录下的 index.js。npm会寻找这个文件,在node_modules/.bin/目录下建立符号链接。在上面的例子中,index.js会建立符号链接node_modules/.bin/index。由于node_modules/.bin/目录会在运行时加入系统的PATH变量,因此在运行npm时,就可以不带路径,直接通过命令来调用这些脚本。 更多package.json配置:https://javascript.ruanyifeng.com/nodejs/packagejson.html#toc4
到现在代码是这样的:
代码语言:javascript复制{
"name": "ljh",
"version": "1.0.0",
"description": "nodejs",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"bin": {
"ljh-cli":"./index.js"
},
"author": "",
"license": "ISC"
}
然后我们在安装一些npm包给我们提供一些方便好用的api。 commander.js:可以自动的解析命令和参数,用于处理用户输入的命令。 download-git-repo:下载并提取 git 仓库,用于下载项目模板。 Inquirer.js:通用的命令行用户界面集合,用于和用户进行交互。 handlebars.js:模板引擎,将用户提交的信息动态填充到文件中。 ora:下载过程久的话,可以用于显示下载中的动画效果。 chalk:可以给终端的字体加上颜色。 log-symbols:可以在终端上显示出 √ 或 × 等的图标。 fs:node内置的文件处理模块。 path:node内置的路径处理、解析模块。 child_process:node中创建子进程模块。 除此之外,还使用了nodejs的几个内置模块:fs、path、child_process
直接一条命令解决:
代码语言:javascript复制npm install commander download-git-repo inquirer handlebars ora chalk log-symbols shelljs -S
或
在package.json 锁死版本 直接 npm i
"dependencies": {
"chalk": "^4.1.0",
"commander": "^6.0.0",
"download-git-repo": "^3.0.2",
"handlebars": "^4.7.6",
"inquirer": "^7.3.3",
"log-symbols": "^4.0.0",
"ora": "^4.0.5",
"shelljs": "^0.8.4"
}`
之后再index.js中我们要在第一行(必须在第一行)加入:
代码语言:javascript复制 #!/usr/bin/env node
!/usr/bin/node是告诉操作系统执行这个脚本的时候,调用/usr/bin下的node解释器; !/usr/bin/env node这种用法是为了防止操作系统用户没有将node装在默认的/usr/bin路径里。当系统看到这一行的时候,首先会到env设置里查找node的安装路径,再调用对应路径下的解释器程序完成操作。 !/usr/bin/node相当于写死了node路径;
接下来开始编写我们的Index.js
代码语言:javascript复制const program = require('commander');
program.version('1.0.0', '-v, --version')
.command('init <name>')
.action((name) => {
console.log(name);
});
program.parse(process.argv);
调用program.version('1.0.0', '-v, --version')会将-v和--version添加到命令行中,调用时可通过带上该参数获取该脚手架的版本号(命令 -v/--version),调用comand('init ')定义初始化命令,name参数必传,作为项目的文件夹名,如 cli init Name action是执行command命令时发生的回调,参数为命令行中输入的name,即init 中的name,项目生成过程便发生在回调函数中。 现在可以通过调用node index.js init test,可以看到控制台中已经打印了输入的项目名,也就是test。 其中:program.parse(process.argv)解析命令行中的参数,解析出name,并传入action回调。
代码语言:javascript复制 inquirer.prompt([
{
name: 'description',
message: '请输入项目描述'
},
{
name: 'author',
message: '请输入作者名称'
}
]).then(res => {
console.log(res)
})
通过inquirer模块的 prompt() 中,可以实现与用户的交互,并且有回调可以进行后续的处理。问题的类型为 input 就是输入类型(不填默认input),name 就是作为答案对象中的 key,message 就是问题了,用户输入的答案就在后面的回调返回的参数中。
代码语言:javascript复制后续还有一些美化处理和动画效果就不一一讲解,上完整代码:
#!/usr/bin/env node
const program = require('commander');// commander.js,可以自动的解析命令和参数,用于处理用户输入的命令。
const inquirer = require('inquirer');// Inquirer.js,通用的命令行用户界面集合,用于和用户进行交互。
const symbols = require('log-symbols');// log-symbols,可以在终端上显示出 √ 或 × 等的图标。
// const download = require('download-git-repo');// download-git-repo,下载并提取 git 仓库,用于下载项目模板。
const handlebars = require('handlebars');// handlebars.js,模板引擎,将用户提交的信息动态填充到文件中。
const chalk = require('chalk');// chalk,可以给终端的字体加上颜色。
const ora = require('ora');// ora,下载过程久的话,可以用于显示下载中的动画效果
const shell = require('shelljs');// shelljs 做的事就是自动化,从耗时的重复性常规动作里解放出来
const child_process = require('child_process');// child_process 创建异步进程(子进程) exec传递的是 command 或 可执行文件
const fs = require('fs');
// const path = require('path');
/**
* @description: program.version 调用该命令时(如 ljh-cli -v) 会携带出'1.0.0'
* @description: program.command 定义初始化命令(如 ljh-cli init <项目名>)
* @description: program.action action是执行command命令时发生的回调
* @param {type} node index.js init test == ljh-cli init ljh
* @return: program.parse(process.argv)解析命令行中的参数,解析出name,并传入action回调。
*/
program.version(chalk.green('♫ ===== Dark,ljh-cli ===== n version: 1.0.0'), '-v, --version').
command('init <name>').
action(name => {
console.log(name);
// fs.existsSync 如果路径存在,则返回 true,否则返回 false
if (!fs.existsSync(name)) {
console.log(chalk.magentaBright('正在创建项目...'));
inquirer.prompt([
{
name: 'description',
message: '请输入项目描述'
},
{
name: 'author',
message: '请输入作者名称'
}
]).then(res => {
console.log(res)
//ora、chalk模块也进行了一些视觉美化
const spinner = ora('正在下载模板...n');
spinner.start();
// 可以使用download 或 child_process
url = 'http://xxx.git'
child_process.exec('git clone ' url, function (err) {
if (err) {
spinner.fail();
console.log(symbols.error, chalk.red('模板下载失败n',err))
} else {
spinner.succeed();
// 将要移动的文件 移动到目标文件 xxx -> __dirname/<projectName>
// __dirname 当前模块的目录名
// shell你可以理解为一个cmd
shell.mv(__dirname '/xxx', __dirname '/' name)
const filename = `${name}/package.json`;
const meta = {
name,
description: res.description,
author: res.author
}
if (fs.existsSync(filename)) {
// 读取目标文件的package文件
const content = fs.readFileSync(filename).toString();
let dt = JSON.parse(content);
dt.name = meta.name;
dt.author = meta.author;
dt.description = meta.description
//改写package.json
fs.writeFile(filename,JSON.stringify(dt),'utf8',(err) => {
if(err)
console.log('写入失败,原因:',err);
else
console.log('写入成功')
})
console.log(symbols.success, chalk.green('项目初始化完成'));
} else {
console.log(symbols.error, chalk.red('package不存在'))
}
}
})
})
} else {
console.log(symbols.error, chalk.red('项目已存在'));
}
})
program.parse(process.argv);
最后,在当前目录下执行 npm link 将你的包链接到全局环境这条命令实际上就是npm的本地调试。你就可以愉快的使用脚手架了,之后可以在npm上发布,下载到全局就可以使用了。
参考文章: https://blog.csdn.net/weixin_34357436/article/details/88811435 https://blog.csdn.net/sinat_17775997/article/details/84099731
最后附上运行效果图: