ホームページ >ウェブフロントエンド >jsチュートリアル >マークダウンのインタラクティブコンポーネント

マークダウンのインタラクティブコンポーネント

Barbara Streisand
Barbara Streisandオリジナル
2024-10-30 15:27:02370ブラウズ

最近、カレンダーを実装したところ、非常にうまくいったので、そのアプローチを文書化して、より幅広い視聴者と共有したいと思いました。そして、実際に自分の作業のテスト可能な結果を​​記事内に載せたかったのです。

私はしばらくウェブサイトを微調整してきましたが、このアイデアをきっかけに次のイテレーションが始まりました。そして最終的にはさらに完全な再構築につながりましたが、この物語は私と完璧主義との戦いについての話ではありません。これを変えることです:

HTML comes with a lot of ready to use and flexible elements, but date selector
has a handful of limitations and the need to write your own calendar / date
input emerges sooner rather than later. In this tutorial I'll walk you through
implementing a calendar view and show how you can extend its functionality to fit
your booking widget or dashboard filter.

Here's how the final result might look like:

<!--render:custom-calendar-component/CalendarExample.ssi.tsx-->

これに:

Interactive Components in Markdown

設定

私の Web サイトは Deno で実行されており、最近から Hono と Hono/JSX を使用していますが、このアプローチはあらゆる JS ベースのランタイムと JSX で機能します。

すでにお気づきのとおり、ブログ投稿は、Marked および Front Matter を使用してビルド時に静的 HTML に変換される属性を持つマークダウン ファイルです。

少し行ったり来たりした後、次のワークフローに落ち着きました。

  • マークダウンで記事を書きます
  • 記事と同じフォルダーに JSX コンポーネントを作成します
  • HTML コメントを使用してマークダウンでコンポーネントを「インポート」します
  • それは魔法のように機能します

コメントには何らかの接頭辞が必要です。レンダリングと基本的にはこのコンポーネントへの単なるパス:

<!--render:custom-calendar-component/CalendarExample.ssi.tsx-->

パスの後にプロパティを追加することもできますが、私のユースケースでは必要なかったので、その部分はスキップしました。

HTMLのレンダリング

ブラウザ上で何かをハイドレートする前に、JSX コンポーネントから HTML をレンダリングする必要があります。そのためには、カスタム レンダラーを使用して HTML レンダリング ロジックをオーバーライドする必要があります:

export default class Renderer extends Marked.Renderer {
  constructor(private baseUrl: string, options?: Marked.marked.MarkedOptions) {
    super(options);
  }

  override html(html: string): string {
    const ssiMatch = /<!--render:(.+)-->/.exec(html);
    if (ssiMatch?.length) {
      const filename = ssiMatch[1];
      const ssi = SSIComponents.get(filename);
      if (!ssi) return html;
      const content = render(createElement(ssi.Component, {}));
      return [
        content,
        `<script type="module" src="${ssi.script}"></script>`,
      ].join("");
    }
    return html;
  }
}

ロジックは非常に単純です。HTML 文字列が // と一致するかどうかを確認してから、JSX をレンダリングします。コンポーネントが手元にあれば、それは簡単です。

コンポーネントのコンパイル

私のブログ コンテンツは静的に生成されるため、当然のことながら同じアプローチを採用しました。

  • コンテンツ フォルダーで *.ssi.tsx コンポーネント (したがってサフィックス) をスキャンします
  • パスで簡単に取得できるように、それらをインポートしてマップに追加するファイルを作成します

私のビルド スクリプトは次のようになります:

const rawContent = await readDir("./content");
const content: Record<string, Article> = {};
const ssi: Array<string> = [];

for (const pathname in rawContent) {
  if (pathname.endsWith(".ssi.tsx")) {
    ssi.push(pathname);
    continue;
  }
}

const scripts = await compileSSI(ssi.map((name) => `./content/${name}`));

const ssiContents = `
import type { FC } from 'hono/jsx';
const SSIComponents = new Map<string,{ Component: FC, script: string }>();
${
  scripts
    ? ssi
        .map(
          (pathname, i) =>
            `SSIComponents.set("${pathname}", { Component: (await import("./${pathname}")).default, script: "${scripts[i]}" })`
        )
        .join("\n")
    : ""
}
export default SSIComponents;
`;

await Deno.writeFile("./content/ssi.ts", new TextEncoder().encode(ssiContents));

Deno 固有の関数にこだわりすぎないでください。ノードやその他のもので簡単に書き換えることができます。

魔法は、JavaScript コードに似たテキスト ファイルを作成することです。

このスクリプト:

const ssiContents = `
import type { FC } from 'hono/jsx';
const SSIComponents = new Map<string,{ Component: FC, script: string }>();
${
  scripts
    ? ssi
        .map(
          (pathname, i) =>
            `SSIComponents.set("${pathname}", { Component: (await import("./${pathname}")).default, script: "${scripts[i]}" })`
        )
        .join("\n")
    : ""
}
export default SSIComponents;
`;

次のような文字列を返します:

import type { FC } from 'hono/jsx';
const SSIComponents = new Map<string,{ Component: FC, script: string }>();
SSIComponents.set("custom-calendar-component/CalendarExample.ssi.tsx", { Component: (await import("./custom-calendar-component/CalendarExample.ssi.tsx")).default, script: "/content/custom-calendar-component/CalendarExample.ssi.js" })
export default SSIComponents;

これをインポートしてレンダラーで使用できます :)

コードを記述するコード!魔法!そして、その過程で AI が被害を受けることはありません。昔ながらのメタプログラミングだけです。

そして最後に、パズルの最後のピースは、フロントエンドのコンポーネントをハイドレートすることです。私は esbuild を使用しましたが、個人的には Vite または HMR に付属する他のものに切り替える予定です。

それにもかかわらず、それは次のようになります:

HTML comes with a lot of ready to use and flexible elements, but date selector
has a handful of limitations and the need to write your own calendar / date
input emerges sooner rather than later. In this tutorial I'll walk you through
implementing a calendar view and show how you can extend its functionality to fit
your booking widget or dashboard filter.

Here's how the final result might look like:

<!--render:custom-calendar-component/CalendarExample.ssi.tsx-->

値がゼロのダミーのエントリ ポイントに注目してください。ただし、esbuild は独自のフォルダー内にファイルを作成し、予測可能なパスを持つように強制されます。

ssi-tsconfig.json は非常に汎用的です:

<!--render:custom-calendar-component/CalendarExample.ssi.tsx-->

実際のフロントエンドのハイドレーションでは、簡単な方法を選択し、これを .ssi.tsx ファイルの最後に追加しました。

export default class Renderer extends Marked.Renderer {
  constructor(private baseUrl: string, options?: Marked.marked.MarkedOptions) {
    super(options);
  }

  override html(html: string): string {
    const ssiMatch = /<!--render:(.+)-->/.exec(html);
    if (ssiMatch?.length) {
      const filename = ssiMatch[1];
      const ssi = SSIComponents.get(filename);
      if (!ssi) return html;
      const content = render(createElement(ssi.Component, {}));
      return [
        content,
        `<script type="module" src="${ssi.script}"></script>`,
      ].join("");
    }
    return html;
  }
}

読者ならもっとエレガントな方法を見つけられると思いますが、ほとんどこれで終わりです。

自由にコードを調整し (リポジトリへのリンクは下にあります)、独自のセンスを追加して、意見を共有してください。

Interactive Components in Markdown ヴァレリアVG / valeriavg.dev

以上がマークダウンのインタラクティブコンポーネントの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。