共享组件库最佳实践

时间:2020-01-24 16:40:06

标签: reactjs npm webpack es6-modules rollup

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

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

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

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

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

5 个答案:

答案 0 :(得分:16)

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

在那段时间里,我将自己的内部图书馆维持了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,仅此而已。
  • 缺点:此设置涉及使用者必须处理node_modules NextJS在其构建配置中的require(“。css”)语句 这是通过next-transpile-modules npm软件包完成的。
  • 注意事项:我们使用我们自己的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
  • 这将自动从消费者项目中的objectSpread,classs等所有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,汇总时遇到任何问题,或者对捆绑/库有任何疑问,请与我联系。

答案 1 :(得分: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 docs

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

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

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

我是否认为我不应该捆绑组件?捆绑产品应由组件的使用者承担吗?我是否还要将其他东西留给组件的使用者?我只需要转换JSX就可以了吗?

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

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

答案 2 :(得分:2)

您可以像lodash一样拆分其组件。

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

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

foo=$(for i in {0..7}; do echo -n "uops_dispatched_port.port_$i|p$i,"; done)
export FOO=${foo%,}

或其各个部分

import {MyComponent} from 'my-components';

消费者将基于他们导入的组件创建自己的捆绑软件。那应该可以防止下载整个软件包。

答案 3 :(得分:1)

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

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

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

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

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

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

答案 4 :(得分:0)

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