开发一个Parcel-vue脚手架工具(详细步骤)


Posted in Javascript onSeptember 22, 2018

前言

像我们熟悉的 vue-cli,create-react-app 等脚手架,只需要输入简单的命令 vue init webpack project,即可快速帮我们生成一个初始项目。在实际工作中,我们可以定制一个属于自己的脚手架,来提高自己的工作效率。

为什么需要需要脚手架?

  • 减少重复性的工作,不再需要复制其他项目再删除无关代码,或者从零创建一个项目和文件。
  • 根据交互动态生成项目结构和配置文件等。
  • 多人协作更为方便,不需要把文件传来传去。

思路

要开发脚手架,首先要理清思路,脚手架是如何工作的?我们可以借鉴 vue-cli 的基本思路。vue-cli 是将项目模板放在 git 上,运行的时候再根据用户交互下载不同的模板,经过模板引擎渲染出来,生成项目。这样将模板和脚手架分离,就可以各自维护,即使模板有变动,只需要上传最新的模板即可,而不需要用户去更新脚手架就可以生成最新的项目。那么就可以按照这个思路来进行开发了。

第三方库

首先来看看会用到哪些库。

  • commander.js,可以自动的解析命令和参数,用于处理用户输入的命令。
  • download-git-repo,下载并提取 git 仓库,用于下载项目模板。
  • Inquirer.js,通用的命令行用户界面集合,用于和用户进行交互。
  • handlebars.js,模板引擎,将用户提交的信息动态填充到文件中。
  • ora,下载过程久的话,可以用于显示下载中的动画效果。
  • chalk,可以给终端的字体加上颜色。
  • log-symbols,可以在终端上显示出 √ 或 × 等的图标。

初始化项目

首先创建一个空项目,然后新建一个 index.js 文件,再执行 npm init 生成一个 package.json 文件。最后安装上面需要用到的依赖。

npm install commander download-git-repo inquirer handlebars ora chalk log-symbols -S

处理命令行

node.js 内置了对命令行操作的支持,在 package.json 中的 bin 字段可以定义命令名和关联的执行文件。所以现在 package.json 中加上 bin 的内容:

{
 "name": "suporka-parcel-vue",
 "version": "1.0.0",
 "description": "a vue cli which use parcel to package object",
 "bin": {
  "suporka-parcel-vue": "index.js"
 },
 ...
}

然后在 index.js 中来定义 init 命令:

#!/usr/bin/env node
const program = require('commander');

program.version('1.0.0', '-v, --version')
  .command('init <name>')
  .action((name) => {
    console.log(name);
  });
program.parse(process.argv);

调用 version('1.0.0', '-v, --version') 会将 -v 和 --version 添加到命令中,可以通过这些选项打印出版本号。

调用 command('init <name>') 定义 init 命令,name 则是必传的参数,为项目名。

action() 则是执行 init 命令会发生的行为,要生成项目的过程就是在这里面执行的,这里暂时只打印出 name。

其实到这里,已经可以执行 init 命令了。我们来测试一下,在同级目录下执行:

node index.js init HelloWorld

可以看到命令行工具也打印出了 HelloWorld,那么很清楚, action((name) => {}) 这里的参数 name,就是我们执行 init 命令时输入的项目名称。

命令已经完成,接下来就要下载模板生成项目结构了。

下载模板

download-git-repo 支持从 Github、Gitlab 和 Bitbucket 下载仓库,各自的具体用法可以参考官方文档。

命令行交互

命令行交互功能可以在用户执行 init 命令后,向用户提出问题,接收用户的输入并作出相应的处理。这里使用 inquirer.js 来实现。

const inquirer = require('inquirer');
inquirer.prompt([
  {
    name: 'description',
    message: 'Input the object description'
  },
  {
    name: 'author',
    message: 'Input the object author'
  }
  ]).then((answers) => {
  console.log(answers.author);
})

通过这里例子可以看出,问题就放在 prompt() 中,问题的类型为 input 就是输入类型,name 就是作为答案对象中的 key,message 就是问题了,用户输入的答案就在 answers 中,使用起来就是这么简单。更多的参数设置可以参考官方文档。

通过命令行交互,获得用户的输入,从而可以把答案渲染到模板中。

渲染模板

这里用 handlebars 的语法对模板中的 package.json 文件做一些修改

{
 "name": "{{name}}",
 "version": "1.0.0",
 "description": "{{description}}",
 "scripts": {
  "test": "echo \"Error: no test specified\" && exit 1"
 },
 "author": "{{author}}",
 "license": "ISC"
}

并在下载模板完成之后将用户输入的答案渲染到 package.json 中

视觉美化

在用户输入答案之后,开始下载模板,这时候使用 ora 来提示用户正在下载中。

const ora = require('ora');
// 开始下载
const spinner = ora('正在下载模板...');
spinner.start();

// 下载失败调用
spinner.fail();

// 下载成功调用
spinner.succeed();

然后通过 chalk 来为打印信息加上样式,比如成功信息为绿色,失败信息为红色,这样子会让用户更加容易分辨,同时也让终端的显示更加的好看。

const chalk = require('chalk');
console.log(chalk.green('项目创建成功'));
console.log(chalk.red('项目创建失败'));

除了给打印信息加上颜色之外,还可以使用 log-symbols 在信息前面加上 √ 或 × 等的图标

const chalk = require('chalk');
const symbols = require('log-symbols');
console.log(symbols.success, chalk.green('项目创建成功'));
console.log(symbols.error, chalk.red('项目创建失败'));

完整示例

// index.js
#!/usr/bin/env node
// 处理用户输入的命令
const program = require('commander');
// 下载模板
const download = require('download-git-repo');
// 问题交互
const inquirer = require('inquirer');
// node 文件模块
const fs = require('fs');
// 填充信息至文件
const handlebars = require('handlebars');
// 动画效果
const ora = require('ora');
// 字体加颜色
const chalk = require('chalk');
// 显示提示图标
const symbols = require('log-symbols');
// 命令行操作
var shell = require("shelljs");

program.version('1.0.1', '-v, --version')
 .command('init <name>')
 .action((name) => {
  if (!fs.existsSync(name)) {
   inquirer.prompt([
    {
     name: 'description',
     message: 'Input the object description'
    },
    {
     name: 'author',
     message: 'Input the object author'
    }
   ]).then((answers) => {
    const spinner = ora('Downloading...');
    spinner.start();
    download('zxpsuper/suporka-parcel-vue', name, (err) => {
     if (err) {
      spinner.fail();
      console.log(symbols.error, chalk.red(err));
     } else {
      spinner.succeed();
      const fileName = `${name}/package.json`;
      const meta = {
       name,
       description: answers.description,
       author: answers.author
      }
      if (fs.existsSync(fileName)) {
       const content = fs.readFileSync(fileName).toString();
       const result = handlebars.compile(content)(meta);
       fs.writeFileSync(fileName, result);
      }
      console.log(symbols.success, chalk.green('The vue object has downloaded successfully!'));
      inquirer.prompt([
       {
        type: 'confirm',
        name: 'ifInstall',
        message: 'Are you want to install dependence now?',
        default: true
       }
      ]).then((answers) => {
       if (answers.ifInstall) {
        inquirer.prompt([
         {
          type: 'list',
          name: 'installWay',
          message: 'Choose the tool to install',
          choices: [
           'npm', 'cnpm'
          ]
         }
        ]).then(ans => {
         if (ans.installWay === 'npm') {
          let spinner = ora('Installing...');
          spinner.start();
          // 命令行操作安装依赖
          shell.exec("cd " + name + " && npm i", function (err, stdout, stderr) {
           if (err) {
            spinner.fail();
            console.log(symbols.error, chalk.red(err));
           }
           else {
            spinner.succeed();
            console.log(symbols.success, chalk.green('The object has installed dependence successfully!'));
           }
          });
         } else {
          let spinner = ora('Installing...');
          spinner.start();
          shell.exec("cd " + name + " && cnpm i", function (err, stdout, stderr) {
           if (err) {
            spinner.fail();
            console.log(symbols.error, chalk.red(err));
           }
           else {
            spinner.succeed();
            console.log(symbols.success, chalk.green('The object has installed dependence successfully!'));
           }
          })
         }
        })
       } else {
        console.log(symbols.success, chalk.green('You should install the dependence by yourself!'));
       }
      })
     }
    })
   })
  } else {
   // 错误提示项目已存在,避免覆盖原有项目
   console.log(symbols.error, chalk.red('The object has exist'));
  }
 });
program.parse(process.argv);

npm publish发布你的项目即可。

本地测试node index init parcel-vue

以上是我写的一个 suporka-parcel-vue 的脚手架源码,suporka-parcel-vue 点击即可查看,欢迎star.希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript制作的可折叠弹出式菜单示例
Apr 04 Javascript
JavaScript检查弹出窗口是否被阻拦的方法技巧
Mar 13 Javascript
javascript实现图片跟随鼠标移动效果的方法
May 13 Javascript
JavaScript实现自动弹出窗口并自动关闭窗口的方法
Aug 06 Javascript
javascript中for/in循环及使用技巧
Sep 01 Javascript
分享bootstrap学习笔记心得(组件及其属性)
Jan 11 Javascript
Angular 2父子组件之间共享服务通信的实现
Jul 04 Javascript
vue不通过路由直接获取url中参数的方法示例
Aug 24 Javascript
不使用JavaScript实现菜单的打开和关闭效果demo
May 01 Javascript
浅谈React 服务器端渲染的使用
May 08 Javascript
vue解决一个方法同时发送多个请求的问题
Sep 25 Javascript
JS调用安卓手机摄像头扫描二维码
Oct 16 Javascript
angular的输入和输出的使用方法
Sep 22 #Javascript
vue构建动态表单的方法示例
Sep 22 #Javascript
小程序实现展开/收起的效果示例
Sep 22 #Javascript
玩转vue的slot内容分发
Sep 22 #Javascript
vue 巧用过渡效果(小结)
Sep 22 #Javascript
vue forEach循环数组拿到自己想要的数据方法
Sep 21 #Javascript
vue2.x集成百度UEditor富文本编辑器的方法
Sep 21 #Javascript
You might like
php 获取页面中指定内容的实现类
2014/01/23 PHP
PHP中Enum(枚举)用法实例详解
2015/12/07 PHP
js模拟点击以提交表单为例兼容主流浏览器
2013/11/29 Javascript
基于NodeJS的前后端分离的思考与实践(五)多终端适配
2014/09/26 NodeJs
JavaScript的类型、值和变量小结
2015/07/09 Javascript
jQuery实现类似老虎机滚动抽奖效果
2015/08/06 Javascript
JS实现同一个网页布局滑动门和TAB选项卡实例
2015/09/23 Javascript
JavaScript获取浏览器信息的方法
2015/11/20 Javascript
浅析jQuery Mobile的初始化事件
2015/12/03 Javascript
ros::spin() 和 ros::spinOnce()函数的区别及详解
2016/10/01 Javascript
jQuery向webApi提交post json数据
2017/01/16 Javascript
基于jQuery实现弹幕APP
2017/02/10 Javascript
vue 验证码界面实现点击后标灰并设置div按钮不可点击状态
2019/10/28 Javascript
基于axios 的responseType类型的设置方法
2019/10/29 Javascript
vue.js 实现a标签href里添加参数
2019/11/12 Javascript
Vue + Scss 动态切换主题颜色实现换肤的示例代码
2020/04/27 Javascript
JavaScript this关键字的深入详解
2021/01/14 Javascript
vue element el-transfer增加拖拽功能
2021/01/15 Vue.js
[03:59]第二届DOTA2亚洲邀请赛选手传记-VGJ.rOtk
2017/04/03 DOTA
Python3实现连接SQLite数据库的方法
2014/08/23 Python
Python3实现的Mysql数据库操作封装类
2018/06/06 Python
实例讲解Python中浮点型的基本内容
2019/02/11 Python
Flask使用Pyecharts在单个页面展示多个图表的方法
2019/08/05 Python
使用sklearn的cross_val_score进行交叉验证实例
2020/02/28 Python
Python使用pyexecjs代码案例解析
2020/07/13 Python
分享一个python的aes加密代码
2020/12/22 Python
玩转CSS3色彩
2010/01/16 HTML / CSS
html5实现完美兼容各大浏览器的播放器
2014/12/26 HTML / CSS
Monki官网:斯堪的纳维亚的独立时尚品牌
2020/11/09 全球购物
2014三八妇女节活动总结范文四篇
2014/03/09 职场文书
总经理助理的职责
2014/03/14 职场文书
淘宝客服专员岗位职责
2014/04/11 职场文书
中学清明节活动总结
2014/07/04 职场文书
高中生个性发展自我评价
2015/03/09 职场文书
nginx搭建NFS网络文件系统
2022/04/14 Servers
Python采集壁纸并实现炫轮播
2022/04/30 Python