开发一个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 相关文章推荐
jquery选择器(常用选择器说明)
Sep 28 Javascript
jquery 学习之二 属性 文本与值(text,val)
Nov 25 Javascript
使用jQuery+HttpHandler+xml模拟一个三级联动的例子
Aug 09 Javascript
JS 排序输出实现table行号自增前端动态生成的tr
Aug 13 Javascript
jQuery简单实现验证邮箱格式
Jul 15 Javascript
JS中innerHTML和pasteHTML的区别实例分析
Jun 22 Javascript
jquery siblings获取同辈元素用法实例分析
Jul 25 Javascript
详解使用nvm管理多版本node的方法
Aug 30 Javascript
小程序实现选择题选择效果
Nov 04 Javascript
图文详解vue框架安装步骤
Feb 12 Javascript
vue实现吸顶、锚点和滚动高亮按钮效果
Oct 21 Javascript
使用element-ui +Vue 解决 table 里包含表单验证的问题
Jul 17 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
骨王战斗力在公会成员中排不进前五,却当选了会长,原因竟是这样
2020/03/02 日漫
第十一节--重载
2006/11/16 PHP
解决PhpMyAdmin中导入2M以上大文件限制的方法分享
2014/06/06 PHP
php5.3以后的版本连接sqlserver2000的方法
2014/07/28 PHP
php从memcache读取数据再批量写入mysql的方法
2014/12/29 PHP
Linux基于php-fpm模式的lamp搭建phpmyadmin的方法
2018/10/25 PHP
PHP的JSON封装、转变及输出操作示例
2019/09/27 PHP
window.open不被拦截的实现代码
2012/08/22 Javascript
转义字符(\)对JavaScript中JSON.parse的影响概述
2013/07/17 Javascript
scrollWidth,clientWidth,offsetWidth的区别
2015/01/13 Javascript
JS 作用域与作用域链详解
2015/04/07 Javascript
jQuery入门基础知识学习指南
2015/08/14 Javascript
JavaScript继承模式粗探
2016/01/12 Javascript
完美解决mui框架off-canvas侧滑超出部分隐藏无法滚动的问题
2018/01/25 Javascript
layer.open 获取不到表单信息的解决方法
2019/09/26 Javascript
js点击事件的执行过程实例分析【冒泡与捕获】
2020/04/11 Javascript
国内常用的js类库大全(CDN公共库)
2020/06/24 Javascript
基于vue实现简易打地鼠游戏
2020/08/21 Javascript
小程序点餐界面添加购物车左右摆动动画
2020/09/23 Javascript
基于JavaScript实现随机点名器
2021/02/25 Javascript
Python实现建立SSH连接的方法
2015/06/03 Python
详解使用python的logging模块在stdout输出的两种方法
2017/05/17 Python
python range()函数取反序遍历sequence的方法
2018/06/25 Python
Python 删除连续出现的指定字符的实例
2018/06/29 Python
Django REST framework内置路由用法
2019/07/26 Python
Python进程间通信multiprocess代码实例
2020/03/18 Python
详解CSS3 用border写 空心三角箭头 (两种写法)
2017/09/29 HTML / CSS
细说CSS3中的选择符
2008/10/17 HTML / CSS
体育教育专业毕业生自荐信
2013/11/15 职场文书
医院工作检讨书范文
2014/02/10 职场文书
出国留学担保书
2014/05/20 职场文书
行政工作试用期自我评价
2014/09/14 职场文书
大学生党性分析材料
2014/12/19 职场文书
安全保证书格式
2015/02/28 职场文书
幼儿园教师读书笔记
2015/06/29 职场文书
2016年暑期社会实践活动总结报告
2016/04/06 职场文书