动态化建站

本文对建站做了一种 demo 实现,并不代表能够应对真正生产环境的复杂场景

调研大量建站平台,发现大部分建站界面长的几乎一致,数据结构层也基本一致,那么就想到一个问题,能不能把通用的数据层/视图层抽离,作为基础,通过动态加载组件的方式满足不同的页面搭建场景。

最开始的想法是通过 rollup + systemjs 的方式,编译组件,然后动态加载组件,但是会有一些问题,比如依赖库共享问题,组件间相互引用问题,一时间没什么头绪就没继续搞。

后来发现 webpack 的模块联邦完美解决了上面的问题,然后开始一顿折腾终于成功完成了demo。

最终效果

效果

注意图中红线的部分都是远端的组件。

实现

结构划分

通过 mobx 将数据层抽离,所以我的建站是一个 monorepo 的项目,项目结构如下

.
├── basicComponents // 动态组件
├── render // 渲染器
├── store // 数据层
└── website // 建站站点

数据层负责组件库,历史记录,编辑器的数据,渲染器的数据,每次数据层的变更都会触发相关数据依赖的组件更新。

渲染器负责初始化渲染器,hoc 注入,组件生命周期回调,提供不同场景下的初始化不同渲染

建站站点就是一个 demo 页面了,主要是视图布局

动态组件是远端组件

动态加载

远端组件库代码入口非常简单

import Button from "./components/Button";
import Page from "./components/Page";
import Image from "./components/Image";

export default {
  components: [Button, Page, Image],
};

只是暴露组件列表即可

编辑器初始化 render 时,动态导入组件

// 这里代码有点不合理 remote/Button 指的是远端的组件库后面没来得及改。
const AButton = await import("remote/Button");
for (const Com of AButton.default.components) {
  if (Com.name === "container") {
    Page = Com;
  }
  editorStore.componentList?.addEditorComponent(Com);
}

因为篇幅有限就不介绍对于初始化 render 的一些操作了。

这样我们就把远端的组件库通过动态 import 的方式导入到项目中了。

达到我们最开始的预期,动态加载远端组件库的目的了。

拓展

实际上现在我们动态加载还处于需要通过静态配置去请求对应远端组件代码。

module.exports = {
  name: "host",
  remotes: {
    remote: "remote@http://localhost:3001/remoteEntry.js",
  },
  shared: {
    // ...dependencies,
    react: {
      singleton: true,
      // requiredVersion: dependencies['react'],
    },
    "react-dom": {
      singleton: true,
      // requiredVersion: dependencies['react-dom'],
    },
  },
};

也就是这部分代码,虽然我们把编辑器和组件之间的关系解耦了,但是并不是是真正意义的揭耦。

我们可以通过动态加载组件的方式进行远端组件的加载。

具体配置可以看这个

function loadComponent(scope, module) {
  return async () => {
    // Initializes the shared scope. Fills it with known provided modules from this build and all remotes
    await __webpack_init_sharing__('default');
    const container = window[scope]; // or get the container somewhere else
    // Initialize the container, it may provide shared modules
    await container.init(__webpack_share_scopes__.default);
    const factory = await window[scope].get(module);
    const Module = factory();
    return Module;
  };
}

loadComponent('abtests', 'test123');

https://webpack.js.org/concepts/module-federation/#dynamic-remote-containers

那么有了这个功能以后,我们可以想象一下,编辑器和组件之间的关系突然从 1 - 1 变成了 1 - n。

我可以从 www.abc.com/components.js 加载组件列表
还可以从 www.cdf.com/components.js 加载组件列表

这样就可以不用每个建站都搞一套自己的建站代码和组件库代码了,可以一套建站代码多套组件库代码,当然这只是我的假象,现实世界可能远比这个复杂,满足不了这个需求。

在进一步我甚至可以做个服务器动态吐组件列表给用户,瞬间解决组件白名单问题,还可以做组件的版本管理…

最后

上述的代码仓库在这里
https://github.com/suxin2017/pixel-editor