如何使用CSS Module为Gatsby中MDX渲染元素设置样式
以下三种方式都可以实现CSS Module作用于MDX渲染内容,按实现成本从低到高排序:
方案1:容器包裹+后代选择器(博客场景首选)
不需要修改MDX渲染配置,仅需给MDX渲染区域套一个绑定CSS Module类名的容器,在SCSS文件中通过后代选择器匹配容器内所有MDX生成的HTML元素即可,做全站统一博客排版成本最低。
首先在博客页组件引入对应的SCSS Module文件:import * as postStyles from './blog-post.module.scss'
修改组件返回的JSX,给MDXRenderer套一层容器:
const BlogPost = ({ data }: { data: DataType }) => { return ( <Layout pageTitle={data.mdx.frontmatter.title}> <p>{data.mdx.frontmatter.date}</p> <div className={postStyles.postContent}> <MDXRenderer>{data.mdx.body}</MDXRenderer> </div> </Layout> ); };
在blog-post.module.scss中直接编写排版样式,所有MDX生成的标签都会被匹配:
.postContent { line-height: 1.7; h2 { font-size: 1.5rem; margin: 2rem 0 1rem; color: #222; } p { margin: 1rem 0; } pre { background: #f6f8fa; padding: 1rem; border-radius: 6px; overflow-x: auto; } code { background: #f6f8fa; padding: 0.2rem 0.4rem; border-radius: 3px; } }
方案2:通过components属性传入自定义封装组件(精准控制单元素样式)
MDXRenderer原生支持components入参,可传入标签与自定义组件的映射对象,将MDX默认渲染的原生HTML标签替换为绑定了CSS Module类名的封装组件,适合需要给标签加独立类、附加定制逻辑(如标题锚点、代码块复制按钮)的场景。
完整修改后的组件代码:
import * as React from "react"; import { graphql } from "gatsby"; import { MDXRenderer } from "gatsby-plugin-mdx"; import Layout from "../../components/Layout"; import * as postStyles from './blog-post.module.scss' interface DataType { mdx: { frontmatter: { title: string; date: string; }; body: string; }; } // 封装绑定CSS Module类的自定义标签组件 const mdxComponents = { h2: ({ children, ...props }) => <h2 className={postStyles.h2} {...props}>{children}</h2>, h3: ({ children, ...props }) => <h3 className={postStyles.h3} {...props}>{children}</h3>, p: ({ children, ...props }) => <p className={postStyles.paragraph} {...props}>{children}</p>, pre: ({ children, ...props }) => <pre className={postStyles.codeBlock} {...props}>{children}</pre>, code: ({ children, ...props }) => <code className={postStyles.inlineCode} {...props}>{children}</code>, } const BlogPost = ({ data }: { data: DataType }) => { return ( <Layout pageTitle={data.mdx.frontmatter.title}> <p>{data.mdx.frontmatter.date}</p> <MDXRenderer components={mdxComponents}>{data.mdx.body}</MDXRenderer> </Layout> ); }; export const query = graphql` query ($id: String) { mdx(id: { eq: $id }) { frontmatter { title date(formatString: "MMMM D, YYYY") } body } } `; export default BlogPost;
这种方式不需要写嵌套后代选择器,每个类名都是CSS Module编译后的独立哈希类,不会和其他组件样式冲突。
方案3:全局MDXProvider注入(全站MDX复用)
如果站点所有MDX内容都要复用同一套样式,无需每个博客页单独传参,可在项目根目录的gatsby-browser.js与gatsby-ssr.js中通过MDXProvider全局注入自定义组件映射,所有页面的MDX渲染会自动应用绑定了CSS Module类的组件。
两个文件的配置代码一致:
import * as React from 'react' import { MDXProvider } from '@mdx-js/react' import * as mdxStyles from './src/styles/mdx-styles.module.scss' const globalMdxComponents = { h2: ({ children, ...props }) => <h2 className={mdxStyles.h2} {...props}>{children}</h2>, h3: ({ children, ...props }) => <h3 className={mdxStyles.h3} {...props}>{children}</h3>, p: ({ children, ...props }) => <p className={mdxStyles.paragraph} {...props}>{children}</p>, // 其余自定义标签组件逻辑同上 } export const wrapRootElement = ({ element }) => { return ( <MDXProvider components={globalMdxComponents}> {element} </MDXProvider> ) }
配置完成后,所有页面的MDXRenderer不需要额外传参,就会自动套用全局定义的带样式组件。
内容的提问来源于stack exchange,提问作者ywbird

