router-loader 介绍

仅有Lavas MPA 模版包含此功能

问题背景

在单页应用中我们使用 vue-router 进行前端路由跳转,而多页应用可以看成多个单页应用,每个单页都可以有各自独立的路由,那么如何做到在各个单页之间进行跳转呢?

例如想从A页面跳转到B页面,发现目标路由规则并不在A页面的规则集中,此时肯定不能展示404页面,需要识别出这是一个有效的路由规则,通过window.location.href而非 vue-router 进行强制跳转。

这就要求我们在构建时收集所有页面使用的路由规则,形成一个项目中有效的路由规则全集。各个页面遇到不匹配的路由时,都需要去这个全集中查看,匹配了其中的某条规则才进行强制跳转,否则依然展示404页面。

具体实现

依据以上实现思路,首先在通用路由生成函数中,加入通用路由处理规则*,在beforeEnter钩子中使用validateRoute校验目标路由是否匹配项目中使用到的合理规则,校验失败依旧展示404页面。

  1. // src/router.js
  2. export function createRouter({routes = []}) {
  3. const router = new Router({
  4. routes: [
  5. ...routes,
  6. {
  7. path: '*',
  8. component: NotFound,
  9. beforeEnter(to, from, next) {
  10. // 有效路由,跳转
  11. if (validateRoute(to.fullPath)) {
  12. window.location.href = to.fullPath;
  13. return;
  14. }
  15. next(); // 无效路由,展示404页面
  16. }

validateRoute方法中,由于动态路由的存在,需要对动态参数进行替换,使用正则进行匹配。

  1. function validateRoute(path) {
  2. return allRoutes.includes(path)
  3. || allRoutes.some(route => {
  4. // 生成路由路径对应的正则表达式 /detail/:id => /^\/detail\/[^\/]+\/?$/
  5. let routeRegex = new RegExp(`^${route.replace(/\/:[^\/]*/g, '/[^\/]+')}\/?$`);
  6. return routeRegex.test(path);
  7. });
  8. }

对于路由规则全集allRoutes,将由 router-loader 负责收集。

router-loader 实现

首先明确路由全集的注入点,在src/router.js中,我们需要将路由全集放入数组allRoutes中。

  1. // src/router.js
  2. const allRoutes = [];

Lavas MPA 模版中,每个页面有独立的路由文件:

  1. lavas-template-vue-mpa
  2. |---src
  3. |---pages 页面存放目录
  4. |---detail 详情页模块
  5. |--- Detail.skeleton.vue 构建时渲染的skeleton组件
  6. |--- Detail.vue 路由组件
  7. |--- entry-skeleton.js skeleton入口
  8. |--- entry.js entry入口
  9. |--- index.html 页面模版,供htmlWebpackPlugin使用
  10. |--- router.js 单页面使用的路由
  11. |---home 主页模块
  12. |---search 搜索页模块
  13. |---...省略其他目录

router-loader 只需要遍历各个页面的路由文件,从内容中提取出使用的路由规则路径(例如path: '/home'),拼接后注入。
另外,在 svg-loader介绍一文中提到了监听文件更新的问题,这里我们也将每个路由文件加入了 webpack 监听文件列表,方便开发使用。

  1. // src/router.js插入点
  2. let pos = source.indexOf(INSERT_POSITION) + INSERT_POSITION.length;
  3. // 各个页面路由路径
  4. let entryRouters = utils.getEntries('./src/pages', 'router.js');
  5. let routePaths = [];
  6. for (let entryName in entryRouters) {
  7. if (entryRouters.hasOwnProperty(entryName)) {
  8. let routerPath = path.resolve(__dirname, '../../', entryRouters[entryName]);
  9. // 读取各个页面路由文件内容
  10. let content = fs.readFileSync(routerPath, 'utf8');
  11. // 解析内容中的路径
  12. routePaths = routePaths.concat(extractRoutePaths(content));
  13. // 加入文件监听列表
  14. this.addDependency(routerPath);
  15. }
  16. }
  17. // 拼接所有路由路径,注入
  18. return insertAt(source, routePaths.join(','), pos);