首页  >  文章  >  web前端  >  Markdown 中的交互组件

Markdown 中的交互组件

Barbara Streisand
Barbara Streisand原创
2024-10-30 15:27:02297浏览

最近我实现了一个日历,结果非常好,我想记录该方法并与更广泛的受众分享。我真的很想在文章中得到我的工作的实际可测试结果。

我已经调整我的网站一段时间了,这个想法开始了下一次迭代。最终导致了又一次全面重建,但这个故事并不是关于我与完美主义的斗争。这是关于转动这个:

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

设置

我的网站在 Deno 上运行,并从最近开始使用 Hono 和 Hono/JSX,但该方法适用于任何基于 JS 的运行时和 JSX。

正如您已经注意到的,博客文章是带有属性的 Markdown 文件,这些属性在构建时使用 Marked 和 Front Matter 转换为静态 HTML。

经过一番反复思考,我决定了这个工作流程:

  • 我用markdown写文章
  • 我在与文章相同的文件夹中创建了一个 JSX 组件
  • 我使用 HTML 注释在 Markdown 中“导入”组件
  • 它的神奇功效

评论需要某种前缀,例如渲染并且基本上只是该组件的路径:

<!--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 特定功能,它可以轻松地用 Node 或其他任何东西重写。

神奇之处在于编写类似于 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;

然后可以在渲染器中导入和使用:)

写代码的代码!魔法!并且在此过程中没有任何人工智能受到伤害:只是你的老式元编程。

最后,最后一个难题是滋润前端的组件。我使用 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

以上是Markdown 中的交互组件的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn