无标题文章

## 什么是路由自动注入

路由自动注入概念学习自[nuxt](https://zh.nuxtjs.org/guide/routing/),我们不需要在`router.js`中每次手动输入代码引入模块而是自动根据`文件目录格式`生成`router.js`

我们把这个功能独立成一个`webpack`插件,并对相关功能进行了完善,而且实现了`vue-router`的所有核心功能

更详细使用指南和文档可以查看我们的[github仓库](https://github.com/Qymh/vue-router-invoke-webpack-plugin)

举一个简单的列子,比如你的目录长这样

```

src

├── views

│  ├── Login

│  │  └── Index.vue

│  └── User

│      ├── Account

│      │  └── Index.vue

│      ├── Home

│      │  └── Index.vue

│      └── Index.vue

```

规则很简单,如果目录的一层是`Index.vue`,则目录名便是当前的路由名字,如果是子文件夹则是第二层路由,之后自动生成的`router.js`会长成这样

```javascript

{

  component: () =>

    import('@/views/Login/Index.vue'),

  name: 'login',

  path: '/login'

},

{

  component: () =>

    import('@/views/User/Index.vue'),

  name: 'user',

  path: '/user'

},

{

  component: () =>

    import('@/views/User/Account/Index.vue'),

  name: 'user-account',

  path: '/user/account'

},

{

  component: () =>

    import('@/views/User/Home/Index.vue'),

  name: 'user-home',

  path: '/user/home'

}

```

这里值得一提的是其实生成的`router.js`是没有必要加入到版本控制当中的,因为不论在开发(`development`)还是生产(`production`)第一次构建项目都会自动生成,比如你项目用到了`git`和`eslint`,那么应该把它放在`.gitignore`和`.eslintignore`中

## 为什么使用路由自动注入

- 方便

不用每次去引用模块,只用创建文件夹,`router.js`会自动生成

- 统一路由命名

![](https://user-gold-cdn.xitu.io/2019/4/12/16a10beb78c86c54?w=934&h=416&f=jpeg&s=66970)

如果有完整的`code review`这个问题是不会存在的,但我们稍微做了一点简便,只要`code review`文件夹的命名就好了,最终生成的路由path会以驼峰命名,生成的name会以驼峰命名并且以连字符`-`连接不同层级的路由

- 统一路由层级

![](https://user-gold-cdn.xitu.io/2019/4/12/16a10c043d6e3568?w=840&h=202&f=jpeg&s=27927)

如图片中的列子,我们无法从文件的命名去判断路由到底在几级,而且经常写的时候,明明是2级或3级路由却和1级路由在一层路由下,这是很不规范而且与逻辑不符的

对比一下使用自动注入划分层级后的路由

```javascript

src/views

├── Index.vue

├── NotFound.vue

├── Withdraw

    <!-- 第一级 -->

│  ├── Index.vue

│  └── Result

│      ├── Description

            <!-- 第三级 -->

│      │  └── Index.vue

        <!-- 第二级 -->

│      └── Index.vue

└── WithdrawHistory

    <!-- 第一级 -->

    └── Index.vue

```

可以从目录结构看出路由的层级

我们再来看看生成的路由,不同层级的路由名字通过连字符`-`连接,层级很清晰

```javascript

{

    component: () => import('@/views/Withdraw/Index.vue'),

    name: 'withdraw',

    path: '/withdraw'

},

{

    component: () => import('@/views/Withdraw/Result/Index.vue'),

    name: 'withdraw-result',

    path: '/withdraw/result'

},

{

    component: () => import('@/views/Withdraw/Result/Description/Index.vue'),

    name: 'withdraw-result-description',

    path: '/withdraw/result/description'

},

{

    component: () => import('@/views/WithdrawHistory/Index.vue'),

    name: 'withdrawHistory',

    path: '/withdrawHistory'

},

```

## 为什么选择`vue-router-invoke-webpack-plugin`

- 完善的单元测试

![](https://user-gold-cdn.xitu.io/2019/4/15/16a1f183fdcf1541?w=1134&h=476&f=jpeg&s=103867)

- types支持

![](https://user-gold-cdn.xitu.io/2019/4/15/16a1f196e37e4ab3?w=1442&h=750&f=jpeg&s=161436)

## `vue-router-invoke-webpack-plugin`中独特的路由划分思维

当我们的页面过多的时候,比如项目有60多个甚至70多个单页面,文件不可能会放在一个目录下,一般这种时候,我们会按`功能`将相似功能的路由放在一个目录下,我们之前也是这么做的,其实这么做也是没啥问题的,但在路由自动注入下,我们提出了另外一种思路按路由`层级`划分

什么是层级划分呢,简单的一句话就是根据页面所在的相对url地址进行划分,举个列子,我们的首页如下

![](https://user-gold-cdn.xitu.io/2019/4/15/16a1f5fc22b5cc28?w=417&h=741&f=jpeg&s=41103)

首页的路由为`/`,我们把首页当作根路由,那么可以进入的一级路由分别为`提现` `提现记录` `分成数据`等,点击提现后,我们进入了提现路由`/withdraw`

![](https://user-gold-cdn.xitu.io/2019/4/15/16a1f6320939c57e?w=442&h=769&f=jpeg&s=61984)

进入提现页面后,会有两处可点击,这两处便是二级页面,放在一级页面的子文件夹中,按刚才的说法,路由目录(截取部分)便是这样

```

src/views

├── Bank

    <!-- 银行卡管理 -->

│  └── Index.vue

├── DivideData

    <!-- 分成数据 -->

│  └── Index.vue

<!- 首页 --->

├── Index.vue

<!-- 404路由 -->

├── NotFound.vue

├── Withdraw

│  ├── BankDetails

        <!-- 提现中查看银行卡信息 -->

│  │  └── Index.vue

│  ├── Description

        <!-- 提现说明 -->

│  │  └── Index.vue

    <!-- 提现页面 -->

│  └── Index.vue

└── WithdrawHistory

    <!--提现记录 -->

    └── Index.vue

```

其实一般这么分下来,相似功能的是会在一个文件夹下面的,也实现了按功能分路由的思路,而且这种层级划分是一目了然的,很容易可以看出路由的从属关系

但有时候也会遇到一个麻烦,就是有些页面可能出现在当前层级下面,也可能出现在另外一个层级下面,按功能分的时候也有这种,就是功能可能存在于两个功能点之间,这种情况其实可以考虑下在哪个层级的权重重一点或者从用户的点击习惯考虑,哪个位置进去会多一点就放在哪个层级下面

## `vue-router-invoke-webpack-plugin`中独特的文件结构

也许大家会有疑问,为啥非要写成`Index.vue`并多加一层文件夹封装,直接命名`vue`文件不好吗,用过`nuxt`的同学可能也会感觉到这一点的区别,这也是我们在`nuxt`的基础上增加的一个`feature`,为了更友好的封装一个单页面

举个列子,如果你的项目没有引用ui库,很多业务组件需要自己写,除了常用的组件会放在目录最外面的`components`文件,其余的对应一个单页面的业务组件你会放在哪里呢,这就是我们预留的位置,比如一个目录结构如下

```

src/views

├── Audit

│  ├── Index.vue

│  ├── components

│  │  └── AuditItem.vue

│  └── images

│      └── AuditIntro.png

```

`Audit`是我们的审批页面,其中用到了一个只有当前页面所用的`AuditItem.vue`组件,也引用了一个只有当前页面所用到的图片`AuditIntro.png`,独特的文件结构就是为了这种需求而生的,当前页面的组件图片放在一个文件夹中会更清晰,但值得一提的是,你也需要在插件中设置`ignore`去忽略掉不被我们解析的目录,比如这样

```javascript

plugins: [

  new VueRouterInvokeWebpackPlugin({

    dir: 'src/views',

    alias: '@/views',

    language: 'javascript',

    ignore: ['images', 'components', 'template.vue']

  })

];

```

那么 `images` `components` `template.vue` 会被忽略不解析

## 聊一聊路由权限控制

关于前端控制路由权限,前段时间看到过一个文章,感觉实现思路稍微复杂了点,其实有一个比较简单的思路,就是后端给定当前用户没有权限的路由,然后前端在`beforeEach`钩子中去匹配,如果匹配到没有权限则直接跳404或者没有权限的页面就行了,如果用`vue-router-invoke-webpack-plugin`写会这么写

`apis.getForbiddenRoute`

```javascript

export default {

  // 请求当前没有权限的路由列表

  async getForbiddenRoute() {

    return ['/single/user'];

  }

};

```

```javascript

plugins: [

    new VueRouterInvokePlugin({

      // 观察的目录

      dir: 'demos/src',

      // 观察目录的别名

      alias: '@/src',

      // 当前语言

      language: 'javascript',

      // 生成router.js的位置

      routerDir: 'demos',

      // 忽略文件夹

      ignore: ['images', 'template.vue', 'components', 'notfound.vue'],

      // 404路由地址

      notFound: '@/src/NotFound.vue',

      // 引用的模块

      modules: [

        {

          name: 'apis',

          package: '@/apis'

        }

      ],

      // 同scrollBehavior

      scrollBehavior: (to, from, savedPosition) => {

        if (savedPosition) {

          return savedPosition;

        } else {

          return { x: 0, y: 0 };

        }

      },

      <!-- 主要是这段代码 -->

      /* eslint-disable */

      beforeEach: async (to, from, next) => {

        // 通过绑定在静态属性上的_cachedForbiddenRoute判断是否请求过接口

        if (!Vue._cachedForbiddenRoute) {

          Vue._cachedForbiddenRoute = [];

          await apis.getForbiddenRoute().then(res => {

            Vue._cachedForbiddenRoute = res;

          });

        }

        // 当当前页面的地址存在于禁止访问的列表中,则直接跳转到404页面

        if (Vue._cachedForbiddenRoute.includes(to.path)) {

          next({

            name: 'notFound'

          });

        } else {

          next();

        }

      }

    }),

]

```

但话说回来,任何实现思路,前端获取的接口数据想篡改还是能绕过去的,所以还是得后端再防一层

## 项目实现思路

项目实现不太复杂,但要照顾到的地方很多

- 基本路由

- 动态路由

- 多层嵌套路由

- 多层嵌套动态路由

- meta替代品

- 文件不符合规则的友好处理

- 命名转换统一

- node中原生`fs`模块十分不友好

要考虑的小细节还挺多的,特别是当路由过于复杂的情况

但node的`fs`的坑点是我没有想到的,特别是在跨平台上,所以我们舍弃了使用原生的`fs`模块,用`chokidar`和`fs-extra`替代了`fs`的部分功能

前段时间也在学习`vue`的ast语法树,所以学习了下思路去尝试构建一棵ast,不过方法还是有区别的,vue构建语法树是通过正则拆分了`元素开始标签` `元素属性` `元素字符`  `元素结束标签`等然后拼接而成的,拼接的过程特别复杂,这个项目会简单很多,直接通过文件读取递归遍历目录就可以生成一棵`ast`了

![](https://user-gold-cdn.xitu.io/2019/4/15/16a1fa69ab9a7851?w=872&h=755&f=jpeg&s=100857)

然后通过语法树去构建字符串的`router.js`,构建的过程还比较麻烦,最后将构建好的字符串写入文件就大功告成了

## 项目还需要完善的地方

- 单元测试

现在的单元测试覆盖率已经100%了,但我觉得仍然有比较多稍微复杂的情况没有写到,之后会不仅看单元测试覆盖率,而是按想到需要测试得功能点去补充完整

- 测试环境

项目接入的是`circleci`,没法在`windows`下测试,平常用的开发环境也是`mac`,所以测试环境方面之后还要去研究研究其他可以支持windows的ci工具,并对不同node版本进行测试

其实现在在`windows`下也有一个bug,但我发现`nuxt`也有这个bug,所以感觉可能这不是一个bug或许是一个feature,之后也会去提一个`issue`去请教一下,也不知道是不是我电脑的问题,简单说就是`fs.watch`去监听文件目录的时候(但这里其实用的是`chokidar`,不过都一样)当去改变之前已有的文件目录的名字是改不了的,`windows`下会提示你什么当前文件被引用了,需要结束掉进程这个文件名才能被修改

- 更友好的支持

项目目前支持的是node版本> 8.15.1,仅支持`webpack4`,之后会支持`webpack3`和即将到来的`webpack5`

2019-04-19 15:41:31 版本>0.2.5

已支持`webpack3`

## 更多的功能

除了刚才提到的一个简单路由的列子和设置忽略项,我们还支持了`vue-router`的其他核心功能,包括`动态路由` `嵌套路由` `全局路由守卫` `meta替代品` 等其他功能,相关功能点都写在了我们开源仓库的文档中,详细的用法和注意事项,可以访问我们的[github仓库](https://github.com/Qymh/vue-router-invoke-webpack-plugin),如果觉得项目还不错的话,可以给我们点一颗小星星,当然如果你在使用中发现了和预期不太一样的情况或者bug可以随时给我们提`issue`

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 写文章[https://www.jianshu.com/writer#/]注册[https://www.jians...
    过场_ab79阅读 314评论 1 1
  • 黑客常用命令大全 net user heibai lovechina /add 加一个heibai的用户密码...
    倒带默写阅读 16,772评论 0 24
  • # 组件说明 vue公共组件说明 ## dialog-弹出框类型组件 ### import-file-dialog...
    烟花渲染离别阅读 195评论 0 0
  • 综和复习本月知识点总结: 一:微信小程序 二:vue复习 三:js复习 四:新知识点 一:微信小程序知识点总结 开...
    飞鸟_45d4阅读 345评论 0 0
  • # Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列...
    小迈克阅读 2,956评论 1 3