使用 Next.js 搭建 Monorepo 组件库文档

news2025/7/8 14:26:09

文章为稀土掘金技术社区首发签约文章,14 天内禁止转载,14 天后未获授权禁止转载,侵权必究!

阅读本文你将:

  • 使用 pnpm 搭建一个 Monorepo 组件库
  • 使用 Next.js 开发一个组件库文档
  • changesets 来管理包的 version 和生成 changelog
  • 使用 vercel 部署在线文档

代码仓库:github.com/maqi1520/ne…

前言

组件化开发是前端的基石,正因为组件化,前端得以百花齐放,百家争鸣。我们每天在项目中都写着各种各样的组件,如果在面试的时候,跟面试官说,你每天的工作是开发组件,那么显然这没有什么优势,如果你说,你开发了一个组件库,并且有一个在线文档可以直接预览,这可能会是你的一个加分项。今天我们就来聊聊组件库的开发,主要是组件库的搭建和文档建设,至于组件数量,那是时间问题,以及你是否有时间维护好这个组件库的问题。

基础组件和业务组件

首先组件库分为基础组件和业务组件,所谓基础组件就是 UI 组件,类似 Ant design,它是单包架构,所有的组件都是在一个包中,一旦其中一个组件有改动,就需要发整包。另外一种是业务组件,组件中包含了一些业务逻辑,它在企业内部是很有必要的。比如飞书文档,包含在线文档,在线 PPT、视频会议等,这些都是独立的产品,单独迭代开发,单独发布,却有一些共同的逻辑,比如没有登录的时候都需要调用一个”登录弹窗“,或者说在项目协同的时候,都需要邀请人员加入,那么需要一个“人员选择组件”, 这就是业务组件。业务组件不同于基础组件,单独安装,依赖发包,而并不是全量发包。那么这些业务组件也需要一个文档,因此我们使用 Monorepo(单仓库管理),这样方便管理和维护。

为什么选用 Next.js 来搭建组件库文档?

组件文档有个特别重要的功能就是“写 markdown 文档,可以看到代码以及运行效果”,这方面有很多优秀的开源库,比如 Ant design 使用的是 bisheng, react use 使用的是 storybook, 还有一些优秀的库,比如:dumi,Docz 等。 本地跑过 Ant design 的同学都知道, Ant design 的启动速度非常慢,因为底层使用的 webpack,要启动开发服务器,必须将所有组件都进行编译,这会对开发者造成一些困扰,因为如果是业务组件的话,开发者只关注单个组件,而不是全部组件。而使用 Next.jz 就有 2 个非常大的优势:

  • 使用 swc 编译,Next.js 中实现了快 3 倍的快速刷新和快 5 倍的构建速度;
  • 按需编译,在开发环境下,只有访问的页面才会进行编译

那么接下来的问题就是:要在 Next.js 中实现 “写 Markdown Example 可预览”的功能,若要自己实现这个功能,确实是一件麻烦的事情。我们换一个思维,组件展示,也就是在 markdown 中运行 react 组件,这不就是 mdx 的功能吗? 而在 Next.js 中可以很方便地集成 MDX。

效果演示  

目前这是一个简易版,只为展示 Next.js 搭建文档

项目初始化

首先我们创建一个 next typescript 作为我们项目的主目录,用于组件库的文档开发

npx create-next-app@latest --ts
复制代码

要想启动 pnpm 的 workspace 功能,需要工程根目录下存在 pnpm-workspace.yaml 配置文件,并且在 pnpm-workspace.yaml 中指定工作空间的目录。比如这里我们所有的子包都是放在 packages 目录下

packages:
  - 'packages/*'
复制代码

接下来,我们在 packages 文件夹下创建三个子项目,分别是:user-select、login 和 utils, 对应用户选择,登录 和工具类。

├── packages
│   ├── user-select
│   ├── login
│   ├── utils
复制代码

user-select 和 login 依赖 utils,我们可以将一些公用方法放到 utils 中。

给每个 package 下面创建 package.json 文件,包名称通常是”@命名空间+包名@“的方式,比如@vite/xx 或@babel/xx,在本例中,这里我们都以@mastack开头

{
  "name": "@mastack/login",
  "version": "1.0.0",
  "description": "",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsc"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
复制代码

给每个 package 安装 typescript

pnpm add typescript -r  -D
复制代码

给每个 package 创建 tsconfig.json 文件

{
  "include": ["src/**/*"],
  "compilerOptions": {
    "jsx": "react",
    "outDir": "dist",
    "target": "ES2020",
    "module": "esnext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "moduleResolution": "node",
    "declaration": true,
    "forceConsistentCasingInFileNames": true
  }
}
复制代码

执行下面代码,往 login 组件中安装 utils;

pnpm i @mastack/utils --filter @mastack/login
复制代码

安装完成后,设置依赖版本的时候推荐用 workspace:*,就可以保持依赖的版本是工作空间里最新版本,不需要每次手动更新依赖版本。

pnpm 提供了 -w--workspace-root 参数,可以将依赖包安装到工程的根目录下,作为所有 package 的公共依赖,这么我们安装 antd

pnpm install antd -w
复制代码

组件开发

我们在 login 组件下,新建一个组件 src/index.tsx

import React, { useState } from "react";
import { Button, Modal } from "antd";

interface Props {
  className: string;
}

export default function Login({ className }: Props) {
  const [open, setopen] = useState(false);
  return (
    <>
      <Button onClick={() => setopen(true)} className={className}>
        登录
      </Button>
      <Modal
        title="登录"
        open={open}
        onCancel={() => setopen(false)}
        onOk={() => setopen(false)}
      >
        <p>登录弹窗</p>
      </Modal>
    </>
  );
}
复制代码

先写一个最简单版本,组件代码并不是最重要的,后续可以再优化。

在package.json 中添加构建命令

"scripts": {
    "build": "tsc"
  }
复制代码

然后在组件目录下执行 yarn build 。此时组件以及可以打包成功!

Next.js 支持 MDX

接下来要让文档支持 MDX,在根目录下执行以下命令,安装 mdx 和 loader 相关包

pnpm add @next/mdx @mdx-js/loader @mdx-js/react -w
复制代码

修改 next.config.js 为以下代码

const withMDX = require('@next/mdx')({
  extension: /\.mdx?$/,
})

module.exports = withMDX({
  pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
  reactStrictMode: true,
  swcMinify: true,
})
复制代码

这样就可以在 Next 中支持 MDX 了。

我们在 src/pages 目录下,新建一个 docs/index.mdx

先写一个简单的 markdown 文件测试下

这样 Next.js 就支持 mdx 文档了。

Next 动态加载 md 文件

接下来,我们要实现动态加载 packages 中的文件 md 文件。新建一个 pages/docs/[...slug].tsx 文件。

export async function getStaticPaths(context: GetStaticPathsContext) {
  return {
    paths: [
      { params: { slug: ["login"] } },
      { params: { slug: ["user-selecter"] } },
    ],
    fallback: false, // SSG 模式
  };
}

export async function getStaticProps({
  params,
}: GetStaticPropsContext<{ slug: string[] }>) {
  const slug = params?.slug.join("/");

  return {
    props: {
      slug,
    }, // 传递给组件的props
  };
}
复制代码

我们使用的是 SSG 模式。上面代码中 getStaticPaths 我先写了 2 条数据,因为我们目前只有 2 个组件,它会在构建的时候会生成静态页面。 getStaticProps函数可以获取 URL 上的参数,我们将 slug 参数传递给组件,然后在 Page 函数中,我们使用 next/dynamic 动态加载 packages 中的 mdx 文件

import React from "react";
import {
  GetStaticPathsContext,
  InferGetServerSidePropsType,
  GetStaticPropsContext,
} from "next";
import dynamic from "next/dynamic";

type Props = InferGetServerSidePropsType<typeof getStaticProps>;

export default function Page({ slug }: Props) {
  const Content = dynamic(() => import(`../packages/${slug}/docs/index.mdx`), {
    ssr: false,
  });

  return (
    <div>
      <Content />
    </div>
  );
}
复制代码

此时我们访问 http://localhost:3000/docs/login 查看效果

在页面上会提示,无法找到@mastack/login 这个包,我们需要在项目的根目录下的 tsconfig.json 中加入别名

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"],
      "@mastack/login": ["packages/login/src"],
      "@mastack/user-select": ["packages/user-select/src"]
    },
  }
}
复制代码

保存后,页面会自动刷新,我们就可以在页面上看到如下效果。

至此文档与 packages 目录下的 mdx 已经打通。修改 packages/login/docs/index.mdx 中的文档,页面会自动热更新。

自定义 mdx 组件

上面代码已经实现了在 md 文档中显示组件和代码,但我们想要的是类似于 ant design 那样的效果,默认代码不展示,点击可以收起和展开,这该怎么实现呢?

我们可以利用 mdx 的自定义组件来实现这个效果。

写 mdx 的时候,在组件 <Login/>和代码外层嵌套一个自定义组件DemoBlock

然后实现一个自定义一个 DemoBlock 组件,提供给 MDXProvider,这样所有的 mdx 文档中,不需要 import 就可以使用组件。

import dynamic from "next/dynamic";
import { MDXProvider } from "@mdx-js/react";

const DemoBlock = ({ children }: any) => {
  console.log(children);
  return null
};

const components = {
  DemoBlock,
};

export default function Page({ slug }: Props) {
  const Content = dynamic(() => import(`packages/${slug}/docs/index.mdx`), {
    ssr: false,
  });

  return (
    <div>
      <MDXProvider components={components}>
        <Content />
      </MDXProvider>
    </div>
  );
}
复制代码

我们先写一个空组件,看下 children 的值。刷新页面, 此时 DemoBlock中的组件和代码不会显示,我们看一下打印出的 children 节点信息;

chilren 为 react 中的 vNode,现在我们就可以根据 type 来判断,返回不同的 jsx,这样就可以实现DemoBlock组件了,代码如下:

import React, { useState } from "react";

const DemoBlock = ({ children }: any) => {
  const [visible, setVisible] = useState(false);

  return (
    <div className="demo-block">
      {children.map((child: any) => {
        if (child.type === "pre") {
          return (
            <div key={child.key}>
              <div
                className="demo-block-button"
                onClick={() => setVisible(!visible)}
              >
                {!visible ? "显示代码" : "收起代码"}
              </div>
              {visible && child}
            </div>
          );
        }
        return child;
      })}
    </div>
  );
};
复制代码

再给组件添加一些样式,给按钮添加一个 svg icon,一起来看下实现效果:

是不是有跟 antd 的 demo block 有些相似了呢? 若要显示更多字段和描述,我们可以修改组件代码,实现完全自定义。

优化文档界面

至此我们的文档,还是有些简陋,我们得优化下文档界面,让我们的界面显示更美观。

  1. 安装并且初始化 tailwindcss
pnpm install -Dw tailwindcss postcss autoprefixer @tailwindcss/typography
pnpx tailwindcss init -p
复制代码

修改 globals.css 为 tailwindcss 默认指令

@tailwind base;
@tailwind components;
@tailwind utilities;
复制代码

修改 tailwind.config.js 配置文件,让我们的应用支持文章默认样式,并且在 md 和 mdx 文件中也可以写 tailwindcss

const defaultTheme = require("tailwindcss/defaultTheme");
const colors = require("tailwindcss/colors");

/** @type {import("tailwindcss").Config } */
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,md,mdx}",
    "./components/**/*.{js,ts,jsx,tsx}",
    "./packages/**/*.{md,mdx}",
  ],
  darkMode: "class",
  plugins: [require("@tailwindcss/typography")],
};
复制代码

在 MDX Content 组件 外层可以加一个 prose class,这样我们的文档就有了默认好看文章样式了。

现在 md 文档功能还很薄弱,我们需要让它强大起来,我们先安装一些 markdown 常用的包

pnpm install remark-gfm remark-footnotes remark-math rehype-katex rehype-slug rehype-autolink-headings rehype-prism-plus -w
复制代码
  • remark-gfm 让 md 支持 GitHub Flavored Markdown (自动超链接链接文字、脚注、删除线、表格、任务列表)

  • remark-math rehype-katex 支持数学公式

  • rehype-slug rehype-autolink-headings 自动给标题加唯一 id

  • rehype-prism-plus 支持代码高亮

修改 next.config.js 为 next.config.mjs,并输入以下代码

// Remark packages
import remarkGfm from "remark-gfm";
import remarkFootnotes from "remark-footnotes";
import remarkMath from "remark-math";
// Rehype packages
import rehypeSlug from "rehype-slug";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypePrismPlus from "rehype-prism-plus";

import nextMDX from "@next/mdx";

const withMDX = nextMDX({
  extension: /\.mdx?$/,
  options: {
    remarkPlugins: [
      remarkMath,
      remarkGfm,
      [remarkFootnotes, { inlineNotes: true }],
    ],
    rehypePlugins: [
      rehypeSlug,
      rehypeAutolinkHeadings,
      [rehypePrismPlus, { ignoreMissing: true }],
    ],
  },
});

export default withMDX({
  pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"],
  reactStrictMode: true,
  swcMinify: true,
});
复制代码

我们在这里可以配置 remarkPlugins 和 rehypePlugins;

markdown 在编译过程中会涉及 3 种 ast 抽象语法树 , remark 负责转换为 mdast,它可以操作 markdown 文件,比如让 markdown 支持更多格式(比如:公式、脚注、任务列表等),需要使用 remark 插件; rehype 负责转换为 hast ,它可以转换 html,比如给 标题加 id,给代码高亮, 这一步是在操作 HTML 后完成的。因此我们也可以自己写插件,具体写什么插件,就要看插件在哪个阶段运行。

最后我们到 github prism-themes 中复制一份代码高亮的样式到我们的 css 文件中,一起来看下效果吧!

发布工作流

workspace 中的包版本管理是一个复杂的任务,pnpm 目前也并未提供内置的解决方案。pnpm 推荐了两个开源的版本控制工具:changesets 和 rush,这里我采用了 changesets 来实现依赖包的管理。

配置

要在 pnpm 工作空间上配置 changesets,请将 changesets 作为开发依赖项安装在工作空间的根目录中:

pnpm add -Dw @changesets/cli
复制代码

然后 changesets 的初始化命令:

pnpm changeset init
复制代码

添加新的 changesets

要生成新的 changesets,请在仓库的根目录中执行pnpm changeset。 .changeset 目录中生成的 markdown 文件需要被提交到到仓库。

发布变更

为了方便所有包的发布过程,在工程根目录下的 pacakge.json 的 scripts 中增加如下几条脚本:

"compile": "pnpm --filter=@mastack/* run build",
"pub": "pnpm compile && pnpm --recursive --registry https://registry.npmjs.org/ publish --access public"
复制代码

编译阶段,生成构建产物

  1. 运行pnpm changeset version。 这将提高先前使用 pnpm changeset (以及它们的任何依赖项)的版本,并更新变更日志文件。
  2. 运行 pnpm install。 这将更新锁文件并重新构建包。
  3. 提交更改。
  4. 运行 pnpm pub。 此命令将发布所有包含被更新版本且尚未出现在包注册源中的包。

部署

部署可以选择 gitbub pages 或者 vercel 部署,他们都是免费的,Github pages 只支持静态网站,vercel 支持动态网站,它会将 nextjs page 中,单独部署成函数的形式。我这里选择使用 vercel,因为它的访问速度相对比 gitbub pages 要快很多。只需要使用 github 账号登录 vercel.com/ 导入项目,便会自动部署,而且会自动分配一个 xxx.vercel.app/ 二级域名。

也可以使用命令行工具,在项目跟目录下执行,根据提示,选择默认即可

npx vercel
复制代码

预览地址:nextjs-components-docs.vercel.app/

小结

本文,我们从零开始,使用 Next.js 和 pnpm 搭建了一个组件库文档,主要使用 Next.js 动态导入功能解决了开发服务缓慢的问题,使用 Next.js 的 SSG 模式来生成静态文档。最后我们使用 changesets 来管理包的 version 和生成 changelog。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/37115.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux进程总结详解(上——初识)

Linux进程 文章目录Linux进程前言——先从硬件和软件谈起一、冯诺依曼体系结构二、操作系统概念一、进程介绍概念进程控制块查看进程通过系统创建进程二、进程状态1.内核代码如下&#xff1a;总结前言——先从硬件和软件谈起 一、冯诺依曼体系结构 定义&#xff1a;数学家冯诺…

【数据结构】二叉树的遍历

目录☀️二叉树的构建☀️二叉树的遍历&#x1f33b;前序遍历&#x1f33b;中序遍历&#x1f33b;后序遍历☀️完整代码展示☀️二叉树的构建 便于理解二叉树的遍历&#xff0c;这里我们手动简单构建一个二叉树&#xff0c;当然&#xff0c;此处二叉树的构建并不是真正二叉树的…

C++类与对象(一)

目录 一、面向过程和面向对象认识 二、类的引入 三、类的定义 类的两种定义方式&#xff1a; 四、类的访问限定符及封装 4.1 访问限定符 4.2 封装 五、类的作用域 六、类的实例化 七、类对象模型 7.1 如何计算类对象的大小​​​​​ 7.2 类对象的存储方式 7.3 结…

SpringBoot+Mybaits搭建通用管理系统实例十一:数据缓存功能实现

一、本章内容 使用ehcache实现系统缓存功能,并配置实现mybatis的二级缓存,自定义分页缓存的key,识别实体类型,并根据实体属性的配置,组合生成key值用于标识缓存数据。 完整课程地址二、开发视频 SpringBoot+Mybaits搭建通用管理系统实例三:缓存管理功能实现 三、缓存配置 …

滚动吧,数字

朋友有个需求关于金币滚动效果&#xff0c;网上也有很多教程&#xff1b;但多不太符合他的需求&#xff0c;所有利用空余时间帮他做了一个通用组件。 效果图如下: 1、按照次数和速度进行&#xff0c;对应的滚动效果。 2、缓动效果可以根据自己的情况进行修改。 项目地址&#x…

windows安装VMware虚拟机(附带CentOS7部署)

软件下载 链接&#xff1a;https://pan.baidu.com/s/1Vw2Bilf9uf-EYR6_MR86aA?pwdd2qr 提取码&#xff1a;d2qr VMware安装 通你上述链接下载VMware安装包&#xff0c;没有特别选项&#xff0c;选安装位置无脑下一步安装&#xff0c;安装完成后会提示你输入激活码&#xf…

Java中的OpenCV-图像处理

我们将在本文中介绍以下高级图像处理操作&#xff1a;Canny 边缘检测轮廓和形状识别Canny 边缘检测&#xff1a;Canny 边缘检测是一种流行的边缘检测算法。它是由 John F. Canny 在 1986 年开发的。它是一个多阶段算法&#xff0c;我们将按如下方式经历每个阶段&#xff1a;噪声…

Java与GitLab OpenAPI交互

通过Gitlab Open api代码来操作代码的合并及关闭&#xff0c;项目的模板生成........ 方式一&#xff1a; 使用java-gitlab-api(推荐) 接口文档Java Gitlab API Documentation <dependency> <groupId>org.gitlab</groupId> <art…

49 多个 classloader 加载的同类限定名的Class 在 jhat 中显示不全

前言 呵呵 这是在之前 排查一个 flink 的相关问题的时候 发现的一个问题 flink 默认的 job 隔离是基于 Classloader 来进行隔离的 直到 最近才有时间来看一下 这个问题的原因, 究其代码 也还是比较容易找到 大致记录一下 以下内容, 截图 基于 jdk8 测试用例 /*** …

要闻 | 人大金仓重磅亮相2022南京软博会

“软件赋能 数智转型”&#xff0c;2022中国&#xff08;南京&#xff09;国际软件产品和信息服务交易博览会&#xff08;下称“南京软博会”&#xff09;于11月23至25日顺利举行。人大金仓重磅亮相本次展会&#xff0c;并受邀出席同期召开的2022中国&#xff08;南京&#xff…

【LeetCode每日一题】——37.解数独

文章目录一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【题目提示】八【时间频度】九【代码实现】十【提交结果】一【题目类别】 数组 二【题目难度】 困难 三【题目编号】 37.解数独 四【题目描述】 编写一个程序&#xff…

抢先看!阿里发布2023最新版分布式核心小册,GitHub标星破已千万

什么是分布式 一个分布式系统你可以看做是一组计算机系统一起工作&#xff0c;而在终端用户的视角看过去&#xff0c;就像一台计算机在工作一样 。 这组一起工作的计算机&#xff0c;拥有共享的状态 &#xff0c;他们同时运行&#xff0c;独立机器的故障不会影响整个系统的正常…

1、认识时间复杂度和简单的排序算法

目录时间复杂度选择排序冒泡排序异或交换解释案例综上插入排序二分查找拓展对数器时间复杂度 如果一个操作时间和数据量没有关系&#xff0c;则是常数时间的操作 比如一个数组arr[n]这就是算一个偏移量&#xff0c;然后找到这个位置的值&#xff0c;这就是常数时间&#xff0c…

力扣(LeetCode)882. 细分图中的可到达节点(C++)

spfa 将边细分成节点&#xff0c;如果一条边细分出 nnn 个结点&#xff0c;那么边的两个端点距离 n1n1n1 &#xff0c;边长 n1n1n1。提示中给出&#xff0c;一共 300030003000 个初始结点&#xff0c;无向边的数目小于等于 100001000010000 &#xff0c;这是提示我们最多有 20…

服务器怎么远程连接控制

服务器怎么远程连接控制 我是艾西&#xff0c;还是有很多小白同学问我服务器怎么远程连接。那么今天我们重点来教教大家如何用电脑远程服务器配上图文教程&#xff0c;让不懂的新手小白一看就会&#xff0c;分分钟上手教程 远程服务器需要一台电脑俗称“PC”就是我们自己平时经…

红外遥控视力自动检测系统的设计与实现

红外线视力检测系统的设计与实现 实现方法 分为两部分 上位机-------------串口通讯-------------下位机 上位机&#xff1a;使用LabVIEW软件编写窗口软件。 串口通讯&#xff1a;USB线进行链接通讯。 下位机&#xff1a;AT89C52单片机开发板 说明&#xff1a;Labvi…

46-pytest-分布式插件pytest-xdist使用

分布式插件pytest-xdist使用前言安装插件分布式用例设计原则使用示例测试报告按一定顺序执行前言 本篇来学习下在pytest中分布式执行测试用例&#xff0c;以节省测试时间。 安装插件 pip install pytest-xdist分布式用例设计原则 用例之间是独立的&#xff0c;用例之间没有…

学生护眼台灯哪个牌子最好?2022双十二4款儿童护眼写字灯推荐

现在的孩子学业压力越来越大了&#xff0c;眼睛也是长期高强度使用&#xff0c;不堪重负&#xff0c;如果不注意保护&#xff0c;加上年龄偏小身体器官尚未发育完全&#xff0c;极易出现各种眼睛问题。所以对于经常晚上看书写字的学生群体来说&#xff0c;有一个健康护眼的照明…

OpenGL ES 学习(二) -- 渲染模式和GLSL

上一章&#xff0c;我们学习了 OpenGL 的基本知识&#xff0c;这一章&#xff0c;一起学习OpenGL的渲染模式和渲染语言GLSL。 一. 渲染流程 OpenGL的渲染流程如下图所示&#xff1a; 从这里看出&#xff0c;OpenGL 需要使用顶点着色器&#xff0c;先绘制好轮廓&#xff0c;再…

Java面向对象之——多态

文章目录一、多态的概念二、多态的条件三、重写四、向上转型和向下转型1、向上转型2、向下转型五、再谈多态六、多态的优缺点总结一、多态的概念 多态是同一个行为具有多个不同表现形式或形态的能力。就比如人吃饭&#xff0c;对于中国人使用筷子吃饭&#xff0c;美国人使用刀…