这里有从零开始构建现代化前端UI组件库所需要的一切(一)

1,991 阅读18分钟

前言

在现代前端开发中,构建一个强大而现代化的UI组件库是许多开发者追求的目标之一。这不仅是技术的展示,更是一项对用户体验和开发效率的投资。从零开始搭建一个UI组件库项目架构,可能看似繁琐,但正是这个过程,让我们深刻理解了组件化、可维护性和可扩展性的重要性。本文将带领你探索这个激动人心的旅程,揭示构建现代化前端UI组件库所需的一切。

我们将聚焦于工程结构的构建,从目录组织到构建工具的选择。通过优化工程结构,我们将为组件库打下坚实的技术基础,提高可维护性和扩展性。

在进入开发流程的优化阶段,我们将深入研究自动化测试、文档生成和版本控制策略等关键技术。这些步骤将确保组件库在不断演进的同时保持高质量,为用户提供稳定可靠的技术体验。

最后,我们将关注组件库的发布和维护,涉及到技术选型和持续集成。这将使你的UI组件库能够适应迅速变化的前端技术和用户需求。

无论是对初学者还是经验丰富的开发者,希望本文能够为你提供一些实用的指导和深入的见解,帮助你成功构建出你所需要的一切的现代化前端UI组件库。


我会新建一个项目,大家会跟着我一起,从项目的雏形开始逐步构建出一个现代化的UI组件库(基于React框架和Typescript)。为了方便大家的访问和参与,项目的代码将会被托管在我的GitHub仓库中:1111mp/blankui。你可以随时访问这个仓库,查看项目的源代码、提交问题和提出建议。

在这个GitHub仓库中,我将使用清晰的commit提交策略,本文的每个节点内容都将对应上一个commit提交。你可以通过查看这些提交历史,了解本文的发展历程和每次更新的内容。

那么文本正式开始。

项目组织策略和构建工具

首先先介绍一下一些常见的代码管理和项目组织策略:

  1. Monorepo(单一仓库): 将多个项目、应用或库的代码存储在同一个版本控制仓库中。这种方式适用于大型项目或组织,有助于共享代码、统一版本控制和简化依赖管理。
  2. Multirepo(多仓库): 将不同的项目或服务分别存储在独立的版本控制仓库中。每个仓库都有独立的开发、构建和发布流程。这种方式适用于较小的项目或团队,以及需要独立部署和迭代的微服务架构。
  3. Microfrontend(微前端): 将前端应用拆分为小的、可独立开发和部署的微前端。每个微前端可以有自己的代码仓库和构建流程,以实现更灵活的团队协作和独立部署。
  4. Hybrid Approach(混合方式): 结合Monorepo和Multirepo的优势,采用混合方式。例如,将共享的代码和库放置在Monorepo中,而将独立的应用或服务存储在独立的仓库中。这种方式可以在团队需要独立开发和发布某些部分时提供灵活性。
  5. Library Repo(库仓库): 将共享的工具库、组件库或功能库存储在独立的仓库中,供多个项目引用。这样可以更好地管理和维护可复用的代码。
  6. Git Submodules(Git子模块): 使用Git的子模块功能,将其他仓库嵌套在主项目中。这种方式允许你在主项目中引用其他独立的仓库,并在需要时更新子模块。但需要注意,Git子模块可能引入一些复杂性。

当然每种项目组织策略都有其适用的场景和优势,选择最合适的方式取决于项目的具体需求、团队的工作流程和开发团队的规模。在做出决策时,考虑项目的长期发展、维护成本和团队的协作效率是很重要的。

因为我们的场景是构建一个前端UI组件库,所以选择 Monorepo 是最合适的,原因有如下几点:

  1. 代码共享和一致性: 在Monorepo中,所有组件、文档和测试代码都在同一个仓库中,使得代码共享更为方便。团队成员可以轻松查看和修改所有组件,保证了一致性和统一的开发体验。
  2. 统一版本管理: 使用Monorepo可以实现统一的版本管理,确保所有组件的版本号一致。这对于维护和升级大型UI组件库至关重要,避免了不同组件之间的依赖版本不一致的问题。
  3. 构建和测试优化: 在Monorepo中,可以实现整体的构建和测试优化。通过使用现代构建工具,如Webpack或者其他专门支持Monorepo的工具,可以加速构建过程,提高整体开发效率。
  4. 便于文档管理: UI组件库通常需要详细的文档,包括组件使用说明、API文档等。在Monorepo中,可以更方便地管理和更新文档,确保文档与实际代码的同步。
  5. 一体化的工作流程Monorepo使得整体的工作流程更为一体化。开发者可以在一个仓库中完成组件的开发、测试、文档更新和版本发布,减少了跨仓库的协作成本。
这里简单补充了一些 Monorepo 的相关概念:

Monorepo(单一仓库)是一种项目组织和版本控制的策略,它将多个相关的项目、应用或库的代码存储在同一个版本控制仓库中。这种方式的目的是通过集中管理所有相关代码,提高代码的共享、一致性和可维护性。以下是Monorepo的一些关键特点和优势:

  1. 统一版本控制: 所有相关的项目、应用或库都受到相同的版本控制管理,使得它们可以共享代码、统一版本号,并确保依赖关系的一致性。
  2. 代码共享: Monorepo使得不同部分的代码可以在同一个仓库中方便地进行共享。这对于共享工具库、组件库、公共配置等方面特别有用,减少了代码的冗余。
  3. 一致性和可维护性: 通过将所有相关项目放在一个仓库中,可以更容易保持代码的一致性。一致的代码风格、开发规范和工作流程有助于项目的可维护性。
  4. 便于跨项目协作: 开发者可以在同一个仓库中协同开发不同的项目,而不需要频繁地切换仓库。这降低了协作的复杂性,减少了跨项目协作的摩擦。
  5. 更好的工程结构: Monorepo通常具有更好的工程结构,允许将相关的代码组织在一起,便于导航和维护。可以使用目录结构、命名空间等方式来组织代码。
  6. 集中化的配置: Monorepo使得项目的配置可以集中管理,包括构建配置、测试配置等。这有助于确保整个项目中使用统一的工具和配置。
  7. 简化依赖管理: 在Monorepo中,依赖关系可以更容易地管理。共享的模块或库可以通过相对路径进行引用,避免了复杂的依赖解析问题。
  8. 整体性的构建和测试: Monorepo允许对整个仓库进行一体化的构建和测试。这有助于提高整体项目的稳定性和一致性。

选择好项目的组织策略之后,我们再确定好对应的项目构建工具,在Monorepo中常见的构建工具有:

  1. Lerna: Lerna是一个专为管理JavaScript项目的Monorepo而设计的工具。它提供了一组命令,用于管理多包仓库中的依赖、发布版本、运行脚本等。Lerna还支持对仓库进行版本控制,并可以将仓库中的每个包发布为独立的npm包。
  2. Bazel: Bazel是一个由Google开发的构建和测试工具,支持Monorepo结构。它可以用于构建各种语言的项目,并具有高度的可扩展性和并行构建的能力。Bazel适用于大型和复杂的代码库。
  3. Nx: Nx是一个由Nrwl(Angular团队的一部分)开发的工具,用于构建和维护Monorepo。它支持多种前端框架(如Angular、React、Vue等)和后端技术,并提供了一套命令行工具来管理项目和依赖关系。
  4. Rush Stack: Rush Stack是一组工具,由Microsoft开发,用于支持Monorepo结构的TypeScript项目。它包括Rush(Monorepo管理工具)和Pnpm(一种快速的包管理器),可用于管理多个包的依赖和构建。
  5. Turborepo: Turborepo是由Vercel开发的Monorepo管理工具,旨在优化大型代码库的构建和开发过程。它使用分布式缓存、智能构建和并行构建等技术以提高构建效率。
  6. Lage: Lage 是一个专注于 TypeScript 项目的构建工具。它采用了类似 Lerna 的 Monorepo 策略,但着重于 TypeScript 项目的构建。Lage 具有并行构建和缓存支持等特性,可以提高构建效率。对于 TypeScript 项目的 Monorepo 管理,Lage 可能是一个有效的选择。
  7. Gradle Build Tool: Gradle 是一种强大的构建工具,支持多语言项目和多模块项目的构建。它可以用于管理和构建 Monorepo 结构的项目。Gradle 具有灵活的配置语法和丰富的插件生态系统,使得可以针对不同项目和需求进行定制。Gradle 适用于 Java、Kotlin、Groovy 等语言的项目。
  8. Yarn Workspaces: Yarn Workspaces是Yarn包管理器的一个功能,用于支持Monorepo结构。它允许你在单个仓库中管理多个项目,并通过yarn workspace命令执行命令、安装依赖等操作。
  9. pnpm Workspaces: pnpm是一种快速、节省磁盘空间的包管理器,特别适用于Monorepo场景。它可以在Monorepo中共享依赖,并提供更快的安装和构建速度。

这里我们选择Turborepo&pnpm,因为它们的不仅简单易于上手,并且在性能方面的表现实在太出色了,爱不释手。各自的使用方式这里就不赘述了,如果没接触过可以点击链接查看其官方文档。

当然这里是一些具体的原因:
  1. Turborepo
    • 分布式缓存和智能构建: Turborepo 提供了分布式缓存和智能构建算法,能够加速大型Monorepo的构建过程。只构建发生更改的文件及其依赖项,从而减少了构建时间。
    • 并行构建: Turborepo 支持并行构建,允许多个构建任务同时执行,提高构建的效率。
    • Facebook内部使用: Turborepo 是由Facebook开发的,它在Facebook内部得到了广泛的应用和验证,因此可能适用于类似规模和需求的项目。
  2. pnpm
    • 快速的包管理: pnpm 是一种快速、节省磁盘空间的包管理器。它通过符号链接共享依赖,减少了重复下载,提高了安装和构建的速度。
    • 支持Monorepo场景: pnpm 特别适用于Monorepo场景,可以在Monorepo中共享依赖,避免了重复下载相同的包。
    • 良好的兼容性: pnpm 兼容npm包管理器的大部分功能,易于集成到已有的项目中。它还提供了类似npm的命令行接口,降低了迁移成本。

项目的组织策略和构建工具确定之后,我们开始创建项目:

  1. 本地创建文件夹并命名为:blankuicd blankui切换到该目录下;

  2. 通过pnpm init创建package.json文件;

  3. 再创建packages文件夹,组件会放在这个文件夹下;

  4. 然后新建pnpm-workspace.yaml文件,用来管理子项目:

    packages:
      - "packages/**/**"
    
  5. pnpm add typescript turbo react react-dom @types/react @types/react-dom -D -w 会在根项目安装相关依赖;

  6. 创建tsconfig.json&turbo.json文件(如果不清楚turbo.json文件的作用,强烈建议先根据官方的这个教程熟悉一下:Turborepo create-new

    turbo.json

    {
      "$schema": "https://turbo.build/schema.json",
      "globalDependencies": ["tsconfig.json"],
      "pipeline": {
        "build": {
          "dependsOn": ["^build"],
          "outputs": [".next/**", "dist/**", "!.next/cache/**"]
        },
        "dev": {
          "cache": false,
          "persistent": true
        },
        "lint": {
          "dependsOn": ["^lint"]
        }
      }
    }
    

    这里稍微解释一下,通过配置pipelineturbo注册了三个taskbuilddevlint),然后通过turbo run <task>运行命令的时候,它会找到每个子项目package.json文件中scripts配置的对应的builddevlint命令并执行,如果没配置就跳过不需要执行。

  7. packages目录下创建components/button/文件夹,这里以button组件为例,并初始化为一个子项目(后续我们的子项目都以 @blankui-org/button前缀方式命名,package.jsonname属性);

这时我们的项目的目录结构为:

|-- blankui
    |-- .gitignore
    |-- LICENSE
    |-- README.md
    |-- package.json
    |-- pnpm-lock.yaml
    |-- pnpm-workspace.yaml
    |-- tsconfig.json
    |-- turbo.json
    |-- packages
        |-- components
            |-- button
                |-- package.json
                |-- src
                    |-- button.css
                    |-- button.tsx
                    |-- index.ts
// package.json
{
  "name": "blankui",
  "version": "1.0.0",
  "description": "Here's everything you need to build a modern Web UI component library from scratch.",
  "keywords": [
    "blankui",
    "monorepe"
  ],
  "author": "",
  "license": "MIT",
  "scripts": {
    "dev": "turbo dev",
    "build": "turbo build",
    "lint": "turbo lint"
  },
  "devDependencies": {
    "@types/react": "^18.2.47",
    "@types/react-dom": "^18.2.18",
    "turbo": "^1.11.3",
    "typescript": "^5.3.3"
  }
}

// packages/component/button/package.json
{
  "name": "@blankui-org/button",
  "version": "1.0.0",
  "description": "Buttons allow users to perform actions and choose with a single tap.",
  "keywords": [
    "button"
  ],
  "author": "",
  "license": "MIT",
  "main": "src/index.ts",
  "scripts": {
    "dev": "echo \"message from @blankui-org/button\" && exit 0"
  },
  "peerDependencies": {
    "react": ">=18",
    "react-dom": ">=18"
  }
}

此时我们在终端运行pnpm dev得到如下输出:

> blankui@1.0.0 dev /Users/******/Documents/public/blankui
> turbo dev

• Packages in scope: @blankui-org/button
• Running dev in 1 packages
• Remote caching disabled
@blankui-org/button:dev: cache bypass, force executing 45139f8091d18fc8
@blankui-org/button:dev: 
@blankui-org/button:dev: > @blankui-org/button@1.0.0 dev /Users/******/Documents/public/blankui/packages/components/button
@blankui-org/button:dev: > echo "message from @blankui-org/button" && exit 0
@blankui-org/button:dev: 
@blankui-org/button:dev: message from @blankui-org/button

 Tasks:    1 successful, 1 total
Cached:    0 cached, 1 total
  Time:    262ms 

此时我们的项目在根目录下可以管理执行子项目的命令,并且子项目可以共用根目录下的依赖包。

到这里为止的源代码:commit 2ac56aa

为组件提供开发和测试界面

项目已经有了基本的结构,并拥有一个最初版本的Button组件。然而,这个组件是根据我们的经验和感觉编写的,其在实际页面中的表现和是否存在bug等情况都尚不明确。现在我们需要搭建一个本地的开发环境,以便为这个组件提供开发和测试的页面,确保其在真实场景中能够正常运行并满足预期。这样做相当于将骡子牵出来遛遛,验证组件的质量和功能。

虽然可以利用工具如Webpack等来搭建本地开发环境,但要实现一个全面且实用的组件开发和测试界面系统,涉及的工作量颇为庞大。在这种情况下,Storybook 就成为了一个极为便捷的解决方案。

Storybook为我们提供了一个专业的组件开发环境,可以快速、可视化地开发、测试和文档化组件。通过Storybook,我们可以创建各种“故事”来展示和验证组件在不同状态和使用场景下的表现,实现了一站式的组件开发和测试。其优势包括:

  1. 快速搭建: Storybook的配置简单,可以迅速集成到项目中,而无需耗费大量时间进行配置。
  2. 交互式开发: 提供了交互式的开发环境,方便开发者通过UI控制组件的各种状态和属性,实时查看效果。
  3. 自动文档生成: Storybook自动生成组件的文档,包括示例代码和props说明,使文档与代码同步更新。
  4. 支持多框架: 支持多种主流前端框架,包括React、Vue、Angular等,适应不同技术栈的项目。
  5. 丰富的插件生态: 具有丰富的插件生态系统,可以扩展其功能,例如添加主题、样式检查、自动化测试等。

通过Storybook,我们能够更加专注于组件的开发和测试,提高了开发效率,同时确保了组件在真实应用中的稳定性和一致性。

所以我们接着继续:

  1. 新建packages/storybook/目录,pnpm init创建package.json文件;

    // packages/storybook/package.json
    {
      "name": "@blankui-org/storybook",
      "version": "1.0.0",
      "description": "The BlankUI components development and testing interface system",
      "author": "",
      "keywords": [
        "storybook"
      ],
      "license": "MIT",
      "scripts": {}
    }
    
  2. 根据 Storybook get-started 文档,将其集成到项目中来:

    • pnpm add react react-dom storybook @storybook/blocks ... -D --filter @blankui-org/storybook,安装Storybook的相关依赖,选择Vite作为开发服务器的构建工具;
    // packages/storybook/package.json
    // ...
    "devDependencies": {
        "@storybook/addon-a11y": "^7.6.7",
        "@storybook/addon-essentials": "^7.6.7",
        "@storybook/addon-interactions": "^7.6.7",
        "@storybook/addon-links": "^7.6.7",
        "@storybook/addon-onboarding": "^1.0.10",
        "@storybook/blocks": "^7.6.7",
        "@storybook/react": "^7.6.7",
        "@storybook/react-vite": "^7.6.7",
        "@storybook/test": "^7.6.7",
        "@storybook/theming": "^7.6.7",
        "react": "^18.2.0",
        "react-dom": "^18.2.0",
        "storybook": "^7.6.7",
        "storybook-dark-mode": "^3.0.3"
      }
    // ...
    
    • packages/storybok/目录下创建.storybook/文件夹,里面放置Storybook相关的配置信息;
    • .storybook/目录下新建如下几个文件:
    |-- .storybook
        |-- main.ts
        |-- preview.ts
        |-- welcome.stories.mdx
    
    // main.ts
    import { join, dirname } from "path";
    
    import type { StorybookConfig } from "@storybook/react-vite";
    
    /**
     * This function is used to resolve the absolute path of a package.
     * It is needed in projects that use Yarn PnP or are set up within a monorepo.
     */
    function getAbsolutePath(value) {
      return dirname(require.resolve(join(value, "package.json")));
    }
    
    const config: StorybookConfig = {
      stories: [
        "./welcome.stories.mdx",
        // 匹配 packages/components/ 下所有组件的 stories
        "../../components/**/stories/**/*.stories.@(js|jsx|ts|tsx)",
      ],
      addons: [
        // 一些插件 具体功能可去官网查看 这里就不赘述了
        getAbsolutePath("storybook-dark-mode"),
        getAbsolutePath("@storybook/addon-a11y"),
        getAbsolutePath("@storybook/addon-links"),
        getAbsolutePath("@storybook/addon-essentials"),
        getAbsolutePath("@storybook/addon-onboarding"),
        getAbsolutePath("@storybook/addon-interactions"),
      ],
      framework: {
        name: getAbsolutePath("@storybook/react-vite") as "@storybook/react-vite",
        options: {},
      },
      docs: {
        autodocs: "tag",
      },
      // packages/storybook/ 目录下新建一个 public/ 目录 所以静态文件服务的路径
      staticDirs: ["../public"],
    };
    export default config;
    
    // preview.ts
    import { themes } from "@storybook/theming";
    
    import type { Preview } from "@storybook/react";
    
    const preview: Preview = {
      parameters: {
        actions: { argTypesRegex: "^on[A-Z].*" },
        controls: {
          matchers: {
            color: /(background|color)$/i,
            date: /Date$/i,
          },
        },
        // 主题相关的配置
        darkMode: {
          current: "dark",
          stylePreview: true,
          darkClass: "dark",
          lightClass: "light",
          classTarget: "html",
          dark: {
            ...themes.dark,
            appBg: "#161616",
            barBg: "black",
            background: "black",
            appContentBg: "black",
            appBorderRadius: 14,
          },
          light: {
            ...themes.light,
            appBorderRadius: 14,
          },
        },
      },
    };
    
    export default preview;
    

    welcome.stories.mdx

    import { Meta } from "@storybook/blocks";
    
    <Meta title="Welcome" />
    
    <br />
    
    # Welcome 👋🏼
    
    <br />
    
    ## BlankUI Version 1 - Design system
    
    <br />
    
  3. 更改packages/storybook/package.json文件,添加启动命令:

    // ...
    "scripts": {
        "dev": "storybook dev -h 127.0.0.1 -p 6006",
        "build": "storybook build",
        "start": "pnpm dlx http-server storybook-static"
      },
    // ...
    

此时在终端中输入pnpm devStorybook服务会成功运行并自动在浏览器打开页面:

Screenshot 2024-01-13 at 13.02.34.png

此时因为我们还没有为Button组件添加stories,所以我们只有一个Welcome的首页。

接下来我们来到packages/components/button/目录下,新建stories/button.stories.tsx文件,并为Button组件添加几个stories

import type { Meta, StoryObj } from '@storybook/react';
// 这里请注意 ts 会抱怨😣找不到 '@storybook/react' module
// 需要在根目录下安装 "pnpm add @storybook/react -D -w"
// 这样所有的组件都可以共用该 module 了

import { Button } from '../src';

// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = {
  // 同时定义了页面左侧菜单
  title: 'Components/Button',
  component: Button,
  parameters: {
    // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
    layout: 'centered',
  },
  // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
  tags: ['autodocs'],
  // More on argTypes: https://storybook.js.org/docs/api/argtypes
  argTypes: {
    backgroundColor: { control: 'color' },
  },
} satisfies Meta<typeof Button>;

export default meta;
type Story = StoryObj<typeof meta>;

// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
// args 会作为 props 传递给组件
// Story的名会作为菜单的名称 自动首字母大写并按照大写字母拆分 UseAsButton => Use As Button
export const Primary: Story = {
  args: {
    primary: true,
    label: 'Button',
  },
};

export const Secondary: Story = {
  args: {
    label: 'Button',
  },
};

export const Large: Story = {
  args: {
    size: 'large',
    label: 'Button',
  },
};

export const Small: Story = {
  args: {
    size: 'small',
    label: 'Button',
  },
};

这时候页面就多出了Button组件了,还自动生成了该组件的文档,并且还都是可交互的:

Screenshot 2024-01-13 at 14.12.56.png

开启了网格测量工具: Screenshot 2024-01-13 at 14.32.24.png

此时我们可以愉快的在页面点击查看并测试该组件了。

本来集成Storybook的工作就全部完成了,但是为了后续启动该服务方便,我们更改一下根目录下package.json文件,添加一个单独启动Stoybool服务的命令dev:sb

// ...
"scripts": {
  "dev": "turbo dev",
  "dev:sb": "turbo dev --filter=@blankui-org/storybook",
  "build": "turbo build",
  "lint": "turbo lint"
},
// ...

那么在终端运行pnpm dev:sb就能够单独启动Storybook的服务了。

此时项目的目录结构应该如下:

|-- public
    |-- .gitignore
    |-- LICENSE
    |-- README.md
    |-- package.json
    |-- pnpm-lock.yaml
    |-- pnpm-workspace.yaml
    |-- tsconfig.json
    |-- turbo.json
    |-- packages
        |-- components
        |   |-- button
        |       |-- package.json
        |       |-- tsconfig.json
        |       |-- src
        |       |   |-- button.css
        |       |   |-- button.tsx
        |       |   |-- index.ts
        |       |-- stories
        |           |-- button.stories.tsx
        |-- storybook
            |-- .gitignore
            |-- package.json
            |-- .storybook
            |   |-- main.ts
            |   |-- preview.ts
            |   |-- welcome.stories.mdx
            |-- public

到这里为止的源代码:commit 1df48bb

先浅谈一下组件的构建和发布模式

在Monorepo下,组件的构建和发布模式通常具有一些特定的考虑和策略,以确保整个仓库中的组件能够高效地构建、发布,并与其他项目和组件协同工作。以下是一些浅谈的方面:

构建模式

  1. 集中式构建: 在Monorepo中,可以采用集中式构建的方式,即所有组件都通过一个集中的构建流程进行构建。这样可以确保构建过程的一致性,同时减少重复的构建步骤。
  2. 独立构建: 另一种方式是允许每个组件独立构建。这种方式可以提高构建的并行性,每个组件可以在独立的流程中进行构建,加速整体的构建过程。
  3. 并行构建: 通过使用支持并行构建的工具或平台,可以在Monorepo中实现组件的并行构建,从而提高构建效率。这对于大型项目尤为重要。

发布模式

  1. 统一版本管理: 在Monorepo中,要确保所有组件的版本号保持一致。可以采用工具如Lerna等,实现对所有组件的版本进行一致性管理。
  2. 自动发布: 考虑使用自动化发布流程,使得每次组件的更新都能够触发自动发布。这可以通过集成CI/CD工具实现,确保发布流程的可靠性和高效性。
  3. 发布到私有仓库: 在Monorepo中,组件的发布可以选择发布到私有的包管理仓库,以便在整个Monorepo内部和其他项目中进行使用。这可以增加组件的可重用性。
  4. 文档自动生成: 随着发布,可以自动生成组件的文档,并确保文档与代码的同步。这有助于其他开发者更容易地理解和使用组件。
  5. 语义化版本控制: 采用语义化版本控制规范,明确组件版本的变更类型(Major、Minor、Patch),以帮助其他开发者了解变更的影响。

我们这个项目将充分利用不同模式下的优点,以实现最佳的灵活性和效率。以下是一些补充的考虑:

构建模式

  1. 混合构建策略: 考虑采用混合构建策略,对于通用的底层组件可以选择集中式构建,以确保一致性和可维护性;对于独立的业务组件,可以选择独立构建,提高并行性,快速迭代。
  2. 构建缓存: 集成构建缓存机制,以避免重复构建相同的组件,减少构建时间。可以考虑使用工具如Bazel等,提供高效的缓存和并行构建支持。
  3. 可选的预构建步骤: 对于特定的底层组件,可以考虑引入可选的预构建步骤,将一些通用的构建产物提前生成,以减轻业务组件的构建负担。

发布模式

  1. 定制发布流程: 实现定制化的发布流程,针对底层组件和业务组件分别考虑发布策略。底层组件可能需要更加谨慎的版本管理,而业务组件可能更适合快速发布。
  2. 自动化发布管道: 建立自动化的发布管道,确保每次组件的变更都能够触发自动发布。这可以结合CI/CD工具和版本管理系统,实现高效的发布流程。
  3. 版本协同: 保持不同组件之间版本的协同工作,确保整个Monorepo中所有组件的版本保持一致。这有助于避免版本冲突和依赖不一致的问题。
  4. 发布到私有仓库和公共仓库: 对于底层组件,可以选择发布到私有的包管理仓库,以供内部项目使用;对于通用的业务组件,可以考虑发布到公共仓库,以便更广泛地分享和使用。
  5. 变更日志自动生成: 集成自动生成变更日志的机制,以便在发布时生成清晰的变更记录,帮助其他开发者了解每个版本的改动。

通过充分灵活地整合这些构建和发布策略,我们可以实现对底层组件和业务组件的不同需求进行精细化的管理,从而在开发过程中获得最佳的开发体验和项目效果。

当然这些都是一些概念上的东西,看起来也比较乏味,大家简单过一下就好,还是以我们实际的blankui项目为主一步一步来实现。

这里我们快速的在packages/目录下新建core/react/目录,这个子项目的目的是后续能够将所有的组件都构建发布到同一个库。该目录下新增如下文件:

|-- react
    |-- package.json
    |-- tsconfig.json
    |-- src
        |-- index.ts

react/package.json

{
  "name": "@blankui-org/react",
  "version": "1.0.0",
  "description": "🚀 Beautiful and modern React UI library.",
  "keywords": ["blankui", "components", "react components", "react ui"],
  "author": "",
  "license": "MIT",
  "main": "src/index.ts",
  "scripts": {},
  "peerDependencies": {
    "react": ">=18",
    "react-dom": ">=18"
  },
  "dependencies": {
    // 引入工作目录下的button组件
    // 具体可查看 https://pnpm.io/zh/workspaces
    "@blankui-org/button": "workspace:*"
  }
}

react/src/index.ts

export * from "@blankui-org/button";

这样我们的项目以后发布的时候可以发布为全量的组件到一个包下面:@blankui-org/react,引入方式:

import {Button,OtherComponent...} from "@blankui-org/react"

也可以自己单独发布:@blankui-org/button,引入方式为:

import {Button} from "@blankui-org/button";
import {OtherComponent} from "@blankui-org/other";
// ...

当然我们还没有完成关于组件的打包和发布流程,别急,这个后面会一一实现的。

此时项目的结构:

|-- public
    |-- .gitignore
    |-- LICENSE
    |-- README.md
    |-- package.json
    |-- pnpm-lock.yaml
    |-- pnpm-workspace.yaml
    |-- tsconfig.json
    |-- turbo.json
    |-- packages
        |-- components
        |   |-- button
        |       |-- package.json
        |       |-- tsconfig.json
        |       |-- src
        |       |   |-- button.css
        |       |   |-- button.tsx
        |       |   |-- index.ts
        |       |-- stories
        |           |-- button.stories.tsx
        |-- core
        |   |-- react
        |       |-- package.json
        |       |-- tsconfig.json
        |       |-- src
        |           |-- index.ts
        |-- storybook
            |-- .gitignore
            |-- package.json
            |-- .storybook
            |   |-- main.ts
            |   |-- preview.ts
            |   |-- welcome.stories.mdx
            |-- public

到这里为止的源代码:commit e599c82

碍于篇幅和接下来实际还有很多工作要做,所以还是将这整个过程拆分为一个系列文章来讲解。

敬请期待系列文章的陆续发布,如果在实践过程中有任何问题或需求,也欢迎随时提出,我将竭诚为您提供支持。愿您在构建现代化前端UI组件库的旅程中取得圆满成功!

《这里有从零开始构建现代化前端UI组件库所需要的一切(二)》