diff --git a/app/src/ui/components/code/CodePreview.tsx b/app/src/ui/components/code/CodePreview.tsx index 9796e93..d79fd3a 100644 --- a/app/src/ui/components/code/CodePreview.tsx +++ b/app/src/ui/components/code/CodePreview.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { useTheme } from "ui/client/use-theme"; -import { cn } from "ui/lib/utils"; +import { cn, importDynamicBrowserModule } from "ui/lib/utils"; export type CodePreviewProps = { code: string; @@ -30,8 +30,10 @@ export const CodePreview = ({ async function highlightCode() { try { // Dynamically import Shiki from CDN - // @ts-expect-error - Dynamic CDN import - const { codeToHtml } = await import("https://esm.sh/shiki@3.13.0"); + const { codeToHtml } = await importDynamicBrowserModule( + "shiki", + "https://esm.sh/shiki@3.13.0", + ); if (cancelled) return; diff --git a/app/src/ui/lib/utils.ts b/app/src/ui/lib/utils.ts index 1af2c04..29eb301 100644 --- a/app/src/ui/lib/utils.ts +++ b/app/src/ui/lib/utils.ts @@ -3,3 +3,30 @@ import { type ClassNameValue, twMerge } from "tailwind-merge"; export function cn(...inputs: ClassNameValue[]) { return twMerge(inputs); } + +/** + * Dynamically import a module from a URL in the browser in a way compatible with all react frameworks (nextjs doesn't support dynamic imports) + */ +export async function importDynamicBrowserModule(name: string, url: string): Promise { + if (!(window as any)[name]) { + const script = document.createElement("script"); + script.type = "module"; + script.async = true; + script.textContent = `import * as ${name} from '${url}';window.${name} = ${name};`; + document.head.appendChild(script); + + // poll for the module to be available + const maxAttempts = 50; // 5s + let attempts = 0; + while (!(window as any)[name] && attempts < maxAttempts) { + await new Promise((resolve) => setTimeout(resolve, 100)); + attempts++; + } + + if (!(window as any)[name]) { + throw new Error(`Browser module "${name}" failed to load`); + } + } + + return (window as any)[name] as T; +}