发布于 2026-01-06 0 阅读
0

编写你的第一个 React UI 库 - 第一部分:Lerna

编写你的第一个 React UI 库 - 第一部分:Lerna

一条多头龙

这是关于如何创建自己的 UI React 库系列文章的第一篇。

我们该怎么办?

  • 使用 Lerna 创建一个包含多个软件包的新项目。
  • 使用我们需要的所有软件包引导整个框架项目。
  • 添加软件包所需的依赖项。
  • 连接我们自己的集中式构建器。

有时可能会感觉我们像是在与多头巨龙作战,但请耐心等待,因为这终将带来回报,而且会结束!

先决条件

  • Node v10+
  • NPM v6+

勒纳

这款工具非常适合管理包含多个包的 JavaScript 库。其基本理念是,库中的每个 UI 组件都将是一个完全独立且可安装到项目中的包。我们正在摒弃所有代码都放在单个包中的单体库,转而采用拆分的方式,以便我们的客户可以只安装他们需要的部分。

建议全局安装,因为我们将使用它中的几个命令:



npm i -g lerna


Enter fullscreen mode Exit fullscreen mode

创建初始项目



# example name that I like
mkdir phoenix
cd phoenix

# Initialize empty package.json
npm init -y

# Initialize Lerna
lerna init


Enter fullscreen mode Exit fullscreen mode

示例生成的文件夹结构
文件夹结构

lerna.json

这是 Lerna 软件包的核心配置。您可以在这里阅读更多关于 Lerna 配置的信息:https://github.com/lerna/lerna#lernajson

默认值



{
  "packages": ["packages/*"],
  "version": "0.0.0"
}


Enter fullscreen mode Exit fullscreen mode

我们将修改为这样



{
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0",
  "hoist": true,
  "stream": true,
  "bootstrap": {
    "npmClientArgs": ["--no-package-lock"]
  }
}


Enter fullscreen mode Exit fullscreen mode

hoist将所有包依赖项提升到根目录,以便去重。
stream运行时打印所有内部包日志。
npmClientArgs阻止为所有这些包生成 package-lock.json 文件。

包裹/

此文件夹将存放我们即将发布的所有软件包的代码。

我们来添加一些组件吧!

npm 作用域

我们希望将这些包发布到 npm,并避免与其他包冲突;为此,我们将创建一个作用域。作用域非常适合为密切相关的包创建命名空间,或者作为一种验证方式,例如@babel/core,本例中的作用域是 `<scope>` @babel。在本例中,我们将作用域设置为:`<scope> @cddev`。

点击此处阅读更多关于作用域的信息:https://docs.npmjs.com/about-scopes

重要提示

  • 这个范围@cddev是我在这份指南中使用的,所以已经被占用了,请创建一个新的范围;一个能够代表你生活中的热情和兴趣的范围 :)
  • 在使用作用域之前,为了避免日后麻烦,请先在 npm 中创建组织: https://www.npmjs.com/org/create

创建包

任何 UI 库都离不开组件,所以让我们使用组件创建一些包Lerna。本指南将创建 4 个包:

  • @cddev/phoenix这样可以将所有包放在一起,以防有人想要进行一次导入。
  • @cddev/phoenix-button按钮组件。
  • @cddev/phoenix-text:文本组件。
  • @cddev/phoenix-builder:所有组件的构建器,集中管理 rollup、babel、post-css 等。

请注意,本教程
将使用默认文件夹结构。Lerna




# Using --yes to skip prompts
lerna create @cddev/phoenix --yes
lerna create @cddev/phoenix-button --yes
lerna create @cddev/phoenix-text --yes
lerna create @cddev/phoenix-builder --yes


Enter fullscreen mode Exit fullscreen mode

使用 Lerna 连接 React 组件

我们希望在组件内部建立关联关系,例如,主 Phoenix 包会导入并导出所有其他包;我们还希望为所有包添加必要的依赖项以便启动。让我们开始吧。



# Add phoenix-button dependency into phoenix
lerna add @cddev/phoenix-button --scope=@cddev/phoenix

# Add phoenix-text dependency into phoenix
lerna add @cddev/phoenix-text --scope=@cddev/phoenix

# We are going to use React for the two UI components, let's add it as dev dependency first for local testing
lerna add react --dev --scope '{@cddev/phoenix-button,@cddev/phoenix-text}'

# And as a peer dependency using major 16 version for consuming applications
lerna add react@16.x --peer --scope '{@cddev/phoenix-button,@cddev/phoenix-text}'

# We are also going to use an utility to toggle classes as needed on the components called "clsx"
lerna add clsx --scope '{@cddev/phoenix-button,@cddev/phoenix-text}'


Enter fullscreen mode Exit fullscreen mode

这样一来,您现在应该能够看到package.json各个软件包中多个设置指针的一些变化,以便您可以引用它们。

让我们编写一些测试 React 代码,以便从 UI 组件中导出。

phoenix-button/lib/phoenix-button.js



import React from 'react';
const Button = ({ children }) => <button>{children}</button>;
export { Button };


Enter fullscreen mode Exit fullscreen mode

phoenix-text/lib/phoenix-text.js



import React from 'react';
const Text = ({ children }) => <p>{children}</p>;
export { Text };


Enter fullscreen mode Exit fullscreen mode

phoenix/lib/phoenix.js



import { Button } from '@cddev/phoenix-button';
import { Text } from '@cddev/phoenix-text';
export { Button, Text };


Enter fullscreen mode Exit fullscreen mode

建造者

我们可以直接发布这些 ES6 包,但一些老旧的客户端可能无法理解这种现代 JavaScript,尤其因为我们使用了 JSX。因此,我们需要将其编译成老旧客户端可以理解的格式,为此我们将使用打包工具。

Rollup是个不错的选择,因为它 API 简洁,而且文档也很完善,便于入门。https
://rollupjs.org/guide/en/

如果像这样使用它来构建这些组件,岂不是很棒吗?



"scripts": {
  "build": "phoenix-builder"
}


Enter fullscreen mode Exit fullscreen mode

在这种情况下,根据我们调用它的上下文,构建器将知道传递给它的所有内容。

为此,我们将使用 Node.js 创建一个命令行可执行文件。https
://developer.okta.com/blog/2019/06/18/command-line-app-with-nodejs

让我们修改一下配置@cddev/phoenix-builder/package.json,让 Node 知道我们正在从这个包中暴露一个可执行文件。在这个例子中,可执行文件将是phoenix-builder.

phoenix-builder/package.json



"bin": {
  "phoenix-builder": "./lib/phoenix-builder.js"
},


Enter fullscreen mode Exit fullscreen mode

接下来,我们需要phoenix-builder.js使用一个虚拟命令进行一些更改以进行测试:

phoenix-builder/lib/phoenix-builder.js



#!/usr/bin/env node
console.log('Woo');


Enter fullscreen mode Exit fullscreen mode

最后,使 JS 文件可执行。



chmod +x packages/phoenix-builder/lib/phoenix-builder.js


Enter fullscreen mode Exit fullscreen mode

我们应该能够将它们连接phoenix-builder到我们各自的组件,这样我们就可以集中构建器并拥有自己的配置,并能够为每个组件运行。



lerna add @cddev/phoenix-builder --dev --scope '{@cddev/phoenix,@cddev/phoenix-button,@cddev/phoenix-text}'


Enter fullscreen mode Exit fullscreen mode

然后使用新build脚本修改所有这些软件包
。例如:phoenix-button/package.json



"scripts": {
  "build": "phoenix-builder",
  "test": "echo \"Error: run tests from root\" && exit 1"
},


Enter fullscreen mode Exit fullscreen mode

接下来,我们可以通过以下步骤进行测试运行:



lerna run build


Enter fullscreen mode Exit fullscreen mode

你应该能够Woo在控制台中成功看到三条消息,这表明操作成功了。

故障排除:
如果出现错误,请确保已package.json 文件中phoenix-builder: command not found导出命令。binphoenix-builder

为了简化脚本运行,让我们在根目录下修改脚本package.json并添加:
root/package.json



"scripts": {
 "build":  "lerna run build"
}


Enter fullscreen mode Exit fullscreen mode

这样,我们就可以在根目录下运行npm run build,它应该能达到同样的效果,而无需lerna每次都调用。

使用 Rollup 编译 JS 文件

现在构建器已经连接好了,我们可以开始添加Rollup和编译代码所需的所有其他依赖项了!

可惜 Lerna 不支持在一个命令中添加多个软件包……唉。



lerna add rollup --scope=@cddev/phoenix-builder
lerna add @babel/core --scope=@cddev/phoenix-builder
lerna add @babel/preset-env --scope=@cddev/phoenix-builder
lerna add @babel/preset-react --scope=@cddev/phoenix-builder
lerna add @rollup/plugin-babel --scope=@cddev/phoenix-builder
lerna add @rollup/plugin-node-resolve --scope=@cddev/phoenix-builder


Enter fullscreen mode Exit fullscreen mode

现在你应该拥有编写代码所需的所有依赖项了。phoenix-builder.js

我们将使用 Rollup 中的 Javascript API 并生成 2 个包:

  1. 适用于旧版客户端的 CommonJS (CJS)。
  2. 适用于新客户端的 ECMAScript 模块 (ESM)。

我们先phoenix-builder.js用以下代码进行修改:

phoenix-builder/lib/phoenix-builder.js



#!/usr/bin/env node
const rollup = require('rollup');
const path = require('path');
const resolve = require('@rollup/plugin-node-resolve').default;
const babel = require('@rollup/plugin-babel').default;

const currentWorkingPath = process.cwd();
const { main, name } = require(path.join(currentWorkingPath, 'package.json'));

const inputPath = path.join(currentWorkingPath, main);

// Little workaround to get package name without scope
const fileName = name.replace('@cddev/', '');

// see below for details on the options
const inputOptions = {
  input: inputPath,
  external: ['react'],
  plugins: [
    resolve(),
    babel({
      presets: ['@babel/preset-env', '@babel/preset-react'],
      babelHelpers: 'bundled',
    }),
  ],
};

const outputOptions = [
  {
    file: `dist/${fileName}.cjs.js`,
    format: 'cjs',
  },
  {
    file: `dist/${fileName}.esm.js`,
    format: 'esm',
  },
];

async function build() {
  // create bundle
  const bundle = await rollup.rollup(inputOptions);
  // loop through the options and write individual bundles
  outputOptions.forEach(async (options) => {
    await bundle.write(options);
  });
}

build();


Enter fullscreen mode Exit fullscreen mode

现在你可以运行:



npm run build


Enter fullscreen mode Exit fullscreen mode

你应该能看到我们组件的编译版本了!
太棒了!!🎉🎉🎉🎉

dist每个 UI 组件包的文件夹中都包含已编译代码示例

编译代码示例

结论

现在你应该已经拥有一个包含两个 React UI 组件的小型库:一个导入它们的单一库和一个集中式构建器。这就是整个 UI 库的框架。在接下来的部分中,我们将添加功能齐全的文档工具、CSS Modules 支持以及一些最终的完善工作,以便能够分发我们的库。

资源

代码:https://github.com/davixyz/phoenix/tree/part1
GitHub:https: //github.com/davixyz
Twitter: https: //twitter.com/carloscastrodev

文章来源:https://dev.to/davixyz/writing-your-first-react-ui-library-part-1-lerna-17kc