在组织内部分发 UI
从架构的角度来看,设计系统是另一种前端依赖项。它们与 moment 或 lodash 等流行的依赖项没有什么不同。UI 组件是代码,因此我们可以依赖成熟的代码重用技术。
本章将介绍设计系统的分发,从打包 UI 组件到将其导入到其他应用程序中。我们还将揭示节省时间的技术,以简化版本控制和发布。
打包设计系统
组织在不同的应用程序中拥有数千个 UI 组件。以前,我们将最常见的组件提取到我们的设计系统中,现在我们需要将这些组件重新引入到应用程序中。
我们的设计系统使用 JavaScript 包管理器 npm 来处理分发、版本控制和依赖项管理。
有很多有效的方法可以打包设计系统。参考 Lonely Planet、Auth0、Salesforce、GitHub 和 Microsoft 的设计系统,了解方法的多样性。有些人将每个组件作为单独的包交付,而另一些人则将所有组件都放在一个包中交付。
对于新兴的设计系统,最直接的方法是发布一个封装以下内容的单一版本化包
- 🏗 通用 UI 组件
- 🎨 设计令牌(又名样式变量)
- 📕 文档
准备导出设计系统
我们已将自定义模板用于开发、测试和文档目的。但是,在发布我们的设计系统之前,我们需要改进其描述性。清理一些初始工件并使用设计系统的详细描述更新 README.md 至关重要。
# Storybook design system tutorial
The Storybook design system tutorial is a subset of the full [Storybook design system](https://github.com/storybookjs/design-system/), created as a learning resource for those interested in learning how to write and publish a design system using best in practice techniques.
Learn more in [Storybook tutorials](https://storybook.org.cn/tutorials/).
现在,让我们检查一下我们将如何构建包系统。为了编译我们的设计系统,我们将使用 Rollup,这是一个 JavaScript 模块打包器,可将小的代码片段组合成更大的库或应用程序。最棒的是,所需的设置和常用入口点已经包含在 src/index.js
文件和 rollup.config.mjs
中,因此我们无需自行配置它们。
// src/index.js
import * as styles from './shared/styles';
import * as global from './shared/global';
import * as animation from './shared/animation';
import * as icons from './shared/icons';
export { styles, global, animation, icons };
export * from './Avatar';
export * from './Badge';
export * from './Button';
export * from './Icon';
export * from './Link';
// rollup.config.mjs
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import terser from '@rollup/plugin-terser';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import { babel } from '@rollup/plugin-babel';
// This is required to read package.json file when
// using Native ES modules in Node.js
// https://rollup.node.org.cn/command-line-interface/#importing-package-json
import { createRequire } from 'node:module';
const requireFile = createRequire(import.meta.url);
const packageJson = requireFile('./package.json');
export default [
{
input: 'src/index.js',
output: [
{
file: packageJson.main,
format: 'cjs',
sourcemap: true,
},
{
file: packageJson.module,
format: 'esm',
exports: 'named',
sourcemap: true,
},
],
plugins: [
peerDepsExternal(),
resolve({
extensions: ['.js', '.jsx'],
}),
commonjs(),
terser(),
babel({
extensions: ['.js', '.jsx'],
exclude: 'node_modules/**',
}),
],
external: ['react', 'react-dom', '@emotion/react', '@emotion/styled'],
},
];
现在我们可以运行 yarn build
将我们的代码构建到 dist
目录中——我们也应该将该目录添加到 .gitignore
中,这样我们就不会意外地提交它
// ...
dist
为发布添加包元数据
我们需要更改 package.json
,以确保我们的包使用者获得所有必要的信息。最简单的方法是简单地运行 yarn init
——这是一个初始化包以进行发布的命令
# Initializes a scoped package
yarn init --scope=@your-npm-username
yarn init v1.22.5
question name (learnstorybook-design-system): @your-npm-username/learnstorybook-design-system
question version (0.1.0):
question description (Learn Storybook design system):Storybook design systems tutorial
question entry point (dist/cjs/index.js):
question repository url (https://github.com/your-username/learnstorybook-design-system.git):
question author (your-npm-username <your-email-address@email-provider.com>):
question license (MIT):
question private: no
该命令会询问我们一系列问题,其中一些问题会预先填写答案,另一些问题我们需要考虑。您需要在 npm 上为包选择一个唯一的名称(您将无法使用 learnstorybook-design-system
——一个不错的选择是 @your-npm-username/learnstorybook-design-system
)。
总而言之,它将更新 package.json
,其中包含这些问题的新值
{
"name": "@your-npm-username/learnstorybook-design-system",
"description": "Storybook design systems tutorial",
"version": "0.1.0",
"license": "MIT",
"main": "dist/cjs/index.js",
"repository": "https://github.com/your-username/learnstorybook-design-system.git"
// ...
}
现在我们已经准备好包,我们可以首次将其发布到 npm!
使用 Auto 进行发布管理
为了将版本发布到 npm,我们将使用一个流程,该流程还会更新描述更改的更改日志、设置合理的版本号并创建将该版本号链接到我们存储库中提交的 git 标签。为了帮助完成所有这些工作,我们将使用一个名为 Auto 的开源工具,该工具专为此目的而设计。Auto 是一个命令行工具,我们可以将其用于围绕发布管理的各种常见任务。您可以在 他们的文档站点上了解有关 Auto 的更多信息。
获取 GitHub 和 npm 令牌
在接下来的几个步骤中,Auto 将与 GitHub 和 npm 通信。为了使其正常工作,我们需要一个个人访问令牌。您可以在 此页面 上获取 GitHub 的令牌。该令牌将需要 repo
和 workflow
作用域。
对于 npm,您可以在 URL:https://npmjs.net.cn/settings/<your-username>/tokens 创建令牌。
您需要一个具有“读取和发布”权限的令牌。
让我们将该令牌添加到我们项目中的一个名为 .env
的文件中
GH_TOKEN=<value you just got from GitHub>
NPM_TOKEN=<value you just got from npm>
通过将文件添加到 .gitignore
,我们确保不会意外地将此值推送到所有用户都可以看到的开源存储库!这至关重要。如果其他维护者需要在本地发布包(稍后我们将设置在拉取请求合并到默认分支时自动发布),他们应该按照此过程设置自己的 .env
文件
dist
.env
在 GitHub 上创建标签
我们使用 Auto 需要做的第一件事是在 GitHub 中创建一组标签。我们将来在更改包时将使用这些标签(请参阅下一章),这将允许 auto
合理地更新包版本并创建更改日志和发行说明。
yarn auto create-labels
如果您在 GitHub 上查看,您现在将看到一组 auto
希望我们使用的标签
我们应该在合并所有未来的 PR 之前,使用以下标签之一标记它们:major
、minor
、patch
、skip-release
、prerelease
、internal
、documentation
。
手动使用 Auto 发布我们的第一个版本
将来,我们将使用 auto
通过脚本计算新的版本号,但对于第一个版本,让我们手动运行命令以了解它们的作用。让我们生成我们的第一个更改日志条目
yarn auto changelog
它将生成一个很长的更改日志条目,其中包含我们迄今为止创建的每个提交(以及我们一直在推送到默认分支的警告,我们应该尽快停止这样做)。
虽然拥有自动生成的更改日志有助于您不会遗漏任何内容,但手动编辑并以对用户最有用的方式制作消息也是一个好主意。在这种情况下,用户不需要了解沿途的所有提交。让我们为我们的第一个 v0.1.0 版本制作一条简洁明了的消息。首先撤消 Auto 刚刚创建的提交(但保留更改
git reset HEAD^
然后我们将更新更改日志并提交它
# v0.1.0 (Mon Jun 12 2023)
- Created first version of the design system, with `Avatar`, `Badge`, `Button`, `Icon` and `Link` components.
#### Authors: 1
- [your-username](https://github.com/your-username)
让我们将该更改日志添加到 git。请注意,我们使用 [skip ci]
来告诉 CI 平台忽略这些提交,否则我们最终会陷入他们的构建和发布循环中。
git add CHANGELOG.md
git commit -m "Changelog for v0.1.0 [skip ci]"
现在我们可以发布
npm --allow-same-version version 0.1.0 -m "Bump version to: %s [skip ci]"
npm publish --access=public
并使用 Auto 在 GitHub 上创建版本
git push --follow-tags origin main
yarn release
太棒了!我们已成功将我们的包发布到 npm 并在 GitHub 上创建了一个版本(运气好的话!)。
💡 虽然我们修改了初始发行说明,使其对于第一个版本有意义,但 auto
会根据未来版本的提交消息自动生成发行说明。
现在,当我们运行 yarn release
时,我们将以自动化的方式完成我们上面运行的所有步骤(除了使用自动生成的更改日志)。对默认分支的所有提交都将被发布。
恭喜!您设置了手动发布设计系统版本的基础设施。现在学习如何使用持续集成来自动化发布。
自动发布版本
我们使用 GitHub Actions 进行持续集成。但在继续之前,我们需要安全地存储之前来自 GitHub 和 NPM 的令牌,以便 Actions 可以访问它们。
将您的令牌添加到 GitHub Secrets
GitHub Secrets 允许我们在我们的存储库中存储敏感信息。在浏览器窗口中,打开您的 GitHub 存储库。
单击 ⚙️ Settings 选项卡,然后在侧边栏中单击 Secrets and variables
下拉列表,然后单击 Actions
链接。您将看到以下屏幕
单击 New repository secret 按钮。使用 NPM_TOKEN
作为名称,并将您在本章前面从 npm 获取的令牌粘贴到其中。
当您将 npm secret 添加到您的存储库时,您将能够将其作为 secrets.NPM_TOKEN
访问。您无需为您的 GitHub 令牌设置另一个 secret。所有 GitHub 用户都会自动获得与其帐户关联的 secrets.GITHUB_TOKEN
。
使用 GitHub Actions 自动化发布
每次我们合并拉取请求时,我们都希望自动发布设计系统。在与我们之前用于发布 Storybook 的文件夹相同的文件夹中创建一个名为 push.yml
的新文件,并添加以下内容
# Name of our action
name: Release
# The event that will trigger the action
on:
push:
branches: [main]
# what the action will do
jobs:
release:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'ci skip') && !contains(github.event.head_commit.message, 'skip ci')"
steps:
- uses: actions/checkout@v2
- name: Prepare repository
run: git fetch --unshallow --tags
- name: Use Node.js 16.x
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
uses: bahmutov/npm-install@v1
- name: Create Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
#👇 npm token, see https://storybook.org.cn/tutorials/design-systems-for-developers/react/en/distribute/ to obtain it
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
yarn release
保存更改并将其提交到远程存储库。
成功!现在,每次您将 PR 合并到默认分支时,它都会自动发布新版本,并根据您添加的标签适当地递增版本号。
在应用程序中导入设计系统
既然我们的设计系统在线,安装依赖项和使用 UI 组件就变得很简单了。
获取示例应用程序
在本教程的前面,我们标准化了一个流行的前端堆栈,其中包括 React 和 Emotion。这意味着我们的示例应用程序也必须使用 React 和 Emotion 才能充分利用设计系统。
💡 尽管其他技术(例如 Svelte 或 Web Components)可以帮助分发 UI 组件而无需依赖特定框架,但我们专注于最常用和最广泛记录的方法,以确保本教程的快速入门。请放心,我们将在即将到来的更新中探索其他方法。
示例应用程序使用 Storybook 来促进组件驱动开发,这是一种用于从底部构建 UI 的应用程序开发方法,从组件开始,以页面结束。在演示期间,我们将并排运行两个 Storybook:一个用于我们的示例应用程序,另一个用于我们的设计系统。
在您的命令行中运行以下命令以设置示例应用程序
# Clones the files locally
npx degit chromaui/learnstorybook-design-system-example-app example-app
cd example-app
# Install the dependencies
yarn install
## Start Storybook
yarn storybook
您应该看到 Storybook 正在运行,其中包含应用程序使用的简单组件的故事
集成设计系统
我们已经发布了设计系统的 Storybook。让我们将其添加到我们的示例应用程序中。我们可以通过将示例应用程序的 .storybook/main.js
更新为以下内容来做到这一点
/** @type { import('@storybook/react-vite').StorybookConfig } */
const config = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
+ refs: {
+ "design-system": {
+ title: 'My design system',
+ //👇 The url provided by Chromatic when it was deployed
+ url: 'https://your-published-url.chromatic.com',
+ },
+ },
};
export default config;
您现在可以在开发示例应用程序时浏览设计系统组件和文档。在功能开发期间展示设计系统增加了开发人员将重用现有组件而不是浪费时间发明自己的组件的可能性。
我们拥有所需的一切,是时候添加我们的设计系统并开始使用它了。在您的终端中运行以下命令
yarn add @your-npm-username/learnstorybook-design-system
我们需要使用设计系统中定义的相同全局样式,因此我们需要更新.storybook/preview.jsx
配置文件并添加一个全局装饰器。
import { Global } from '@emotion/react';
// The styles imported from the design system.
import { global as designSystemGlobal } from '@your-npm-username/learnstorybook-design-system';
const { GlobalStyle } = designSystemGlobal;
/** @type { import('@storybook/react').Preview } */
const preview = {
/*
* Adds a global decorator to include the imported styles from the design system.
* More on Storybook decorators at:
* https://storybook.org.cn/docs/react/writing-stories/decorators#global-decorators
*/
decorators: [
(Story) => (
<>
<Global styles={GlobalStyle} />
<Story />
</>
),
],
/*
* More on Storybook parameters at:
* https://storybook.org.cn/docs/react/writing-stories/parameters#global-parameters
*/
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
};
export default preview;
我们将在示例应用程序的 UserItem
组件中使用来自我们设计系统的 Avatar
组件。UserItem
应该呈现有关用户的信息,包括姓名和个人资料照片。
在您的编辑器中,打开位于 src/components/UserItem.js
中的 UserItem
组件。此外,在您的 Storybook 中选择 UserItem
,以立即通过热模块重新加载查看我们即将进行的代码更改。
导入 Avatar 组件。
import { Avatar } from '@your-npm-username/learnstorybook-design-system';
我们想在用户名旁边渲染 Avatar。
import PropTypes from 'prop-types';
import styled from '@emotion/styled';
+ import { Avatar } from '@your-npm-username/learnstorybook-design-system';
const Container = styled.div`
background: #eee;
margin-bottom: 1em;
padding: 0.5em;
`;
- const Avatar = styled.img`
- border: 1px solid black;
- width: 30px;
- height: 30px;
- margin-right: 0.5em;
- `;
const Name = styled.span`
color: #333;
font-size: 16px;
`;
export default ({ user: { name, avatarUrl } }) => (
<Container>
+ <Avatar username={name} src={avatarUrl} />
<Name>{name}</Name>
</Container>
);
UserItem.propTypes = {
user: PropTypes.shape({
name: PropTypes.string,
avatarUrl: PropTypes.string,
}),
};
保存后,UserItem
组件将在 Storybook 中更新以显示新的 Avatar 组件。由于 UserItem
是 UserList
组件的一部分,您还将在 UserList
中看到 Avatar
。
您成功了!您刚刚将设计系统组件导入到示例应用程序中。每当您在设计系统中发布 Avatar 组件的更新时,当您更新包时,该更改也会反映在示例应用程序中。
掌握设计系统工作流程
设计系统工作流程从在 Storybook 中开发 UI 组件开始,到将它们分发到客户端应用程序结束。但这还不是全部。设计系统必须不断发展以满足不断变化的产品需求,而我们的工作才刚刚开始。
第 8 章说明了我们在本指南中创建的端到端设计系统工作流程。我们将看到 UI 更改如何从设计系统向外扩散。