模块架构指南:跨页面复用原语的依赖隔离实践)
Logto Experience 共享资源shared/模块架构指南跨页面复用原语的依赖隔离实践【免费下载链接】logto Authentication and authorization infrastructure for SaaS and AI apps, built on OIDC and OAuth 2.1 with multi-tenancy, SSO, and RBAC.项目地址: https://gitcode.com/GitHub_Trending/lo/logto导读Logto 是构建在 OIDC 与 OAuth 2.1 之上的身份认证与授权基础设施其前端experience包承载着用户注册、登录、找回密码、同意授权等完整终端流程。随着流程页面pages、容器containers与布局layouts不断增多如何组织跨页面复用的基础组件、工具函数与样式直接决定了代码库的长期可维护性。本文以 packages/experience/src/shared/README.md 为骨架结合该目录下的真实源码系统讲解 Logto 团队为shared/目录制定的四条架构准则以及它们在NavBar、InputField、search-parameters等模块中的落地形态。读完你将掌握一套可复制的前端共享模块依赖隔离实践并能在 Logto 仓库中快速定位与评估任何共享原语。一、shared/ 的定位多界面消费的共享原语缓存packages/experience/src/shared目录在experience包中承担一个明确角色一切位于该目录下的内容都是为了被多个界面experience 页面、布局、容器等共同消费而存在的构建单元。从目录结构看它被划分为六个子区域各司其职components/跨流程复用的 UI 原语包括Button、NavBar、InputField、PasswordInputField、SmartInputField、VerificationCode、Toast、DynamicT、ErrorMessage、FlipOnRtl、IconButton、LoadingLayer、LogtoSignature、PageMeta、TextLinkhooks/跨组件复用的 React Hook目前包含use-password-error-message.tsutils/纯函数工具如a11y.ts键盘事件处理、logo.ts、search-parameters.tsURL 查询参数与会话恢复、validate-username.ts、username-policy-description.tstypes/共享类型定义如Platform web | mobile、SignInExperienceResponsescss/全局样式原语包括_colors.scss、_fonts.scss、_underscore.scss与normalized.scssassets/跨页面共用的图标arrow-next.svg、checkbox-icon.svg、logto-logo-dark/light.svg等与站点图标favicon.png、apple-touch-icon.png。这一目录的核心价值在于dependency-light 的缓存——README 原文将其定位为a safe, dependency-light cache of primitives that every experience can rely on。换句话说shared/应当是整个 experience 包中最稳定、最不被业务细节污染的一层任何页面都能安全地依赖它而它绝不反向依赖任何页面。二、准则一保持导入自包含Keep imports self-contained第一条也是最根本的一条共享模块不得回探reach back到 experience 根目录下任何功能专属目录——即components、hooks、pages、utils等业务目录。如果共享模块依赖了某个目前位于共享树之外的辅助代码Hook、工具函数、样式或资源必须把该辅助代码一并迁移进共享树让shared/目录对应用的其余部分保持零依赖。这条规则的实践可以从 NavBar 组件 中看到它同时引用了/shared/assets/icons/arrow-prev.svg?react、/shared/assets/icons/nav-close.svg?react图标资产、/shared/utils/a11y工具函数以及同目录下的../FlipOnRtl兄弟组件——所有依赖都被就近收拢在 shared 树内没有一行指向业务目录。这正是迁移辅助代码到共享树准则的直接体现组件、样式、资产、兄弟组件一起移动形成自洽的模块。类似的还有 InputField它依赖的ErrorMessage来自/shared/components/ErrorMessageNotchedBorder是自身目录下的兄弟子组件样式则通过 CSS Modules 以./index.module.scss形式随组件走。共享组件可以自由引用其他共享组件但绝不能引用某个具体页面里的私有实现。三、准则二层级整体迁移Move hierarchies together第二条规则是针对提升promote场景的当要把某个组件README 以NavBar举例提升为共享模块时它的子组件、样式、同级模块以及所需资产图标、图片等必须一起迁移。这样消费者只需要依赖共享入口点而不是去翻找散落在各处的零散文件。观察 SmartInputField 的目录布局即可理解这条规则的落地形态它不是一个孤立文件而是一个完整的子树——AnimatedPrefix/子组件 样式CountryCodeSelector/其下又包含CountryCodeDropdown/子组件与样式index.tsx与index.module.scss主组件与样式use-smart-input-field.ts专属 Hookutils.ts与utils.test.ts专属工具与单元测试整个国际区号 智能前缀输入框功能被封装为单一子树任何页面引入SmartInputField时无需关心它内部还依赖了CountryCodeDropdown或use-smart-input-field。层级整体迁移保证了共享树的边界即依赖的边界目录里有什么消费者就能用什么目录之外没有任何隐藏依赖。四、准则三暴露稳定入口点Expose stable entry points第三条规则关注消费方式共享条目应优先使用具名导出named exports或来自 index 文件的清晰默认导出让消费者可以直接从/shared/...导入而无需深入实现细节。/shared前缀是 Vite 路径别名在 packages/experience/vite.config.ts 中配置同时服务于svgr插件对 SVG 的按需 React 组件转换它为所有体验界面提供了稳定的导入面。各组件目录中的index.tsx/index.ts承担入口职责例如DynamicT/index.tsx 默认导出一个渲染动态 i18n 翻译键的组件forKey缺省时返回null翻译结果为字符串时直接渲染否则渲染一条明确的错误占位文案search-parameters.ts 则以具名导出暴露searchKeys、handleSearchParametersData、removeSearchParameters消费者按需引入不会被多余的默认导出拖累。更关键的是README 明确了一条补充约定如果共享模块需要功能专属行为应通过 props/options 传入而不是直接 import 功能代码。这意味着共享模块的能力边界由调用方通过参数注入共享层自身永远保持业务中立。例如InputField的label、description、errorMessage、prefix、suffix全部通过 Props 传入NavBar 的title、typeback | close、onBack、onClose、onSkip也全部是外部注入的回调——组件本身不关心是哪个页面、哪个流程在用它。五、准则四不确定时文档化意图Document intent when unsure第四条规则针对语义微妙的共享抽象如果某个共享模块含有隐含约定README 特别举例期望搜索参数被存储在 session 中应在代码中补充简短注释或 README 说明把这份契约写清楚避免后续重构无意间破坏共享边界。search-parameters.ts是这条规则的绝佳样本。它管理三类 URL 查询参数映射关系见下方源码常量export const searchKeys Object.freeze({ organizationId: organization_id, // 可用于覆盖默认设置的组织 ID appId: app_id, // 当前应用 ID uiLocales: ui_locales, // 以空格分隔的 BCP47 语言标签列表如 en、en-US、en-US en } satisfies RecordSearchKeysCamelCase, string);其行为契约全部以注释显式记录handleSearchParametersData会把已知的搜索键存入 sessionStorage 并从 URL 中移除以保持 URL 干净唯一的例外是app_id保留在 URL 中以便恢复会话Keep app_id in the URL for resuming sessionsreplaceSearchParameters使用window.history.replaceState重写地址并特意保留window.history.state——注释说明这是为了避免丢失进行中的流程数据如 Continue 页面的interactionEvent在刷新时丢失同样的文档化契约风格也出现在 validate-username.ts 中其注释解释了硬性底线校验非空、不以数字开头、合法字符集与租户策略校验的区别并说明登录/找回密码流程只应用硬性底线而注册流程才应用完整策略以镜像服务端写路径的检查。配套测试 search-parameters.test.ts 则把这个契约固化为可验证的行为移除one_time_token、login_hint后路径保持/reset-password、查询串仅剩?foobar、#section锚点原样保留——证明该模块的选择性参数清理行为是经过刻意设计的稳定约定。六、从源码看共享模块的纵深价值四条准则共同塑造了shared/目录的质感。以下三个模块可以更具体地展示依赖隔离带来的工程收益1. 跨流程复用的表单原语。InputFields下的InputField是注册、登录、重置密码等所有表单页面的底座。它内部处理了标签浮动通过NotchedBorder实现 label 凹槽边框、焦点/值状态机isFocused、hasValue、isActive三者组合、浏览器自动填充检测通过监听animationstart中onAutoFillStart/onAutoFillCancel动画名、多行错误消息渲染\n分割后以ul列表展示、可选字段的label_with_optional翻译后缀以及 RTL 方向支持styles[i18n.dir()]。这些琐碎但关键的细节被封装进共享组件后各页面无需重复实现也不会出现样式漂移。2. 策略驱动的错误呈现。use-password-error-message.ts 把密码策略校验结果翻译成用户可读的 i18n 文案优先单独展示unsupported_characters、too_short、character_types、too_long、pwned等应最先且单独显示的错误再对password_rejected.restricted.*类错误做聚合展示restricted_found列表。配合 validate-username.ts 的violationToErrorType映射表用户名/密码策略错误从前端校验到文案呈现形成了一条完整的共享链路且两端都引用了logto/core-kit的策略原语保证与核心服务端的校验语义一致。3. 全局一致的安全与可访问性。a11y.ts 提供onKeyDownHandler等键盘事件辅助被NavBar、InputField等所有可交互共享组件引用确保整个体验包在键盘可达性上行为统一scss/下的_colors.scss、_fonts.scss则是全局设计令牌任何共享组件都通过它们保持视觉一致。七、总结shared/ 是体验包的地基回到 README 的最终论断遵循上述四条原则能让shared/保持为一个安全、轻依赖、可被所有 experience 界面信赖的原语缓存。用一句话概括这套架构哲学共享模块向外提供能力但不向外索取依赖能力边界由参数注入行为契约由注释与测试固化。对于想要参与 Logto 前端贡献的开发者这四条准则也是实际的准入标准——无论是新增一个跨页面复用的组件、还是把某个页面内组件提升进shared/都需要检查导入是否完全自包含层级是否整体迁移入口是否稳定清晰隐含约定是否已文档化本文所引用的 NavBar、InputField、search-parameters.ts、validate-username.ts、use-password-error-message.ts 以及测试文件均可作为逐条对照的活样本。【免费下载链接】logto Authentication and authorization infrastructure for SaaS and AI apps, built on OIDC and OAuth 2.1 with multi-tenancy, SSO, and RBAC.项目地址: https://gitcode.com/GitHub_Trending/lo/logto创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考