路由自动注入概念学习自nuxt,咱们不须要在router.js
中每次手动输入代码引入模块而是自动根据文件目录格式
生成router.js
javascript
咱们把这个功能独立成一个webpack
插件,并对相关功能进行了完善,并且实现了vue-router
的全部核心功能前端
更详细使用指南和文档能够查看咱们的github仓库vue
举一个简单的列子,好比你的目录长这样java
src
├── views
│ ├── Login
│ │ └── Index.vue
│ └── User
│ ├── Account
│ │ └── Index.vue
│ ├── Home
│ │ └── Index.vue
│ └── Index.vue
复制代码
规则很简单,若是目录的一层是Index.vue
,则目录名即是当前的路由名字,若是是子文件夹则是第二层路由,以后自动生成的router.js
会长成这样node
{
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
中webpack
不用每次去引用模块,只用建立文件夹,router.js
会自动生成git
若是有完整的code review
这个问题是不会存在的,但咱们稍微作了一点简便,只要code review
文件夹的命名就行了,最终生成的路由path会以驼峰命名,生成的name会以驼峰命名而且以连字符-
链接不一样层级的路由github
如图片中的列子,咱们没法从文件的命名去判断路由到底在几级,并且常常写的时候,明明是2级或3级路由却和1级路由在一层路由下,这是很不规范并且与逻辑不符的web
对比一下使用自动注入划分层级后的路由vue-router
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-router-invoke-webpack-plugin
中独特的路由划分思惟当咱们的页面过多的时候,好比项目有60多个甚至70多个单页面,文件不可能会放在一个目录下,通常这种时候,咱们会按功能
将类似功能的路由放在一个目录下,咱们以前也是这么作的,其实这么作也是没啥问题的,但在路由自动注入下,咱们提出了另一种思路按路由层级
划分
什么是层级划分呢,简单的一句话就是根据页面所在的相对url地址进行划分,举个列子,咱们的首页以下
首页的路由为/
,咱们把首页看成根路由,那么能够进入的一级路由分别为提现
提现记录
分红数据
等,点击提现后,咱们进入了提现路由/withdraw
进入提现页面后,会有两处可点击,这两处即是二级页面,放在一级页面的子文件夹中,按刚才的说法,路由目录(截取部分)即是这样
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();
}
}
}),
]
复制代码
但话说回来,任何实现思路,前端获取的接口数据想篡改仍是能绕过去的,因此仍是得后端再防一层
项目实现不太复杂,但要照顾到的地方不少
fs
模块十分不友好要考虑的小细节还挺多的,特别是当路由过于复杂的状况
但node的fs
的坑点是我没有想到的,特别是在跨平台上,因此咱们舍弃了使用原生的fs
模块,用chokidar
和fs-extra
替代了fs
的部分功能
前段时间也在学习vue
的ast语法树,因此学习了下思路去尝试构建一棵ast,不过方法仍是有区别的,vue构建语法树是经过正则拆分了元素开始标签
元素属性
元素字符
元素结束标签
等而后拼接而成的,拼接的过程特别复杂,这个项目会简单不少,直接经过文件读取递归遍历目录就能够生成一棵ast
了
而后经过语法树去构建字符串的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仓库,若是以为项目还不错的话,能够给咱们点一颗小星星,固然若是你在使用中发现了和预期不太同样的状况或者bug能够随时给咱们提issue