如何实现模块化加载的前端和后端代码?

67 天前
 llej

首先我定义一下我这里的模块概念:一个文件夹下的代码(也就是前端的话可能涉及多个页面以及组件等,后端同理)

其实如果只是追求打包时动态加载不同的模块是很简单的,通过环境变量约束一下打包工具即可

但我还想要有强类型支持,以及直接剪切文件夹就能新增和移除模块。

架构畅想

  1. 使用 MonoRepo 的形式进行项目管理

  2. 每个顶级模块(包)都可能包含一个后端模块和一个前端模块(也就是可以是单纯的前端模块或后端模块)

  3. 存在一个基座包,这是整个项目的核心,所有的其他包都会依赖这个

  4. 非基座包的后端模块和前端模块都能直接引用到基座包中的后端依赖和前端依赖,而不需要特别专门的配置

  5. 同一个包内的前端模块可以直接引用到后端模块的 api (强类型)以及其他包的后端模块的 api

  6. 所有包的后端模块都能直接声明需要的 context ,然后编译时能够感知到基座包是否兜底的提供了所有 context (即编译时能够报错某个 context 缺失)

    1. 为什么基座包需要兜底:因为是从基座包启动的 http server ,他是入口也是出口
  7. 可以直接剪切文件夹就能新增包和移除包,如果包之间有依赖但对应的包被移除了则编译时应该报错

如何实现?

期待各位朋友的指点(❁´◡`❁)

3721 次点击
所在节点    程序员
28 条回复
asdjgfr
67 天前
新建一个 ee 文件夹,每个客户代码都是一个私有 git 仓库,通过 git 的子模块引入:ee/zhangsan ee/lisi ,分发的时候只拉取某个客户的就可以了,git submodule update --init --recursive zhangsan
llej
67 天前
@dssxzuxc 你说的很对,关于前后端交互我是自己实现了一个前端可以直接引用后端接口类型的 ts proxy ,类似于 tRPC ,不过我这里提到的类型严谨是关于后端的 context 传递,我是基于 Effect 实现的依赖倒置,这样我能够直接通过类型看到每个模块还有哪些 context 没有传入,但是我还没想到要怎么才能够做到“可剪切式”的添加和移除模块,这块我还比较混乱,说的也不够清晰。

我最近的想法是编译前根据环境变量 生成对应的 import 模块的 ts 代码,这样就能直接解决类型问题
llej
67 天前
@SorcererXW 我不希望将全部源码给用户
nekochyan
67 天前
听起来意思是依赖注入,其他模块想用另一个模块都通过基座包去获取;我们项目大概是这么实现的:每个模块是一个文件夹,文件夹下只可引用基座包,里面调用基座包注入自身模块,这样剪切文件夹就实现了新增和移除模块

至于强类型支持就需要 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');
duan602728596
67 天前
既然是 MonoRepo ,直接可以模块间互相引用了吧,workspaces 或者 lerna 都可以,比如有
- packages/a
- packages/b
直接 import {} from '@xxx/a',import {} from '@xxx/b'就可以了
给每个子模块加个 package.json 就可以了

声明需要的 context ,装 node_modules 就可能会报错了,或者编译时也会提示缺失模块的。或者写个脚本检查一下

类型甚至可以自动生成,把模块加到 declare global {}里
asmoker
67 天前
蹲一个~
2han9wen71an
66 天前
java 有 osgi
dododada
66 天前
nodejs 没写过,不过以前做数据库审计的时候,mysql 有个 plugin 机制,直接安装运行,你感兴趣可以研究看看

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1152245

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX