likes
comments
collection
share

深入浅出 "import all from 'umi' " 实现原理

作者站长头像
站长
· 阅读数 71

前言

由于部门业务和组织的调整,笔者负责的业务域新增了多个产品线,涉及多个中后台系统。目前的问题是这些系统所使用的技术栈和开发流程都不统一,上手成本较高,每个系统基本都是专人维护,当某个业务线需求吞吐出现压力时也难以临时调配资源。为了降低开发和上手成本,考虑去适当地统一下研发范式,而应用框架是中后台领域范式统一的最佳实践之一,所以笔者和小伙伴们最近也在调研一些开源的应用框架,如 umiice 等。

Umi 作为蚂蚁推出的一款开源的企业级前端应用框架,拥有生命周期完善的插件体系,自然进入了我们的调研范围。近期 Umi 发布了 4.0 版本,笔者在翻看其官方文档时,看到以下一段有意思的话:

深入浅出 "import all from 'umi' " 实现原理

原文链接: https://umijs.org/docs/introduce/philosophy#import-all-from-umi

从这段话中我们发现了一个有趣的功能 import all from 'umi',简单说就是所有能力都从 umi 中去 import 获取,Umi 还支持通过插件扩展 import all from 'umi' 的能力。

那么 Umi 究竟是怎么做到的呢?别着急,我们再来翻一翻 Umi 的源码一探究竟。

源码分析

umi

从 demo 说起

我们先从一个一般性的使用案例着手分析,这里以 $ pnpm dlx create-umi@latest 创建的一个新 Umi 项目为例:

深入浅出 "import all from 'umi' " 实现原理

可以看到,在页面中是直接通过 import { useModel } from '@umijs/max' 来使用 useModel 方法的。

由此顺藤摸瓜,我们直接到 /examples/with-use-model/node_modules/@umijs/max/index.js 下的 @umijs/max 看下是否有导出 useModel 方法:

深入浅出 "import all from 'umi' " 实现原理

@umijs/max 导出的其实是 umi 包的全部方法,那么继续在 node_modules 中看下 umi 的代码,其入口在 /examples/with-use-model/node_modules/.pnpm/node_modules/dist/index.js 中:

注:关于 pnpm 可以参考笔者的另外一篇文章了解详情,从一个构建问题再谈依赖包加载机制

深入浅出 "import all from 'umi' " 实现原理

由此看出,其实 umi 包也没有实际导出 useModel 方法。

webpack 构建时

按照 Umi 官方的说法,@umijs/max 是一个插件集@umijs/max 内置了数据流管理插件,那么 useModel 一定是由插件注入的,.umirc.ts 配置文件也证实了这点:

深入浅出 "import all from 'umi' " 实现原理

但由于我们的主题并不是研究 Umi 插件本身,所以在这里我们不过多去纠结 Umi 插件实现原理,而专注于分析 Umi 插件是如何搞定 webpack 构建时并以此来支持通过插件扩展 import from 'umi' 的能力的。

alias

这个时候我们需要翻看下@umijs/max 的源码,在 /packages/max/src/plugins/maxAlias.ts 文件中,@umijs/max 基于 Umi 插件协议修改了 webpack 配置,配置了 alias 别名 @@/exports

深入浅出 "import all from 'umi' " 实现原理

那么 @@/eports 的物理文件究竟又指向哪里呢?我们可以在 /packages/preset-umi/src/features/configPlugins/configPlugins.ts 中找到答案:

深入浅出 "import all from 'umi' " 实现原理

也就是说,实际上 @umijs/max@@/exports 最后都是指向的同一个物理文件,即 /examples/with-use-model/src/.umi/exports

深入浅出 "import all from 'umi' " 实现原理

模板

众所周知,Umi 项目中 .umi 目录下存放的是临时文件,那么这个 /.umi/exports 临时文件又是如何生成的呢?答案在 packages/preset-umi/src/features/tmpFiles/tmpFiles.ts 中:

深入浅出 "import all from 'umi' " 实现原理

这里向插件 register 了一个临时文件的生产方法 hooksapplyPlugins 使用,在项目 devbuild 的时候就会生成 /.umi/exports 文件。

以上,便是 Umi 实现 "import all from '@umijs/max' " 的全过程。

vitekit

Umi 官方文档中也提到:“Remix、prisma、vitekit 等框架和工具都有类似实现”,为了加深理解,我们也一道来分析一下 vitekit 又是如何实现这个能力的呢。

从 demo 说起

同样地,将 vitekit 源码 clone 到本地后,继续从官方使用案例着手分析,这里以 /playground/test-simple/app/routes/index.vue 为例:

深入浅出 "import all from 'umi' " 实现原理

相同的地方是,页面中依然是直接从 .vitekit-package/index 中获取 useRouteuseNavigate 方法。

不同的地方是,vitekit 不是利用 alias 别名,而是利用 .vitekit-package 依赖包的方式:

深入浅出 "import all from 'umi' " 实现原理

.vitekit-package 中又间接引用了 @vitekit/framework-vue

深入浅出 "import all from 'umi' " 实现原理

vite 构建时

vitekit 的 “magic” 在于:其实项目中也没有直接依赖 .vitekit-package 依赖包,而 node_modules 中却自动引入了这个依赖。

深入浅出 "import all from 'umi' " 实现原理

很显然,这也肯定离不开构建时的支持,在源码 /vitekit/src/node.ts 中可以一目了然:

  • 首先,vitekit 也定义了一个小巧的插件协议,内部实现了插件加载机制,修改文件导入导出。

深入浅出 "import all from 'umi' " 实现原理

  • 其次,自动生成 /node_modules/.vitekit-package 依赖包:

深入浅出 "import all from 'umi' " 实现原理

深入浅出 "import all from 'umi' " 实现原理

以上,便是 vitekit 的实现方式。这种通过 node_modules 依赖的方式,好处在于不太需要一个比较复杂的构建时插件协议,去暴露出来修改构建时配置的能力。

综上,umivitekit 的实现思路大体是类似的:

  • 首先,定义了构建时插件机制,让插件能拓展 import all from 'xxx' 的能力。
  • 其次,通过临时文件(.umi)或临时依赖(.vitekit-package) 的方式,把各个插件拓展的能力收集在一个文件或者包中,统一导出,供业务中统一使用。
  • 最后,通过 alias 别名或者 node_modules 依赖的方式,实际暴露 import all from 'xxx' 的能力。

手写实践

本着 talk is cheap, show me the code 的原则,我们参考 umi 的思路,尝试来写一个极简的 demo,并以此完成本次学习之旅。

第一步,编写一个生成一个临时文件 exports 的函数:

深入浅出 "import all from 'umi' " 实现原理

第二步,修改 webpack 配置,配置 alias 别名:

深入浅出 "import all from 'umi' " 实现原理

第三步,准备好测试 demo。这里我们以一个简单输出 hello world 的函数为例,并在页面中引用这个函数:

深入浅出 "import all from 'umi' " 实现原理深入浅出 "import all from 'umi' " 实现原理

最后,执行 pnpm start 启动项目,可以看到最后生成的临时文件:

深入浅出 "import all from 'umi' " 实现原理

同时去检查下页面展现和控制台输出,与预期相符:

深入浅出 "import all from 'umi' " 实现原理

注:笔者功力有限,本文仍然可能存在理解纰漏,欢迎留言指正。 另外,欢迎关注笔者的语雀数字空间,工作之余一起探讨和学习。

参考文档

转载自:https://juejin.cn/post/7231080269503283259
评论
请登录