浅入vue脚手架 手把手教你撸一个简单脚手架

2022-08-07 10:51:58 浏览数 (1)

nodejs是个非常好用的工具,同时对我们前端同学来说学习成本低,非常友善,可以使用js来开发服务端,同时兼顾前端,实现了语言统一化,这里我不展开说了,主要展开说一下脚手架是怎么实现的。前端的同学想必都使用过vue脚手架(vue-cli),一条简单的命令vue init 就可以将一个简单的单页面应用包括webpack的简单配置全部搭建好并且你只用关注开发层面的东西(如果没有什么特殊的要求的话),他的实现原理其实也不难。先上vue脚手架的原理图:

整个vue init大致流程如我上图所示,应该还是比较好理解的。这里我大致阐述一下大致的流程。

  1. vue-cli会先判断你的模板在远程github仓库上还是在你的本地某个文件里面,若是本地文件夹则会立即跳到第3步,反之则走第2步。
  2. 第2步会判断是否为官方模板,官方模板则会从官方github仓库中下载模板到本地的默认仓库下,即根目录下.vue-templates文件夹下。
  3. 第3步则读取模板目录下meta.js或者meta.json文件,根据里面的内容会询问开发者,根据开发者的回答,确定一些修改。
  4. 根据模板内容以及开发者的回答,渲染出项目结构并生成到指定目录。

引用:https://blog.csdn.net/sinat_17775997/article/details/84099731

脚手架的优势: 减少重复性的工作,不再需要复制其他项目再删除无关代码,或者从零创建一个项目和文件。你可以将常用的组件、工具类、样式等全部抽离出来放在git或者其他的模板库里,再用脚手架进行拉取,这样开发类似风格的新业务时候就不需要复制其他的代码。

开始搭建我们的node脚手架

首先新建文件夹,cd进该文件夹并且npm init 初始化一个node项目包括项目名称、版本、作者、依赖等相关信息。他会在当前目录下生成一个package.json文件。

bin文件的作用: 很多包都有一个或多个可执行的文件,希望放在PATH中,(实际上,就是这个功能让npm可执行的)。 当你要用这个功能时,需要给package.json中的bin字段添加一个命令名,并指向需要执行的文件(即后文的入口文件)。初始化的时候npm会将他链接到prefix/bin(全局初始化)或者./node_modules/.bin/(本地初始化)。 引用:https://blog.csdn.net/weixin_34357436/article/details/88811435 比如:我们在bin目录下新建一个index.js文件(当前目录也可以,随便你)

代码语言:javascript复制
  "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

最后附上运行效果图:

0 人点赞