共有コンポーネントライブラリのベストプラクティス


12

共有可能なReactコンポーネントライブラリを作成しています。

ライブラリには多くのコンポーネントが含まれていますが、エンドユーザーはそれらのいくつかを使用するだけで済みます。

コードをWebpack(またはParcelまたはRollup)にバンドルすると、すべてのコードを含む1つのファイルが作成されます

パフォーマンス上の理由から、実際に使用されない限り、すべてのコードをブラウザでダウンロードしたくありません。コンポーネントをバンドルするべきではないと私は思いますか?バンドルはコンポーネントの消費者に任せるべきですか?コンポーネントの消費者に他に何かを残しますか?JSXをトランスパイルするだけですか?

同じリポジトリに多数の異なるコンポーネントが含まれている場合、main.jsには何を含める必要がありますか?


1
私が正しくあなたの質問を理解している場合、あなたはこのようなアプローチを探しています1そのソースコードを見て、あなたは彼らがすべてのコンポーネントだけでなく、個々のものをエクスポートして、クライアントアプリケーションがそのコンポーネント(および輸入は、個々の使用時にいることがわかりますモジュール全体ではなくコンポーネント)webpackはimported、コードに含まれていたファイルのみをプルするため、バンドルサイズが小さくなります。
エドワードチョプリアン

回答:


5

「ベストプラクティス」の方法は数行の回答よりも複雑であるため、この質問は非常に長く詳細な回答に値するため、これは非常に長い回答です。

私たちは3.5年以上社内ライブラリを維持していましたが、その間に2つの方法で解決しました。ライブラリをバンドルする必要があると思います。トレードオフは、ライブラリの大きさに依存します。消費者。

方法1:公開するすべてのエクスポートされたindex.tsファイルを作成し、このファイルでのターゲットロールアップを入力として使用します。ライブラリ全体を単一のindex.jsファイルとindex.cssファイルにバンドルします。ライブラリコードの重複を避けるために、外部依存関係はコンシューマープロジェクトから継承されます。(要旨は設定例の下部に含まれています)

  • 長所:プロジェクトの消費者がルートの相対ライブラリパスからすべてをインポートできるため、消費が簡単 import { Foo, Bar } from "library"
  • 短所:これは決してツリーを揺さぶることはありません。そして人々がESMでこれを行うと言う前に、それはツリーシェイク可能になります。NextJSは現在の段階ではESMをサポートしておらず、多くのプロジェクト設定もサポートしていないため、このビルドをCJSだけにコンパイルするのは依然として良い考えです。誰かがコンポーネントの1つをインポートすると、すべてのコンポーネントのすべてのcssとすべてのJavaScriptが取得されます。

方法2:これは上級ユーザー向けです。すべてのエクスポート用に新しいファイルを作成し、オプション「preserveModules:true」でrollup-plugin-multi-inputを使用して、使用しているcssシステムがどのように使用しているかを確認します。 cssは単一のファイルにマージされませんが、各cssファイルのrequire( "。css")ステートメントは、ロールアップ後に出力ファイル内に残され、そのcssファイルが存在します。

  • 長所:ユーザーが「library / dist / foo」から{Foo}をインポートすると、Fooのコードのみが取得され、Fooのcssのみが取得されます。
  • 短所:この設定では、コンシューマがNextJSを使用してビルド構成でnode_modules require( "。css")ステートメントを処理する必要があります。 next-transpile-modules npmパッケージでます。
  • 警告:あなたがここに見つけることができるプラグイン我々は独自のバベルを使用https://www.npmjs.com/package/babel-plugin-qubicの人ができるようにするためにimport { Foo,Bar } from "library"、その後、バベルとにそれを変換します...
import { Foo } from "library/dist/export/foo"
import { Bar } from "library/dist/export/bar"

実際には両方の方法を使用する複数のロールアップ構成があります。ですから、木を揺るがすことを気にしない図書館の消費者は、"Foo from "library"は、単一のcssファイルをしてインポートするます。また、木を揺さぶって重要なCSSのみを使用する図書館の利用者は、babelプラグインをオンにするだけで済みます。

ベストプラクティスのロールアップガイド:

typescriptを使用しているかどうかにかかわらず"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、クラスなどのすべてのバベルヘルパー関数で自動的にrequire( "..")し、バンドルサイズからさらに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

バベル、ロールアップで問題が発生した場合、またはバンドル/ライブラリについて質問がある場合は、お知らせください。


3

コードをWebpack(またはParcelまたはRollup)にバンドルすると、すべてのコードを含む1つのファイルが作成されます。

パフォーマンス上の理由から、実際に使用されない限り、すべてのコードをブラウザでダウンロードしたくありません

コンポーネントごとに個別のファイルを生成することができます。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

Bitを見てください。これは、コンポーネントを共有、再利用、視覚化するための優れたソリューションだと思います。

セットアップはとても簡単です。あなたはあなたのビットライブラリまたは単にコンポーネントをインストールすることができます:

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

次に、コンポーネントをアプリにインポートします。

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

良い点は、Webpackやその他すべてのジャズの構成について心配する必要がないことです。Bitはコンポーネントのバージョン管理もサポートします。このはタイトルリストの反応コンポーネントを示しているので、これが要件を満たしているかどうかを確認できます


0

チャンクファイルを作成するための構成がwebpackにあります。まず、メインバンドルを複数のチャンクに作成し、必要に応じてロードします。プロジェクトに適切に構造化されたモジュールがある場合、不要なコードは読み込まれません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.