什么是垂直微前端垂直微前端是一种架构模式单个独立团队拥有应用程序功能的完整切片从用户界面一直到底层的CI/CD流水线。这些切片通过域名上的路径来定义你可以将各个独立的Worker与特定路径关联起来/ 营销网站 /docs 文档 /blog 博客 /dash 仪表盘我们还可以进一步细化在更细粒度的子路径上关联不同的Worker。比如在仪表盘中你可能通过各种功能或产品来划分URL路径的深度例如/dash/product-a在两个产品之间导航可能意味着两个完全不同的代码库。现在有了垂直微前端我们还可以这样设计/dash/product-a WorkerA /dash/product-b WorkerB上面的每个路径都是独立的前端项目它们之间没有任何共享代码。product-a和product-b路由映射到分别部署的前端应用它们有自己的框架、库、CI/CD流水线由各自的团队定义和拥有。你可以端到端地拥有自己的代码。但现在我们需要找到一种方法将这些独立的项目缝合在一起更重要的是让它们感觉像是一个统一的体验。Cloudflare自己也在经历这个痛点因为仪表盘有许多独立的团队负责各自的产品。团队必须面对一个事实在他们控制范围之外所做的更改会影响用户对其产品的体验。在内部我们现在对自己的仪表盘也采用了类似的策略。当用户从核心仪表盘导航到我们的ZeroTrust产品时实际上它们是两个完全独立的项目用户只是通过路径/:accountId/one被路由到那个项目。视觉上的统一体验将这些独立项目缝合在一起让它们感觉像一个统一的体验并没有你想象的那么困难只需要几行CSS魔法。我们绝对不希望发生的事情是将我们的实现细节和内部决策泄露给用户。如果我们无法让这个用户体验感觉像一个统一的前端那我们就对用户犯下了严重的错误。要实现这种巧妙的手法让我们先了解一下视图过渡和文档预加载是如何发挥作用的。视图过渡当我们想要在两个不同页面之间无缝导航同时让最终用户感觉流畅时视图过渡非常有用。在页面上定义特定的DOM元素让它们一直保留到下一页可见并定义任何变化的处理方式这成为了多页应用的强大缝合工具。然而在某些情况下让各个垂直微前端感觉不同也是完全可以接受的。比如我们的营销网站、文档和仪表盘它们各自都有独特的定义。用户不会期望这三者在导航时都感觉统一。但是……如果你决定在单个体验中引入垂直切片例如/dash/product-a和/dash/product-b那么用户绝对不应该知道它们底层是两个不同的仓库/Worker/项目。好了说得够多了——让我们开始动手吧。我说过让两个独立的项目对用户来说感觉像是一个是低成本的如果你还没有听说过CSS视图过渡那么接下来我要让你大开眼界了。如果我告诉你你可以在单页应用SPA或多页应用MPA的不同视图之间创建动画过渡让它们感觉像是一个整体在添加任何视图过渡之前如果我们导航属于两个不同Worker的页面中间加载状态会是浏览器中的白色空白屏幕持续几百毫秒直到下一页开始渲染。页面不会感觉统一当然也不会像单页应用。如果希望元素保留而不是看到白色空白页我们可以通过定义CSS视图过渡来实现。通过下面的代码我们告诉当前文档页面当视图过渡事件即将发生时将navDOM元素保留在屏幕上如果现有页面和目标页面之间存在任何外观差异我们将使用ease-in-out过渡来动画展示。突然之间两个不同的Worker感觉就像一个了。supports (view-transition-name: none) { ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 0.3s; animation-timing-function: ease-in-out; } nav { view-transition-name: navigation; } }预加载在两个页面之间过渡让它看起来无缝——我们还希望它感觉像客户端SPA一样即时。虽然目前Firefox和Safari不支持Speculation Rules但Chrome/Edge/Opera确实支持这个较新的API。Speculation Rules API旨在提高未来导航的性能特别是对于文档URL让多页应用感觉更像单页应用。分解成代码我们需要定义一个特定格式的脚本规则告诉支持的浏览器如何预取与我们Web应用程序连接的其他垂直切片——可能通过某些共享导航链接。script typespeculationrules { prefetch: [ { urls: [https://product-a.com, https://product-b.com], requires: [anonymous-client-ip-when-cross-origin], referrer_policy: no-referrer } ] } /script有了这些我们的应用程序会预取其他微前端并将它们保留在内存缓存中所以如果我们导航到那些页面会感觉几乎是即时的。对于明显可区分的垂直切片营销、文档、仪表盘你可能不需要这样做因为用户在它们之间导航时会预期有轻微的加载。然而当垂直切片定义在特定可见体验内时例如在仪表盘页面中强烈建议使用。通过视图过渡和推测规则我们能够将完全不同的代码仓库联系在一起感觉就像它们来自单页应用一样。如果你问我这太神奇了。零配置请求路由现在我们需要一种机制来托管多个应用程序以及一种在请求流入时将它们缝合在一起的方法。定义一个Cloudflare Worker作为路由器允许在边缘的单个逻辑点处理网络请求然后将它们转发给负责该URL路径的垂直微前端。而且我们可以将单个域名映射到该路由器Worker其余的就正常工作了。服务绑定如果你还没有探索过Cloudflare Worker服务绑定那么值得花点时间了解一下。服务绑定允许一个Worker调用另一个Worker而无需经过公开可访问的URL。服务绑定允许Worker A调用Worker B上的方法或将请求从Worker A转发到Worker。进一步分解路由器Worker可以调用已定义的每个垂直微前端Worker例如营销、文档、仪表盘假设它们都是Cloudflare Workers。这为什么重要这正是将这些垂直切片缝合在一起的机制。我们将在下一节深入探讨请求路由如何处理流量分割。但要定义这些微前端中的每一个我们需要更新路由器Worker的wrangler定义这样它就知道允许调用哪些前端。{ $schema: ./node_modules/wrangler/config-schema.json, name: router, main: ./src/router.js, services: [ { binding: HOME, service: worker_marketing }, { binding: DOCS, service: worker_docs }, { binding: DASH, service: worker_dash } ] }上面的示例定义在我们的路由器Worker中然后告诉我们被允许向三个独立的额外Worker营销、文档和仪表盘发出请求。授予权限就这么简单但让我们深入研究一些更复杂的逻辑包括请求路由和HTML重写网络响应。请求路由了解了在需要时可以调用的各种其他Worker之后现在我们需要一些逻辑来确定何时将网络请求定向到哪里。由于路由器Worker被分配到我们的自定义域名所有传入的请求首先在网络边缘到达它。然后它确定哪个Worker应该处理请求并管理结果响应。第一步是将URL路径映射到关联的Worker。当收到某个请求URL时我们需要知道它需要被转发到哪里。我们通过定义规则来实现这一点。虽然我们支持通配符路由、动态路径和参数约束但我们将专注于基础——字面路径前缀——因为它更清楚地说明了要点。在这个例子中我们有三个微前端/ 营销 /docs 文档 /dash 仪表盘上面的每个路径都需要映射到一个实际的Worker参见上面章节中的wrangler服务定义。对于我们的路由器Worker我们定义一个额外的变量包含以下数据这样我们就知道哪些路径应该映射到哪些服务绑定。现在我们知道当请求进来时应该将用户路由到哪里定义一个名为ROUTES的wrangler变量内容如下{ routes: [ {binding: HOME, path: /}, {binding: DOCS, path: /docs}, {binding: DASH, path: /dash} ] }让我们设想一个用户访问我们网站的路径/docs/installation。在底层发生的情况是请求首先到达我们的路由器Worker它负责了解什么URL路径映射到哪个独立的Worker。它理解/docs路径前缀映射到我们的DOCS服务绑定参照我们的wrangler文件指向我们的worker_docs项目。我们的路由器Worker知道/docs被定义为垂直微前端路由从路径中移除/docs前缀将请求转发给我们的worker_docsWorker来处理请求然后最终返回我们得到的任何响应。为什么要删除/docs路径呢这是一个实现细节的选择目的是当Worker通过路由器Worker访问时它可以清理URL来处理请求就像它是从路由器Worker外部调用的一样。像任何Cloudflare Worker一样我们的worker_docs服务可能有自己的独立URL可以访问。我们决定希望该服务URL继续独立工作。当它附加到我们的新路由器Worker时它会自动处理移除前缀这样服务就可以从自己定义的URL或通过我们的路由器Worker访问……任何地方都可以无所谓。HTMLRewriter用URL路径分割我们的各种前端服务例如/docs或/dash让我们很容易转发请求但当我们的响应包含不知道它被通过路径组件反向代理的HTML时……嗯这就会出问题。假设我们的文档网站在响应中有一个图片标签img src./logo.png /。如果我们的用户正在访问页面https://website.com/docs/那么加载logo.png文件可能会失败因为我们的/docs路径只是由我们的路由器Worker人为定义的。只有当我们的服务通过路由器Worker访问时我们才需要对一些绝对路径进行HTML重写这样我们返回的浏览器响应才能引用有效的资源。实际上发生的是当请求通过我们的路由器Worker时我们将请求传递给正确的服务绑定并从中接收响应。在将其传回客户端之前我们有机会重写DOM——所以在看到绝对路径的地方我们继续用代理路径预先填充它。以前我们的HTML返回的图片标签是img src./logo.png /现在我们修改为在返回客户端浏览器之前img src./docs/logo.png /。让我们回到CSS视图过渡和文档预加载的魔法。我们当然可以把那段代码手动放到我们的项目中并让它工作但这个路由器Worker也会使用HTMLRewriter自动为我们处理这些逻辑。在你的路由器WorkerROUTES变量中如果你在根级别设置smoothTransitions为true那么CSS过渡视图代码会自动添加。此外如果你在路由中设置preload键为true那么该路由的推测规则脚本代码也会自动添加。下面是两者结合使用的示例{ smoothTransitions: true, routes: [ {binding: APP1, path: /app1, preload: true}, {binding: APP2, path: /app2, preload: true} ] }开始使用你今天就可以开始使用垂直微前端模板构建了。访问Cloudflare仪表盘的链接或者进入Workers Pages并点击创建应用程序按钮开始。从那里点击选择模板然后创建微前端你就可以开始配置你的设置了。