perf(site): split InlineMarkdown out of Markdown to avoid loading PrismJS in initial bundle (#24192)

\`InlineMarkdown\` and \`MemoizedInlineMarkdown\` lived in
\`Markdown.tsx\`
alongside a static \`import { Prism as SyntaxHighlighter } from
"react-syntax-highlighter"\` — the full PrismJS build with ~300 language
grammars. Because \`DashboardLayout\` eagerly imports
\`AnnouncementBannerView → InlineMarkdown\`, every authenticated page
loaded and evaluated the entire Prism/refractor bundle on startup even
though syntax highlighting is only used in secondary views.

This PR moves \`InlineMarkdown\` and \`MemoizedInlineMarkdown\` into
their
own \`InlineMarkdown.tsx\` file that depends only on \`react-markdown\`
and
updates all six consumers to import from the new module.
\`Markdown.tsx\`
keeps the PrismJS import for the full \`Markdown\` component, which is
only reached through lazy-loaded routes.

> 🤖 Generated by Coder Agents
This commit is contained in:
Jaayden Halko
2026-04-10 07:34:31 +01:00
committed by GitHub
parent 1d0653cdab
commit 19e0e0e8e6
10 changed files with 111 additions and 88 deletions
@@ -0,0 +1,28 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { InlineMarkdown } from "./InlineMarkdown";
const meta: Meta<typeof InlineMarkdown> = {
title: "components/Markdown/InlineMarkdown",
component: InlineMarkdown,
};
export default meta;
type Story = StoryObj<typeof InlineMarkdown>;
export const WithFormatting: Story = {
args: {
children: "This supports **bold** and *italic* text.",
},
};
export const WithLink: Story = {
args: {
children: "Read the [documentation](https://coder.com/docs).",
},
};
export const WithCode: Story = {
args: {
children: "Run `coder templates push` to publish your template.",
},
};
@@ -0,0 +1,74 @@
import Link from "@mui/material/Link";
import isEqual from "lodash/isEqual";
import { type FC, memo } from "react";
import ReactMarkdown, { type Options } from "react-markdown";
interface InlineMarkdownProps {
/**
* The Markdown text to parse and render
*/
children: string;
/**
* Additional element types to allow.
* Allows italic, bold, links, and inline code snippets by default.
* eg. `["ol", "ul", "li"]` to support lists.
*/
allowedElements?: readonly string[];
className?: string;
/**
* Can override the behavior of the generated elements
*/
components?: Options["components"];
}
/**
* Supports a strict subset of Markdown that behaves well as inline/confined
* content. Separated from the full Markdown component so that importing it
* does not pull in the heavy PrismJS syntax-highlighting bundle.
*/
export const InlineMarkdown: FC<InlineMarkdownProps> = (props) => {
const { children, allowedElements = [], className, components = {} } = props;
return (
<ReactMarkdown
className={className}
allowedElements={[
"p",
"em",
"strong",
"a",
"pre",
"code",
...allowedElements,
]}
unwrapDisallowed
components={{
p: ({ children }) => <>{children}</>,
a: ({ href, target, children }) => (
<Link href={href} target={target}>
{children}
</Link>
),
code: ({ node, className, children, style, ...props }) => (
<code
className="rounded-sm bg-border px-1 py-px text-[14px] text-content-primary"
{...props}
>
{children}
</code>
),
...components,
}}
>
{children}
</ReactMarkdown>
);
};
export const MemoizedInlineMarkdown = memo(InlineMarkdown, isEqual);
@@ -102,6 +102,6 @@ export const GFMAlertWithInlineFormatting: Story = {
> [!IMPORTANT]
> Larger **instances** cost more. Choose based on your workload.
> Test line two
`,
`,
},
};
+2 -81
View File
@@ -85,14 +85,8 @@ export const Markdown: FC<MarkdownProps> = (props) => {
</SyntaxHighlighter>
) : (
<code
css={(theme) => ({
padding: "1px 4px",
background: theme.palette.divider,
borderRadius: 4,
color: theme.palette.text.primary,
fontSize: 14,
})}
{...props}
className="rounded-sm bg-border px-1 py-px text-[14px] text-content-primary"
{...restProps}
>
{children}
</code>
@@ -155,80 +149,7 @@ export const Markdown: FC<MarkdownProps> = (props) => {
);
};
interface InlineMarkdownProps {
/**
* The Markdown text to parse and render
*/
children: string;
/**
* Additional element types to allow.
* Allows italic, bold, links, and inline code snippets by default.
* eg. `["ol", "ul", "li"]` to support lists.
*/
allowedElements?: readonly string[];
className?: string;
/**
* Can override the behavior of the generated elements
*/
components?: Options["components"];
}
/**
* Supports a strict subset of Markdown that behaves well as inline/confined content.
*/
export const InlineMarkdown: FC<InlineMarkdownProps> = (props) => {
const { children, allowedElements = [], className, components = {} } = props;
return (
<ReactMarkdown
className={className}
allowedElements={[
"p",
"em",
"strong",
"a",
"pre",
"code",
...allowedElements,
]}
unwrapDisallowed
components={{
p: ({ children }) => <>{children}</>,
a: ({ href, target, children }) => (
<Link href={href} target={target}>
{children}
</Link>
),
code: ({ node, className, children, style, ...props }) => (
<code
css={(theme) => ({
padding: "1px 4px",
background: theme.palette.divider,
borderRadius: 4,
color: theme.palette.text.primary,
fontSize: 14,
})}
{...props}
>
{children}
</code>
),
...components,
}}
>
{children}
</ReactMarkdown>
);
};
export const MemoizedMarkdown = memo(Markdown, isEqual);
export const MemoizedInlineMarkdown = memo(InlineMarkdown, isEqual);
const githubFlavoredMarkdownAlertTypes = [
"tip",
@@ -1,5 +1,5 @@
import type { FC } from "react";
import { InlineMarkdown } from "#/components/Markdown/Markdown";
import { InlineMarkdown } from "#/components/Markdown/InlineMarkdown";
import { readableForegroundColor } from "#/utils/colors";
interface AnnouncementBannerViewProps {
+1 -1
View File
@@ -4,7 +4,7 @@ import type { WorkspaceAgent, WorkspaceResource } from "#/api/typesGenerated";
import { ChevronDownIcon } from "#/components/AnimatedIcons/ChevronDown";
import { Button } from "#/components/Button/Button";
import { CopyableValue } from "#/components/CopyableValue/CopyableValue";
import { MemoizedInlineMarkdown } from "#/components/Markdown/Markdown";
import { MemoizedInlineMarkdown } from "#/components/Markdown/InlineMarkdown";
import { Stack } from "#/components/Stack/Stack";
import {
Tooltip,
@@ -9,7 +9,7 @@ import type {
WorkspaceBuildParameter,
} from "#/api/typesGenerated";
import { ConfirmDialog } from "#/components/Dialogs/ConfirmDialog/ConfirmDialog";
import { MemoizedInlineMarkdown } from "#/components/Markdown/Markdown";
import { MemoizedInlineMarkdown } from "#/components/Markdown/InlineMarkdown";
import { UpdateBuildParametersDialog } from "#/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialog";
import { UpdateBuildParametersDialogExperimental } from "#/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialogExperimental";
@@ -31,7 +31,7 @@ import {
DropdownMenuTrigger,
} from "#/components/DropdownMenu/DropdownMenu";
import { Margins } from "#/components/Margins/Margins";
import { MemoizedInlineMarkdown } from "#/components/Markdown/Markdown";
import { MemoizedInlineMarkdown } from "#/components/Markdown/InlineMarkdown";
import {
PageHeader,
PageHeaderSubtitle,
@@ -2,7 +2,7 @@ import type { Interpolation, Theme } from "@emotion/react";
import { Children, type FC, type HTMLAttributes } from "react";
import type { WorkspaceResource } from "#/api/typesGenerated";
import { CopyableValue } from "#/components/CopyableValue/CopyableValue";
import { MemoizedInlineMarkdown } from "#/components/Markdown/Markdown";
import { MemoizedInlineMarkdown } from "#/components/Markdown/InlineMarkdown";
import { SensitiveValue } from "#/modules/resources/SensitiveValue";
type ResourceMetadataProps = Omit<HTMLAttributes<HTMLElement>, "resource"> & {
@@ -10,7 +10,7 @@ import type {
Workspace,
WorkspaceBuild,
} from "#/api/typesGenerated";
import { MemoizedInlineMarkdown } from "#/components/Markdown/Markdown";
import { MemoizedInlineMarkdown } from "#/components/Markdown/InlineMarkdown";
import { useDashboard } from "#/modules/dashboard/useDashboard";
import { TemplateUpdateMessage } from "#/modules/templates/TemplateUpdateMessage";
import { getAgentHealthIssue } from "#/modules/workspaces/health";