从0到1实现一个vue cli工具,使vue项目支持约定式路由

541 阅读5分钟

约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置。Vue通用应用框架Nuxt.js就支持约定式路由。

如果我们不使用Nuxt.js也希望Vue项目能支持约定式路由。怎么办?现在我们就从0到1实现一个cli工具,使我们的项目也能支持约定式路由。

需要实现的功能

假设 pages 的目录结构如下:

pages/
--| user/
-----| index.vue
-----| one.vue
--| index.vue

那么,自动生成的路由配置如下:

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'user',
      path: '/user',
      component: 'pages/user/index.vue'
    },
    {
      name: 'user-one',
      path: '/user/one',
      component: 'pages/user/one.vue'
    }
  ]
}

现在我们开始实现npm的功能

1. 创建工程

1.1 创建项目

mkdir vue-router-cli
cd vue-router-cli
npm init -y

1.2 新建执行文件bin/cli.js

bin/cli.js

#!/usr/bin/env node

console.log('cli...')

ps: 需要在脚本的第一行写上#!/usr/bin/env node ,用于指明该脚本文件要使用node来执行

1.3 package.json 增加 bin 脚本

package.json

"bin": {
  "auto-router": "./bin/cli.js"
}

1.4 将 npm 模块连接到对应的运行项目中去

npm link

连接成功如下图: image.png 连接成功后再命令行中输入auto-router可以直接看到脚本输出的内容 image.png

2. 使用commander定制命令行界面

Commander.js 是一个完整的 node.js 命令行解决方案。是前端开发node cli 必备技能

npm install commander -s

bin/cli.js

#!/usr/bin/env node

const program = require("commander");

// 版本号
program.version(require("../package.json").version);

program
  .command("init <name>")
  .description("init project")
  .action((name) => {
    console.log("init " + name);
  });

program.parse(process.argv);

定制完命令行之后测试效果:

image.png

3. 打印欢迎界面

3.1 安装依赖

npm install clear figlet chalk -s
  • clear 清除终端屏幕
  • figlet 在终端生成艺术字体
  • chalk 使终端输出不再单调,添加文字背景和改变字体颜色

3.2 打印欢迎界面代码

lib/init.js :

const { promisify } = require("util");
const figlet = promisify(require("figlet"));
const clear = require("clear");
const chalk = require("chalk");

// 可以在终端输出绿色的字体
const log = (msg) => console.log(chalk.green(msg));

module.exports = async (name) => {
  // 清屏
  clear();
  // 在命令行里显示艺术字
  const msg = await figlet("Auto-router welcome");
  log(msg);
};

bin/cli.js

#!/usr/bin/env node

const program = require("commander");

// 版本号
program.version(require("../package.json").version);

program
  .command("init <name>")
  .description("init project")
  .action(require('../lib/init'));

program.parse(process.argv);

执行命令

auto-router init test

输出 image.png ps: 输出的样式可以通过figlet 修改字体样式;通过 chalk 修改字体和背景颜色.

4. 克隆脚手架

4.1 安装依赖

npm install download-git-repo ora@^5.0.0 -s
  • ora Elegant terminal spinner
  • download-git-repo 从 node 下载并提取 git 仓库(GitHub、 GitLab、 Bitbucket)

4.2 从github上克隆代码到本地

lib/download.js

const { promisify } = require("util");
module.exports.clone = async (repo, desc) => {
  const download = promisify(require("download-git-repo"));
  const ora = require("ora");
  const process = ora(`下载...${repo}`);
  process.start();
  await download(repo, desc);
  process.succeed();
};

lib/init.js

module.exports = async (name) => {
  ...
  // 创建项目
  log(`🚀创建项目:${name}`);
  // 从github克隆项目到指定文件夹
  await clone("github:banixing/vue-template", name);

};

终端输入:

auto-router init test

输出:

image.png

5. 安装依赖

lib/init.js

**
 * 对接输出流
 * @param  {...any} args 
 * @returns 
 */
const promisitySpawn = async (...args) => {
  const { spawn } = require("child_process");
  return new Promise((resolve) => {
    const proc = spawn(...args);
    proc.stdout.pipe(process.stdout);
    proc.stderr.pipe(process.stderr);
    proc.on("close", () => {
      resolve();
    });
  });
};

module.exports = async (name) => {
  // 清屏
  clear();
  // 在命令行里显示艺术字
  const msg = await figlet("Auto-router welcome");
  log(msg);
  // 创建项目
  log(`🚀创建项目:${name}`);
  // 从github克隆项目到指定文件夹
  await clone("github:banixing/vue-template", name);
  
  log("安装依赖");
  await promisitySpawn("npm", ["install"], { cwd: `./${name}` });
  log(`
👌安装完成:
To get Start:
===========================
    cd ${name}
    npm run serve
===========================
            `);
};

6. 启动项目

安装依赖

npm install open -s

lib/init.js

const open = require("open");

module.exports = async (name) => {
  // ...
  // 打开浏览器
  open("http://localhost:8080");
  await promisitySpawn("npm", ["run", "serve"], { cwd: `./${name}` });
};

7. 约定路由功能

实现思路

  1. 读取pages文件夹下面的文件,
  2. 使用模板引擎生成路由
  3. 使用模板引擎生成菜单 安装handlebars
npm install handlebars

handlebars 轻量的语义化模板, Handlebars 会将模板编译为 JavaScript 函数。这使得 Handlebars 的执行速度比其他大多数模板引擎都要快。

实现 lib/refresh.js

const fs = require("fs");
const handlebars = require("handlebars");
const chalk = require("chalk");

/**
 * 编译模板文件
 * @param {*} meta 数据定义
 * @param {*} filePath 目标文件路径
 * @param {*} templatePath 模板文件路径
 */
function compileFile(meta, filePath, templatePath) {
  if (fs.existsSync(templatePath)) {
    const content = fs.readFileSync(templatePath).toString();
    const reslut = handlebars.compile(content)(meta);
    fs.writeFileSync(filePath, reslut);
    console.log(chalk.red(`🚀${filePath} 创建成功`));
  }
}

module.exports = async () => {
  // 1. 获取页面列表
  const list = fs
    .readdirSync("./src/pages")
    .filter((f) => f !== "Home.vue")
    .map((f) => ({
      name: f.replace(".vue", "").toString(),
      file: f,
    }));
  // 2. 生成路由
  compileFile(
    {
      list,
    },
    "./src/router.js",
    "./template/router.js.hbs"
  );

  // 3. 生成菜单
  compileFile(
    {
      list,
    },
    "./src/App.vue",
    "./template/App.vue.hbs"
  );
};

增加 refresh 命令 bin/cli.js

program
  .command("refresh")
  .description("refresh routers...")
  .action(require("../lib/refresh"));

实现以上功能之后,在pages文件夹下新增文件User.vue,在终端执行

auto-router refresh

可以在页面考到自动生成了User菜单,点击菜单可以跳转到User页面。 image.png

8. 实现对 src/pages 目录的监听

lib/serve.js

const spawn = (...args) => {
    const { spawn } = require('child_process');
    const proc = spawn(...args)
    proc.stdout.pipe(process.stdout)
    proc.stderr.pipe(process.stderr)
    return proc
}

module.exports = async () => {
    const watch = require('watch')
    let process
    let isRefresh = false
    watch.watchTree('./src', async (f) => {
        if (!isRefresh) {
            isRefresh = true
            process && process.kill()
            await require('./refresh')()
            setTimeout(() => { isRefresh = false }, 5000)
            process = spawn('npm', ['run', 'serve'])
        }
    })
}

bin/cli.js

program
  .command("refresh")
  .description("refresh routers...")
  .action(require("../lib/refresh"));

9. 发布npm

新建文件:publish.sh

#!/usr/bin/env bash
npm config get registry # 检查仓库镜像库
npm config set registry=http://registry.npmjs.org
echo '请进行登录相关操作:'
npm login # 登陆
echo "-------publishing-------"
npm publish # 发布
npm config set registry=https://registry.npm.taobao.org # 设置为淘宝镜像
echo "发布完成"
exit

终端里运行

bash publish.sh

根据提示输入npm的账号和密码即可发布

如果发布不成功,如下

image.png

Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/

npm 发布公告称,从 2021 年 10 月 4 日开始,所有与 npm 网站和 npm registry 的连接(包括软件包的安装),都必须使用 TLS 1.2 或更高版本。

但如果看到的是 TLS 错误消息,建议开发者升级到当前支持的 Node.js 版本和最新版本的 npm v7

mac升级npm到最新版本

# 升级npm到最新版本
sudo npm install -g npm

npm -v
# 7.24.0

重新发布npm一次。

源码地址:github.com/banixing/no…