
React Router 架构决策解读用 npm 管理 Deno 项目的 NPM 依赖【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router本篇解读 React Router 仓库中一份已接受accepted的架构决策记录《Usenpmto manage NPM dependencies for Deno projects》。文章围绕这份 决策文档 的完整脉络展开——Deno 的三种依赖管理方式、项目对依赖体系的三项硬性要求Tree-shaking、运行时环境切换、peer dependencies、各方案的缺陷分析以及最终以 npm node_modules 管理 NPM 依赖、保留 URL 导入、放弃 import maps的决策细节与配套编辑器配置并结合当前仓库源码验证其决策逻辑在今日代码中的延续。读完本文你能理解为什么看起来更现代的 URL 导入不适合承载react这类 peer 依赖并掌握在 Deno 编辑器中为 npm 依赖恢复类型提示的完整配置方法。决策记录ADR在 React Router 仓库中的位置React Router 仓库在 decisions/ 目录下维护了一套架构决策记录Architecture Decision Record每份记录遵循 decisions/template.md 定义的统一结构标题、日期、状态proposed / rejected / accepted / deprecated / superseded以及 Context背景、Decision决策、Consequences后果三节。本文解读的是其中 2022-05-10 记录、状态为 accepted 的 decisions/0001-use-npm-to-manage-npm-dependencies-for-deno-projects.md。从仓库的决策编年看这份 ADR 成文于项目以 Remix 为主体的时期——后续的 decisions/0005-remixing-react-router.md 与 decisions/0007-remix-on-react-router-6-4-0.md 记录了 Remix 能力并入 React Router 的过程而本文所讲的依赖策略正是那段时期的框架级工程决策。它的结论NPM 依赖走 npm、Deno 原生模块走 URL 导入为所有在 Deno 运行时使用 React 生态的部署形态划定了依赖边界。背景Deno 的三种依赖管理方式决策文档的 Context 部分首先列出 Deno 管理依赖的三条既有路径内联 URL 导入直接在源码中写import {...} from https://deno.land/x/blah模块地址即 import 语句的一部分deps.ts 模式用一个deps.ts文件集中转述第三方模块再被业务代码引用Deno 官方手册中推荐的手动依赖管理方式Import maps以 JSON 映射表把裸说明符如react重定向到 URL。除此之外NPM 包还可以通过Deno-friendly CDN如 esm.sh 这类按 ESM 编译 NPM 包的 CDN以 Deno 模块的形式访问。框架对依赖体系的三项硬性要求文档指出RemixReact Router 的前身对依赖管理有三条硬性要求这构成了评估上述各方案的判据Tree-shaking框架会对无副作用的依赖做树摇以优化包体积并区分浏览器端与服务端代码运行时环境框架在运行时通过NODE_ENV环境变量统一设置 dev/prod/test 环境且该设置必须覆盖依赖代码Peer dependencies框架依赖若干必须声明为 peer dependency 的 NPM 包最典型的是react与react-dom。Tree-shakingURL 导入缺少副作用标记机制为优化 bundle 体积框架会 tree-shake 用户代码及其依赖。从源码结构看这一要求的实现基础就在仓库中packages/react-router/package.json 明确声明了sideEffects: false。决策文档对此的技术解释是底层编译器当时为 esbuild与其他打包器一样依赖package.json中的sideEffects字段来判断删除未使用 import 是否安全。而URL 导入没有标准机制来标记一个包是 side-effect free 的——没有package.json就没有可供打包器消费的sideEffects元数据。这就是 URL 导入方案在Tree-shaking这一要求上先天不合格的原因。当前仓库中 Vite 插件在解析配置时同样以production作为默认 mode 与默认 NODE_ENV参见 packages/react-router-dev/vite/plugin.ts 中vite.resolveConfig的调用延续了构建/运行时环境统一显式可控的工程取向与决策文档中环境必须可统一设置的要求一脉相承。环境切换CDN 用查询参数而非环境变量Deno-friendly CDN 通过URL 查询参数例如?dev而非环境变量来区分 dev/prod/test 构建。这意味着切换环境就必须修改源码里的 import URL。文档分析了用多份 import mapdev.json、prod.json等绕行的思路但指出 import maps 还有两个结构性限制管理 import maps 的标准化工具链不可用import maps不可组合任何自身使用 import map 的依赖都必须由用户手工逐一登记无法自动传递。Peer dependenciesCDN 的孤立编译使版本对齐成为噩梦即便把 import maps 做到完美CDN 也是逐个孤立地编译每个依赖。指定 peer dependency 因此变得繁琐且易错用户必须判断哪些依赖哪怕只是间接地依赖react之类的 peer dependency人工推导出一个能在所有这些依赖间工作的react版本在每一个被识别依赖的 URL 中把该版本以查询参数形式设置上去。一旦依赖集合发生任何变化新增、删除、版本升级上述全部步骤必须重做。这正是 URL CDN 方案在Peer dependencies要求上的致命伤。决策内容三条子决策文档的 Decision 部分由三条明确的子决策组成1. 用 npm 管理 Deno 项目的 NPM 依赖不要在 Deno 项目的 Remix/React Router 项目中使用 Deno-friendly CDN 来承载 NPM 依赖。即使在 Deno 环境下运行react这类 NPM 依赖也应像常规 NPM 项目一样通过npm和node_modules/来管理。而 Deno原生模块例如来自https://deno.land的依赖仍然可以继续使用 URL 导入管理。换言之决策划出了一条清晰边界NPM 生态走node_modules/Deno 生态走 URL。这一边界在当前仓库中仍有对应痕迹脚手架包 packages/create-react-router/index.ts 中的validPackageManagers列表包含denodetectPackageManager通过npm_config_user_agent环境变量识别运行create-react-router的包管理器——当用户以 Deno 安装 CLI 时会被识别为deno包管理器。这印证了项目级工具链与依赖管理可以并存于不同机制的设计前提。2. 允许 URL 导入打包器将其保留为外部依赖框架在构建产物中原样保留任何 URL 导入将其视为外部依赖交给浏览器运行时或服务端运行时自行处理。由此得到的能力是可以在浏览器端使用 URL 导入在服务端使用 URL 导入——前提是服务端运行时支持它。文档给出的例子是Node 对 URL 导入会抛错而 Deno 会像处理普通模块一样解析 URL 导入。这一打包器不干预、运行时自负的策略把 Deno 原生模块的可用性完全交给运行时能力避免了框架替用户做不可靠的 URL 解析假设。3. 不支持 import mapsRemix/React Router不会支持 import maps。这是基于上一节三个结构性缺陷无标准工具链、不可组合、无法承载副作用与环境语义作出的明确取舍。决策后果Consequences文档的 Consequences 部分列出了三条直接后果URL 导入不会被 tree-shake——因为如前所述没有sideEffects元数据可供打包器使用用户可以在运行时通过NODE_ENV环境变量指定环境不再需要改 import URL用户不再需要做易错的、手工的依赖版本协调peer dependency 的版本解析回归 npm 的常规机制。编辑器配套用 VS Code 导入 map 恢复类型提示由于运行时的 import map 被放弃Deno 编辑器VS Code 的 Deno 扩展无法自动为node_modules/中的 NPM 依赖提供类型提示。文档给出的解法是仅针对 Deno 扩展配置一份 import map仅用于编辑器的解析不参与构建。.vscode/resolve_npm_imports_in_deno.json{ // This import map is used solely for the denoland.vscode-deno extension.: , // Remix does not support import maps.: , // Dependency management is done through npm and node_modules/ instead.: , // Deno-only dependencies may be imported via URL imports (without using import maps).: , imports: { react: https://esm.sh/react18.0.0, react-dom: https://esm.sh/react-dom18.0.0, react-dom/server: https://esm.sh/react-dom18.0.0/server } }.vscode/settings.json{ deno.enable: true, deno.importMap: ./.vscode/resolve_npm_imports_in_deno.json }配置文件里以// ...开头的键值对是 JSON 里模拟注释的惯用写法其语义值得注意这份 import map仅供 denoland.vscode-deno 扩展使用框架本身不支持 import maps依赖管理走npm与node_modules/Deno 专属依赖仍可直接用 URL 导入不经过 import map。settings.json中的deno.importMap指向该文件从而让编辑器把react等裸说明符解析到 CDN 上的类型声明来源恢复智能提示能力。小结这条决策边界为何仍然成立把整份决策记录串起来看它实际上回答了一个常见困惑——Deno 都能直接 import URL 了为什么还要 node_modules。答案浓缩为三点判据打包器语义URL 导入没有sideEffects之类的标准元数据tree-shaking 无从谈起仓库自身以 packages/react-router/package.json 的sideEffects: false声明了对打包器元数据的依赖环境语义CDN 以查询参数编码 dev/prod破坏了用NODE_ENV一处切换全局环境的模型依赖图语义CDN 孤立编译使 peer dependency 的版本对齐退化为手工劳动。因此最终方案是一个务实的双轨制NPM 生态react 等走 npmDeno 生态走 URL 导入import map 既不做运行时机制也不做构建机制只在编辑器配置里作为类型提示的影子存在。这条 ADR 虽写于 Remix 时期但其确立的依赖边界与 decisions/ 目录下其他决策一起构成了当前 React Router 在 Deno 部署形态下的依赖管理基线供使用deno作为包管理器如 packages/create-react-router/index.ts 所识别的形态的读者作为工程判断的参照。【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考