Nodejs 第二十二章 脚手架
跟着现代网页应用程序的杂乱性添加,前端开发的杂乱度也相应提高。在这样的背景下,前端脚手架(Scaffolding)东西的出现就成为了开发流程中的一个必要东西
脚手架出现的原因
- 项目结构标准化:在没有脚手架的时分,每个开发者或团队或许会有自己的办法去安排项目结构,这导致了协作和保护的困难。脚手架协助一致项目结构,使得新成员加入项目和了解代码变得更容易。
- 主动化流程:前端项目通常需求一系列重复性的设置过程,包含装备文件的创立、目录结构的设置等。脚手架可以主动化这些流程,提高功率。
- 快速发动新项目:脚手架可以快速生成项目根底架构和文件,使开发者可以直接进入开发阶段,削减重复性作业。
- 技能栈整合:现代前端开发往往触及多个技能栈的整合,如React、Vue、Angular、Babel、Webpack等,脚手架可以预先装备好这些东西的整合,削减开发者的装备负担。
- 规范开发流程:脚手架可以强制履行代码风格、提交信息格局等规范,坚持团队开发风格的一致性。
把握脚手架的必要性
- 提高开发功率:经过削减重复和繁琐的初始化作业,脚手架让开发者可以更快地开端实践的开发作业。
- 削减错误:手动创立项目结构和装备或许会引进错误。脚手架经过主动化过程,下降了犯错的时机。
- 教育和学习:关于新手开发者,脚手架供给了学习项目结构和装备的时机,也协助他们了解职业最佳实践。
- 适应新技能:跟着新技能和东西的不断出现,脚手架协助开发者适应新的技能生态,保护项目的现代化。
- 坚持竞争力:在剧烈的市场竞争中,可以快速发动和交给项目是至关重要的。脚手架东西供给了这一才能,确保团队可以快速呼应市场需求。
而了解脚手架最快的办法便是自己完结一遍,那流程不就通透了,有哪个前端不想具有自己的一套脚手架,在这一章节你会用到和学到非常多的第三方库
- 让咱们来看一个经过
npm init vue
初始化后的vue脚手架吧!
- 完结一个具体完好的脚手架是很困难的,但经过一个小突破,完结以点破面却是可以下降难度,那就让咱们正式开端吧!
东西介绍
-
让咱们来看下在接下来搭建脚手架中,会用到的第三方库
-
在运用之前,一定要记住先下载,毕竟这些都是第三方库
- npm i commander inquirer ora download-git-repo
commander
Commander 是一个用于构建指令行东西的 npm 库。它供给了一种简略而直观的办法来创立指令行接口,并处理指令行参数和选项。运用 Commander,可以轻松界说指令、子指令、选项和协助信息。它还可以处理指令行的交互,运用户可以与你的指令行东西进行交互
// 引进 Commander 库
const { program } = require('commander');
// 设置你的CLI东西的版别和描绘
program
.version('0.0.1')
.description('An example CLI tool built with Commander');
// 界说一个指令和它的参数,以及履行该指令时要运转的动作
program
.command('greet <name>')//这里的<xxx>中,xxx是可以随意命名的,最终经过action办法可以拿到xxx的内容
.description('say hello to <name>')//这是一个给用户的描绘
.action((name) => {
console.log(`Hello, ${name}!`);
});
// 界说一个带选项的指令
program
.command('order <type>')
.description('order a pizza')
.option('-s, --size <size>', 'choose the size of the pizza', 'medium')
.action((type, options) => {
console.log(`Order received: ${options.size} ${type}`);
});
// 解析指令行参数
program.parse(process.argv);
// 1.引进Commander库,并从中获取program方针。
// 2.设置CLI东西的版别和描绘,这会显现在协助信息中。
// 3.用command()办法界说一个名为greet的指令,接受一个参数<name>。当greet指令被调用时,action()中的函数会履行,并打印出问候语。
// 4.又界说了一个order指令,它带有一个<type>参数和一个--size选项。假如用户不指定--size,它会默认为medium。指令履行时会反应订单信息。
// 5.最终,program.parse()解析指令行参数并触发对应的指令。
- 要运用这个脚本,咱们可以将它保存为一个
.js
文件,并在指令行中经过Node.js运转。例如,假如保存为cli.js
,可以这样用:
node cli.js greet XiaoYu
//然后会输出 Hello, XiaoYu!
//其间greet是指令,而XiaoYu是参数
- 或许,可以这样运用
order
指令:
node cli.js order pizza --size large
//会输出Order received: large pizza
Inquirer
Inquirer 是一个强壮的指令行交互东西,用于与用户进行交互和搜集信息。它供给了各种丰富的交互式提示(如输入框、挑选列表、承认框等),可以协助你构建灵敏的指令行界面。经过 Inquirer,你可以向用户提出问题,获取用户的输入,并依据用户的答复采纳相应的操作。
Inquirer支撑多种类型的问题,例如:
-
input
:普通的文本输入。 -
number
:数字输入。 -
confirm
:承认框,用户输入yes或no。 -
list
:答应用户从列表中挑选一个选项。 -
checkbox
:答应用户挑选多个选项。 -
password
:隐藏输入内容。
// 引进Inquirer库
const inquirer = require('inquirer');
// 界说一组问题
const questions = [
{
type: 'input', // 文本输入类型
name: 'name', // 接纳输入值的变量名
message: 'What is your name?', // 显现给用户的提示信息
validate: function(value) { // 输入验证函数
var pass = value.match(
/^[a-zA-Zs]+$/i
);
if (pass) {
return true;
}
return 'Please enter a valid name (alphabetical characters only)';
}
},
{
type: 'confirm', // 承认类型
name: 'likeNode', // 接纳输入值的变量名
message: 'Do you like Node.js?', // 显现给用户的提示信息
default: true // 默认值
},
{
type: 'list', // 列表挑选类型
name: 'favoriteFramework', // 接纳输入值的变量名
message: 'Which Node.js framework do you prefer?', // 显现给用户的提示信息
choices: ['Express', 'Koa', 'Hapi', 'Sails'], // 可挑选的列表项
filter: function(val) { // 过滤处理输入值
return val.toLowerCase();
}
}
];
// 运用prompt办法显现问题并接纳用户的输入
inquirer.prompt(questions).then(answers => {
// 输出用户的答复
console.log(`Hello ${answers.name}, you said ${answers.likeNode ? 'yes' : 'no'} to liking Node.js.`);
console.log(`Your favorite Node.js framework is ${answers.favoriteFramework}.`);
});
ora
Ora 是一个用于在指令行界面显现加载动画的 npm 库。它可以协助你在履行耗时的使命时供给一个友爱的加载状况提示。Ora 供给了一系列自界说的加载动画,如旋转器、进度条等,你可以依据需求挑选合适的加载动画效果,并在使命履行期间显现对应的加载状况。
-
创立一个根本的加载指示器(spinner)
- 咱们运用start()办法进行简略的旋转(也可以完结更多咱们想要的加载状况),并且调用成功或许失败的办法来进一步完结后续提示
// 引进 ora 库
const ora = require('ora');
// 创立一个 spinner 方针并开端旋转
const spinner = ora('Loading unicorns').start();
// 模仿一项长时间运转的使命,比如数据加载或许杂乱计算
setTimeout(() => {
// 使命完结后,可以调用 succeed 办法标记成功完结
spinner.succeed('Unicorns loaded successfully');
// 或许,假如使命失败,可以调用 fail 办法
// spinner.fail('Failed to load unicorns');
}, 2000); // 模仿使命运转了 2000 毫秒(2秒)
download-git-repo
Download-git-repo 是一个用于下载 Git 库房的 npm 库。它供给了一个简略的接口,可以方便地从远程 Git 库房中下载项目代码。你可以指定要下载的库房和方针目录,并可挑选指定分支或标签。Download-git-repo 支撑从各种 Git 保管渠道(如 GitHub、GitLab、Bitbucket 等)下载代码。
// 引进 download-git-repo 库
const download = require('download-git-repo');
// 界说库房来历,格局为:'直接库房地址或渠道:用户名/库房名#分支'
// 例如,从GitHub下载Node.js库房的master分支
const repository = 'github:nodejs/node#master';
// 界说方针目录,即将库房代码下载到哪里
const destination = './node-repo';
// 调用 download 函数下载库房
download(repository, destination, { clone: true }, function (err) {
if (err) {
console.error('Failed to download repo ' + repository + ': ' + err.message);
} else {
console.log('Successfully downloaded ' + repository + ' into ' + destination);
}
});
运用过程
-
引进库:
- 运用
require
办法引进download-git-repo
模块。
- 运用
-
设置库房来历:
-
repository
变量设置为从GitHub下载Node.js项目的master分支。 - 格局遵从
download-git-repo
的规定,通常是platform:user/repo#branch
。
-
-
设置方针目录:
-
destination
变量界说了下载文件的存放途径,这里是当时目录下的node-repo
文件夹。
-
-
下载库房:
- 调用
download
函数,传入上面界说的repository
和destination
。 - 第三个参数
{ clone: true }
指定运用git clone
进行下载,这通常更快,并能保存.git
目录。 - 该函数异步履行,供给了一个回调函数来处理完结后的成功或失败情况。
- 调用
编写脚手架
脚手架需求
-
编写的脚手架需求满意以下几点要求:
- 不运用node最初的指令,而是自界说的指令去履行咱们的脚本
- 在指令后边能顺便参数例如-V –help 等功用去和指令行交互
- 依据搭配好的装备,能做到去下载模板到本地
- 依据前面第三方库的介绍,我想咱们是可以完结的了
自界说脚本
- 想要完结第一点需求,咱们需求在文件最上面加上一行代码
#!/usr/bin/env node
-
这行代码是很重要的,代表着node履行文件从显式变成了隐式
- 相当于告诉操作系统,我履行自界说指令的时分,你帮我用node去履行这个文件
- 比如说
node xiaoyu-cli xxx
就可以变成xiaoyu-cli xxx
-
加上之后,咱们就需求去
package.json
文件中装备一下这个指令- 就算是类似发动指令npm run dev都需求在package.json中进行装备,而咱们的自界说脚本也是相同的
- 首先咱们承认一下type类型为module,然后进行bin装备
- 经过bin装备,咱们就将咱们的自界说指令(xiaoyu-cli) 和进口文件(index.js) 映射在一起了
-
那此刻的话,其实还不能运用。因为咱们还没将这个指令挂载到大局,所以说履行的时分是找不到这个指令的
- 这个时分就可以经过npm link创立一个软链接,把咱们的文件挂载到大局上了。想要挂载记住package.json文件是要有name这个特点才可以的
- 这样就可以运用了
-
然后咱们能看到,欸?为什么前面连着咱们的途径都打印出来了,并且console.log()自身也显现出来了?
- 这是因为咱们忘记在最上面加上
#!/usr/bin/env node
了 - 像下图这样就彻底没问题了
- 这是因为咱们忘记在最上面加上
创立自己顺便参数
-
这个便是咱们的第二步操作了
- 咱们现已完结了
xiaoyu-cli
最初的自界说指令,那怎么更近一步,变成xiaoyu-cli xxxx
?其间xxxx是参数 - 这时分咱们就需求用到第一个库commander了,用这个库来解析咱们的参数
- 咱们现已完结了
-
那咱们要怎么获取到咱们输入的这些参数?
- 在前面中,咱们process的那章节,就经过process.argv获取到了后边输入的参数,返回一个数组
- 将参数赋值给这个库,是放在最终面的
- 因为在前面咱们需求先设置好自界说的参数装备,最终再把拿到的参数和咱们现已准备好的参数装备进行配合。因为代码是由上向下履行的,假如赋值参数放在前面,参数装备信息在后边。则前面匹配了个孤寂
- 那咱们就先完结一个版别号的获取
#!/usr/bin/env node
import { program } from "commander"
program.version('1.0.0')
program.parse(process.argv)//经过process.argv拿到了终端输入的参数
-
经过成果,咱们可以看到确实是输出了版别号了。但这个版别号是写死的,正常情况下哪里可以写死呢?咱们应该要从package.json中去获取才行
- 因为咱们在package.json中进行装备了
type
为modules了,所以咱们只能运用ESM的写法 - 运用fs模块获取
package.json
文件的内容,且因为获取到的内容是不是JSON格局的,咱们没办法直接拿到里面的version版别,所以需求运用JSON.parse进行转化一下
- 因为咱们在package.json中进行装备了
#!/usr/bin/env node
import { program } from "commander"
import fs from "node:fs"
//获取到package.json文件的内容
let json = fs.readFileSync('./package.json', 'utf-8')
//进行格局转化(JSON格局)
json = JSON.parse(json);
//获取文件内JSON内容中的version版别
program.version(json.version)
program.parse(process.argv)
- 经过这过程进行操作,很显然是成功了
- 那咱们接下来就按部就班的,当用户进行创立项目的时分,咱们让他起一个项目名称和挑选是否选用TS的模板
#!/usr/bin/env node
import { program } from "commander"
import fs from "node:fs"
let json = fs.readFileSync('./package.json', 'utf-8')
json = JSON.parse(json);
program.version(json.version)
program.command('create <projectName>').description('创立新项目').action(res => {
console.log(res);
})
program.parse(process.argv)
-
经过这样,咱们简略的自界说了create参数以及参数之后的内容进行打印输出
- 以这个为输进口,咱们创立项目之后,就可以开端给这个即即将创立的模板起目录姓名(或许叫做文件夹姓名)和是否选用TS模板
- 对其进行一个款式的优化,就完结一大半了
- 而款式的优化和互动,来自Inquirer这个第三方库,咱们要进行运用了
#!/usr/bin/env node
import { program } from "commander"
import inquirer from 'inquirer'
import fs from "node:fs"
let json = fs.readFileSync('./package.json', 'utf-8')
json = JSON.parse(json);
program.version(json.version)
program.command('create <projectName>').description('创立新项目').action(projectName => {
inquirer.prompt([
// 设置项目名称
{
type: 'input',
name: 'projectName',
message: '请输入项目名称',
default: projectName
},
// 设置是否选用TS模板
{
type: "confirm",
name: "isTS",
message: "项目是否支撑TypeScript"
}
]).then(answers => {
console.log(answers)
})
})
program.parse(process.argv)
- 而第二个prompt的type类型是一个可选项confirm,Y为Yes,n为No
- 当咱们输入结束之后,成果会以Promise的办法,以then的办法进行返还成果,咱们接纳到的内容则是一个方针方法的数据
验证途径
-
当然,咱们现已完结了一大半的功用了,此刻咱们需求进行一点鸿沟条件的判别
- 咱们创立的这个模板,是否会与当时文件夹内的文件名发送抵触?
- 关于这个验证途径的函数办法,咱们就写在utils.js文件中,然后导入index.js进口文件即可,避免进口文件的臃肿
import fs from 'node:fs'
//验证途径
export const checkPath = (path) => {
return fs.existsSync(path) ? true : false
}
- 然后导入到index.js中,且在then后进行判别
import { checkPath } from "./utils.js"
//前面的不触及内容进行省掉
.then(answers => {
// 判别当时创立的文件夹是否与已有文件夹重名
if(checkPath(answers.projectName)){
console.log("文件夹现已存在");
return
}
// 判别是否需求TS模板
if(answers.isTS){
console.log("TypeScript模板")
}
})
下载模板到本地
-
此刻依据现已设置好的内容,咱们要运用另一个第三方库**
download-git-repo
**,将放在网上的模板下载到本地了- 而这个下载逻辑,咱们也放到utils东西文件内
- 而第三个参数
{ clone: true }
指定运用git clone
进行下载,这通常更快,并能保存.git
目录
//下载,咱们就经过branch来确认分支,然后在git途径后边加上#分支,就能切到想要下载的分支上了
export const downloadTemp = (branch,project) => {
return new Promise((resolve,reject)=>{
//需求加上direct:这个前缀,而project则是咱们的项目目录名称
download(`direct:https://gitee.com/chinafaker/vue-template.git#${branch}`, project , { clone: true, }, function (err) {
if (err) {
reject(err)
console.log(err)
}
resolve()
})
})
}
- 经过这个gitee库房,提早建立好了分支,这样就可以依据是否挑选TS模板,挑选不同的模板分支进行下载到本地
为什么需求direct:
前缀
-
绕过预设的解析器:
-
download-git-repo
默认支撑GitHub
,GitLab
, 和Bitbucket
的简写方法,例如github:user/repo
或gitlab:user/repo
。 - 运用
direct:
前缀答应咱们绕过这种主动解析,直接指向一个具体的Git库房URL。
-
-
明确指定库房位置:
- 当从一个不属于上述三大保管渠道的Git库房下载时,例如自保管的Git库房或小众渠道,需求明确指出完好的Git URL。
- 这样
download-git-repo
就不会尝试去解析URL,而是直接运用咱们供给的链接。
-
运用完好的Git URL:
- 经过运用完好的Git URL,可以更精确地操控下载的库房和分支。
- 这种办法相同支撑拜访私有库房,只要URL中包含了满足的权限信息(如在URL中嵌入拜访令牌)。
ora的运用
-
为了在下载模板内容到本地的时分有一个加载动画,避免等待太过单调.咱们可以运用ora进行装备
- 在utils东西文件中,导入ora:
const spinner = ora('下载中...')
,然后放在下载函数中
- 在utils东西文件中,导入ora:
const spinner = ora('下载中...')
export const downloadTemp = (branch,project) => {
spinner.start()//经过这个办法运用加载动画
return new Promise(
//省掉...
spinner.succeed('下载完结')//在下载完了之后,给出下载成功的提示
)
}
- 然后在index进口文件中进行装备
import { checkPath , downloadTemp } from "./utils.js"
//无关内容现已省掉
.then(answers => {
// 判别是否需求TS模板
answers.isTs ? downloadTemp('ts', answers.projectName) : downloadTemp('js', answers.projectName)
})
- 这样就有下载的动画了
- 进行完好过程的流程,可以看到咱们现已成功了
-
同时,咱们需求验证一下当目录下现已存在该文件名的时分,咱们前面设置的验证途径鸿沟判别是否收效
- 经过下图,也是输出了文件夹现已存在,没有持续拉取代码进行掩盖本地文件
完好代码
- 那经过上面的过程流程,咱们也是完好的完结了一次怎么自己装备脚手架,以下便是我装备脚手架用到的完好代码
index.js
#!/usr/bin/env node
import { program } from "commander"
import inquirer from 'inquirer'
import fs from "node:fs"
import { checkPath , downloadTemp } from "./utils.js"
let json = fs.readFileSync('./package.json', 'utf-8')
json = JSON.parse(json);
program.version(json.version)
program.command('create <projectName>').description('创立新项目').action(projectName => {
inquirer.prompt([
// 设置项目名称
{
type: 'input',
name: 'projectName',
message: '请输入项目名称',
default: projectName
},
// 设置是否选用TS模板
{
type: "confirm",
name: "isTS",
message: "项目是否支撑TypeScript"
}
]).then(answers => {
// 判别当时创立的文件夹是否与已有文件夹重名
if(checkPath(answers.projectName)){
console.log("文件夹现已存在");
return
}
// 判别是否需求TS模板
answers.isTs ? downloadTemp('ts', answers.projectName) : downloadTemp('js', answers.projectName)
})
})
program.parse(process.argv)
utils文件
import fs from 'node:fs'
import download from 'download-git-repo'
import ora from 'ora'
const spinner = ora('下载中...')
//验证途径
export const checkPath = (path) => {
return fs.existsSync(path) ? true : false
}
//下载
export const downloadTemp = (branch,project) => {
spinner.start()
return new Promise((resolve,reject)=>{
download(`direct:https://gitee.com/chinafaker/vue-template.git#${branch}`, project , { clone: true, }, function (err) {
if (err) {
reject(err)
console.log(err)
}
resolve()
spinner.succeed('下载完结')
})
})
}
package.json文件
{
"name": "xiaoyu",
"main": "index.js",
"version": "1.0.1",
"scripts": {
"dev": "node index.js"
},
"type": "module",
"bin": {
"xiaoyu-cli":"src/index.js"
},
"description": "",
"dependencies": {
"commander": "^12.0.0",
"download-git-repo": "^3.0.2",
"inquirer": "^9.2.19",
"ora": "^8.0.1"
}
}