前端自定义集成cli工具(基于vue-cli)

前端开发已经两年多了,大大小小的项目也做过不少,每次开新项目时不是直接使用vue-cli从头搭建架构,就是把之前的项目直接复制粘贴过来修修改改。当然稍微聪明一点的是弄个基础模板放在本地或者放在github,需要的时候直接clone过来。
前段时间刚好换新工作,相对于之前的公司来说事少了很多,就开始想为什么不做一个类似vue-cli的命令行工具呢(刚好现在这家公司也没有)。说干就干,经过一段时间的资料搜索,也是如愿以偿的做了出来。在这里给想要偷懒的小伙伴分享下。

项目的远程地址在 github ,有需要的同学可自行clone,有用的话别忘了点个start哦~

首先贴下目录结构

		├─.gitignore
		├─config.json       // 拉取远程模板工程目录(我这里用的是gitee,因为github有时候clone不下来)
		├─LICENSE
		├─package.json
		├─pnpm-lock.yaml
		├─README.md
		├─test 				// 测试用目录
		|  ├─chalk.js
		|  ├─child_process.js
		|  ├─commander.js
		|  ├─inquirer.js
		|  └ora.js
		├─lib				// 	功能目录
		|  ├─utils			// 方法封装
		|  |   ├─config.js
		|  |   ├─execFn.js
		|  |   └removeDir.js
		|  ├─core			// cli核心命令
		|  |  ├─add.js			// 新增模板(向config.json写入模板)
		|  |  ├─create.js		// 创建项目命令
		|  |  ├─delete.js		// 删除模板(移除config.json模板)
		|  |  ├─list.js			// 展示现有模板列表
		|  |  └upgrade.js		// 检查更新
		├─bin				// 执行文件目录
		|  └cli.js				// 命令执行文件

一. 初始化package.json

这里采用的是EJS语法(因为有些库像commander的新版本使用的是ejs),如果要用cjs就把 “type”: “module” 这句删除就可以了(当然某些依赖包响应的就需要降到旧版本了),node目前默认是cjs环境(也就是 require 、module.exports 等语法)。不过使用ejs还是有一些不兼容的东西,比如__dirname在ejs环境就不能直接使用,后面提到的时候会说解决办法

{
  "name": "sy-prt-cli",
  "version": "1.0.5",
  "description": "集成项目开发模板",
  "main": "/bin/cli.js",
  "type": "module",
  "bin": {
    "sy": "bin/cli.js"  //  这里的 sy (名字可以随便取) 就是全局命令了,后面的命令都是通过 sy xxx来执行
  },
  "directories": {
    "lib": "lib"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/zhiquanli-web/sy-prt-cli.git"
  },
  "keywords": [
    "cli",
    "vue3",
    "node"
  ],
  "author": "sy",
  "license": "ISC",
  "dependencies": {
    "chalk": "^5.0.1",
    "child_process": "^1.0.2",
    "clear": "^0.1.0",
    "commander": "^9.4.0",
    "handlebars": "^4.7.7",
    "inquirer": "^9.1.2",
    "ora": "^6.1.2",
    "update-notifier": "^6.0.2"
  }
}

二. 依赖包简要说明
	1. chalk: 用来给输出内容添加各种颜色
		console.log(chalk.blue('hello world'))
		console.log(chalk.rgb(255,0,255)('hello world'))
		...
		
	2. child_process:  可以帮助我们创建子进程来执行命令,本项目主要使用exec方法,具体用法执行查看npm
		const cmd = "git clone -b master https://gitee.com/qy-tingyun/vue-admin-template.git ./test";
		exec("cmd", (error) => {
		  if (error) {
		    console.log("发生了一个错误", chalk.red(JSON.stringify(error)));
		    process.exit();
		  }
		  resolve(true);
		});
		
	3. clear:清空前面所有输出内容

	4. commander:负责将命令行输入的参数解析为选项和命令参数

	5. handlebars::是一个页面模板化工具,本项目中用于覆写模板文件package.json中的{{name}},更多用法执行查看npm
	
	6. inquirer:命令行交互工具(用于跟使用者进行交互如: 解析输入,校验问答等)
	
	7. ora:主要用来实现node.js命令行环境的loading效果,和显示各种状态的图标等
	
	8.  update-notifier:检测本地与远程包是否有版本更新
三. 配置bin/cli.js

#! /usr/bin/env node 一定要置顶,表示用node来执行此文件

#! /usr/bin/env node

//  Commander 负责将命令行输入的参数解析为选项和命令参数
// 具体信息及使用方法可访问:https://github.com/tj/commander.js/blob/master/Readme.md
import { program } from "commander";
import { readFile } from "fs/promises";

// 获取版本号
// 正如前文所说,在ejs环境无法使用require导入json文件,这里的是用下面的方法来做替换方案
// 如果用的是cjs就不需要这么麻烦了,直接使用require('../package.json')就可以引入
program.version(
  JSON.parse(await readFile(new URL("../package.json", import.meta.url)))
    .version,
  "-V, --version"
);

// 这个一定要有,用于解析node环境下的一些参数等
program.parse(process.argv);

if (!program.args.length) {
  program.help();
}

到这里之后 可以在命令行执行 npm link,将项目关联起来(类似于使用npm安装了个全局包)。之后你就可以在任何位置使用命令 sy -V 或 sy --version 来查看版本

四. 模板文件config.json配置信息如下
{
  "templates": {
    "vue-template": { // 模板名
      "url": "https://gitee.com/qy-tingyun/vue-admin-template", // 远程模板地址
      "branch": "master" // 代码拉取分支
    },
    "vue-emement-template": {
      "url": "https://gitee.com/qy-tingyun/vue-admin-template",
      "branch": "element-template"
    },
    "vue-antd-template": {
      "url": "https://gitee.com/qy-tingyun/vue-admin-template",
      "branch": "antd-template"
    }
  }
}
五. 创建项目(sy create [project])
  • 1.bin/cli.js
#! /usr/bin/env node

import { program } from "commander";
import { readFile } from "fs/promises";

import myCreate from "../lib/core/create.js";

// 获取版本号
program.version(
  JSON.parse(await readFile(new URL("../package.json", import.meta.url)))
    .version,
  "-V, --version"
);

myCreate(program);

program.parse(process.argv);

if (!program.args.length) {
  program.help();
}


  • 2.lib/core/create.js
import clear from "clear";
import inquirer from "inquirer";
import ora from "ora";
import handlebars from "handlebars";
import fs from "fs";
import chalk from "chalk";

import execFn from "../utils/execFn.js";
import removeDir from "../utils/removeDir.js";
import myConfig from "../utils/config.js";

const spinner = ora();

function myInit(program) {
  program
    .command("create <project>")
    .description("create a new project")
    .action(async (projectName) => {
      const config = await myConfig();
      clear();
      const answer = await inquirer.prompt([
        {
          name: "templateName",
          type: "list",
          message: "请选择你的目标模板",
          choices: Object.keys(config.templates),
        },
      ]);
      spinner.start();
      spinner.text = "正在下载...";
      const { url, branch } = config.templates[answer.templateName];
      const cloneCmd = `git clone -b ${branch} ${url} ${projectName}`;
      await execFn(cloneCmd, spinner);
      // 覆写模板项目中的{{name}}
      const meta = {
        name: projectName,
      };
      const content = fs.readFileSync(`${projectName}/package.json`).toString();
      const result = handlebars.compile(content)(meta);
      fs.writeFileSync(`${projectName}/package.json`, result);
      // 删除模板中的 .git 文件
      removeDir(`${projectName}/.git`);
      // git init
      let gitInitCmd = `git -C ./${projectName} init`;
      await execFn(gitInitCmd);
      // git add . && git commit
      const gitCommitCmd = `git -C ./${projectName} add . && git -C ./${projectName} commit -m init`;
      await execFn(gitCommitCmd);

      spinner.succeed("下载完成");
      console.log(chalk.cyan(`\n cd ${projectName} && pnpm i \n`));
      process.exit();
    });
}

export default myInit;

  • 3.lib/utils
	// execFn.js
	import { exec } from "child_process";
	import chalk from "chalk";
	
	function execFn(cmd, spinner) {
	  return new Promise((resolve) => {
	    exec(cmd, (error) => {
	      if (error) {
	        console.log("发生了一个错误:", chalk.red(JSON.stringify(error)));
	        spinner.fail("下载失败");
	        process.exit();
	      }
	      resolve(true);
	    });
	  });
	}
	export default execFn;
	
	// removeDir.js
	import fs from "fs";
	import path from "path";
	
	function removeDir(dir) {
	  let files = fs.readdirSync(dir); //返回一个包含“指定目录下所有文件名称”的数组对象
	  for (var i = 0; i < files.length; i++) {
	    let newPath = path.join(dir, files[i]);
	    let stat = fs.statSync(newPath);
	    if (stat.isDirectory()) {
	      removeDir(newPath); //判断是否是文件夹,如果是文件夹就递归下去
	    } else {
	      fs.unlinkSync(newPath); //删除文件
	    }
	  }
	  fs.rmdirSync(dir); //如果文件夹是空的,就将自己删除掉
	}
	export default removeDir;
	
 	// config.js
 	import { readFile } from "fs/promises";
	// 读取配置文件
	async function myConfig() {
	  const config = JSON.parse(
	    await readFile(new URL("../../config.json", import.meta.url))
	  );
	  return config;
	}
	export default myConfig;
六. 查看模板列表(sy list 或 sy ls)
// bin/cli.js
import myList from "../lib/core/list.js";
myList(program);

// lib/core/list.js
import chalk from "chalk";
import myConfig from "../utils/config.js";

function myList(program) {
  program
    .command("list")
    .alias("ls")
    .description("show template list")
    .action(async () => {
      const config = await myConfig();
      let str = "";
      Object.keys(config.templates).forEach((item, index, array) => {
        if (index === array.length - 1) {
          str += item;
        } else {
          str += `${item} \n`;
        }
      });
      console.log(chalk.cyan(str));
      process.exit();
    });
}
export default myList;

七. 查看远程是否有新版本(sy upgrade 或 sy u)
// bin/cli.js
import myUpgrade from "../lib/core/upgrade.js";
myUpgrade(program);

// lib/core/upgrade.js
import chalk from "chalk";
import { readFile } from "fs/promises";
import updateNotifier from "update-notifier";

const TIME = 1000 * 60 * 60 * 24;

function myUpgrade(program) {
  program
    .command("upgrade")
    .alias("u")
    .description("check the sy-prt-cli version.")
    .action(async () => {
      const pkg = JSON.parse(
        await readFile(new URL("../../package.json", import.meta.url))
      );
      const notifier = updateNotifier({
        pkg,
        updateCheckInterval: TIME,
      });
      if (notifier.update) {
        console.log(
          `有新版本可用:${chalk.cyan(
            notifier.update.latest
          )},建议您在使用前进行更新`
        );
        notifier.notify();
      } else {
        console.log(chalk.cyan("已经是最新版本"));
      }
    });
}

export default myUpgrade;
八.发布

执行 npm publish,当然在这之前你需要登录npm login;如果没有npm账号的需要去注册个。这里还要注意npm中是否已存在相同的包名,
可以先在npm搜索下在发布。发布完成之后就可以使用了

补充

在ejs环境下 无法直接使用 __dirname,需要使用下面的替换方案

import path, { dirname } from "path";
import { fileURLToPath } from "url";

const __dirname = dirname(fileURLToPath(import.meta.url));

path.resolve(__dirname, "../../config.json"),

剩下的 添加、删除模板就不贴上来了(当然有需要的自行clone远程完整项目),基本上弄懂前面的要实现都不难

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: vue-cli2和vue-cli3有一些重要的区别: 1.配置文件: vue-cli2使用的是`config/index.js`文件来配置项目,而vue-cli3使用的是`.env`和`.env.xxx`文件来配置项目. 2.构建工具: vue-cli2使用的是webpack1,而vue-cli3使用的是webpack4. 3.项目结构: vue-cli2项目结构比较简单,而vue-cli3提供了更加灵活的项目结构. 4.插件系统: vue-cli2使用的是全局插件,而vue-cli3使用的是局部插件. 5.使用方式: vue-cli2是全局安装,而vue-cli3是局部安装. ### 回答2: Vue-cli是一个vue.js的官方脚手架工具,它可以帮助我们更快地搭建一个基于Vue的项目。Vue-cli2和Vue-cli3是Vue-cli的不同版本,在一些方面存在一些显著的差异。 1. 项目结构: Vue-cli2生成的项目结构中,代码和配置信息都放在同一个文件夹中,每一个功能模块都需要手动创建;而Vue-cli3采用了新的配置方案,将项目的配置信息单独抽离出来,并且在工具创建项目时自动生成了更完整的项目结构,使项目结构更加清晰和易于管理。 2. 配置方式: Vue-cli2是通过修改webpack.config.js文件来进行项目配置的,而Vue-cli3则是通过创建vue.config.js文件来进行项目配置的。在Vue-cli3中,我们可以直接在vue.config.js中添加一些特定的配置,而无需修改webpack.config.js文件。这样做更加方便,也更加容易管理项目的开发和部署。 3. 优化: 在Vue-cli3中,作者对项目进行了一些自动优化的处理,例如:自动抽取第三方库等,以优化打包和运行速度。而Vue-cli2则需要手动配置优化选项。 总之,Vue-cli3相比Vue-cli2在工程化方面有了更多的改进和优化,我们将会更加方便、快捷地构建一个基于Vue的项目。 ### 回答3: Vue是一个流行的JavaScript框架,它允许开发人员构建动态Web应用程序。而Vue CLI是Vue的脚手架工具,用于快速构建Vue项目。Vue CLI 2和Vue CLI 3是Vue CLI的不同版本,下面将介绍它们之间的不同: 1.项目结构:Vue CLI 2生成的项目结构是采用传统的单一的Webpack配置文件,而Vue CLI 3则是基于插件的灵活的配置,将Webpack配置拆分为多个小的配置文件。 2.依赖:Vue CLI 2使用的是Vue Router 2,而Vue CLI 3升级到了Vue Router 3,同时也使用了全新的Vuex。 3.插件化: Vue CLI 3采用了插件化的概念,每个插件都可以为开发者提供定制的Webpack配置和功能增强。 4.内置功能:Vue CLI 3集成了一些内置功能如:PWA支持、自动生成样式和文档等。 5.提高性能:Vue CLI 3对webpack的配置进行了深度优化,提供了更优秀的性能表现,同时还加入了预编译,tree shaking 外置化了 `webpack`,对项目编译速度支持了更大的提升。 总的来说,Vue CLI 3采用了更先进的技术、更简单的配置结构、更多的内置功能和更好的性能。如果你计划开始一个新的Vue项目,强烈建议使用Vue CLI 3,以提高代码的开发效率和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值