我的大部分工作是写后端,但是前一天有一项任务要在React上启动组件库。 几年前,当React版本与我在前端开发中的经验一样小时,我已经对弹丸采取了一种方法,结果变得笨拙而笨拙。 考虑到当前React生态系统的成熟度以及我不断增长的经验,这次我受到启发,可以轻松便捷地完成所有工作。 结果,我对未来的图书馆感到一片空白,并且为了避免忘记任何东西并将所有东西收集在一个地方,撰写了这篇备忘单文章,这也应该帮助那些不知道从哪里开始的人。 让我们看看我做了什么。
TL / DR:可以在github上查看准备启动的库的代码
这个问题可以从两个方面解决:
- 查找现成的入门套件,样板或CLI生成器
- 自己收集一切
当您绝对不想处理配置和连接必要的程序包时,第一种方法对于快速入门非常有用。 另外,此选项适用于不知道从哪里开始以及库与常规应用程序之间应该有什么区别的初学者。
最初,我采用第一种方法,但是后来我决定更新依赖关系,并固定几个其他的程序包,然后各种错误和不一致性逐渐减少。 结果,他卷起袖子,自己做了一切。 但是我会提到库生成器。
创建反应库
大多数处理React的开发人员都听说过便捷的React应用程序启动器,该启动器可让您最小化项目配置并提供合理的默认值- 创建React App (CRA)。 原则上,它可以用于图书馆( 有关Habré的文章 )。 但是,ui-kit开发的项目结构和方法与通常的SPA略有不同。 我们需要一个单独的目录,其中包含组件的源代码(src),用于其开发和调试的沙箱(示例),文档和演示工具(“展示柜”)以及一个单独的目录,其中包含准备导出的文件(dist)。 同样,库组件不会添加到SPA应用程序中,而是通过索引文件导出。 考虑到这一点,我去搜索并迅速发现了一个类似的CRA软件包-Creat React Library (CRL)。
与CRA一样,CRL是一个易于使用的CLI实用程序。 使用它,您可以生成一个项目。 它将包含:
- 配置汇总以构建cjs和es模块以及具有对CSS模块的支持的源映射
- 在ES5中进行转译的通天塔
- 开玩笑
- 我们希望使用TypeScript(TS)作为选项
要生成库项目, 我们可以做到( npx允许我们不全局安装软件包):
npx create-react-library
通过该实用程序,我们得到了一个已生成且可以立即使用的组件库项目。
然后出了点问题...今天的依赖关系有些过时,因此我决定使用npm-check将所有依赖关系更新为最新版本:
npx npm-check -u
另一个可悲的事实是, example
目录中的沙箱应用程序是用js生成的。 您将必须手动将其重写为TypeScript,添加tsconfig.json
和一些依赖项(例如, @types
本身和基本的@types
)。
另外, react-scripts-ts
包被声明为deprecated
,不再受支持。 相反,您应该安装react-scripts
,因为一段时间以来,CRA(其软件包为react-scripts
)从框中开始支持TypeScript(使用Babel 7)。
结果,我对库的想法没有掌握拉动react-scripts
。 据我所知,此程序包中的Jest需要isolatedModules
编译器选项,这与我从库生成和导出d.ts
愿望d.ts
(所有这些都与Babel 7的局限性有关, d.ts
和react-scripts
使用Babel 7的局限性来编译TS ) 因此,我eject
了react-scripts
,查看了结果并用手重做了所有内容,稍后将进行介绍。
tsdx
感谢用户StanEgo ,他谈到了创建React库-tsdx的绝佳替代方法。 此cli-utility也类似于CRA,并且在一个命令中将使用配置的Rollup,TS,Prettier,husky,Jest和React为您的库创建基础。 React是一个选项。 做起来很简单:
npx tsdx create mytslib
因此,将安装软件包的必要新版本并进行所有设置。 获得一个类似CRL的项目。 与CRL的主要区别在于零配置。 也就是说,在tsdx中对我们隐藏了汇总配置(就像CRA一样)。
在快速浏览文档后,我没有找到推荐的方法来进行更精细的配置或在CRA中弹出之类的方法。 看了这个项目的问题之后,我发现到目前为止没有这种可能性 。 对于某些项目,这可能很关键,在这种情况下,您将需要动手一点。 如果您不需要它,那么tsdx是快速入门的好方法。
掌握控制权
但是,如果您走第二种方式自己收集所有东西,该怎么办? 因此,让我们从头开始。 运行npm init
并为该库生成package.json
。 在此处添加有关我们软件包的一些信息。 例如,我们在engines
字段中编写node和npm的最低版本。 收集和导出的文件将放置在dist
目录中。 我们在files
字段中指出这一点。 我们正在创建一个React组件库,因此我们希望用户拥有必要的软件包-我们在peerDependencies
字段中peerDependencies
react
和react-dom
peerDependencies
最低要求版本。
现在,将react
和react-dom
软件包以及必要的类型(因为我们将在TypeScript上看到组件)安装为devDependencies(类似于本文中的所有软件包):
npm install --save-dev react react-dom @types/react @types/react-dom
安装TypeScript:
npm install --save-dev typescript
让我们为主要代码和测试创建配置文件: tsconfig.json
和tsconfig.test.json
。 我们的target
将在es5
,我们将生成sourceMap
等。 可能的选项及其含义的完整列表可以在文档中找到。 不要忘记在include
源目录,并在node_modules
添加node_modules
和dist
目录。 在package.json
我们在类型字段中指示获取库类型的位置dist/index
。
为库的源组件创建src
目录。 添加各种各样的小东西,例如.gitignore
, .editorconfig
,具有许可证的文件和README.md
。
汇总
根据CRL的建议,我们将使用汇总进行组装。 必需的程序包和配置,我还监视了CRL。 总的来说,我听到这样的观点,即汇总对于库和Webpack对于应用程序都是有益的。 但是,我没有配置Webpack(CRA为我做了什么),但是Rollup确实非常好,简单且美观。
安装:
npm install --save-dev rollup rollup-plugin-babel rollup-plugin-commonjs rollup-plugin-node-resolve rollup-plugin-peer-deps-external rollup-plugin-postcss rollup-plugin-typescript2 rollup-plugin-url @svgr/rollup
在package.json
按照rollup
推荐给我们的字段添加收集的库包分发的字段-pkg.module :
"main": "dist/index.js", "module": "dist/index.es.js", "jsnext:main": "dist/index.es.js"
创建配置文件rollup.config.js import typescript from 'rollup-plugin-typescript2'; import commonjs from 'rollup-plugin-commonjs'; import external from 'rollup-plugin-peer-deps-external'; import postcss from 'rollup-plugin-postcss'; import resolve from 'rollup-plugin-node-resolve'; import url from 'rollup-plugin-url'; import svgr from '@svgr/rollup'; import pkg from './package.json'; export default { input: 'src/index.tsx', output: [ { file: pkg.main, format: 'cjs', exports: 'named', sourcemap: true }, { file: pkg.module, format: 'es', exports: 'named', sourcemap: true } ], plugins: [ external(), postcss({ modules: false, extract: true, minimize: true, sourceMap: true }), url(), svgr(), resolve(), typescript({ rollupCommonJSResolveHack: true, clean: true }), commonjs() ] };
配置是一个js文件,或者是一个导出的对象。 在input
字段中,指定用于注册我们的库导出文件的文件。 output
-描述我们对output
的期望-在哪个模块中以格式进行编译以及将其放置在何处。
在scripts
package.json
字段中添加一个命令以进行构建( "build": "rollup -c"
)并在开发过程中以监视模式启动该程序集( "start": "rollup -c -w && npm run prettier-watch"
) 。
第一个组件和导出文件
现在,我们将编写最简单的react组件来检查我们的程序集如何工作。 库中的每个组件都将放置在父目录src/components/ExampleComponent
的单独目录中。 该目录将包含与组件相关的所有文件test.tsx
, css
, test.tsx
等。
让我们为组件创建一些样式文件,并为组件本身创建tsx
文件。
ExampleComponent.tsx import * as React from 'react'; import './ExampleComponent.css'; export interface Props { text: string; } export class ExampleComponent extends React.Component<Props> { render() { const { text } = this.props; return ( <div className="test"> Example Component: {text} <p>Coool!</p> </div> ); } } export default ExampleComponent;
同样在src
您需要创建一个具有库通用类型的文件,其中将声明css和svg的类型(在CRL处窥视)。
打字 declare module '*.css' { const content: { [className: string]: string }; export default content; } interface SvgrComponent extends React.FunctionComponent<React.SVGAttributes<SVGElement>> {} declare module '*.svg' { const svgUrl: string; const svgComponent: SvgrComponent; export default svgUrl; export { svgComponent as ReactComponent }; }
必须在导出文件中指定所有导出的组件和CSS。 我们有src/index.tsx
。 如果项目中未使用某些css,并且未将其列为导入src/index.tsx
的那些src/index.tsx
,那么它将被扔出程序集,这很好。
src / index.tsx import { ExampleComponent, Props } from './ExampleComponent'; import './export.css'; export { ExampleComponent, Props };
现在,您可以尝试构建库npm run build
。 结果, rollup
开始,并将我们的库收集到包中,这些包将在dist
目录中找到。
接下来,我们添加了一些工具来改善我们的开发过程及其结果的质量。
我讨厌在代码审查中指出格式化对于项目来说是粗心的或非标准的,甚至对此争论不休。 这些缺陷自然应该得到修复,但是开发人员应该专注于代码的功能和方式,而不是外观。 这些修补程序是自动化的第一个候选对象。 这个漂亮的程序包非常漂亮 。 安装它:
npm install --save-dev prettier
添加配置以略微完善格式设置规则。
.prettierrc.json { "tabWidth": 3, "singleQuote": true, "jsxBracketSameLine": true, "arrowParens": "always", "printWidth": 100, "semi": true, "bracketSpacing": true }
您可以在文档中看到可用规则的含义。 创建配置文件本身后,WebStrom将在通过IDE开始格式化时建议使用prettier
样式。 为避免格式化浪费时间,请使用.prettierignore
文件(格式类似于.gitignore
)将/node_modules
和/dist
目录添加到异常中。 现在,您可以通过将格式化规则应用于源代码来prettier
运行:
prettier --write "**/*"
为了避免每次都用手动方式显式运行命令并确保其他项目开发人员的代码也将被格式化,请在precommit-hook
为标记为staged
的文件添加prettier
运行(通过git add
)。 对于这样的事情,我们需要两个工具。 首先,它是hasky ,负责在提交,推送等之前执行任何命令。 其次,它是lint-staged的 ,可以在已staged
文件上运行不同的linter。 我们只需要执行一行就可以交付这些软件包并将启动命令添加到package.json
:
npx mrm lint-staged
我们不能在提交之前等待格式,但是请确保在我们的工作过程中, prettier
文件在修改后的文件中始终有效。 是的,我们需要另一个package- onchange 。 它使您可以监视项目中文件的更改,并立即对其执行必要的命令。 安装:
npm install --save-dev --save-exact onchange
然后,在package.json
添加到scripts
字段命令:
"prettier-watch": "onchange 'src/**/*' -- prettier --write {{changed}}"
因此,有关项目格式的所有争议都可以视为已解决。
避免ESLint错误
ESLint早已成为一种标准,几乎可以在所有js和ts项目中找到。 他也会帮助我们。 在ESLint配置中,我信任CRA,因此只需从CRA中获取必要的软件包并将其插入我们的库即可。 另外,为TS和prettier
配置添加配置(以避免ESLint
和prettier
冲突):
npm install --save-dev eslint eslint-config-react-app eslint-loader eslint-plugin-flowtype eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/eslint-plugin @typescript-eslint/parser babel-eslint eslint-config-prettier eslint-plugin-prettier
使用配置文件ESLint
。
.eslintrc.json { "extends": [ "plugin:@typescript-eslint/recommended", "react-app", "prettier", "prettier/@typescript-eslint" ], "plugins": [ "@typescript-eslint", "react" ], "rules": { "@typescript-eslint/no-empty-interface": "off", "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-member-accessibility": "off" } }
将命令lint
eslint src/**/* --ext .ts,.tsx --fix
到package.json
的scripts
字段。 现在您可以通过npm run lint
运行eslint。
用笑话测试
要编写库组件的单元测试,请安装并配置Jest (来自Facebook的测试库)。 但是,由于 我们不是通过babel 7而是通过tsc编译TS,那么我们也需要安装ts-jest软件包:
npm install --save-dev jest ts-jest @types/jest
为了使Jest正确接受css或其他文件的导入,您需要将其替换为mokami。 创建__mocks__
目录,并在其中创建两个文件。
styleMock.ts
:
module.exports = {};
fileMock.ts
:
module.exports = 'test-file-stub';
现在创建Jest配置。
jest.config.js module.exports = { preset: 'ts-jest', testEnvironment: 'node', moduleNameMapper: { '\\.(css|less|sass|scss)$': '<rootDir>/__mocks__/styleMock.ts', '\\.(gif|ttf|eot|svg)$': '<rootDir>/__mocks__/fileMock.ts' } };
我们将在其目录中为ExampleComponent
编写最简单的测试。
ExampleComponent.test.tsx import { ExampleComponent } from './ExampleComponent'; describe('ExampleComponent', () => { it('is truthy', () => { expect(ExampleComponent).toBeTruthy(); }); });
将test
npm run lint && jest
命令添加到package.json
的scripts
字段。 为了提高可靠性,我们还将驾驶短绒。 现在,您可以运行我们的测试,并确保它们通过npm run test
。 为了使测试不会在汇编过程中掉入dist
,请将Rollup
config插件中的exclude
字段添加到exclude
字段- ['src/**/*.test.(tsx|ts)']
。 在运行lint-staged
之前,在husky pre-commit hook
指定运行测试- "pre-commit": "npm run test && lint-staged"
。
使用故事书设计,记录和欣赏组件
每个图书馆都需要良好的文档才能成功且富有成效地使用。 至于界面组件库,我不仅要阅读它们,而且要看它们的外观,最好触摸和更改它们。 为了支持这样的心愿单,有几种解决方案。 我曾经使用Styleguidist 。 这个包允许您以markdown格式编写文档,并将所描述的React组件的示例插入其中。 此外,还收集了文档,并从中获得了site-showcase-catalog,您可以在其中找到该组件,阅读有关该组件的文档,了解其参数以及向其中戳入一根魔杖。
但是,这次我决定仔细看看他的竞争对手-Storybook 。 如今,其插件系统似乎更加强大。 此外,它不断发展,拥有一个庞大的社区,并且不久还将开始使用markdown文件生成其文档页面。 故事书的另一个优点是它是一个沙箱-用于隔离组件开发的环境。 这意味着我们不需要任何完整的示例应用程序来进行组件开发(如CRL所建议的)。 在故事书中,我们编写stories
-ts文件,在其中将带有一些输入props
组件转移到特殊功能(最好查看代码以使其更清楚)。 结果,从这些stories
构建了一个展示应用程序。
运行初始化故事书的脚本:
npx -p @storybook/cli sb init
现在与TS交朋友。 为此,我们需要更多软件包,同时,我们将添加几个有用的附加组件:
npm install --save-dev awesome-typescript-loader @types/storybook__react @storybook/addon-info react-docgen-typescript-loader @storybook/addon-actions @storybook/addon-knobs @types/storybook__addon-info @types/storybook__addon-knobs webpack-blocks
该脚本创建了一个带有storybook
配置的目录.storybook
并创建了一个我们无情删除的示例目录。 在配置目录中,我们将扩展addons
和config
更改为ts
。 我们将addons添加到addons.ts
文件:
import '@storybook/addon-actions/register'; import '@storybook/addon-links/register'; import '@storybook/addon-knobs/register';
现在,您需要使用.storybook
目录中的webpack配置来帮助故事书。
webpack.config.js module.exports = ({ config }) => { config.module.rules.push({ test: /\.(ts|tsx)$/, use: [ { loader: require.resolve('awesome-typescript-loader') },
让我们对config.ts
配置进行一些调整,添加装饰器以将附加组件连接到我们所有的故事。
配置文件 import { configure } from '@storybook/react'; import { addDecorator } from '@storybook/react'; import { withInfo } from '@storybook/addon-info'; import { withKnobs } from '@storybook/addon-knobs';
我们将在组件目录ExampleComponent
编写第一个story
ExampleComponent.stories.tsx import * as React from 'react'; import { storiesOf } from '@storybook/react'; import { ExampleComponent } from './ExampleComponent'; import { text } from '@storybook/addon-knobs/react'; const stories = storiesOf('ExampleComponent', module); stories.add('ExampleComponent', () => <ExampleComponent text={text('text', 'Some text')} />, { info: { inline: true }, text: ` ### Notes Simple example component ### Usage ~~~js <ExampleComponent text="Some text" /> ~~~ ` });
我们使用了插件:
- 旋钮 -可以实时更改故事书中显示的组件中的道具。 为此,将道具包装在故事的特殊功能中
- 信息 -允许您向故事页面添加道具的文档和描述
现在注意,故事书初始化脚本将storybook命令添加到了我们的package.json
。 使用它来运行npm run storybook
。 故事书将在http://localhost:6006
处组装并运行。 不要忘记在Rollup
config- 'src/**/*.stories.tsx'
为typescript
模块添加例外。
我们正在发展
因此,在为自己准备了许多方便的工具并为工作做好准备之后,就可以开始开发新的组件了。 每个组件将以src/components
名称放在src/components
目录中。 它将包含与之关联的所有文件-css,tsx文件中的组件本身,测试,故事。 我们启动故事书,为组件创建故事,然后在其中编写文档。 我们创建测试并进行测试。 完成组件的进出口记录在index.ts
。
此外,您可以登录npm
并将库作为新的npm软件包发布。 您可以直接从master和其他分支的git存储库中将其连接。 例如,对于我的工件,您可以执行以下操作:
npm i -s git+https://github.com/jmorozov/react-library-example.git
因此,在库使用者应用程序的node_modules
目录中,只有dist
目录的内容处于组装状态,您需要在scripts
字段中添加"prepare": "npm run build"
命令"prepare": "npm run build"
。
另外,由于有了TS,IDE中的自动补全功能就可以使用了。
总结一下
在2019年中,您可以使用便捷的开发工具迅速开始在React和TypeScript上开发组件库。 借助自动化实用程序和手动模式均可获得此结果。 如果需要当前程序包和更多控制权,则首选第二种方法。 最主要的是要知道在哪里进行挖掘,并希望借助本文中的示例,这变得有些容易。
您也可以在此处取出生成的工件。
除其他外,我不假装是终极真理,并且通常来说,我从事前端工作。 您可以选择其他软件包和配置选项,也可以成功创建组件库。 如果您在评论中分享您的食谱,我会很高兴。 编码愉快!