启动React和TypeScript组件库


我的大部分工作是写后端,但是前一天有一项任务要在React上启动组件库。 几年前,当React版本与我在前端开发中的经验一样小时,我已经对弹丸采取了一种方法,结果变得笨拙而笨拙。 考虑到当前React生态系统的成熟度以及我不断增长的经验,这次我受到启发,可以轻松便捷地完成所有工作。 结果,我对未来的图书馆感到一片空白,并且为了避免忘记任何东西并将所有东西收集在一个地方,撰写了这篇备忘单文章,这也应该帮助那些不知道从哪里开始的人。 让我们看看我做了什么。


TL / DR:可以在github上查看准备启动的库的代码


这个问题可以从两个方面解决:


  1. 查找现成的入门套件,样板或CLI生成器
  2. 自己收集一切

当您绝对不想处理配置和连接必要的程序包时,第一种方法对于快速入门非常有用。 另外,此选项适用于不知道从哪里开始以及库与常规应用程序之间应该有什么区别的初学者。


最初,我采用第一种方法,但是后来我决定更新依赖关系,并固定几个其他的程序包,然后各种错误和不一致性逐渐减少。 结果,他卷起袖子,自己做了一切。 但是我会提到库生成器。


创建反应库


大多数处理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 

我们回答提出的问题。

CLR问题


通过该实用程序,我们得到了一个已生成且可以立即使用的组件库项目。


具有一定的结构

Clr树


然后出了点问题...

今天的依赖关系有些过时,因此我决定使用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.tsreact-scripts使用Babel 7的局限性来编译TS ) 因此,我ejectreact-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 reactreact-dom peerDependencies最低要求版本。


现在,将reactreact-dom软件包以及必要的类型(因为我们将在TypeScript上看到组件)安装为devDependencies(类似于本文中的所有软件包):


 npm install --save-dev react react-dom @types/react @types/react-dom 

安装TypeScript:


 npm install --save-dev typescript 

让我们为主要代码和测试创建配置文件: tsconfig.jsontsconfig.test.json 。 我们的target将在es5 ,我们将生成sourceMap等。 可能的选项及其含义的完整列表可以在文档中找到。 不要忘记在include源目录,并在node_modules添加node_modulesdist目录。 在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的期望-在哪个模块中以格式进行编译以及将其放置在何处。


接下来是带有插件列表和配置的字段
  • rollup-plugin-peer-deps-external-允许您从bundle排除peerDependencies以减小其大小。 这是合理的,因为期望库用户使用peerDependencies
  • rollup-plugin-postcss-集成PostCss和Rollup。 在这里,我们禁用css-modules,将css包含在库中的export包中,将其最小化并启用sourceMap的创建。 如果不导出库组件使用的CSS以外的任何CSS,则可以避免extract -组件中必要的CSS将根据需要最终添加到页面的head标签中。 但是,在我的情况下,有必要分发一些其他的CSS(网格,颜色等),并且客户端将必须将CSS捆绑库显式连接到其自身。
  • rollup-plugin-url-允许您导出各种资源,例如图片
  • svgr-将svg转换为React组件
  • rollup-plugin-node- resolve-定义第三方模块在node_modules中的位置
  • rollup-plugin-typescript2-连接TypeScript编译器并提供对其进行配置的功能
  • rollup-plugin-commonjs-将commonjs依赖项模块转换为es模块,以便可以将它们包含在bundle中

scripts package.json字段中添加一个命令以进行构建( "build": "rollup -c" )并在开发过程中以监视模式启动该程序集( "start": "rollup -c -w && npm run prettier-watch" ) 。


第一个组件和导出文件


现在,我们将编写最简单的react组件来检查我们的程序集如何工作。 库中的每个组件都将放置在父目录src/components/ExampleComponent的单独目录中。 该目录将包含与组件相关的所有文件test.tsxcsstest.tsx等。
让我们为组件创建一些样式文件,并为组件本身创建tsx文件。


ExampleComponent.tsx
 /** * @class ExampleComponent */ import * as React from 'react'; import './ExampleComponent.css'; export interface Props { /** * Simple text prop **/ text: string; } /** My First component */ 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处窥视)。


打字
 /** * Default CSS definition for typescript, * will be overridden with file-specific definitions by rollup */ 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目录中找到。


接下来,我们添加了一些工具来改善我们的开发过程及其结果的质量。


忘记使用Prettier进行代码格式化


我讨厌在代码审查中指出格式化对于项目来说是粗心的或非标准的,甚至对此争论不休。 这些缺陷自然应该得到修复,但是开发人员应该专注于代码的功能和方式,而不是外观。 这些修补程序是自动化的第一个候选对象。 这个漂亮的程序包非常漂亮 。 安装它:


 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配置添加配置(以避免ESLintprettier冲突):


 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 --fixpackage.jsonscripts字段。 现在您可以通过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.jsonscripts字段。 为了提高可靠性,我们还将驾驶短绒。 现在,您可以运行我们的测试,并确保它们通过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并创建了一个我们无情删除的示例目录。 在配置目录中,我们将扩展addonsconfig更改为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') }, // Optional { loader: require.resolve('react-docgen-typescript-loader') } ] }); config.resolve.extensions.push('.ts', '.tsx'); return config; }; 

让我们对config.ts配置进行一些调整,添加装饰器以将附加组件连接到我们所有的故事。


配置文件
 import { configure } from '@storybook/react'; import { addDecorator } from '@storybook/react'; import { withInfo } from '@storybook/addon-info'; import { withKnobs } from '@storybook/addon-knobs'; // automatically import all files ending in *.stories.tsx const req = require.context('../src', true, /\.stories\.tsx$/); function loadStories() { req.keys().forEach(req); } configure(loadStories, module); addDecorator(withInfo); addDecorator(withKnobs); 

我们将在组件目录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上开发组件库。 借助自动化实用程序和手动模式均可获得此结果。 如果需要当前程序包和更多控制权,则首选第二种方法。 最主要的是要知道在哪里进行挖掘,并希望借助本文中的示例,这变得有些容易。


您也可以在此处取出生成的工件。


除其他外,我不假装是终极真理,并且通常来说,我从事前端工作。 您可以选择其他软件包和配置选项,也可以成功创建组件库。 如果您在评论中分享您的食谱,我会很高兴。 编码愉快!

Source: https://habr.com/ru/post/zh-CN461439/


All Articles