共享组件库最佳实践


12

我正在创建一个可共享的React组件库。

该库包含许多组件,但最终用户可能只需要使用其中一些组件。

当您将代码与Webpack(或Parcel或Rollup)捆绑在一起时,它将使用所有代码创建一个文件。

出于性能原因,除非实际使用该代码,否则我不希望浏览器下载所有这些代码。我是否认为我不应该捆绑组件是正确的吗?捆绑产品应该留给组件的使用者使用吗?我是否还要将其他东西留给组件的使用者?我只是编译JSX就是这样吗?

如果同一仓库包含很多不同的组件,那么main.js应该是什么?


1
如果我理解正确你的问题,你正在寻找这样的做法一个看看他们的源代码,你会发现它们导出所有组件以及个别及当客户端应用程序使用的组件(和个人进口组件而不是整个模块),webpack将仅提取imported代码中的那些文件,从而减小捆绑包的大小。
Edward Chopuryan

Answers:


5

这是一个非常漫长的答案,因为该问题值得一个非常漫长而详尽的答案,因为“最佳实践”的方式要比仅几行回答更为复杂。

在那段时间里,我将我们的内部图书馆维持了3.5年以上,我以两种方式解决我认为图书馆应该捆绑在一起的问题,取舍取决于您的图书馆的规模,而我们个人汇编两种方式来取悦两个子集消费者。

方法1:创建一个index.ts文件,将要公开的所有内容导出并以该文件的目标汇总作为输入。将整个库捆绑到单个index.js文件和index.css文件中;用外部依赖项从使用者项目继承来避免重复的库代码。(要点包含在示例配置的底部)

  • 优点:易于使用,因为项目使用者可以从根相对库路径导入所有内容 import { Foo, Bar } from "library"
  • 缺点:这将永远不可摇树。而且在人们说用ESM做到这一点之前,它是可以摇摇欲坠的。NextJS目前不支持ESM,也没有大量项目设置,这就是为什么将其编译为CJS还是一个好主意。如果有人导入您的组件中的1个,他们将获得所有组件的所有CSS和所有javascript。

方法2:适用于高级用户:为每个导出创建一个新文件,并使用rollup-plugin-multi-input和“ preserveModules:true”选项,具体取决于您所使用的CSS系统,还需要确保您的css不会合并到单个文件中,而是在汇总后每个css文件require(“。css”)语句保留在输出文件中并且该css文件存在。

  • 优点:当用户从“ library / dist / foo”导入{Foo}时,他们只会获得Foo的代码,而获得Foo的css等等。
  • 缺点:此设置涉及使用者必须使用NextJS在其构建配置中处理node_modules require(“。css”)语句,这是通过next-transpile-modulesnpm包完成的。
  • 警告:我们使用我们自己的babel插件,您可以在这里找到:https : //www.npmjs.com/package/babel-plugin-qubic以允许人们import { Foo,Bar } from "library"使用babel然后将其转换为...
import { Foo } from "library/dist/export/foo"
import { Bar } from "library/dist/export/bar"

我们有多种汇总配置,实际上我们同时使用两种方法。因此,对于那些不关心摇树的图书馆用户来说,可以这样做"Foo from "library"并导入单个css文件;对于关心树木摇晃并且仅使用关键CSS的图书馆用户,他们只需打开我们的babel插件即可。

最佳实践汇总指南:

无论您使用的是打字稿,还是始终使用生成,"rollup-plugin-babel": "5.0.0-alpha.1" 请确保您的.babelrc如下所示。

{
  "presets": [
    ["@babel/preset-env", {
      "targets": {"chrome": "58", "ie": "11"},
      "useBuiltIns": false
    }],
    "@babel/preset-react",
    "@babel/preset-typescript"
  ],
  "plugins": [
    ["@babel/plugin-transform-runtime", {
      "absoluteRuntime": false,
      "corejs": false,
      "helpers": true,
      "regenerator": true,
      "useESModules": false,
      "version": "^7.8.3"
    }],
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-transform-classes",
    ["@babel/plugin-proposal-optional-chaining", {
      "loose": true
    }]
  ]
}

并在汇总中使用babel插件时看起来像这样...

        babel({
            babelHelpers: "runtime",
            extensions,
            include: ["src/**/*"],
            exclude: "node_modules/**",
            babelrc: true
        }),

您的package.json看起来像ATLEAST:

    "dependencies": {
        "@babel/runtime": "^7.8.3",
        "react": "^16.10.2",
        "react-dom": "^16.10.2",
        "regenerator-runtime": "^0.13.3"
    },
    "peerDependencies": {
        "react": "^16.12.0",
        "react-dom": "^16.12.0",
    }

最后,您的外部汇总看起来像这样的ATLEAST。

const makeExternalPredicate = externalArr => {
    if (externalArr.length === 0) return () => false;
    return id => new RegExp(`^(${externalArr.join('|')})($|/)`).test(id);
};

//... rest of rollup config above external.
    external: makeExternalPredicate(Object.keys(pkg.peerDependencies || {}).concat(Object.keys(pkg.dependencies || {}))),
// rest of rollup config below external.

为什么?

  • 这会将您的屎捆绑在一起,以自动从消费者项目继承react / react-dom和您的其他对等/外部依赖关系,这意味着它们不会在您的捆绑包中重复。
  • 这将捆绑到ES5
  • 这将自动在消费者项目的所有babel帮助器函数中用于objectSpread,类等的babel帮助程序,这将从您的捆绑包大小中擦除另外15-25KB,这意味着objectSpread的辅助函数不会在您的库中重复输出+消耗项目捆绑输出。
  • 异步功能仍然可以使用
  • 外部将匹配以该对等相关性后缀开头的任何内容,即babel-helpers将匹配babel-helpers / helpers / object-spread的外部

最后,这里是一个示例index.js文件输出汇总配置文件的要点。 https://gist.github.com/ShanonJackson/deb65ebf5b2094b3eac6141b9c25a0e3 目标src / export / index.ts如下所示...

export { Button } from "../components/Button/Button";
export * from "../components/Button/Button.styles";

export { Checkbox } from "../components/Checkbox/Checkbox";
export * from "../components/Checkbox/Checkbox.styles";

export { DatePicker } from "../components/DateTimePicker/DatePicker/DatePicker";
export { TimePicker } from "../components/DateTimePicker/TimePicker/TimePicker";
export { DayPicker } from "../components/DayPicker/DayPicker";
// etc etc etc

如果您在使用babel,汇总时遇到任何问题,或者对捆绑/库有任何疑问,请告诉我。


3

当您将代码与Webpack(或Parcel或Rollup)捆绑在一起时,它将使用所有代码创建一个文件。

出于性能方面的考虑,我不希望浏览器下载所有代码,除非实际使用了这些代码

有可能为每个组件生成单独的文件。Webpack通过定义多个条目和输出而具有这种能力。假设您具有以下项目结构

- my-cool-react-components
  - src // Folder contains all source code
    - index.js
    - componentA.js
    - componentB.js
    - ...
  - lib // Folder is generated when build
    - index.js // Contains components all together
    - componentA.js
    - componentB.js
    - ...

Webpack文件看起来像这样

const path = require('path');

module.exports = {
  entry: {
    index: './src/index.js',
    componentA: './src/componentA.js',
    componentB: './src/componentB.js',
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'lib'),
  },
};

有关“代码拆分”的更多信息,请参见 Webpack文档中提供了

如果同一仓库包含很多不同的组件,那么main.js应该是什么?

文件中有一个package.json名为的字段main,最好lib/index.js根据上面的项目结构放置其值。并在index.js文件中导出所有组件。如果消费者想要使用单个组件,只需做一下即可达到

const componentX = require('my-cool-react-components/lib/componentX');

我是否认为我不应该捆绑组件是正确的吗?捆绑产品应该留给组件的使用者使用吗?我是否还要将其他东西留给组件的使用者?我只是编译JSX就是这样吗?

好吧,这取决于您。我发现有些React库是以原始方式发布的,其他的则是以捆绑方式发布的。如果您需要一些构建过程,请对其进行定义并导出捆绑的版本。

希望您的所有问题都能得到回答:)


感谢您的回复。就像您的示例一样,我不想每次添加新组件时都必须更新Webpack配置。“由您决定。我发现有些React库是以原始方式发布的,另一些是以捆绑方式发布的。” 事实证明并非如此。Create React App可以与我的非捆绑组件一起使用,但是,Next JS会引发错误,并且显然仅适用于捆绑组件,这使我无法做出决定。
otw

我已经尽力研究了:)“我不想在每次添加新组件时都必须更新Webpack配置” –可以使用一些通配符而不列出所有组件,从而解决了以下问题为每个新组件更新webpack配置。“下一个JS抛出错误”-好吧,然后捆绑您的包:)显然,如果仅将原始包包含在消费者项目的捆绑包中,原始包就可以工作。捆绑版将100%使用。
Rashad Ibrahimov

1

您可以像lodash一样拆分组件为它们的方法。

您可能拥有的是单独的组件,您可以允许它们分别导入或通过主组件导入。

然后,消费者可以导入整个包装

import {MyComponent} from 'my-components';

或其各个部分

import MyComponent from 'my-components/my-component';

消费者将根据他们导入的组件创建自己的捆绑包。那应该防止您的整个捆绑包被下载。


1

你应该看看,我认为这是共享,重用和可视化组件的好方法。

设置非常容易。您可以使用以下方法安装位库或仅安装组件:

npm i @bit/bit.your-library.components.buttons

然后,您可以使用以下命令将组件导入应用程序:

import Button3 from '@bit/bit.your-library.components.buttons';

好处是您不必担心配置Webpack和所有爵士乐。Bit甚至支持组件的版本控制。此示例显示了标题列表react组件,因此您可以查看它是否满足您的要求


0

webpack中有一个配置可以创建块文件。首先,它将主捆绑包分成多个块,并在需要时加载它。如果您的项目具有结构良好的模块,则不会加载任何不需要的代码。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.