mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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:
@@ -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
|
||||
`,
|
||||
`,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user