>  기사  >  웹 프론트엔드  >  마크다운의 대화형 구성요소

마크다운의 대화형 구성요소

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에서 작동합니다.

이미 아시다시피 블로그 게시물은 Marked 및 Front Matter를 사용하여 빌드 시 정적 HTML로 변환되는 속성을 가진 마크다운 파일입니다.

약간 고민 끝에 다음 작업 흐름을 결정했습니다.

  • 마크다운으로 기사를 작성합니다
  • 기사와 같은 폴더에 JSX 컴포넌트를 생성합니다
  • HTML 주석을 사용하여 마크다운의 구성요소를 "가져옵니다"
  • 마법처럼 작동합니다

댓글에는 일종의 접두사가 필요합니다. 렌더링하고 기본적으로 이 구성요소에 대한 경로입니다.

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

경로 뒤에 props를 추가할 수도 있지만 제 사용 사례에서는 필요하지 않아서 해당 부분을 건너뛰었습니다.

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-->

값이 0이지만 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.