作者:苏梓铭
背景介绍
日前,古茗前端团队内部统一采用了 React 技术栈,所有新项目和基本建设均基于 React 框架研发。然而,许多老旧的 Vue 应用仍在运用和运行,因此呢咱们必须进行项目迁移,统一技术栈,减少研发认知包袱,接轨现有基建,加强可守护性,以便更好地满足业务新需求。
尤其地,以笔者负责的学院业务域为例,其业务拥有这么一个特点:存在众多外边链接转场景(例如,发布给加盟商的考试待办链接、张贴在店内设备上的资料二维码等)。在迁移老项目的过程中,咱们亦要格外关注外链治理。
本文重点讲述笔者治理老项目存量外链投放问题的处理思路。
状况及目的分析
在迁移老项目的过程中,咱们发掘了许多问题:
外链更新的转问题
咱们对外投放的链接一般拥有很强的时效性,例如给加盟商发送的考试链接等,况且这些外链是静态、没法直接修改的,因此呢咱们一般会采用重定向规律,将旧路由映射到新路由,让老外链亦能转到新页面。
在老项目的导航守卫里,已然累积了海量的重定向规律,并且每一个模块都有一套自己单独的重定向策略,如下图所示: 现有规律
因此呢,当老项目迁移完成下线后,已然投放出去的老链接将会没法拜访(由于所有的重定向规律都在老项目内完成)
其次,外链投放还存在必定的不确定性,有的乃至是产研团队自己都不晓得的外链投放,这就引起咱们有时对页面路由进行改造后,会得到其他业务域的业务反馈。
路由传参的心智包袱
在分析页面迁移时,咱们重视到新旧页面的路由经常经过 URL 传递海量参数。缺乏统一规范来守护这些参数,使得页面的守护变得繁杂。
目的分析
针对以上问题状况,咱们能够总结出如下目的: 老项目路由重定向:将重定向规律做保存,实现对历史外链的兼容操作可感知:用户的拜访应当有完善的链路能够被感知到,用户拜访了老项目路由时,咱们必须能够记录日志,当用户拜访 404 时咱们必须晓得是哪个页面报了 404,并且自动即时地告警同步给业务 Owner 排查。统一技术栈/统一规范:咱们应当全链路接入前端基建,运用最新的统一技术栈,针对路由转等场景必须有一个统一的技术规范知道了几个基本目的后,咱们就能够着手进行代码结构设计了
核心代码设计与实现
路由重定向怎样转发——Nginx, NodeJS OR JavaScript?
在设计重定向转发方法之前,咱们首要思虑的是直接运用 Nginx 或 Node 做转发,但因为日前两个 H5 项目都运用 Hash 路由,而咱们晓得 Hash 路由 # 后面的参数是不会发送给服务器的,因此呢 Nginx 和 Node 乃至都没法读取到路由本身,更不消提做重定向了,因此呢咱们确定了最后处理方法还是交由客户端处理,Nginx 层只做映射路径的转发(将老项目 / 的路径直接转发到 /college 下就可) API 调用方式设计
设计一个简洁明了的 API 调用方式一样是技术方法的要紧一环。基于配置化的思想,咱们必须把原有的导航守卫中 if else 的糅杂一团的重定向规律,改导致守护一个路由转表。即经过配置固定格式的原 URL 和目的 URL 来声明重定向,调用方式大致如下: const redirector = new
Redirector() .register(/knowledge/learnList, /pages/material-list/index
) .register(/knowledge/detail/:id, /pages/material-detail/index?id=:id
);
这般一来咱们注册重定向就变得非常简单。注册完成之后,咱们就能够经过调用redirector.run办法来执行一次重定向,接下来是详细的代码实现。 重定向路由转表设计名词解析
在设计转表之前,咱们必须知道,路由传参有两种类型的方式,一种是 params,一种是 query。其中 params 是做为路由路径的一部分存在的,例如 /pending/list/1234567 中的 1234567 就表率着 id 这个参数;而 query 便是在 ? 之后的键值对参数了,例如 /pages/list/index?id=1234567 这种形式。
上文的 API 设计中,仅做了 params 类型的传参映射,而针对页面的其他参数是无处理的,这儿咱们需求,新老页面的出入参要保持相同,这般就能够直接共用一套 query 参数,减轻咱们的研发成本 怎样转换路由正则匹配式
原先的 Vue2 老项目实现重定向是利用 vue-router 自带的实现。经过调研,咱们选取运用 path-to-regex 库做路由解析,它是 Vue-Router 等众多知名路由库的底层依赖库,正如其名,它能够将路由转化成正则表达式去匹配链接,并且支持动态路由参数等许多功能,官方示例如下: 官方示例实现 Redirector 类
path-to-regex 拥有非常简单易用的 API,针对咱们检测和提取路由参数的需求来讲已然完全足够,咱们能够经过调用 match 办法获取到解析路由的正则表达式,并据此实现一个 Redirector 类: import{ history }from @tarojs/router
; import Taro from @tarojs/taro
; import { match, MatchFunction } from path-to-regexp
; type RegisterPath = `/${string}`
; interface
RouteObject {
matchFn: MatchFunction; target: string
;
} class
Redirector { // 本地注册的路由转配置 private
routes: RouteObject[] = [];
register(oldPath: RegisterPath, newPath: RegisterPath): Redirector { this
.routes.push({
matchFn: match(oldPath),
target: newPath,
}); // 链式调用 return this
;
} redirect(path: string, query = ): void
{ this.routes.some(({ matchFn, target }) =>
{ const
result = matchFn(path); if
(result) { // 替换路由参数 Object.entries(result.params).forEach(([key, value]) =>
{ target = target.replace(`:${key}`
, value);
}); // 处理 query,Taro 路由的必须直接拼在参数上 target = `${target}${target.includes(?) ? & : ?}${query}`
; // 执行重定向
Taro.redirectTo({ url: target }); return true
;
} return false
;
});
} run(): void
{ const [path, query] = location.hash.slice(1).split(?
); this
.redirect(path, query);
}
} // 直接注册重定向后导出实例就可const redirector = new
Redirector() .register(/knowledge/learnList, /pages/material-list/index
) .register(/knowledge/detail/:id, /pages/material-detail/index?id=:id
); export default
redirector;
其基本思路便是经过 match 办法生成的正则表达式,匹配摘出原 URL 中的路由参数,并拼接和替换到新的 URL 上 何时执行——完整的一次重定向链路
这次迁移的 C 端 H5 微应用同期由两个项目构成,分别是运用 Vue2 技术栈的老项目和运用 Taro React 的新项目。两个项目经过 Nginx 转发到同一个域名的区别路由下:老项目映射在 / 根目录,而新项目则是映射在 /college 目录下,所处同一个域,从而能够共享有些本地数据如 localStorage、sessionStorage 等。 (1)用户拜访,Nginx 层转发
以用户外链转到老项目路由为例: https://host/#/knowledge/learnList?rankId=aaa&ListName=bbb
因为老页面会下线,拜访服务时 Nginx 正常匹配其他路由,兜底匹配根路由 /直接转发到 /college 上: https://host/college/#/pages/material-list/index?rankId=aaa&ListName=bbb
因此呢用户在 Nginx 层被第1次重定向到: https://host/college/#/knowledge/learnList?rankId=aaa&ListName=bbb(2)执行重定向
重定向后,咱们必须在项目内解析路由参数后拼接转。这儿 Taro 没法匹配到页面,然则 app.tsx 中仍然会执行代码规律,因此呢咱们在新项目的 app.tsx 中执行重定向
// app.tsx
import redirector from "@/utils"
function App({ children }){
useMount(() => {
redirector.run()
})
}
必须监听路由变化吗?
咱们能够经过 history.listen 办法监听路由的变化,并且在每次路由改变时都去匹配一次重定向: history.listen(({ location }) =>
{ const
{ pathname, search } = location;
redirector.redirect(pathname, search);
});
但实质上咱们并不必须做到这么全面,咱们完全能够保准在学院新项目里不会显现老项目的路由,因此呢倘若用户已然进入了应用里,就不会经过路由转进入到老项目了。倘若监听了路由变更反而会带来额外的性能开销。
操作感知
倘若无触发重定向或是重定向失败,都会引起出现 404 错误,咱们会上报一次错误日志。而只要触发了重定向,咱们就会上报一次重定向记录 404 感知function App({ children })
{ usePageNotFound((e) =>
{ const tag = PAGE_NOT_FOUND
; const log = `path=${e.path}`
;
Slardar.logger()?.error(log, tag);
Taro.redirectTo({ url: `/pages/404/index?from=${e.path}`
,
});
})
}
详细操做为:在 App.tsx 下新增 usePageNotFound hook,调用 Taro 原生路由回调,经过数据平台上报 error 日志,并在 404 页面展示提示和链接,引导用户回到学院应用和反馈 404 页面咱们能够在数据中心查看上报的日志信息等
关联文案:古茗是怎样做前端数据中心的 - 掘金 日志查找
因为 404 错误通常不会偶现,只要显现就表率页面路由或是外链投放存在问题。咱们经过配置错误告警策略,来即时通告研发查看日志,发掘并处理路由问题。 告警配置重定向感知
在执行重定向的时候上报执行日志,提交原路由和目的路由的完整信息
class Redirector {
private routes: RouteObject[] = [];
register(oldPath: RegisterPath, newPath: RegisterPath): Redirector {}
redirect(path: string, query = ): void {
this.routes.some(({ matchFn, target }) => {
const result = matchFn(path);
if (result) {
Object.entries(result.params).forEach(([key, value]) => {
target = target.replace(`{key}`, value);
});
target = `${target}${target.includes(?) ? & : ?}${query}`; + const tag = AGE_NOT_FOUND;+ const log = `from=${path}&to=${target}`;+ Slardar.logger()?.info(log, tag);
Taro.redirectTo({ url: target });
return true;
}
return false;
});
}
run(): void {
const [path, query] = location.hash.slice(1).split(?);
this.redirect(path, query);
}
}
这次咱们把新迁移的页面路由和已有的重定向路由所有加入到配置表中,并且在重定向出现时上报拜访的链接和转的链接,收集 30 日的数据后再进行一次整理,观察线上流量分布,逐步下掉无运用的重定向,减少守护成本
统一规范——路由转办法
因为老项目的迁移过程中触及到了海量的路由传参,经常会显现链接里挂了一大堆参数,实质页面里却基本无用到的状况,咱们很难分辨那些路由参数是有效的,那些路由参数又是能够舍弃的。因此呢,咱们在本业务域内的移动端项目内做了如下统一规范: 针对页面守护 TS 入参类型定义;路由转和获取入参运用公共办法,传入页面入参的类型定义做为泛型参数,规范页面转
详细调用方式如下: 转页面运用 formatUrlParams 格式化 query 参数import { BaseAssignmentParams } from "@/pages/assignment/index/index"consthandleClick =(assignment: CourseAssignmentItem) =>
{ const
baseQuery = formatUrlParams({
trainingId: trainingDetail.id,
semesterId: trainingDetail.semesterId,
assignmentId: assignment.id,
});
Taro.navigateTo({ url: `/pages/assignment/index/index?${baseQuery}`
,
});
}; 承接页面运用 getUrlParams 反序列化路由参数export interface
AssignmentParams { /** 培训 id */ trainingId: string
; /** 学期 id */ semesterId: string
; /** 任务 id */ assignmentId: string
;
} const Assignment: React.FC = () =>
{ const
{ semesterId, trainingId, assignmentId } = getUrlParams();
} export default
Assignment;
如此一来,就在两个页面成功经过类型系统架起了一道桥梁,倘若页面新增或是删改了入参,都能够经过静态类型分析在研发周期就得到提示,修改路由出入参时的心智包袱显著减小,项目可守护性得到了明显加强。
完整流程图完整流程图总结
本文介绍了笔者在做业务项目迁移的时候处理外链规律以及对项目内的页面出入参和转场景进行统一化治理的思路,期盼能给一样被历史项目和技术债熬煎的朋友们供给有些可行性意见,亦欢迎大众在评论区一块交流,一起进步
最后
|