island原理-神三元大佬的热乎的项目

753 阅读1分钟

项目地址

github.com/sanyuan0704…

island最近很火, 从deno的fresh框架到兼容多组件的astro

首先他们都是基于ssr的,普通的ssr是页面级注水, island是组件粒度的注水,今天神三元写了个demo帮我们理解island都干了什么

 Object.entries(manifest.routes).forEach(([path, mod]) => {
    router.get(path, async (ctx, next) => {
      function renderFn(data: any) {
        ISLAND_PROPS = [];
        ENCOUNTER_ISLANDS = [];
        const bodyHtml = renderToString(createElement(mod.default, data));
        // 渲染模版
        // 因为拦截了React.createElement所以将组件信息存储到了上面两个数组里面
        // 并且将island加上了一个attribute
        const importmap = {
          imports: {
            react: "https://esm.sh/react@18.2.0",
            "react-dom": "https://esm.sh/react-dom@18.2.0",
          },
        };
        const hydrateScript = [
          `import { revive } from "/revive";`,
          // 这里加载了所有island的组件的代码, 不打包版本的
          ENCOUNTER_ISLANDS.map((island) => {
            return `import ${island.id.split("/").pop()} from "${island.id}";`;
          }).join(""),
          // 将组件注水时用到的信息挂在在window上
          `window.ISLANDS = {${ENCOUNTER_ISLANDS.map(
            ({ id }) => `"${id}": ${id.split("/").pop()}`
          ).join(",")}};`,
          `window.ISLAND_PROPS = [${ISLAND_PROPS.map((props) =>
            JSON.stringify(props)
          )}];`,
          // 在这个recvive方法里完成注水, 
          `revive();`,
        ].join("");
        const template = (head: string, body: string) => {
          return `<!DOCTYPE html>
<html lang="en">
<head>
  ${head}
<script type="importmap">${JSON.stringify(importmap)}</script>
</head>
<body>
  ${body}
<script type="module">${hydrateScript}</script>
</body>
</html>`;
        };
        return template("", bodyHtml);
      }
      ctx.body = await mod.handler(ctx.request, {
        render: renderFn,
      });
    });
  });

看一下这个revive

export function revive() {
  const islands = document.querySelectorAll("[__island]");
  for (let i = 0; i < islands.length; i++) {
    const island = islands[i];
    // 获取到所有带__island 属性的标签,这些标签是注水的组件
    // 然后从window上读取组件进行注水
    const [id, index] = island.getAttribute("__island")!.split(":");
    hydrate(
      // @ts-ignore
      createElement(window.ISLANDS[id], window.ISLAND_PROPS[index]),
      island
    );
  }
}

拦截的react方法

React.createElement = (type: ElementType, props: any, ...children: any[]) => {
  let matchedIsland: Island | undefined;
  if (
    typeof type === "function" &&
    (matchedIsland = ISLANDS.find(({ component }) => component === type))
  ) {
    ISLAND_PROPS.push(props);
    ENCOUNTER_ISLANDS.push(matchedIsland);
    // 这样所有的 props 都被记录了
    //  __island属性用来匹配注水节点
    return originCreateElement(
      `div`,
      {
        __island: `${matchedIsland.id}:${ISLAND_PROPS.length - 1}`,
      },
      originCreateElement(type, props, ...children)
    );
  }
  return originCreateElement(type, props, ...children);
};

流程就是生成模版的同时生成注水数据->挂载数据->选择注水节点->用保存的数据注水

Ps: 因为是demo比较简单,没有注水优先级等更多的功能,