Nodejs 第二十二章 脚手架

跟着现代网页应用程序的杂乱性添加,前端开发的杂乱度也相应提高。在这样的背景下,前端脚手架(Scaffolding)东西的出现就成为了开发流程中的一个必要东西

脚手架出现的原因

  1. 项目结构标准化:在没有脚手架的时分,每个开发者或团队或许会有自己的办法去安排项目结构,这导致了协作和保护的困难。脚手架协助一致项目结构,使得新成员加入项目和了解代码变得更容易。
  2. 主动化流程:前端项目通常需求一系列重复性的设置过程,包含装备文件的创立、目录结构的设置等。脚手架可以主动化这些流程,提高功率。
  3. 快速发动新项目:脚手架可以快速生成项目根底架构和文件,使开发者可以直接进入开发阶段,削减重复性作业。
  4. 技能栈整合:现代前端开发往往触及多个技能栈的整合,如React、Vue、Angular、Babel、Webpack等,脚手架可以预先装备好这些东西的整合,削减开发者的装备负担。
  5. 规范开发流程:脚手架可以强制履行代码风格、提交信息格局等规范,坚持团队开发风格的一致性。

把握脚手架的必要性

  1. 提高开发功率:经过削减重复和繁琐的初始化作业,脚手架让开发者可以更快地开端实践的开发作业。
  2. 削减错误:手动创立项目结构和装备或许会引进错误。脚手架经过主动化过程,下降了犯错的时机。
  3. 教育和学习:关于新手开发者,脚手架供给了学习项目结构和装备的时机,也协助他们了解职业最佳实践。
  4. 适应新技能:跟着新技能和东西的不断出现,脚手架协助开发者适应新的技能生态,保护项目的现代化。
  5. 坚持竞争力:在剧烈的市场竞争中,可以快速发动和交给项目是至关重要的。脚手架东西供给了这一才能,确保团队可以快速呼应市场需求。

而了解脚手架最快的办法便是自己完结一遍,那流程不就通透了,有哪个前端不想具有自己的一套脚手架,在这一章节你会用到和学到非常多的第三方库

  • 让咱们来看一个经过npm init vue初始化后的vue脚手架吧!

Nodejs 第二十二章 脚手架

  • 完结一个具体完好的脚手架是很困难的,但经过一个小突破,完结以点破面却是可以下降难度,那就让咱们正式开端吧!

东西介绍

  • 让咱们来看下在接下来搭建脚手架中,会用到的第三方库

  • 在运用之前,一定要记住先下载,毕竟这些都是第三方库

    • 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

Nodejs 第二十二章 脚手架

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);
   }
});

运用过程

  1. 引进库

    • 运用require办法引进download-git-repo模块。
  2. 设置库房来历

    • repository变量设置为从GitHub下载Node.js项目的master分支。
    • 格局遵从download-git-repo的规定,通常是platform:user/repo#branch
  3. 设置方针目录

    • destination变量界说了下载文件的存放途径,这里是当时目录下的node-repo文件夹。
  4. 下载库房

    • 调用download函数,传入上面界说的repositorydestination
    • 第三个参数{ clone: true }指定运用git clone进行下载,这通常更快,并能保存.git目录。
    • 该函数异步履行,供给了一个回调函数来处理完结后的成功或失败情况。

编写脚手架

脚手架需求

  • 编写的脚手架需求满意以下几点要求:

    1. 不运用node最初的指令,而是自界说的指令去履行咱们的脚本
    2. 在指令后边能顺便参数例如-V –help 等功用去和指令行交互
    3. 依据搭配好的装备,能做到去下载模板到本地
  • 依据前面第三方库的介绍,我想咱们是可以完结的了

自界说脚本

  • 想要完结第一点需求,咱们需求在文件最上面加上一行代码
#!/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) 映射在一起了

Nodejs 第二十二章 脚手架

  • 那此刻的话,其实还不能运用。因为咱们还没将这个指令挂载到大局,所以说履行的时分是找不到这个指令的

    • 这个时分就可以经过npm link创立一个软链接,把咱们的文件挂载到大局上了。想要挂载记住package.json文件是要有name这个特点才可以的
    • 这样就可以运用了

Nodejs 第二十二章 脚手架

  • 然后咱们能看到,欸?为什么前面连着咱们的途径都打印出来了,并且console.log()自身也显现出来了?

    • 这是因为咱们忘记在最上面加上#!/usr/bin/env node
    • 像下图这样就彻底没问题了

Nodejs 第二十二章 脚手架

创立自己顺便参数

  • 这个便是咱们的第二步操作了

    • 咱们现已完结了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拿到了终端输入的参数

Nodejs 第二十二章 脚手架

  • 经过成果,咱们可以看到确实是输出了版别号了。但这个版别号是写死的,正常情况下哪里可以写死呢?咱们应该要从package.json中去获取才行

    • 因为咱们在package.json中进行装备了type为modules了,所以咱们只能运用ESM的写法
    • 运用fs模块获取package.json文件的内容,且因为获取到的内容是不是JSON格局的,咱们没办法直接拿到里面的version版别,所以需求运用JSON.parse进行转化一下
#!/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)

Nodejs 第二十二章 脚手架

  • 经过这过程进行操作,很显然是成功了
  • 那咱们接下来就按部就班的,当用户进行创立项目的时分,咱们让他起一个项目名称和挑选是否选用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)

Nodejs 第二十二章 脚手架

  • 经过这样,咱们简略的自界说了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)

Nodejs 第二十二章 脚手架

  • 而第二个prompt的type类型是一个可选项confirm,Y为Yes,n为No

Nodejs 第二十二章 脚手架

  • 当咱们输入结束之后,成果会以Promise的办法,以then的办法进行返还成果,咱们接纳到的内容则是一个方针方法的数据

Nodejs 第二十二章 脚手架

验证途径

  • 当然,咱们现已完结了一大半的功用了,此刻咱们需求进行一点鸿沟条件的判别

    • 咱们创立的这个模板,是否会与当时文件夹内的文件名发送抵触?
    • 关于这个验证途径的函数办法,咱们就写在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模板,挑选不同的模板分支进行下载到本地

Nodejs 第二十二章 脚手架

为什么需求direct:前缀

  1. 绕过预设的解析器

    • download-git-repo默认支撑GitHub, GitLab, 和Bitbucket的简写方法,例如github:user/repogitlab:user/repo
    • 运用direct:前缀答应咱们绕过这种主动解析,直接指向一个具体的Git库房URL。
  2. 明确指定库房位置

    • 当从一个不属于上述三大保管渠道的Git库房下载时,例如自保管的Git库房或小众渠道,需求明确指出完好的Git URL。
    • 这样download-git-repo就不会尝试去解析URL,而是直接运用咱们供给的链接。
  3. 运用完好的Git URL

    • 经过运用完好的Git URL,可以更精确地操控下载的库房和分支。
    • 这种办法相同支撑拜访私有库房,只要URL中包含了满足的权限信息(如在URL中嵌入拜访令牌)。

ora的运用

  • 为了在下载模板内容到本地的时分有一个加载动画,避免等待太过单调.咱们可以运用ora进行装备

    • 在utils东西文件中,导入ora:const spinner = 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)
})
  • 这样就有下载的动画了

Nodejs 第二十二章 脚手架

  • 进行完好过程的流程,可以看到咱们现已成功了

Nodejs 第二十二章 脚手架

  • 同时,咱们需求验证一下当目录下现已存在该文件名的时分,咱们前面设置的验证途径鸿沟判别是否收效

    • 经过下图,也是输出了文件夹现已存在,没有持续拉取代码进行掩盖本地文件

Nodejs 第二十二章 脚手架

完好代码

  • 那经过上面的过程流程,咱们也是完好的完结了一次怎么自己装备脚手架,以下便是我装备脚手架用到的完好代码

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"
  }
}