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

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

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

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

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

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

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

ベストアンサー1

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

私は社内ライブラリを 3 年半以上保守してきましたが、その間にライブラリをバンドルする 2 つの方法に落ち着きました。トレードオフはライブラリのサイズによって異なり、個人的には両方の消費者のサブセットを満足させるために両方の方法でコンパイルしています。

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

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

方法 2: これは上級ユーザー向けです。エクスポートごとに新しいファイルを作成し、使用している CSS システムに応じて、オプション「preserveModules: true」を指定して rollup-plugin-multi-input を使用します。また、CSS が 1 つのファイルにマージされず、各 CSS ファイルの requires(".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-qubicimport { 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
    }]
  ]
}

そして、rollup の babel プラグインは次のようになります...

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

そして、package.json は少なくとも次のようになります:

    "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",
    }

そして最後に、ロールアップされた外観は少なくともこのようになります。

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、クラスなどのすべての babel ヘルパー関数に自動的に require("..") が使用されるようになり、バンドル サイズがさらに 15 ~ 25 KB 削減され、objectSpread のヘルパー関数がライブラリ出力とコンシューマー プロジェクトのバンドル出力に重複しなくなります。
  • 非同期関数は引き続き動作します
  • externals は、そのピア依存関係サフィックスで始まるものと一致します。つまり、babel-helpers は、babel-helpers/helpers/object-spread の external と一致します。

最後に、単一の index.js ファイル出力ロールアップ構成ファイルの例の gist を示します。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、rollup で問題が発生した場合、またはバンドル/ライブラリに関して質問がある場合はお知らせください。

おすすめ記事