Skip to content

Astro 搭建博客系列:添加 Aside 提示组件

Posted on:July 31, 2023 at 04:29 PM
   
7 min read

在 md 文件中无法使用自定义组件,因此我们需要将 md 文件修改为 mdx,然后在 mdx 文件中使用 Aside 组件。

当然,我们不需要在每个 mdx 文件中手动引入,可以通过修改 astro 配置使其静默加载。

支持 mdx

步骤 1,安装依赖

Terminal window
yarn add @astrojs/mdx mdast-util-mdx-jsx

步骤 2,修改 astro 配置

astro.config.mjs
import mdx from "@astrojs/mdx";
export default defineConfig({
integrations: [
//...
react(),
sitemap(),
mdx(),
],
});

添加 Aside 组件

步骤 1,新建 Aside.astro 文件

src/components/Aside.astro
---
export interface Props {
type?: "note" | "tip" | "caution" | "danger";
title?: string;
}
const { type = "note", title } = Astro.props as Props;
const icons: Record<
NonNullable<Props["type"]>,
{ viewBox: string; d: string }
> = {
note: {
viewBox: "0 0 18 18",
d: "M0 3.75C0 2.784.784 2 1.75 2h12.5c.966 0 1.75.784 1.75 1.75v8.5A1.75 1.75 0 0114.25 14H1.75A1.75 1.75 0 010 12.25v-8.5zm1.75-.25a.25.25 0 00-.25.25v8.5c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25v-8.5a.25.25 0 00-.25-.25H1.75zM3.5 6.25a.75.75 0 01.75-.75h7a.75.75 0 010 1.5h-7a.75.75 0 01-.75-.75zm.75 2.25a.75.75 0 000 1.5h4a.75.75 0 000-1.5h-4z",
},
tip: {
viewBox: "0 0 18 18",
d: "M14 0a8.8 8.8 0 0 0-6 2.6l-.5.4-.9 1H3.3a1.8 1.8 0 0 0-1.5.8L.1 7.6a.8.8 0 0 0 .4 1.1l3.1 1 .2.1 2.4 2.4.1.2 1 3a.8.8 0 0 0 1 .5l2.9-1.7a1.8 1.8 0 0 0 .8-1.5V9.5l1-1 .4-.4A8.8 8.8 0 0 0 16 2v-.1A1.8 1.8 0 0 0 14.2 0h-.1zm-3.5 10.6-.3.2L8 12.3l.5 1.8 2-1.2a.3.3 0 0 0 .1-.2v-2zM3.7 8.1l1.5-2.3.2-.3h-2a.3.3 0 0 0-.3.1l-1.2 2 1.8.5zm5.2-4.5a7.3 7.3 0 0 1 5.2-2.1h.1a.3.3 0 0 1 .3.3v.1a7.3 7.3 0 0 1-2.1 5.2l-.5.4a15.2 15.2 0 0 1-2.5 2L7.1 11 5 9l1.5-2.3a15.3 15.3 0 0 1 2-2.5l.4-.5zM12 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm-8.4 9.6a1.5 1.5 0 1 0-2.2-2.2 7 7 0 0 0-1.1 3 .2.2 0 0 0 .3.3c.6 0 2.2-.4 3-1.1z",
},
caution: {
viewBox: "-1 1 18 18",
d: "M8.9 1.5C8.7 1.2 8.4 1 8 1s-.7.2-.9.5l-7 12a1 1 0 0 0 0 1c.2.3.6.5 1 .5H15c.4 0 .7-.2.9-.5a1 1 0 0 0 0-1l-7-12zM9 13H7v-2h2v2zm0-3H7V6h2v4z",
},
danger: {
viewBox: "0 1 14 17",
d: "M5 .3c.9 2.2.5 3.4-.5 4.3C3.5 5.6 2 6.5 1 8c-1.5 2-1.7 6.5 3.5 7.7-2.2-1.2-2.6-4.5-.3-6.6-.6 2 .6 3.3 2 2.8 1.4-.4 2.3.6 2.2 1.7 0 .8-.3 1.4-1 1.8A5.6 5.6 0 0 0 12 10c0-2.9-2.5-3.3-1.3-5.7-1.5.2-2 1.2-1.8 2.8 0 1-1 1.8-2 1.3-.6-.4-.6-1.2 0-1.8C8.2 5.3 8.7 2.5 5 .3z",
},
};
const { viewBox, d } = icons[type];
---
<aside class={`content ${type}`} aria-label={title}>
<p class="title" aria-hidden="true">
<span class="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox={viewBox}
width={16}
height={16}
>
<path fill-rule="evenodd" d={d}></path>
</svg>
</span>
{title}
</p>
<section>
<slot />
</section>
</aside>

步骤 2,新建 aside.css 样式文件

src/styles/aside.css
aside {
--aside-color-base: var(--color-base-purple);
--aside-color-lightness: 54%;
--aside-accent-color: hsl(
var(--aside-color-base),
var(--aside-color-lightness)
);
--aside-text-lightness: 20%;
--aside-text-accent-color: hsl(
var(--aside-color-base),
var(--aside-text-lightness)
);
border-inline-start: 4px solid var(--aside-accent-color);
padding: 1rem;
background-color: hsla(
var(--aside-color-base),
var(--aside-color-lightness),
var(--theme-accent-opacity)
);
outline: 1px solid transparent;
margin: 1rem 0;
}
.theme-dark aside {
--aside-text-lightness: 85%;
}
.title {
line-height: 1;
margin: 0 0 0.5rem 0 !important;
font-size: 0.9rem;
letter-spacing: 0.05em;
font-weight: bold;
text-transform: uppercase;
color: var(--aside-text-accent-color);
}
.icon svg {
width: 1.5em;
height: 1.5em;
vertical-align: middle;
fill: currentcolor;
}
aside a,
aside a > code:not([class*="language"]) {
color: var(--aside-text-accent-color);
}
aside section {
padding-left: 0;
}
aside section p {
margin: 0 !important;
}
aside p,
aside.content ul {
color: var(--theme-text);
}
.theme-dark aside code:not([class*="language"]) {
color: var(--theme-code-text);
}
aside pre {
margin-left: 0;
margin-right: 0;
}
.tip {
--aside-color-lightness: 42%;
--aside-color-base: var(--color-base-teal);
}
.caution {
--aside-color-lightness: 59%;
--aside-color-base: var(--color-base-yellow);
}
.danger {
--aside-color-lightness: 54%;
--aside-color-base: var(--color-base-red);
}

步骤 3,在布局组件中引入样式

src/layouts/Main.astro
---
import "@styles/aside.css";
---

步骤 4,接下来需要在 integrations 引入我们创建的组件,因此需要写一个自定义插件。 在此之前还需要安装一些依赖

Terminal window
yarn add remark-directive unist-util-remove

然后新建 astro-aside.ts 插件

src/utils/astro-aside.ts
import type { AstroIntegration } from "astro";
import type * as mdast from "mdast";
import remarkDirective from "remark-directive";
import type * as unified from "unified";
import { remove } from "unist-util-remove";
import { visit } from "unist-util-visit";
import type { BlockContent } from "mdast";
import type { MdxJsxAttribute } from "mdast-util-mdx-jsx";
interface NodeProps {
attributes?: Record<string, string | boolean | number | undefined | null>;
}
const AsideTagname = "AutoImportedAside";
export const asideAutoImport: Record<string, [string, string][]> = {
"./src/components/Aside.astro": [["default", AsideTagname]],
};
export function makeComponentNode(
name: string,
{ attributes = {} }: NodeProps = {},
...children: BlockContent[]
): any {
return {
type: "mdxJsxFlowElement",
name,
attributes: Object.entries(attributes)
// Filter out non-truthy attributes to avoid empty attrs being parsed as `true`.
.filter(([_k, v]) => v !== false && Boolean(v))
.map(([name, value]) => ({
type: "mdxJsxAttribute",
name,
value: value as MdxJsxAttribute["value"],
})),
children,
};
}
function remarkAsides(): unified.Plugin<[], mdast.Root> {
const variants = new Set(["note", "tip", "caution", "danger"]);
const transformer: unified.Transformer<mdast.Root> = tree => {
visit(tree, (node: any, index, parent) => {
if (!parent || index === null || node.type !== "containerDirective")
return;
const type = node.name;
if (!variants.has(type)) return;
let title: string | undefined;
remove(node, (child: any) => {
if (child.data?.directiveLabel) {
if ("children" in child && "value" in child.children[0]) {
title = child.children[0].value;
}
return true;
}
});
// Replace this node with the aside component it represents.
parent.children[index] = makeComponentNode(
AsideTagname,
{ attributes: { type, title } },
...node.children
);
});
};
return function attacher() {
return transformer;
};
}
export function astroAsides(): AstroIntegration {
return {
name: "@astrojs/asides",
hooks: {
"astro:config:setup": ({ updateConfig }) => {
updateConfig({
markdown: {
remarkPlugins: [remarkDirective, remarkAsides()],
},
});
},
},
};
}

这个文件导出了两个方法:asideAutoImport 和 makeComponentNode,分别用于自动导入组件和创建 aside 节点

步骤 5,修改我们的 astro 配置文件

astro.config.mjs
import { asideAutoImport, astroAsides } from "./src/utils/astro-asides";
import AutoImport from "astro-auto-import";
export default defineConfig({
integrations: [
// ...
AutoImport({
imports: [asideAutoImport],
}),
astroAsides(),
astroDocsExpressiveCode(),
react(),
sitemap(),
mdx(),
],
});

使用方法

直接在 mdx 文件中,使用 ::: 来表示。

:::[type][[title]]
[des]
:::

注意,title 需用 [] 包裹。 其中,title 表示标题,des 是描述,type 有四种类型: