详解Vue路由自动注入实践


Posted in Javascript onApril 17, 2019

什么是路由自动注入

路由自动注入概念学习自nuxt,我们不需要在 router.js 中每次手动输入代码引入模块而是自动根据 文件目录格式 生成 router.js

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

更详细使用指南和文档可以查看我们的 github仓库

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

src
├── views
│ ├── Login
│ │ └── Index.vue
│ └── User
│  ├── Account
│  │ └── Index.vue
│  ├── Home
│  │ └── Index.vue
│  └── Index.vue

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

{
 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 )第一次构建项目都会自动生成,比如你项目用到了 giteslint ,那么应该把它放在 .gitignore.eslintignore

为什么使用路由自动注入

方便

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

统一路由命名

详解Vue路由自动注入实践

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

统一路由层级

详解Vue路由自动注入实践

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

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

src/views
├── Index.vue
├── NotFound.vue
├── Withdraw
 <!-- 第一级 -->
│ ├── Index.vue
│ └── Result
│  ├── Description
   <!-- 第三级 -->
│  │ └── Index.vue
  <!-- 第二级 -->
│  └── Index.vue
└── WithdrawHistory
 <!-- 第一级 -->
 └── Index.vue

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

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

{
 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

完善的单元测试

详解Vue路由自动注入实践

types支持

详解Vue路由自动注入实践

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

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

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

详解Vue路由自动注入实践

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

详解Vue路由自动注入实践

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

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 去忽略掉不被我们解析的目录,比如这样

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

export default {
 // 请求当前没有权限的路由列表
 async getForbiddenRoute() {
 return ['/single/user'];
 }
};
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 模块,用 chokidarfs-extra 替代了 fs 的部分功能

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

详解Vue路由自动注入实践

然后通过语法树去构建字符串的 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

更多的功能

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
ajax 文件上传应用简单实现
Mar 03 Javascript
Mootools 1.2教程 类(一)
Sep 15 Javascript
jQuery Ajax之$.get()方法和$.post()方法
Oct 12 Javascript
文本框input聚焦失焦样式实现代码
Oct 12 Javascript
javascript中call,apply,bind的用法对比分析
Feb 12 Javascript
jQuery实现鼠标经过提示信息的地图热点效果
Apr 26 Javascript
javascript判断并获取注册表中可信任站点的方法
Jun 01 Javascript
JavaScript模拟push
Mar 06 Javascript
jQuery Easyui学习教程之实现datagrid在没有数据时显示相关提示内容
Jul 09 Javascript
微信小程序  生命周期详解
Oct 27 Javascript
vue插件实现v-model功能
Sep 10 Javascript
代码块高亮可复制显示js插件highlight.js+clipboard.js整合
Feb 15 Javascript
在Vue项目中使用jsencrypt.js对数据进行加密传输的方法
Apr 17 #Javascript
js的继承方法小结(prototype、call、apply)(推荐)
Apr 17 #Javascript
详解JavaScript的内存空间、赋值和深浅拷贝
Apr 17 #Javascript
Vue源码探究之虚拟节点的实现
Apr 17 #Javascript
ES6知识点整理之数组解构和字符串解构的应用示例
Apr 17 #Javascript
ES6知识点整理之对象解构赋值应用示例
Apr 17 #Javascript
ES6知识点整理之函数对象参数默认值及其解构应用示例
Apr 17 #Javascript
You might like
php实现的遍历文件夹下所有文件,编辑删除
2010/01/05 PHP
让PHP显示Facebook的粉丝数量方法
2014/01/08 PHP
Yii中的cookie的发送和读取
2016/07/27 PHP
js 操作select相关方法函数
2009/12/06 Javascript
仿谷歌主页js动画效果实现代码
2013/07/14 Javascript
JavaScript中instanceof与typeof运算符的用法及区别详细解析
2013/11/19 Javascript
node.js中的fs.utimes方法使用说明
2014/12/15 Javascript
ExpressJS入门实例
2015/01/14 Javascript
JavaScript动态添加列的方法
2015/03/25 Javascript
js实现仿阿里巴巴城市选择框效果实例
2015/06/24 Javascript
JavaScript实现的类字典插入或更新方法实例
2015/07/10 Javascript
javascript创建对象的3种方法
2016/11/02 Javascript
Vue.js组件tree实现无限级树形菜单
2016/12/02 Javascript
vue.js实现数据动态响应 Vue.set的简单应用
2017/06/15 Javascript
js 发布订阅模式的实例讲解
2017/09/10 Javascript
详解React Native 采用Fetch方式发送跨域POST请求
2017/11/15 Javascript
微信小程序使用Promise简化回调
2018/02/06 Javascript
在react-router4中进行代码拆分的方法(基于webpack)
2018/03/08 Javascript
nodejs中函数的调用实例详解
2018/10/31 NodeJs
爬虫利器Puppeteer实战
2019/01/09 Javascript
jQuery实现合并表格单元格中相同行操作示例
2019/01/28 jQuery
小程序封装wx.request请求并创建接口管理文件的实现
2019/04/29 Javascript
Vue中错误图片的处理的实现代码
2019/11/07 Javascript
[06:57]DOTA2-DPC中国联赛 正赛 Ehome vs PSG.LGD 选手采访
2021/03/11 DOTA
python通过线程实现定时器timer的方法
2015/03/16 Python
Python处理字符串之isspace()方法的使用
2015/05/19 Python
Python数据类型详解(二)列表
2016/05/08 Python
Python设计模式之观察者模式简单示例
2018/01/10 Python
使用Flask集成bootstrap的方法
2018/07/24 Python
Python自动巡检H3C交换机实现过程解析
2020/08/14 Python
为2021年的第一场雪锦上添花:用matplotlib绘制雪花和雪景
2021/01/05 Python
精选鞋类、服装和配饰的全球领先目的地:Bodega
2021/02/27 全球购物
夜不归宿检讨书
2014/02/25 职场文书
对孩子的寄语
2014/04/09 职场文书
2015年暑期社会实践总结
2015/07/13 职场文书
一篇带你入门Java垃圾回收器
2021/06/16 Java/Android