PerfectPan

PerfectPan

初探 Github 區塊

什麼是 Github Blocks#

Github Blocks 是 Github 提供的擴展你的 CodeBase 的方式,他試圖改變你在網站上查看 Github 代碼的方式。Block 的基本單位有兩種,一個是 File,一個是 Folder,當你瀏覽一個代碼文件或者代碼文件夾的時候,可以通過切換不同的 Block 去決定如何渲染你的代碼。

如圖所示,紅框框住的位置就是我們切換 Block 的地方,下面一整塊是渲染這個 excalidraw 文件的結果。

github-blocks

點擊紅框框住的地方我們可以看到其他可以渲染這個文件類型的 Block,我們可以點擊切換,同時我們可以在搜索欄上搜索 Block,可以搜文字來篩選,也可以直接貼別人 Block 代碼倉庫的 URL,這其實是為了能夠搜索出隱藏的 Block,因為如果一個開發者想要對外暴露 Block,他就必須要在他的倉庫上打一個 github-blocks 的標籤才能被 Github 找到,但我們仍然可以通過貼 URL 的方式找到這個隱藏的 Block。

github-blocks-picker

目前官方已經提供很多 Block 了,比如 Markdown Block,JSON Block,JS Sandbox Block 等等。

如何開發 Github Blocks#

當社區已有的 Block 不能滿足你的需求的時候,你可以自行去開發一個 Block,官方也提供了很好的模版代碼開發教程,這裡只簡單的講一下。

通過模版代碼和開發教程我們可以了解到一個 Block 項目需要在項目根目錄存放一個 blocks.config.json 的文件,裡面描述了你項目 Block 的信息,比如標題,描述,是 File Block 還是 Folder Block,可以識別的後綴,比如只有 .js 後綴的文件才能使用我的 Block,Block 加載的入口地址等,支持多 Block 的開發。然後你只需要在對應的文件實現一個 React 組件,最後調用官方提供的腳手架去打包你的代碼即可。Github 會往 React 組件裡去注入他暴露的能力,和一些生命週期鉤子。

整體開發體驗是挺好的,通過腳手架的命令啟動以後我們就可以在 Github 頁面上去調試我們的 Block 了,通過閱讀腳手架的代碼,我們可以知道它整體是基於 vite 和 esbuild 來實現本地開發和最終打包的,最終打包出來的代碼是以 var BlockBundle = function() { ... } 的格式,同時 exclude 掉了 React 相關的庫。

// https://github.com/githubnext/blocks-dev/blob/main/scripts/build.js
const esbuild = require("esbuild");
const path = require("path");

process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';

require('./config/env');

const build = async () => {
  const blocksConfigPath = path.resolve(process.cwd(), "blocks.config.json");
  const blocksConfig = require(blocksConfigPath);

  const blockBuildFuncs = blocksConfig.map((block) => {
    return esbuild.build({
      entryPoints: [`./` + block.entry],
      bundle: true,
      outdir: `dist/${block.id}`,
      format: "iife",
      globalName: "BlockBundle",
      minify: true,
      external: ["fs", "path", "assert", "react", "react-dom", "@primer/react"],
      loader: {
        '.ttf': 'file',
      },
    });
  });

  try {
    await Promise.all(blockBuildFuncs);
  } catch (e) {
    console.error("Error bundling blocks", e);
  }
}
build()

module.exports = build;

Github Blocks 實現原理介紹#

上文提到在開發 Blocks 的過程中,我們只需要對外暴露一個 React 組件,而且最終打包的時候 exclude 掉了 React 相關的庫,那麼他最終是如何加載的呢?

查看源代碼我們可以到整個 Block 的加載就是加載了一個 iframe 標籤,iframe src 上的哈希值存儲了待加載 Block 的源信息。然後裡面會有一段 runtime 的代碼,這段代碼也已經開源了。

<iframe class="w-full h-full" allow="camera;microphone;xr-spatial-tracking" sandbox="allow-scripts allow-same-origin allow-forms allow-top-navigation-by-user-activation allow-popups" src="https://blocks-sandbox.githubnext.com#%7B%22block%22%3A%7B%22type%22%3A%22folder%22%2C%22id%22%3A%22dashboard%22%2C%22title%22%3A%22Dashboard%22%2C%22description%22%3A%22View%20other%20blocks%20in%20a%20dashboard%20view%22%2C%22entry%22%3A%22blocks%2Ffolder-blocks%2Fdashboard%2Findex.tsx%22%2C%22example_path%22%3A%22https%3A%2F%2Fgithub.com%2Fgithubnext%2Fblocks-tutorial%22%2C%22owner%22%3A%22githubnext%22%2C%22repo%22%3A%22blocks-examples%22%7D%2C%22context%22%3A%7B%22repo%22%3A%22blocks%22%2C%22owner%22%3A%22githubnext%22%2C%22path%22%3A%22docs%2FDeveloping%20blocks%22%2C%22sha%22%3A%22main%22%7D%7D"></iframe>

通過閱讀代碼我們可以知道整個 Block 在加載的時候首先會加載這個 runtime 的代碼,runtime 做的工作就是和主頁面進行通信,當頁面加載好的時候會發送 loaded 信息告訴主頁面,然後主頁面會把相關信息(當前瀏覽的文件內容,還有加載 Block 的代碼)發送過來,這時候 runtime 就來加載我們的 Block,具體加載的方式就是把我們的代碼變成一個 script 標籤 append 進 DOM 樹:

const loadReactContent = (content: string) => {
  return `
var BlockBundle = ({ React, ReactJSXRuntime, ReactDOM, ReactDOMClient, PrimerReact }) => {
  function require(name) {
    switch (name) {
      case "react":
        return React;
      case "react/jsx-runtime":
        return ReactJSXRuntime;
      case "react-dom":
        return ReactDOM;
      case "react-dom/client":
        return ReactDOMClient;
      case "@primer/react":
      case "@primer/components":
        return PrimerReact;
      default:
        console.log("no module '" + name + "'");
        return null;
    }
  }
${content}
  return BlockBundle;
};`;
};

從代碼裡可以看到打包時候 exclude 掉的庫文件通過 runtime 注入的形式補上了,這樣可以讓業務代碼體積盡可能的小,同時 runtime 是復用的,因為當你切換 Block 的時候本質上是切換 url 的 hash,不會觸發整個 iframe 的重新加載,iframe 裡的頁面監聽到 hash 發生變化了就會再向主頁面去要相關的信息來完成下一次的渲染,因此差值是你的業務代碼包的大小。

append 進 DOM 樹以後,window 對象上就有 BlockBundle 這個函數了,runtime 這時候去調用執行渲染 Block 的代碼就完成首次渲染了。

通過 Runtime 的代碼閱讀我們也可以知道 Blocks 提供其他技術棧的開發方式,但是全局變量名字要叫 VanillaBlockBundle,官方也提供了 VueSvelte 的模版代碼,從模版代碼我們可以看出官方是希望你用 React 的技術棧的,畢竟 Vue 和 Svelte 需要額外打一個對應庫的 runtime 進來,性能會有點受損。

總結#

本文從使用到開發到實現介紹了 Github Blocks,其實 Github 已經提供了代碼查看,代碼編輯和版本管理,如果能用 Block 去實現在線加載代碼並展示,那已經可以實現一個簡陋版的 Cloud IDE 了(缺少 Terminal,Extension 等功能),但這個功能本身就已經和市面上的很多產品重疊了(stackblitz,codesandbox),所以看起來實現這個也沒有什麼意義。整體體驗下來功能會比較雞肋,因為你還要去 blocks.githubnext.com 才能體驗,當然這個功能可能是因為沒有 public 所以暫時以這種方式見面,然後整體交互會感覺比較重,頻繁的切換 Block 性能感覺很差,也沒有看到是否能固定一個 Folder Block 展示,這樣其實是不能實現我去改其他代碼,然後整個項目相關的內容刷新的(或許需要一個 Project Block?),會有一個來回切換加載 Block 的過程,目前想不太到更加有想象力的 Block,只能說期待後面社區的 idea 吧。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。