编写你的第一个 React UI 库 - 第一部分:Lerna
这是关于如何创建自己的 UI React 库系列文章的第一篇。
我们该怎么办?
- 使用 Lerna 创建一个包含多个软件包的新项目。
- 使用我们需要的所有软件包引导整个框架项目。
- 添加软件包所需的依赖项。
- 连接我们自己的集中式构建器。
有时可能会感觉我们像是在与多头巨龙作战,但请耐心等待,因为这终将带来回报,而且会结束!
先决条件
- Node v10+
- NPM v6+
勒纳
这款工具非常适合管理包含多个包的 JavaScript 库。其基本理念是,库中的每个 UI 组件都将是一个完全独立且可安装到项目中的包。我们正在摒弃所有代码都放在单个包中的单体库,转而采用拆分的方式,以便我们的客户可以只安装他们需要的部分。
建议全局安装,因为我们将使用它中的几个命令:
npm i -g lerna
创建初始项目
# example name that I like
mkdir phoenix
cd phoenix
# Initialize empty package.json
npm init -y
# Initialize Lerna
lerna init
lerna.json
这是 Lerna 软件包的核心配置。您可以在这里阅读更多关于 Lerna 配置的信息:https://github.com/lerna/lerna#lernajson
默认值
{
"packages": ["packages/*"],
"version": "0.0.0"
}
我们将修改为这样
{
"packages": [
"packages/*"
],
"version": "0.0.0",
"hoist": true,
"stream": true,
"bootstrap": {
"npmClientArgs": ["--no-package-lock"]
}
}
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
使用 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}'
这样一来,您现在应该能够看到package.json各个软件包中多个设置指针的一些变化,以便您可以引用它们。
让我们编写一些测试 React 代码,以便从 UI 组件中导出。
phoenix-button/lib/phoenix-button.js
import React from 'react';
const Button = ({ children }) => <button>{children}</button>;
export { Button };
phoenix-text/lib/phoenix-text.js
import React from 'react';
const Text = ({ children }) => <p>{children}</p>;
export { Text };
phoenix/lib/phoenix.js
import { Button } from '@cddev/phoenix-button';
import { Text } from '@cddev/phoenix-text';
export { Button, Text };
建造者
我们可以直接发布这些 ES6 包,但一些老旧的客户端可能无法理解这种现代 JavaScript,尤其因为我们使用了 JSX。因此,我们需要将其编译成老旧客户端可以理解的格式,为此我们将使用打包工具。
Rollup是个不错的选择,因为它 API 简洁,而且文档也很完善,便于入门。https
://rollupjs.org/guide/en/
如果像这样使用它来构建这些组件,岂不是很棒吗?
"scripts": {
"build": "phoenix-builder"
}
在这种情况下,根据我们调用它的上下文,构建器将知道传递给它的所有内容。
为此,我们将使用 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"
},
接下来,我们需要phoenix-builder.js使用一个虚拟命令进行一些更改以进行测试:
phoenix-builder/lib/phoenix-builder.js
#!/usr/bin/env node
console.log('Woo');
最后,使 JS 文件可执行。
chmod +x packages/phoenix-builder/lib/phoenix-builder.js
我们应该能够将它们连接phoenix-builder到我们各自的组件,这样我们就可以集中构建器并拥有自己的配置,并能够为每个组件运行。
lerna add @cddev/phoenix-builder --dev --scope '{@cddev/phoenix,@cddev/phoenix-button,@cddev/phoenix-text}'
然后使用新build脚本修改所有这些软件包
。例如:phoenix-button/package.json
"scripts": {
"build": "phoenix-builder",
"test": "echo \"Error: run tests from root\" && exit 1"
},
接下来,我们可以通过以下步骤进行测试运行:
lerna run build
你应该能够Woo在控制台中成功看到三条消息,这表明操作成功了。
故障排除:
如果出现错误,请确保已在package.json 文件中phoenix-builder: command not found导出命令。binphoenix-builder
为了简化脚本运行,让我们在根目录下修改脚本package.json并添加:root/package.json
"scripts": {
"build": "lerna run build"
}
这样,我们就可以在根目录下运行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
现在你应该拥有编写代码所需的所有依赖项了。phoenix-builder.js
我们将使用 Rollup 中的 Javascript API 并生成 2 个包:
- 适用于旧版客户端的 CommonJS (CJS)。
- 适用于新客户端的 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();
现在你可以运行:
npm run build
你应该能看到我们组件的编译版本了!
太棒了!!🎉🎉🎉🎉
dist每个 UI 组件包的文件夹中都包含已编译代码示例
结论
现在你应该已经拥有一个包含两个 React UI 组件的小型库:一个导入它们的单一库和一个集中式构建器。这就是整个 UI 库的框架。在接下来的部分中,我们将添加功能齐全的文档工具、CSS Modules 支持以及一些最终的完善工作,以便能够分发我们的库。
资源
代码:https://github.com/davixyz/phoenix/tree/part1
GitHub:https: //github.com/davixyz
Twitter: https: //twitter.com/carloscastrodev


