在上一篇《这里有从零开始构建现代化前端UI组件库所需要的一切(三)》中,我们实现了一套高效的版本管理和发布的解决方案,其实到这里我们组件库的架构基本上就差不多了(当然如果有遗漏欢迎留言补充),接下来的几篇文章,我们会首先为组件库搭建一个文档网站,然后再扩展一些组件开发中的一些基础功能(CSS、主题等)。
那么这篇文章就是向大家展示组件库文档网站搭建的整个过程,其中包括比如:Markdown
快速生成组件文档、组件示例及其代码的展示、快速创建实例并跳转至codesandbox
等核心功能。
文档网站的搭建
搭建组件文档网站时,你可以根据项目需求、团队熟悉度和个人偏好选择适合的技术。以下是一些流行的技术选型:
-
- 适用于 Vue.js 项目。
- 使用 Markdown 编写文档,支持 Vue 组件。
- 简单易用,提供了默认主题,同时支持自定义主题。
-
- 适用于 React 项目。
- 由 Facebook 开发,支持 Markdown 和 MDX 编写文档。
- 提供了专业外观的默认主题,易于定制。
-
- 适用于 Python 项目,但也可以用于其他语言。
- 使用 Markdown 编写文档,易于上手。
- 提供了多个主题供选择,可以通过插件进行扩展。
-
- 支持多种语言和框架。
- 使用 Markdown 编写文档,提供实时预览。
- 具有丰富的插件生态系统。
-
Hugo:
- 使用 Go 语言构建的静态网站生成器。
- 支持 Markdown 编写文档,速度快。
- 提供多个主题和布局。
-
- 轻量级的文档工具,适用于小型项目。
- 使用 Markdown 编写文档,无需构建过程,实时预览。
- 支持自定义主题和插件。
-
- 适用于 React 项目,可用于构建静态站点。
- 支持 Markdown 和 MDX 编写文档。
- 功能强大,支持数据源插件和定制主题。
-
- 适用于 Vue.js 项目。
- 可以使用 Nuxt Content 模块来管理文档,支持 Markdown 编写。
- 提供默认主题和可定制性。
-
- 适用于 React 项目。
- 支持 Markdown 和 MDX 编写文档。
- 提供默认主题和灵活的定制选项。
-
- 适用于 Vue.js 项目,可用于构建静态站点。
- 支持 Markdown 和 GraphQL 查询。
- 提供默认主题和可定制性。
选择适合你项目需求的技术,考虑到文档的复杂度、团队的熟悉度以及所需的扩展性和定制性。最重要的是确保文档工具能够简化文档编写和维护的流程。
这里我们选择Next.js
,因为其具有以下一些优势:
-
React 支持
: Next.js 是一个 React 框架,如果你的组件库是基于 React 构建的,那么使用 Next.js 可以更好地集成和展示 React 组件。 -
支持 Markdown 和 MDX
: Next.js 支持使用 Markdown 和 MDX 编写文档,这使得编写和管理文档变得更加灵活和简单。 -
数据获取
: Next.js 具有灵活的数据获取方法,你可以通过获取外部数据或者使用 mock 数据来呈现组件的示例和文档。 -
静态生成和服务端渲染
: Next.js 提供了静态生成和服务端渲染的能力,可以根据需要选择最适合你文档网站的渲染方式,提高性能和用户体验。 -
动态路由
: 使用 Next.js 的动态路由,你可以轻松地为每个组件生成动态页面,展示组件的不同用法和变化。 -
插件和生态系统
: Next.js 拥有庞大的插件和生态系统,你可以根据需求引入各种插件,以扩展和定制你的文档网站。 -
热模块替换(HMR)
: Next.js 支持热模块替换,使得在开发过程中对文档进行实时更新变得更加高效。 -
社区支持
: Next.js 拥有强大的社区支持,你可以在社区中获取到丰富的资源、解决方案和交流。
当然大家可以根据自己的技术栈选择一个合适自己的,对于我而言Next.js
是最理想的选择了。当然如果你对Next.js
还不熟悉,也不用担心,因为为组件搭建一个网站其实跟使用哪个框架并不是强关系的,你只需要了解到这样的网站需要的一些核心的功能及其如何实现即可。当然,我觉得你最后也会爱上Next.js
生态的。
那么我们开始搭建我们的组件文档网站。
-
blankui
的根目录下创建apps/
目录 -
cd apps/
,然后运行:pnpm create t3-app@latest
shell.../Library/pnpm/store/v3/tmp/dlx-76242 | +149 +++++++++++++++ .../Library/pnpm/store/v3/tmp/dlx-76242 | Progress: resolved 149, reused 120, downloaded 29, added 149, done ___ ___ ___ __ _____ ___ _____ ____ __ ___ ___ / __| _ \ __| / \_ _| __| |_ _|__ / / \ | _ \ _ \ | (__| / _| / /\ \| | | _| | | |_ \ / /\ \| _/ _/ \___|_|_\___|_/‾‾\_\_| |___| |_| |___/ /_/‾‾\_\_| |_| │ ◇ What will your project be called? │ @blankui-org/docs │ ◇ Will you be using TypeScript or JavaScript? │ TypeScript │ ◇ Will you be using Tailwind CSS for styling? │ Yes │ ◇ Would you like to use tRPC? │ No │ ◇ What authentication provider would you like to use? │ None │ ◇ What database ORM would you like to use? │ None │ ◇ EXPERIMENTAL Would you like to use Next.js App Router? │ Yes │ ◇ Should we initialize a Git repository and stage the changes? │ No │ ◇ Should we run 'pnpm install' for you? │ No │ ◇ What import alias would you like to use? │ @/ ✔ docs scaffolded successfully! Adding boilerplate... ✔ Successfully setup boilerplate for tailwind ✔ Successfully setup boilerplate for envVariables Next steps: cd docs pnpm install pnpm dev git commit -m "initial commit" Thank you for trying out the App Router option. If you encounter any issues, please open an issue!
这里我们使用 Create T3 App 来快速创建出一个
Next.js
的项目,并且添加了TailwindCSS
等功能。Create-t3-app 是一个 CLI(命令行界面),可帮助用户快速启动新的全栈 Web 应用程序。 它由 T3 Stack 开发人员创建,旨在简化设置模块化 T3 Stack 应用程序的过程。
也可以使用
Next.js
官方的create-nextjs-app快速创建一个Next.js
应用。这里就不过多介绍了,大家有兴趣可以去官网查看其详细的文档。
-
cd ../
回到根目录下,先编辑pnpm-workspace.yaml
文件,然后执行pnpm install
安装依赖ymlpackages: - "apps/**/**" - "packages/**/**"
-
编辑根目录下的
package.json
,我们添加一个dev:docs
命令:json// ... "scripts": { // ... "dev:docs": "turbo dev --filter=@blankui-org/docs", // ... } // ...
这时候我们运行pnpm dev:docs
,会报错:
shell
> blankui@1.0.0 dev:docs /Users/******/Documents/public/blankui
> turbo dev --filter=@blankui-org/docs
ERROR run failed: package.json must have a name field:
/Users/******/Documents/public/blankui/apps/docs/docs/.next/types/package.json
ELIFECYCLE Command failed with exit code 1.
需要在pnpm-workspace.yaml
中过滤掉.next/
&dist/
目录:
yml
packages:
- "apps/**/**"
- "packages/**/**"
- "!**/.next/**" # NEW
- "!**/dist/**" # NEW
再次运行pnpm dev:docs
,则成功启动,浏览器打开http://localhost:3000/
可以看到默认的首页:
运行
pnpm dev
会同时启动@blankui-org/storybook
&@blankui-org/docs
到这里为止的源代码:commit d8127d3
主题和国际化
网站的主题和国际化功能分别由 next-themes & next-intl 来实现:
- 安装:
pnpm add next-themes next-intl --filter @blankui-org/docs
- 配置:
-
next-themes
:-
@blankui-org/docs
项目下新建src/app/theme-provider.tsx
文件ts"use client"; import { ThemeProvider as NextThemeProvider } from "next-themes"; export function ThemeProvider({ children }: { children: React.ReactNode }) { return <NextThemeProvider attribute="class">{children}</NextThemeProvider>; }
-
src/app/layout.tsx
中引入ThemeProvider
:ts// ... import { ThemeProvider } from "./theme-provider"; // ... return ( <html lang="en" suppressHydrationWarning> <body className={`font-sans ${inter.variable}`}> <ThemeProvider>{children}</ThemeProvider> </body> </html> ); // ...
-
我们使用的是
TailwindCSS
,所以更改一下tailwind.config.ts
配置:tsexport default { // ... darkMode: "class", // ... } satisfies Config;
-
-
next-intl
...next-intl
的配置步骤比较多,这里就不做展开了,因为确实会很严重影响文章的篇幅,大家可以直接找到下面当前节点提交的代码查看👇。(后面很多的重复冗余的不那么重要的工作我也会直接带过,大家直接查看该节点的commit
代码即可。)
-
next-themes
&next-intl
的配置代码:commit 415b9e5
这时候页面是空的,我们快速添加一点东西:
使用Markdown
快速为组件生成文档页面
这里我们需要安装一个库:Contentlayer
Contentlayer 是一个用于 Next.js 应用程序的数据查询和转换工具,旨在简化在 Next.js 项目中处理结构化内容的过程。以下是 Contentlayer 的主要特点:
主要特点:
-
内容查询:
- Contentlayer 提供了一种类似于 GraphQL 的查询语法,让你能够从各种内容源中提取所需的数据。
- 查询语法可以用于指定你需要获取的内容类型、字段和过滤条件。
-
多种内容源支持:
- 支持从不同类型的内容源中获取数据,包括 Markdown 文件、JSON 文件和 YAML 文件。
- 这使得你可以根据项目的需要选择最适合的内容来源。
-
强类型支持:
- Contentlayer 与 TypeScript 集成,为查询结果提供强类型支持。
- 在编写代码时,你可以受益于 TypeScript 的静态类型检查,避免潜在的类型错误。
-
可定制的模式:
- 你可以定义一个模式,规定内容的结构和字段。
- 这有助于保持一致的数据结构,使得在整个项目中使用内容变得更加可靠。
-
与 Next.js 紧密集成:
- Contentlayer 专为 Next.js 项目设计,与 Next.js 的数据获取系统无缝集成。
- 你可以在页面和组件中轻松使用 Contentlayer 查询到的数据。
-
Markdown 内容处理:
- Contentlayer 可以处理 Markdown 内容,将其解析为结构化数据,方便在应用中使用。
将Contentlayer
集成到@blankui-org/docs
中:
-
安装:
shellpnpm add contentlayer next-contentlayer date-fns --filter @blankui-org/docs
-
配置,请参考官方的 configuration 文档,这里就只列出核心的配置:
-
@blankui-org/docs
根目录下创建contentlayer.config.ts
文件:tsimport { defineDocumentType, makeSource } from "contentlayer/source-files"; import highlight from "rehype-highlight"; import remarkGfm from "remark-gfm"; export const Doc = defineDocumentType(() => ({ name: "Doc", filePathPattern: `**/*.mdx`, contentType: "mdx", fields: { title: { type: "string", required: true }, description: { type: "string", required: false }, date: { type: "date", required: true }, }, computedFields: { url: { type: "string", resolve: (doc) => `/${doc._raw.flattenedPath}`, }, }, })); export default makeSource({ contentDirPath: "./src/docs", documentTypes: [Doc], mdx: { remarkPlugins: [remarkGfm], // @ts-expect-error: Unreachable code error rehypePlugins: [highlight], }, });
这里我们将
type
配置成mdx
:MDX 将 JSX 组件引入 Markdown,这可以为内容片段的主体区域提供强大的功能和灵活性,意思就是在 Markdown 可以直接引入组件,根据 MDX 文档,需要额外安装:pnpm add rehype-highlight remark-gfm @types/mdx --filter @blankui-org/docs
-
next.config.js
:jsimport withNextIntl from "next-intl/plugin"; import { withContentlayer } from "next-contentlayer"; /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful * for Docker builds. */ await import("./src/env.js"); /** @type {import("next").NextConfig} */ const config = { reactStrictMode: true, swcMinify: true, webpack: (config) => { // https://github.com/contentlayerdev/contentlayer/issues/272 config.infrastructureLogging = { level: "error", }; return config; }, }; export default withNextIntl()(withContentlayer(config));
-
更改
package.json
的dev
命令(需要pnpm add concurrently -D -w
):json// ... "dev": "concurrently \"contentlayer dev\" \"next dev\"", // ...
这时候启动项目
Contentlayer
就会自动生成文档文件。
-
-
项目中使用,在这里只列出核心的代码:
-
src/app/[locale]/
目录下新建docs/[[...slug]]/page.tsx
文件:tsimport { notFound } from "next/navigation"; import { allDocs } from "contentlayer/generated"; import { MdxComponent } from "@/components/mdx-components"; export async function generateStaticParams() { return allDocs.map((doc) => ({ slug: doc._raw.flattenedPath.split("/"), })); } export default async function Page({ params }: { params: { slug: string[] } }) { // Find the post for the current page. const slug = params.slug?.join("/") || ""; // console.log(slug); const doc = allDocs.find((doc) => doc._raw.flattenedPath === slug); // 404 if the post does not exist. if (!doc) notFound(); return ( <div> <MdxComponent code={doc.body.code} /> </div> ); }
-
根据
contentlayer.config.ts
的配置,我们创建src/docs/components/button.mdx
文件:md--- title: "Button" description: "Buttons allow users to perform actions and choose with a single tap." date: 2024-01-12 --- # Button Buttons allow users to perform actions and choose with a single tap. ## hello
-
这时候我们在浏览器打开 URL http://localhost:3000/docs/components/button
:
根据button.mdx
文档就生成了页面了,同理,其他组件也可以通过mdx
文件生成页面,这样就很大程度上提升了生产力。
然后我们在button.mdx
中直接使用Code
组件:
yaml
---
title: "Button"
description: "Buttons allow users to perform actions and choose with a single tap."
date: 2024-01-12
---
# Button
Buttons allow users to perform actions and choose with a single tap.
## hello
content from the Code component:
<Code />
需要扩展 MdxComponent 组件
ts
export const components: MDXComponents = {
NextImage,
Code, // 这里引入
h1: (props: React.HTMLAttributes<HTMLHeadElement>) => (
<h1 className="text-red-600 font-bold" {...props} />
),
h2: (props: React.HTMLAttributes<HTMLHeadElement>) => (
<h2 className="text-green-400" {...props} />
),
h3: (props: React.HTMLAttributes<HTMLHeadElement>) => <h3 {...props} />,
h4: (props: React.HTMLAttributes<HTMLHeadElement>) => <h4 {...props} />,
strong: (props: React.HTMLAttributes<HTMLElement>) => (
<strong className="font-medium" {...props} />
),
a: (props: React.HTMLAttributes<HTMLAnchorElement>) => <a {...props} />,
Steps: ({ ...props }) => (
<div
className="[&>h3]:step [&>h4]:step border-default-100 relative mb-12 ml-4 border-l pl-[1.625rem] [counter-reset:step] [&>h3>a]:pt-0.5 [&>h4>a]:pt-0.5"
{...props}
/>
),
};
import { useMDXComponent } from "next-contentlayer/hooks";
import { components } from "./components";
interface MdxProps {
code: string;
}
export function MdxComponent({ code }: MdxProps) {
const Component = useMDXComponent(code);
return <Component components={components} />;
}
Code.tsx
:
ts
import { useTranslations } from "next-intl";
export const Code: React.FC<{}> = () => {
const t = useTranslations("Button");
return <p>{t("desc")}</p>;
};
这时候页面:
中英文切换也都没问题。
页面好像非常简陋...是的,因为这里只是提供一个例子让你能够快速理解核心的功能,当然如果你有时间的话,可以自己完善它。
稍微加了点样式和页面跳转:
为组件提供代码示例及其在线预览
组件的代码示例和在线预览的功能我们将借助 react-live 实现,我们先简单介绍一下它。
react-live
是一个 React 组件库,用于在实时编辑环境中展示和运行 React 代码。它通常用于文档、教程、博客等场景,允许用户直接在页面上编辑和执行 React 代码,并即时查看结果。以下是 react-live
的主要特点和使用说明:
特点:
-
实时编辑:
- 用户可以直接在页面上编辑 React 代码,而不需要离开页面或刷新浏览器。
- 每次编辑都会触发实时更新,用户可以立即看到代码变化的效果。
-
支持 JSX 和组件:
react-live
支持编辑和展示 JSX 语法的 React 代码。- 用户可以使用组件、状态和其他 React 特性,实现更丰富的交互和演示效果。
-
代码高亮:
- 代码编辑器支持语法高亮,提高代码的可读性。
- 这对于展示代码示例和教育性的内容非常有用。
-
错误处理:
react-live
能够捕获并展示代码中的错误信息。- 当用户输入的代码存在错误时,会在编辑器中明显显示,帮助用户更轻松地找到和解决问题。
-
自定义主题:
- 支持自定义编辑器的主题,以适应不同项目或网站的设计风格。
-
可扩展性:
- 可以轻松扩展
react-live
的功能,添加自定义渲染器、预处理器等。 - 这使得它能够适应各种需求和项目。
- 可以轻松扩展
使用说明:
-
安装
react-live
:-
运行以下命令安装
react-live
:bashnpm install react-live
-
-
在项目中使用:
-
在你的 React 项目中导入
LiveProvider
和LiveEditor
等组件。jsximport { LiveProvider, LiveEditor, LiveError, LivePreview } from 'react-live';
-
-
使用
LiveProvider
包装代码:-
将你的 React 代码包装在
LiveProvider
组件中。jsx<LiveProvider code="<div>Hello World</div>"> <LiveEditor /> <LiveError /> <LivePreview /> </LiveProvider>
-
-
运行应用:
- 启动你的应用,然后你就可以在页面上看到实时编辑和执行的效果了。
jsx
// 完整的示例
import React from 'react';
import { LiveProvider, LiveEditor, LiveError, LivePreview } from 'react-live';
function LiveCodeExample() {
return (
<LiveProvider code={`<div style={{ color: 'red' }}>Hello World</div>`}>
<LiveEditor />
<LiveError />
<LivePreview />
</LiveProvider>
);
}
export default LiveCodeExample;
通过这种方式,你可以在你的 React 项目中使用 react-live
来展示实时编辑和执行的 React 代码。
那么我们开始将react-live
安装到我们组件的文档网站中来:
- 安装:
pnpm add react-live prism-react-renderer --filter @blankui-org/docs
- 使用(这里只列出一些核心的代码):
-
更改
Code
组件:ts"use client"; import { themes } from "prism-react-renderer"; // import { useTranslations } from "next-intl"; import { LiveProvider, LivePreview, LiveError, LiveEditor } from "react-live"; import * as Components from "@blankui-org/react"; type Props = { code?: string; }; export const scope = { ...Components, } as Record<string, unknown>; console.log(scope); export const Code: React.FC<Props> = ({ code = "<strong>Hello World!</strong>", }) => { // const t = useTranslations("Button"); return ( <LiveProvider code={`const App = () => { return ( <Button primary={true} label="Button" onClick={() => alert("I'm Button")} /> ); } render(<App/>);`} noInline={true} scope={scope} > <LiveEditor theme={themes.oneDark} /> <LiveError /> <LivePreview /> </LiveProvider> ); };
-
这时候页面:
点击Button
按钮也都正常,可以实时修改代码:
所以根据react-live
我们可以为组件文档实现代码示例和在线预览的功能,比如一些很流程的UI框架的官方组件文档都会实现这个功能。
快速创建实例并跳转至codesandbox
@codesandbox/sandpack-react 是 CodeSandbox 团队为 React 项目提供的一个工具库,用于在 CodeSandbox 中构建和运行 React 代码。它通过提供一个基于 Webpack 的构建环境,使得在 CodeSandbox 中编辑和实时预览 React 项目变得更加方便。
以下是 @codesandbox/sandpack-react
的主要特点:
特点:
-
集成 React 环境:
@codesandbox/sandpack-react
提供了一个集成了 React 的构建环境,适用于在 CodeSandbox 中构建 React 项目。
-
实时预览:
- 通过
@codesandbox/sandpack-react
,你可以在 CodeSandbox 中实时预览你的 React 代码。 - 每次编辑都会触发构建和预览,使得你可以立即查看结果。
- 通过
-
支持 JSX 和组件:
- 该工具库支持编辑和展示 JSX 语法的 React 代码。
- 你可以使用 React 组件、状态和其他 React 特性,实现更丰富的交互和演示效果。
-
依赖管理:
@codesandbox/sandpack-react
能够处理项目中的依赖关系,确保所需的库和模块正确引入。
-
自定义配置:
- 你可以通过提供自定义配置,调整构建环境以满足项目的特定需求。
-
用于 CodeSandbox 的适配器:
@codesandbox/sandpack-react
提供了与 CodeSandbox 集成的适配器,使得在 CodeSandbox 中运行 React 项目更为流畅。
还是一样,我们将其集成到项目里面来:
-
安装:
pnpm add @codesandbox/sandpack-react --filter @blankui-org/docs
-
使用:
ts"use client"; import { SandpackProvider, SandpackLayout, SandpackCodeEditor, SandpackPreview, } from "@codesandbox/sandpack-react"; type Props = {}; export const Sandpack: React.FC<Props> = () => { return ( <SandpackProvider customSetup={{ dependencies: { "@blankui-org/react": "^2.0.1", }, entry: "index.tsx", }} files={{ "index.tsx": { code: ` import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import "./styles.css"; ReactDOM.createRoot(document.getElementById("root")).render( <React.StrictMode> <App /> </React.StrictMode> ); `, hidden: true, }, "styles.css":` @import url("node_modules/@blankui-org/react/dist/index.css"); body { font-family: sans-serif; -webkit-font-smoothing: auto; -moz-font-smoothing: auto; -moz-osx-font-smoothing: grayscale; font-smoothing: auto; text-rendering: optimizeLegibility; font-smooth: always; -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none; } h1 { font-size: 1.5rem; } `, "/App.tsx": `import {Button} from "@blankui-org/react"; export default function App() { return ( <Button primary={true} label="Button" onClick={() => alert("I'm Button")} /> ); } `, }} template="vite-react-ts" > <SandpackLayout> <SandpackCodeEditor /> <SandpackPreview /> </SandpackLayout> </SandpackProvider> ); };
button.mdx
:md--- title: "Button" description: "Buttons allow users to perform actions and choose with a single tap." date: 2024-01-12 --- import { buttonContent } from "@/docs/content/button"; # Button Buttons allow users to perform actions and choose with a single tap. ## Usage <Code code={buttonContent.usage} /> ## Sandpack <Sandpack />
页面:
此时我们基本上实现了创建codesandbox
示例的过程了,当然这里只是为了快速提供示例,后续的话代码肯定得重新封装组织一下的。建议是结合react-live
一起,实现一个用户体验更好的文档,后续我会实现一下然后将代码贴出来。
刚刚从其它地方学到的代码,虽然页面没啥变化,但是代码重新封装组织了一下,方便复用,并且现在Code
&Sandpack
可以使用同一个模版文件渲染了:
md
---
title: "Button"
description: "Buttons allow users to perform actions and choose with a single tap."
date: 2024-01-12
---
import { buttonContent } from "@/docs/content/button";
# Button
Buttons allow users to perform actions and choose with a single tap.
## Usage
<Code files={buttonContent.usage} />
## Sandpack
<Sandpack files={buttonContent.usage} />
buttonContent
:
ts
const App = `import {Button} from "@blankui-org/react";
export default function App() {
return (
<Button primary label="Button" />
);
}`;
const react = {
"/App.tsx": App,
};
export default {
...react,
};
这篇文章可能有点长,但是大家只需要简单过一遍即可,因为这里面的内容需要大家根据自己的实际情况来使用。好啦,那就下篇见啦~