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

sxkk20082年前知识分享200

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

阅读本文你将:

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

代码仓库:https://github.com/maqi1520/nextjs-components-docs

前言

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

基础组件和业务组件

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

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

组件文档有个特别重要的功能就是“写 markdown 文档,可以看到代码以及运行效果”,这方面有很多优秀的开源库,比如 Ant design 使用的是 bisheng, react use 使用的是 storybook, 还有一些优秀的库,比如:dumiDocz 等。 本地跑过 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 信息

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

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 查看效果

Next.js 编译报错

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

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

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

Next.js  动态加载 mdx

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

自定义 mdx 组件

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

ant design 代码块

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

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

markdown 信息

然后实现一个自定义一个 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 节点信息;

 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,一起来看下实现效果:

组件文档 demo 效果

是不是有跟 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.jsnext.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 账号登录 https://vercel.com/ 导入项目,便会自动部署,而且会自动分配一个 https://xxx.vercel.app/ 二级域名。

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

npx vercel

vercel 部署

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

小结

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

好了,以上就是本文的全部内容,你学会了吗?接下来我将继续分享 Next.js 相关的实战文章,欢迎各位关注我的《 Next.js 全栈开发实战》 专栏,感谢您的阅读。

相关文章

AI技术广泛应用-从医疗到金融的探索与挑战

AI技术广泛应用-从医疗到金融的探索与挑战

  近年来,随着技术的不断进步,人工智能(AI)逐渐实现了从科幻梦想到现实应用的转变。尤其是在医疗、金融等领域,AI技术正在广泛应用,为人类创造了更多的可持续发展和公平共享的...

语音转文字API:提供高效便捷的语音转文字服务

语音转文字API:提供高效便捷的语音转文字服务

  随着科技的不断发展,人们对于语音转换为文字的需求也越来越多。在传统的应用中,我们通常需要借助于键盘输入或手写输入来将语音转化为文字。然而,随着语音转文字API的出现,这个...

虚拟主持:让事件更加生动和互动的必备工具

虚拟主持:让事件更加生动和互动的必备工具

  现代科技的快速发展为各行各业带来了前所未有的创新与变革,而虚拟主持作为其中之一,正逐渐成为各类活动和节目中不可或缺的重要角色。虚拟主持能够通过与观众的互动,提升活动的效益...

人脸融合在线生成器:独特而有趣的创作工具

人脸融合在线生成器:独特而有趣的创作工具

  人脸融合在线生成器为用户提供了一种创意拍摄和娱乐的新方式。这个创新的工具通过调整和合成不同人脸的特征,使人们能够创造出令人惊叹的图像和视频。这篇文章将探讨人脸融合在线生成...

其实 devtool.tech 里面的每个工具都挺好用的。

HTML 转 markdown 是如何实现?

其实有个包

HTML 转 Markdown 如此简单

前言现在好的技术文章非常多,每天各种技术群里,各种技术社区,有很多质量非常好的技术文章,比如 CSDN,掘金、微信公众号等, 于是我们就收藏了,收藏等于学会。可是问题来了,我们收藏到哪呢? CSDN...

如果你会 TailwindCSS 我推荐 VSCODE 安装 这个插件tailwind-snippets 可以快速帮我们来发出一个常用的代码片段,大家可以在 https://www.tailwindsnippets.ml/snippets 查看效果,快速实现我们的 html 页面

tailwind-snippets 预览

部署

Vercel

Next.js 开发商 Vercel 获得最近 1.5 亿美元 D 轮融资。Vercel 注册什么的我就不讲了,建议使用GitHub 登录, 点击new project创建一个项目,这个项目可以从自己的 GitHub 库导入或者选择 Vercel 给的模板,Vercel 给的模板(下图)首先也会导入进自己的 GitHub 库,总之要先把内容导入进 GitHub 库才行。

Vercel 支持的框架

Vercel 为个人用户提供了

  1. 自动 HTTPS/SSL
  2. 带宽 100 GB
  3. 并发构建,每天 10 万次调用
  4. Serverless Function

所以 Vercel 不光支持静态网站也支持 nodejs 动态网站,如果想要其他后端语言

可以选择 heroku

heroku

Heroku 是一个支持多种编程语言的云平台,并且提供了 Heroku PostgresHeroku RedisApache Kafka on Heroku

Heroku 支持的语言

Heroku 虽然提供了比较全面的编程语言和数据库支持,免费用户还支持

  1. 使用 Git 和 Docker 部署
  2. 自定义二级域名
  3. 容器编排
  4. 自动操作系统补丁

但 heroku 对国内用户支持不是很友好,第一点访问国内速度比不上 Vercel, 第二点 163 和 QQ 邮箱都不能注册,想要注册得要其他邮箱, 第三没有免费的 ssl。第四项目源代码只能有 500M。

数据库选择

MongoDB

选择 https://cloud.mongodb.com/

mongodb 首页截图

创建 database 的时候选择 free;

选择免费截图 地域可以选择日本或者新加坡。

接着创建一个用户 创建一个用户 密码是自动生成的,要把密码拷贝下来

接着要创建一个允许链接的 IP 地址

在 mongodb.com 设置允许链接的IP

如何白嫖一个动态网站

前言我们知道,想要搭建一个网站往往需要一下几个步骤:域名注册服务器购买数据库购买或部署网站设计网站开发网站备案网站上线在国内上线一个网站,域名还必须得备案,光是域名备案的话还的几个星期,整个流程下来,...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。