自己动手封装一个vuecli3自动生成路由插件

在使用vue spa时,配置路由是很重要的一环。众所周知nuxtjs中具备了依据pages文件自动生成vue-router 模块的路由配置。

此文将会引用nuxtjs中该部分源码,并稍加改动,生成一个vue-cli3的插件

nuxt官方网站

第一部分

nuxt自动生成路由的使用方式

nuxt会根据pages下的文件自动生成路由并引入,支持vue-router的基础路由,动态路由,嵌套路由等。基础路由很简单,需要注意的是,在使用动态路由时,需要创建对应的以下划线作为前缀的 Vue文件或目录

//pages文件
pages/
--| users/
-----| _id.vue
--| index.vue
//生成的路由
router: {
  routes: [{
           name: 'index',
           path: '/',
           component: 'pages/index.vue'
          },
          {
           name: 'users-id',
           path: '/users/:id?',
           component: 'pages/users/_id.vue'
          }]
        }

创建内嵌子路由,你需要添加一个 Vue 文件,同时添加一个与该文件同名的目录用来存放子视图组件。

pages/
--| users/
-----| _id.vue
-----| index.vue
--| users.vue
//生成的路由
router: {
  routes: [
    {
      path: '/users',
      component: 'pages/users.vue',
      children: [
        {
          path: '',
          component: 'pages/users/index.vue',
          name: 'users'
        },
        {
          path: ':id',
          component: 'pages/users/_id.vue',
          name: 'users-id'
        }
      ]
    }
  ]
}

nuxt源码展示

源码地址
分析一下nuxtjs源码,主要由以下几部分完成此功能

  1. generateRoutesAndFiles方法,引入glob库对pages下的文件进行遍历和字符串处理,然后将vue文件地址,整个项目地址和pages作为参数传给createRoutes方法
//builder.js
 else if (this._nuxtPages) {
      // Use nuxt.js createRoutes bases on pages/
      const files = {}
        ; (await glob(`${this.options.dir.pages}/**/*.{vue,js}`, {
        cwd: this.options.srcDir,
        ignore: this.options.ignore
      })).forEach((f) => {
        const key = f.replace(/\.(js|vue)$/, '');
        if (/\.vue$/.test(f) || !files[key]) {
          files[key] = f.replace(/('|")/g, '\\$1');
        }
      });
      templateVars.router.routes = common.createRoutes(
        Object.values(files),
        this.options.srcDir,
        this.options.dir.pages
      );
    }

2.在createRoutes函数中对传过来的所有文件地址进行遍历,再对每一个文件地址字符串处理,以中划线进行拼接。以此作为route.name

//common.js
const createRoutes = function createRoutes(files, srcDir, pagesDir) {
  const routes = [];
  files.forEach((file) => {
    const keys = file
      .replace(RegExp(`^${pagesDir}`), '')
      .replace(/\.(vue|js)$/, '')
      .replace(/\/{2,}/g, '/')
      .split('/')
      .slice(1);
    const route = { name: '', path: '', component: r(srcDir, file) };
    let parent = routes;
    keys.forEach((key, i) => {
      // remove underscore only, if its the prefix
      const sanitizedKey = key.startsWith('_') ? key.substr(1) : key;

      route.name = route.name
        ? route.name + '-' + sanitizedKey
        : sanitizedKey;
      route.name += key === '_' ? 'all' : '';
      route.chunkName = file.replace(/\.(vue|js)$/, '');
      const child = parent.find(parentRoute => parentRoute.name === route.name);

      if (child) {
        child.children = child.children || [];
        parent = child.children;
        route.path = '';
      } else if (key === 'index' && i + 1 === keys.length) {
        route.path += i > 0 ? '' : '/';
      } else {
        route.path += '/' + getRoutePathExtension(key);

        if (key.startsWith('_') && key.length > 1) {
          route.path += '?';
        }
      }
    });
    parent.push(route);
  });

  sortRoutes(routes);
  return cleanChildrenRoutes(routes)
};

至此,经过这两个方法处理之后的内容,大致上就是我们想要的东西

//生成一个数组对象
[{
       name: 'index',
       path: '/',
       component: 'pages/index.vue'
  },
  {
       name: 'users-id',
       path: '/users/:id?',
       component: 'pages/users/_id.vue'
}]

第二部分

vue-cli3插件

具体请移步vue-cli3插件开发指南

//插件目录
├── README.md
├── generator.js  # generator (可选)
├── prompts.js    # prompt 文件 (可选)
├── index.js      # service 插件
└── package.json
//index.js
module.exports = (api, projectOptions) => {
  api.chainWebpack(webpackConfig => {
    // 通过 webpack-chain 修改 webpack 配置
  })

  api.configureWebpack(webpackConfig => {
    // 修改 webpack 配置
    // 或返回通过 webpack-merge 合并的配置对象
  })

  api.registerCommand('test', args => {
    // 注册 `vue-cli-service test`
  })
}

懒加载路由

当打包构建应用时,Javascript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。(详情请戳路由懒加载

第三部分

自己动手做一个cli3插件

首先,关于插件的目录结构上面已经给出了,这里只需要service插件即可,而且暂时不需要读取webpack配置内容,只需要给出一个执行的契机

//插件目录
├── README.md
├── generateRouter 
        ├── index.js        # 插件方法 
├── index.js      # service 插件
└── package.json
const creatRouter = require('./generateRouter/index')

module.exports = (api, projectOptions) => {
// 这里只是需要一个时机执行,暂时不需要读取webpack中的配置
    api.configureWebpack(webpackConfig => {
        creatRouter.creatRouter(false)
    })
}

package.json中的name要严格按照cli3的格式,要不然会出问题

//package.json
{
  "name": "vue-cli-plugin-<name>",
  "version": "1.0.0",
  "description": "your description",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "you",
  "license": "ISC",
  "dependencies": {},
  "devDependencies": {}
}

接下来看一看generateRouter/index中的内容
其中必要包含了前文中提到的nuxtjs的方法,只是对这两个方法进行了一下改造

generateRoutesAndFiles = async () => {
    const files = {};
    // 这里要读取的不是pages文件而是views文件了
    (await glob(`views/**/*.{vue,js}`, {
        cwd: path.resolve(process.cwd(), './src'),
        ignore: ['**/*.test.*', '**/*.spec.*', '**/-*.*']
    })).forEach((f) => {
        const key = f.replace(/\.(js|vue)$/, '')
        if (/\.vue$/.test(f) || !files[key]) {
            files[key] = f.replace(/('|")/g, '\\$1')
        }
    })

    return createRoutes(
        Object.values(files),
        path.resolve(process.cwd(), './src'),
        'views'
    )
}
function createRoutes(files, srcDir, pagesDir) {
    const routes = []
    const requireComponent = []
    files.forEach((file) => {
        const keys = file
            .replace(RegExp(`^${pagesDir}`), '')
            .replace(/\.(vue|js)$/, '')
            .replace(/\/{2,}/g, '/')
            .split('/')
            .slice(1)

        const route = {
            name: '',
            path: '',
            component: `views${camelCase(keys.join('-').replace('_', ''))}`
        }

        requireComponent.push(`const views${camelCase(keys.join('-').replace('_', ''))} = resolve => require(['../views/${keys.join('/')}'], resolve)`)
        let parent = routes
        keys.forEach((key, i) => {
            route.name = key.startsWith('_') ? key.substr(1) : key
            route.name += key === '_' ? 'all' : ''
            const child = parent.find(parentRoute => parentRoute.name === route.name)
            if (child) {
                child.children = child.children || []
                parent = child.children
                route.path = ''
            } else if (key === 'index' && i + 1 === keys.length) {
                route.path += i > 0 ? '' : '/'
            } else {
                route.path = `/` + getRoutePathExtension(key)

                if (key.startsWith('_') && key.length > 1) {
                    route.path += '?'
                }
            }
        })
        parent.push(route)
    })
    sortRoutes(routes)
    return {
        'routes': cleanChildrenRoutes(routes),
        'requireComponent': requireComponent
    }
}

一个cli3插件基本已经出炉,接下来只需要publish到npm上,然后使用vue add <your name>来加载插件即可

第四部分

使用方法

首先看一下cli3的项目目录结构,在使用了本插件之后,只需要在views中存放根组件(子组件存放于component中),router.js注册路由信息,当使用npm start时,将会根据views中的文件自动生成router/route.js文件

|—node-modules
|—public
|—src
    |—assets
    |—component
    |—views
    |—router
         |—route.js
    |—router.js
    |—App.vue
    |—main.js
|—package.json

// 在router.js中注册
import Vue from 'vue'
import Router from 'vue-router'
import {routes} from './router/route'

Vue.use(Router)

export default new Router({
  routes: routes
})

以下是生成出来的样例

//router/route.js
const viewsAbout = resolve => require(['../views/About'], resolve)
const viewsHome = resolve => require(['../views/Home'], resolve)
const viewsuserUser = resolve => require(['../views/user/_user'], resolve)
const viewsuserUsernameUsername = resolve => require(['../views/user/username/username'], resolve)
export const routes = [
  {
    name: "About",
    path: "/About",
    component: viewsAbout
  },
  {
    name: "Home",
    path: "/Home",
    component: viewsHome
  },
  {
    name: "user-username-username",
    path: "/user/username/username",
    component: viewsuserUsernameUsername
  },
  {
    name: "user-user",
    path: "/user/:user?",
    component: viewsuserUser
  }
]

以上便是本插件的全部内容,如果有疑问或者是bug可以联系我
qq:1053189708
git: https://github.com/zhangOking/vuecli3plugin-generatererouter

推荐阅读更多精彩内容

  • 基于Vue的组件库 https://github.com/ElemeFE/element" element 饿了...
    _执着执着再执着阅读 29,217评论 4 225
  • 转载 :OpenDiggawesome-github-vue 是由OpenDigg整理并维护的Vue相关开源项目库...
    果汁密码阅读 20,755评论 6 120
  • 基于Vue的一些资料 内容 UI组件 开发框架 实用库 服务端 辅助工具 应用实例 Demo示例 element★...
    尝了又尝阅读 795评论 0 1
  • awesome-github-vue 是由OpenDigg整理并维护的Vue相关开源项目库集合。我们会定期同步Op...
    奋斗的七月阅读 1,972评论 0 19
  • 半夜竟饿了起来。 这时我想到爷爷的白菜。那些在夏天的尾巴上撒播的紫色的种子,应该早已破土,抽芽,并长出了多片嫩绿的...
    秋天打柿子阅读 166评论 8 7