前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浅入vue脚手架 手把手教你撸一个简单脚手架

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

作者头像
饼干_
发布2022-08-07 10:51:58
1.3K0
发布2022-08-07 10:51:58
举报

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

最后附上运行效果图:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-05-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开始搭建我们的node脚手架
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档