首先我定义一下我这里的模块概念:一个文件夹下的代码(也就是前端的话可能涉及多个页面以及组件等,后端同理)
其实如果只是追求打包时动态加载不同的模块是很简单的,通过环境变量约束一下打包工具即可
但我还想要有强类型支持,以及直接剪切文件夹就能新增和移除模块。
使用 MonoRepo 的形式进行项目管理
每个顶级模块(包)都可能包含一个后端模块和一个前端模块(也就是可以是单纯的前端模块或后端模块)
存在一个基座包,这是整个项目的核心,所有的其他包都会依赖这个
非基座包的后端模块和前端模块都能直接引用到基座包中的后端依赖和前端依赖,而不需要特别专门的配置
同一个包内的前端模块可以直接引用到后端模块的 api (强类型)以及其他包的后端模块的 api
所有包的后端模块都能直接声明需要的 context ,然后编译时能够感知到基座包是否兜底的提供了所有 context (即编译时能够报错某个 context 缺失)
可以直接剪切文件夹就能新增包和移除包,如果包之间有依赖但对应的包被移除了则编译时应该报错
期待各位朋友的指点(❁´◡`❁)
![]() |
1
JoeJoeJoe PRO ![]() 可以搜一下 python 的一个 erp 框架 叫 Odoo , 跟你说的有些差异 但是他的模块化做的很好
|
![]() |
2
tcper 22 天前
你说的目录是源码结构,最终打包结构和源码显然不是一回事,严重怀疑没做过真实项目才有这种设想
|
3
layxy 22 天前
前端可以通过微前端实现,后端 java 倒是可以通过插件实现
|
![]() |
4
sentinelK 22 天前
所以楼主说的和 jar 、dll 的引用,以及 js 的 import 有啥区别?
楼主的意思是不想改配置? 那你直接把你项目 src 中的某个文件夹、dll 、jar 直接删了不就行了…… 直接删了,IDE 直接标红,就满足了楼主说的“如果包之间有依赖但对应的包被移除了则编译时应该报错” |
6
wn990916 22 天前
根据不同的用户构建不同的版本呢?
|
![]() |
7
llej OP @sentinelK 是的,我不想改配置,所以我需要实现,copy 一个模块的文件夹进来(包含了对应模块的前端和后端代码)然后直接就能运行项目,前端路由这块我已经实现可以这样加载了,但是后端还没搞定,主要是我想要 ts 类型严谨,否则我直接 require 也能实现
|
![]() |
9
sentinelK 22 天前
@llej 那不是应该从 Git 管理的角度入手吗。每个用户是不同的 Git 分支。否则你如何做后续支持?每次支持难道你都要“手动删除文件夹”到和用户环境一致的程度吗
|
![]() |
10
weixind 22 天前 ![]() 前端没那么复杂,使用文件路由就可以了。
文件路由打包的时候就是先读取你的文件夹,动态生成路由文件。 node 的后端也可以用同样的方式处理,但是不一定有现成的方案。其他语言的后端不清楚。 可以搜一下哪些 router 方案支持文件路由。要自己写的话可以参考 taro 的 h5 方案。 |
![]() |
11
llej OP @sentinelK 我会有一个环境变量文件的,比如 用户 a.env 的是包含了模块 a 模块 b ,那么我的脚本打包的时候就只包含这两,也有脚本一键导出只包含这两模块的源码发给他(有很多用户就是想要源码,我提供源码也可以跟他要价更高一些)
不使用 git 分支管理不同用户(因为我在实现某个用户的功能的时候可能又改善了某些通用模块,那么我不停切换分支来修改合并代码太麻烦了) |
![]() |
13
dssxzuxc 22 天前 ![]() @llej node 实现 ts 类型严谨具体是指什么,是前端调用了不存在的接口就类型报错吗,调用对应接口拿到入参类型和返回类型?
你可以看一下 tPRC ,或者 hono 的 RPC ,虽然重构的工作量可能会很大。 hono 的 RPC 是后端导出一个或者多个路由实例类型给前端使用,客户端底层是基于 fetch 实现,前端调用这个接口就像直接调用后端接口方法一样,带来的收益是无需专门为接口类型定义一堆的类型到处混乱地引用,因为接口本身已经有类型了。 在你这个场景下,可以每个模块都导出一个路由实例,最后在基座包组装所有路由,再把类型导入到前端,最终得到包含所有接口路径、入参、返回类型的一个 http 客户端,前端只需要调用这个客户端就行,不存在的接口或者入参不对都会报错。你也可以让一部分路由只给同个顶级包下的前端模块引用,这样就实现了或许会用到的接口 public/private 修饰功能。 |
![]() |
14
zhuangzhuang1988 22 天前 ![]() 学习下 kibana , 就是要系统的设计才行。
举例 https://github.com/elastic/kibana/tree/main/x-pack/examples/alerting_example |
15
flytsuki 22 天前 ![]() |
![]() |
16
SorcererXW 22 天前
后端为啥要模块化加载,都部署不就好了吗,能差多少呢,对应模块没有流量也没啥开销
|
![]() |
17
crysislinux 22 天前 via Android ![]() https://nx.dev/ 看看这个呢。一个组件也可以是一个 package ,你想分多细分多细。也不用分前端还是后端,只要 package 没有平台依赖的代码就随便用。
|
18
realJamespond 22 天前 ![]() 每个用户建一个 git 仓库包含对应模块 git submodules 的库
|
19
neoblackcap 22 天前 ![]() 后端不就是动态加载嘛,你完全可以将模块写成独立的 dll, so 文件,然后自己写一个 loader 在程序启动的时候载入这些模块。
对应做法就是,一个程序许可证( license )可以解码出对应的权限以及模块清单,然后运行时根据这个清单去查找对应的模块并载入就可以了 |
![]() |
20
yandif 22 天前 ![]() 试一下 elysia.js ,https://elysiajs.com/at-glance.html
![]() 这个后端框架有个 eden 功能实现了这个强类型 ![]() |
![]() |
21
asdjgfr 22 天前 ![]() 新建一个 ee 文件夹,每个客户代码都是一个私有 git 仓库,通过 git 的子模块引入:ee/zhangsan ee/lisi ,分发的时候只拉取某个客户的就可以了,git submodule update --init --recursive zhangsan
|
![]() |
22
llej OP @dssxzuxc 你说的很对,关于前后端交互我是自己实现了一个前端可以直接引用后端接口类型的 ts proxy ,类似于 tRPC ,不过我这里提到的类型严谨是关于后端的 context 传递,我是基于 Effect 实现的依赖倒置,这样我能够直接通过类型看到每个模块还有哪些 context 没有传入,但是我还没想到要怎么才能够做到“可剪切式”的添加和移除模块,这块我还比较混乱,说的也不够清晰。
我最近的想法是编译前根据环境变量 生成对应的 import 模块的 ts 代码,这样就能直接解决类型问题 |
![]() |
23
llej OP @SorcererXW 我不希望将全部源码给用户
|
![]() |
24
nekochyan 22 天前 ![]() 听起来意思是依赖注入,其他模块想用另一个模块都通过基座包去获取;我们项目大概是这么实现的:每个模块是一个文件夹,文件夹下只可引用基座包,里面调用基座包注入自身模块,这样剪切文件夹就实现了新增和移除模块
至于强类型支持就需要 ts 的类型体操了,可以写一个模块生成器实现在依赖注入,声明其注入的一定是某个类型 下面是我们项目的简单示例 模块生成器文件: // 声明所有模块都是 TypeModules 类型 declare global { interface TypeModules { } } class _coreModule { static instance: _coreModule; moduleMap: { [key: string]: any } = {}; static initInstance(): _coreModule { return _coreModule.instance = new _coreModule(); } setModule<T extends keyof TypeModules>(name: T, module: TypeModules[T]) { this.moduleMap[name] = module; } getModule<T extends keyof TypeModules>(name: T): TypeModules[T] { return this.moduleMap[name] as TypeModules[T]; } } export const coreModule = _coreModule.initInstance(); /** * @desc: 模块生成器 * @param {T} name 模块名称 * @param {new () => TypeModules[T]} moduleClass 模块类 * @return {TypeModules[T]} 模块实例 */ export function ModuleGenerate<T extends keyof TypeModules>(name: T, moduleClass: new () => TypeModules[T]): TypeModules[T] { // eg:具体的实现; coreModule 保存注入依赖 const instance = new moduleClass(); coreModule.setModule(name, instance); return instance; } 测试文件: declare global { interface TypeModules { /** * @desc: [模块] 测试模块 */ testData: TestDataClass } } export class TestDataClass { // 具体实现 } // 注入 const testData = ModuleGenerate('testData', TestDataClass); 其他 文件或模块中 // testData 类型就是 TestDataClass ,完全不需要引入 测试模块的文件,但当测试模块删除时,这段代码编译时会报错 let testData = coreModule.getModule('testData'); |
![]() |
25
duan602728596 22 天前 ![]() 既然是 MonoRepo ,直接可以模块间互相引用了吧,workspaces 或者 lerna 都可以,比如有
- packages/a - packages/b 直接 import {} from '@xxx/a',import {} from '@xxx/b'就可以了 给每个子模块加个 package.json 就可以了 声明需要的 context ,装 node_modules 就可能会报错了,或者编译时也会提示缺失模块的。或者写个脚本检查一下 类型甚至可以自动生成,把模块加到 declare global {}里 |
![]() |
26
asmoker 22 天前
蹲一个~
|
27
2han9wen71an 21 天前
java 有 osgi
|
![]() |
28
dododada 21 天前
nodejs 没写过,不过以前做数据库审计的时候,mysql 有个 plugin 机制,直接安装运行,你感兴趣可以研究看看
|