Github Blocks とは何か#
Github Blocks は、Github が提供する CodeBase を拡張する方法であり、ウェブサイト上で Github コードを表示する方法を変えようとしています。Block の基本単位は二つあり、一つは File、もう一つは Folder です。コードファイルやコードフォルダをブラウズする際に、異なる Block を切り替えることでコードのレンダリング方法を決定できます。
図のように、赤い枠で囲まれた部分が Block を切り替える場所で、下の大部分はこの excalidraw ファイルのレンダリング結果です。
赤い枠で囲まれた部分をクリックすると、このファイルタイプをレンダリングできる他の Block が表示されます。切り替えが可能で、検索バーで Block を検索することもできます。テキストを検索してフィルタリングしたり、他の人の Block コードリポジトリの URL を直接貼り付けることもできます。これは、隠された Block を検索できるようにするためです。開発者が Block を外部に公開したい場合、彼は自分のリポジトリに github-blocks
タグを付ける必要がありますが、URL を貼り付けることでこの隠された Block を見つけることができます。
現在、公式は多くの 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() { ... }
の形式で、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 コンポーネントだけであり、最終的なパッケージ化の際に 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 タグに変換して 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;
};`;
};
コードから、パッケージ化時に除外されたライブラリファイルが runtime によって注入されることがわかります。これにより、ビジネスコードのサイズをできるだけ小さく保つことができます。また、runtime は再利用可能です。Block を切り替えると、実際には URL のハッシュを切り替えるだけで、iframe 全体の再読み込みは発生しません。iframe 内のページはハッシュの変化を監視し、変化があった場合は再度メインページに関連情報を要求して次回のレンダリングを完了します。したがって、差分はビジネスコードパッケージのサイズです。
DOM ツリーに追加された後、window オブジェクト上に BlockBundle という関数が存在し、runtime はこの時点で Block をレンダリングするコードを呼び出して初回レンダリングを完了します。
Runtime のコードを読むことで、Blocks は他の技術スタックの開発方法も提供していることがわかりますが、グローバル変数の名前は VanillaBlockBundle である必要があります。公式は Vue と Svelte のテンプレートコードも提供しており、テンプレートコードから公式が React の技術スタックを使用することを望んでいることがわかります。結局のところ、Vue と Svelte は追加の対応ライブラリの runtime を読み込む必要があり、パフォーマンスに影響を与える可能性があります。
まとめ#
この記事では、使用から開発、実現まで Github Blocks を紹介しました。実際、Github はコードの表示、コードの編集、バージョン管理をすでに提供しています。Block を使用してオンラインでコードを読み込み、表示できるようになれば、簡易版の Cloud IDE を実現できるでしょう(Terminal、Extension などの機能が欠けています)。しかし、この機能自体はすでに市場にある多くの製品(stackblitz、codesandbox)と重複していますので、実現することにあまり意味がないように思えます。全体的な体験としては、機能がやや使いにくいと感じます。なぜなら、blocks.githubnext.com に行かなければ体験できないからです。この機能は公開されていないため、このような形で一時的に提供されているのかもしれません。また、全体のインタラクションは重く感じられ、頻繁に Block を切り替えるとパフォーマンスが悪く感じます。Folder Block を固定して表示できるかどうかもわかりません。これでは他のコードを変更してもプロジェクト関連の内容が更新されない(おそらく Project Block が必要?)ため、Block を切り替えて読み込むプロセスが発生します。現在、より想像力豊かな Block を思いつくことはできませんが、今後のコミュニティのアイデアに期待するしかありません。