我为什么要写这个插件

做 uni-app 项目时,路由路径通常长这样:

  1. /pages/profile/index
  2. /pages/article/detail
  3. /packages/showcase/campaign/index

从框架内部看,这没什么问题。pages.json 就是这么组织页面的,uni.navigateTouni.switchTab 也都认这种真实路径。

但一旦这些路径要出现在用户面前,就有点别扭了。

比如一个个人中心页面,对外更像应该叫 /mine;一篇文章详情页,更适合叫 /post?id=9527;一个活动页,可能希望跟 PC 端保持同一个链接结构,比如 PC 是 /campaign,移动端也最好能打开 /campaign,而不是让用户看到 /pages/campaign/index/packages/demo/subpage/index

这就是 uni-routealias 想解决的问题:真实页面路径继续交给 uni-app,公开路径交给业务。

/pages 最大的问题不是长,而是不稳定

很多时候我们会觉得 /pages/xxx/index 只是“不好看”。但真正麻烦的是,它把项目目录结构暴露给了外部链接。

目录一改,链接就跟着变。页面从主包挪到分包,链接也要重新处理。PC 端已经上线的链接是 /article/123,移动端却只能识别 /pages/article/detail?id=123,最后就会出现一堆临时判断和重定向。

这些判断短期看能跑,长期看会变成路由债务。

我的做法是把路径拆成两层:

  • 真实路径:框架内部使用,比如 /pages/article/detail
  • 公开路径:对外展示和传播,比如 /post

真实路径可以随着项目结构调整,公开路径尽量保持稳定。用户、运营、PC 端、外部投放链接都只关心公开路径。

uni-routealias 做了什么

uni-routealias 的核心思路很简单:构建期读取 pages.json,生成一份路由映射;运行时再根据当前平台,把公开路径转换成真正可跳转的路径。

它主要做几件事:

  • 自动读取 pages.json 里的页面和分包配置
  • 支持在页面配置上写 routeAlias
  • /pages/... 页面生成默认 alias,比如 /pages/profile/index 可以对应 /profile/index
  • H5 下把 alias 注入到路由表,让地址栏能显示更干净的路径
  • App 和小程序下仍然返回 uni-app 能识别的真实页面路径
  • 保留 query 和 hash,不会因为转换路径丢参数
  • 对重复 alias、/ 保留路径、和真实路径冲突的 alias 给出 warning

我不想让它变成一个大而全的路由系统。它只做一件事:把“公开路径”和“真实页面路径”之间的转换管好。

安装和接入

包已经发布到 npm:

  1. npm install uni-routealias

也可以用 pnpm:

  1. pnpm add uni-routealias

如果你习惯用 HBuilderX 或 uni_modules 的方式管理插件,也可以直接从 DCloud 插件市场安装:https://ext.dcloud.net.cn/plugin?id=27693

这两个渠道没有本质区别。npm 更适合纯工程化项目,插件市场更适合已经围绕 HBuilderX 和 DCloud 生态开发的项目。选你项目里更顺手的那个就行。

然后在 vite.config.ts 里注册构建插件:

  1. import { defineConfig } from "vite";
  2. import uni from "@dcloudio/vite-plugin-uni";
  3. import { createRouteAliasVitePlugin } from "uni-routealias/vite";
  4. export default defineConfig({
  5. plugins: [createRouteAliasVitePlugin("./src/pages.json"), uni()],
  6. });

如果你的 pages.json 就在默认位置,通常也可以直接写:

  1. createRouteAliasVitePlugin()

插件会优先找 src/pages.json,找不到再回退到项目根目录的 pages.json

接着在 main.ts 安装运行时插件:

  1. import App from "./App.vue";
  2. import { createSSRApp } from "vue";
  3. import { installRouteAliasPlugin } from "uni-routealias";
  4. installRouteAliasPlugin();
  5. export function createApp() {
  6. const app = createSSRApp(App);
  7. return { app };
  8. }

最后在 pages.json 里给页面写别名:

  1. {
  2. "pages": [
  3. {
  4. "path": "pages/home/index",
  5. "routeAlias": "/home",
  6. "style": {
  7. "navigationBarTitleText": "首页"
  8. }
  9. },
  10. {
  11. "path": "pages/custom/index",
  12. "routeAlias": "/custom-entry",
  13. "style": {
  14. "navigationBarTitleText": "自定义 alias 示例"
  15. }
  16. }
  17. ],
  18. "subPackages": [
  19. {
  20. "root": "packages/demo",
  21. "pages": [
  22. {
  23. "path": "subpage/index",
  24. "routeAlias": "/subpackage-entry"
  25. }
  26. ]
  27. }
  28. ]
  29. }

routeAlias 要写在页面项的根级,不要写进 style。另外,/ 是首页快捷入口,别把某个页面的 alias 配成 /

跳转时不要自己猜平台

这个插件里我最常用的是 resolvePlatformRouteUrl

  1. import { resolvePlatformRouteUrl } from "uni-routealias";
  2. uni.navigateTo({
  3. url: resolvePlatformRouteUrl("/custom-entry?from=home"),
  4. });

在 H5 里,它会尽量返回公开路径,让地址栏保持 /custom-entry?from=home 这种更适合传播的形式。

在 App 和小程序里,它会返回真实页面路径,例如 /pages/custom/index?from=home,因为这些平台的 uni.navigateTo 更关心 pages.json 里注册过的路径。

也就是说,业务代码可以一直写公开路径,把平台差异交给插件处理。

这点很重要。否则项目里很容易散落一堆这样的代码:

  1. // 不推荐:平台判断散在业务里,后面很难维护
  2. const url = isH5 ? "/custom-entry" : "/pages/custom/index";

我更希望它变成这样:

  1. const url = resolvePlatformRouteUrl("/custom-entry");

路径规则集中之后,链接兼容问题就不会到处扩散。

和 PC 端链接保持一致

uni-routealias 还有一个我很在意的用途:让移动端兼容 PC 端已经存在的链接。

很多业务最早是 PC 页面先上线,链接可能已经被搜索、投放、短信、公众号或者客户系统引用了。PC 端链接通常不会带 /pages,比如:

  1. /product/detail?id=1001
  2. /campaign
  3. /article?id=9527

而移动端 uni-app 项目的真实路径可能是:

  1. /pages/product/detail
  2. /packages/demo/campaign/index
  3. /pages/article/detail

如果不做兼容,就会出现两个问题:

第一,PC 链接发到手机上打不开,或者要额外写一层中转页。

第二,移动端分享出去的链接和 PC 端不一致,后续做统计、投放归因、客服排查都会麻烦。

有了 routeAlias 之后,可以直接把移动端页面挂到和 PC 一样的公开路径上:

  1. {
  2. "path": "pages/article/detail",
  3. "routeAlias": "/article"
  4. }

业务里继续用:

  1. uni.navigateTo({
  2. url: resolvePlatformRouteUrl("/article?id=9527"),
  3. });

这样 H5 地址栏、外部分享链接、PC 端已有链接都可以围绕 /article?id=9527 这套公开地址来设计。移动端内部需要的 /pages/article/detail?id=9527,插件会在合适的平台上自动转换。

这不是为了“好看”而做的优化,它解决的是平台链接兼容性。

分享链接也应该用公开路径

跳转是一个场景,生成分享链接是另一个场景。

如果你手里拿到的是真实路径,可以用 resolvePublicRouteUrl 转成对外展示的路径:

  1. import { resolvePublicRouteUrl } from "uni-routealias";
  2. const shareUrl = resolvePublicRouteUrl("/pages/article/detail?id=9527");
  3. // /article?id=9527

这样分享出去的链接不会带着项目目录结构。对用户来说,它更短;对开发来说,它也更稳定。

还有一个辅助方法 getRoutePublicPaths,可以拿到某个页面对应的所有可识别路径。排查路由问题时挺有用,尤其是页面同时有真实路径、默认 alias、自定义 alias 的时候。

分包页面要显式写 routeAlias

默认 alias 只会自动裁掉 /pages 前缀。

比如:

  1. /pages/default-alias/index -> /default-alias/index

但分包路径一般不是 /pages 开头,像这样:

  1. /packages/demo/subpage/index

插件不会擅自把它猜成某个短路径。分包页面如果要有稳定的公开地址,我建议显式写 routeAlias

  1. {
  2. "path": "subpage/index",
  3. "routeAlias": "/subpackage-entry"
  4. }

这也是我刻意保守的地方。路由插件最怕“猜得很聪明”,结果线上跳到了错误页面。相比自动脑补,我更愿意让重要的对外路径被明确写出来。

有些限制是故意留下的

routeAlias 只能是纯路径,不能带 ?#

也就是说,应该这样写:

  1. {
  2. "routeAlias": "/article"
  3. }

然后在跳转时带参数:

  1. resolvePlatformRouteUrl("/article?id=9527#comments")

不要这样写:

  1. {
  2. "routeAlias": "/article?id=9527"
  3. }

这个限制看起来麻烦,但它能让路由表保持清晰:路径归路径,参数归参数。

另外,未知路径不会被强行推断成某个 /pages/... 页面。如果你想做严格判断,可以用 tryResolveActualRoutePathtryResolveActualRouteUrl。解析失败时它们会返回 null,比静默兜底更适合做错误处理。

我推荐的使用方式

如果项目刚接入,我建议按这个顺序来:

  1. 先给需要对外传播的页面配置 routeAlias
  2. 业务跳转统一走 resolvePlatformRouteUrl
  3. 分享、短信、投放链接统一用 resolvePublicRouteUrl
  4. PC 和移动端共用的链接,优先以 PC 已有公开路径为准
  5. 分包页面不要依赖默认 alias,直接显式配置

这样改动不会太大,也不会一次性把项目里的所有路由都翻出来重构。

最后

uni-routealias 不是一个复杂插件,它只是把 uni-app 项目里经常被忽略的路由细节收拢起来。

以前我们可能会在 H5、App、小程序、PC 兼容链接之间反复写判断。现在可以把规则写在 pages.json,把跳转入口收敛到几个 API 上。

对我来说,这类工具最好的状态就是:平时感觉不到它,链接发出去之后也不需要再解释“为什么这里有个 /pages”。

如果你也在维护 uni-app Vue3 + Vite 项目,并且已经被 H5 地址栏、分享链接、PC 和移动端链接兼容折腾过,可以试试这个包: