美文网首页
自己动手封装一个vuecli3自动生成路由插件

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

作者: 广告位招租 | 来源:发表于2019-01-07 15:36 被阅读0次

    在使用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

    相关文章

      网友评论

          本文标题:自己动手封装一个vuecli3自动生成路由插件

          本文链接:https://www.haomeiwen.com/subject/raldrqtx.html