校对:soup
Ecopage的客户端导航和视图过渡. 它截取同源链接点击,以提供平滑的页面转换而无需全页重载.
特征
- 客户端导航: 拦截
<a>点击以获得强大的,快速的导航. - 高效 DOM 分发: 用途 形态 仅更新更改的内容,保留滚动位置和内部状态.
- 国家的持久性键
data-eco-persist永远不会重新创建, 保存 Web 组件状态, 事件听众, 并形成值 . - 查看过渡: 与视图过渡API的可选集成.
- 生命周期事件: 钩入导航
eco:before-swap, (中文(简体) ),eco:after-swap, (中文(简体) ),eco:page-load。 。 。 .
核心导航管道共享文档获取、解析、头调和、样式表显示、脚本激活、命名视图过渡和生命周期事件 @ecopages/react-router。浏览器-路由器总是对即将到来的天体进行变形,无论视图-过渡设置或重运行脚本,都在整个导航中保留了恒定元素。 查看过渡只包入形态;滚动还原使用共享的后游钩.
浏览器-路由器保留了自己预留和访问页面的缓存:一次重访会让缓存的HTML立即被刷新,而缓存副本在背景中不被刷新. 设定 prefetch: false 以关闭缓存,所以每个导航都会获取新的 HTML. @ecopages/react-router 不读取此缓存, 并总是获取新的 HTML .
兼容性
这个软件包是为MPA风格的渲染而设计的,服务器返回了完整的HTML页面(如KitaJS,Lit,vanilla JS,或组件级反应岛).
[尖叫声] 不兼容完整 React 应用程序 . 如果您正在构建反应应用程序, 请使用 @ ecopages/ react- routter 相反,由于React管理着自己的虚拟DOM和水分生命周期.
[注]
@ecopages/browser-router仍然可以参与包含React-router页面和非React页面的混合站点. 在该设置中,浏览器-router仍然负责DOM- switching非React文档,React-router可以通过共享的导航协调器将已经触摸到的非React页面交给它.
安装
bun add @ecopages/browser-router
使用量
在 a 中创建并启动路由器 全球 客户端脚本( 例如 ), src/layouts/base-layout.script.ts) (中文(简体) ).
[大 确保路由器脚本注入到一个 连续顺序 内部
<head>横跨所有页面。 顺序不一致的原因morphdom以重新装入样式,从而导致“未设定内容的闪烁”.
import { createRouter } from '@ecopages/browser-router/client';
// Creates and starts the router with default options
const router = createRouter();
带有自定义选项 :
import { createRouter } from '@ecopages/browser-router/client';
const router = createRouter({
viewTransitions: true,
scrollBehavior: 'auto',
documentElementAttributesToSync: ['lang', 'dir', 'data-theme'],
});
默认情况下,当视图转换启用时,路由器会选择文档退出 UA 根组(在暗主题上没有更轻的闪光),只调用 startViewTransition 当页面使用时 data-view-transition 共有元素形态。 设定 viewTransitions: false 以禁用.
默认情况下, 浏览器路由器只同步 root <html> 它拥有的元数据。 客户端管理的属性以及主题状态等类被保留,除非您明确将其包含在 documentElementAttributesToSync。 。 。 .
对于高级案例,浏览器-路由器也会导出低等文档同步工具而不改变路由器实例 API:
import {
createRouter,
DEFAULT_DOCUMENT_ELEMENT_ATTRIBUTES_TO_SYNC,
syncDocumentElementAttributes,
} from '@ecopages/browser-router';
const router = createRouter();
document.addEventListener('eco:before-swap', (event) => {
syncDocumentElementAttributes(document, event.detail.newDocument, [
...DEFAULT_DOCUMENT_ELEMENT_ATTRIBUTES_TO_SYNC,
'data-theme',
]);
});
装入路由器脚本是该页外壳上浏览器-路由器管理的导航的选入点. 没有路由器脚本的页面继续使用普通文档导航.
路由器单子
createRouter() 返回每个浏览器标签的单个活动路由器。 第一个呼叫创建、启动和存储实例 window.__ecopages_browser_router__。以后会调回同一实例,而忽略新的选项.
打电话 router.stop() 在长寿会话中替换路由器脚本之前(例如之后) data-eco-rerun 重新装入),如果需要新实例,有不同的选项.
配置
| 选项 | 类型 | 默认 | 说明 |
|---|---|---|---|
linkSelector | string | 'a[href]' | 要截取的链接选择器 |
documentElementAttributesToSync | string[] | ['lang', 'dir', 'data-eco-document-owner'] | <html> 属性以同步; 保留其他根属性 |
persistAttribute | string | 'data-eco-persist' | 标记 DOM 持久性元素的属性 |
reloadAttribute | string | 'data-eco-reload' | 强制重新装入完整页面的属性 |
updateHistory | boolean | true | 是否更新浏览器历史 |
scrollBehavior | 'top' | 'preserve' | 'auto' | 'top' | 导航后滚动行为 |
viewTransitions | boolean | true | 查看过渡 API; 默认情况下禁用 UA root 交叉 . false 禁用 . |
smoothScroll | boolean | false | 导航时使用平滑滚动 |
reloadDocument | (href: string) => void | (href) => window.location.assign(href) | 当路由器将导航递回浏览器时, 将 URL 装入为完整文档 |
持久性
在每个页面上标记具有相同持久性密钥的元素,以在整个导航中保存. 当接入的页面将元素保存在一个相容的祖先结构下时,它会保持连接,并且永远不会被再创建;形态会完全跳过. 当视图过渡被禁用时, 以及进入的页面已关闭时, 此选项将保留 data-eco-rerun 剧本. 改变元素的祖先标记可能要求更换其所包含路径,因此在连接连续性重要时保持该结构的稳定.
在导航到另一个 URL 时, 当进入的页面有一个 data-eco-rerun 脚本,浏览器-路由器在运行前会刷新匹配非已存在的元素,除了正体,坚持元素,其祖先路径,以及该路径上自定义元素的后人. 这些定制的后人留在原地,因为连接的东道主可能附靠他们。 保持处理器捆绑的儿童标记在结构上跨页相容,或者当其儿童标记改变时其组件被重新组合;替换一个儿童不会重新连接主机. 安装在刷新元素上的事件处理器附加到新的节点上,而不是堆放在由形态保留下来的节点上. 一个在任何保留节点上安装处理器的再运行脚本必须使该注册同位素,因为这些节点仍然连接在通航上.
<!-- This counter keeps its internal state across all navigations -->
<radiant-counter data-eco-persist="counter"></radiant-counter>
重新装入当前页面
reloadCurrentPage() 在导航运行时间上,当服务器发送的模板如页面、版式、包含或视图被保存,当模块更新重新加载活动页面时,HMR将调用(clearCache: false),再次获取当前 URL,并将上个文档中每个正文元素的服务器标记与新文档中的标记比较,而不是与活文元素比较. 一个其服务器标记没有改变的元素会停留在它的状态,子,阴影根和运行时间属性;一个已改变的自定义元素被替换,一个已改变的平原元素被变形,当自己的服务器属性没有改变时会保留活的属性. 重放脚本不会刷新重装时的元素, 所以 a data-eco-rerun 脚本必须在重装不替换的每个元素上登记处理器,这些元素只是更改了的自定义元素. 导航到上文所述的另一种URL形态. 路由器仅在开发 HMR 调用后才记录服务器标记 enableReloadStatePreservation() 在导航运行时间, 所以生产导航 复制什么.
已知限制 :
- 在全页负载后的第一个保存比作正文,因为路由器在开始录制时找到了它,所以一个在之前在客户端上渲染出或改变其属性的组件被替换一次.
- 状态跟随树形位置,因此当插入或去除较早的兄弟姐妹时,相同的兄弟姐妹主机可以互换状态.
导航脚本
互换后,一头脚本只在上页头没有的情况下运行,而身体脚本运行在每个导航上,如"涡轮":即使上页脚本相同,身体也是新内容. 这涵盖了内置脚本和外部经典脚本;一个外部模块脚本只在标签中每个URL运行一次,因为浏览器对每个模块URL进行一次评价. 经典剧本的顶级 const, (中文(简体) ), let 财务报告和审定财务报表 class 声明活在页面的全球范围,所以当一个经典的正文脚本再次运行时,声明两次并扔出 SyntaxError: Identifier '…' has already been declared。不写脚本(使用 var 或功能,或将代码包裹在一个块或一个IIFE中,或使其成为模块脚本,每个 URL 运行一次,然后听 eco:page-load 每次访问都必须执行的代码 标记元素内的脚本 data-eco-persist 保持原样,不再运行。 脚本按一个顺序运行:新头脚本,然后 data-eco-rerun 脚本,然后是正文脚本,每组按文档顺序排列. 每个都等待到较早的封禁脚本( 外部经典脚本没有 async, (中文(简体) ), defer,或 nomodule) 已经装入, 如在页面解析时一样, 因此一个内置的脚本会使用一个库, 页面加载在它之后运行 。 外部 defer 和模块脚本也保留该插入顺序;只有脚本页标记 async 他们一上膛就马上跑 如果下一个导航在脚本仍在装入时互换页面,则前作序列的其余部分不运行.
脚本重执行
要强制一个头脚本在每个导航上重新执行(例如分析),请添加 data-eco-rerun数字 :
<script data-eco-rerun="true">
trackPageview();
</script>
[注]
data-eco-script-id是可选的。 重运行脚本需要明确稳定身份时使用. 没有它,一个HTMLid优先;否则路由器按正本匹配src或内含内容。 相对匹配时src,不同身份的身份保持不变<base>URL,同时一个外部模块从接入的页面解析的URL中重新运行.
强制全部重装
使用 data-eco-reload 在锁定标签上绕过路由器并强制重新装入完整页面:
<a href="/logout" data-eco-reload>Logout</a>
活动
收听导航周期事件 document。事件细节类型 @ecopages/core/client。 。 。 .
document.addEventListener('eco:before-swap', (e) => {
console.log('Navigating to:', e.detail.url);
// Call e.detail.reload() to abort client-side navigation and do a full reload
});
document.addEventListener('eco:after-swap', (e) => {
console.log('Swapped to:', e.detail.url);
});
document.addEventListener('eco:page-load', (e) => {
console.log('Page loaded:', e.detail.url);
});
e.detail.newDocument 是解析后收到的文档,一个听众可能会更改它。 自定义路由器所插入或替换的元素是文档本身的节点,所以您在它们上设置的属性,以及它们的嵌入节点,当浏览器升级和连接时仍然在那里:
document.addEventListener('eco:before-swap', (e) => {
const host = e.detail.newDocument.querySelector('my-host');
if (host) host.engine = engine;
});
路由器所保存的元素,例如恒定元素,忽略其来对应方设置的属性. 一个元素升级前设置的属性阴影了同名的类存取器,被该名称的类域所重置:该组件应当读取自有值,删除并通过其构造器中的存取器进行分配.
程序导航
使用路由器实例在程序上导航 :
import { createRouter } from '@ecopages/browser-router/client';
const router = createRouter();
// Navigate with pushState
await router.navigate('/new-page');
// Navigate with replaceState
await router.navigate('/new-page', { replace: true });