Thư viện thành phần chia sẻ thực tiễn tốt nhất


12

Tôi đang tạo một thư viện thành phần React có thể chia sẻ.

Thư viện chứa nhiều thành phần nhưng người dùng cuối có thể chỉ cần sử dụng một vài trong số họ.

Khi bạn gói mã bằng Webpack (hoặc Parcel hoặc Rollup), nó sẽ tạo một tệp duy nhất với tất cả mã .

Vì lý do hiệu suất, tôi không muốn tất cả mã đó được trình duyệt tải xuống trừ khi nó thực sự được sử dụng. Tôi có đúng khi nghĩ rằng tôi không nên bó các thành phần? Có nên để lại cho người tiêu dùng của các thành phần? Tôi có để lại bất cứ điều gì khác cho người tiêu dùng của các thành phần? Tôi có chỉ cần dịch mã JSX không?

Nếu cùng một repo chứa nhiều thành phần khác nhau, thì nên có gì trong main.js?


1
Nếu tôi hiểu câu hỏi của bạn một cách chính xác bạn đang tìm kiếm một cách tiếp cận như thế này một hãy nhìn vào mã nguồn của họ và bạn sẽ thấy rằng họ xuất khẩu tất cả các thành phần cũng như những cá nhân và khi một ứng dụng client sử dụng các thành phần của họ (và nhập khẩu cá nhân các thành phần thay vì toàn bộ mô-đun) webpack sẽ chỉ kéo các tệp có importedtrong mã do đó làm giảm kích thước gói.
Edward Chopuryan

Câu trả lời:


5

Đây là một câu trả lời cực kỳ dài bởi vì câu hỏi này xứng đáng có một câu trả lời cực kỳ dài và chi tiết vì cách "thực hành tốt nhất" phức tạp hơn chỉ là một vài câu trả lời.

Iv'e đã ​​duy trì các thư viện nội bộ của chúng tôi trong hơn 3,5 năm trong thời gian đó. Họ đã giải quyết theo hai cách tôi nghĩ rằng các thư viện nên được kết hợp với sự đánh đổi tùy thuộc vào thư viện của bạn lớn như thế nào và cá nhân chúng tôi biên dịch cả hai cách để làm hài lòng cả hai tập hợp con người tiêu dùng.

Phương pháp 1: Tạo tệp index.ts với mọi thứ bạn muốn được hiển thị và cuộn mục tiêu tại tệp này làm đầu vào. Gói toàn bộ thư viện của bạn vào một tệp index.js và tệp index.css; Với sự phụ thuộc bên ngoài được kế thừa từ dự án tiêu dùng để tránh trùng lặp mã thư viện. (ý chính bao gồm ở dưới cùng của cấu hình ví dụ)

  • Ưu điểm: Dễ tiêu thụ vì người tiêu dùng dự án có thể nhập mọi thứ từ đường dẫn thư viện gốc import { Foo, Bar } from "library"
  • Nhược điểm: Điều này sẽ không bao giờ bị rung chuyển cây; và trước khi mọi người nói làm điều này với ESM và nó sẽ được treeshakizable. NextJS không hỗ trợ ESM ở giai đoạn hiện tại và cũng không có nhiều thiết lập dự án, đó là lý do tại sao vẫn nên biên dịch bản dựng này cho chỉ CJS. Nếu ai đó nhập 1 trong các thành phần của bạn, họ sẽ nhận được tất cả css và tất cả javascript cho tất cả các thành phần của bạn.

Phương pháp 2: Cách này dành cho người dùng nâng cao: Tạo một tệp mới cho mỗi lần xuất và sử dụng rollup-plugin-multi-input với tùy chọn "reservedModules: true" tùy thuộc vào cách hệ thống css bạn đang sử dụng cũng cần đảm bảo rằng css của bạn KHÔNG được hợp nhất thành một tệp duy nhất nhưng mỗi tệp css yêu cầu câu lệnh (". css") được để lại bên trong tệp đầu ra sau khi cuộn và tệp css đó tồn tại.

  • Ưu điểm: Khi người dùng nhập {Foo} từ "library / dist / foo", họ sẽ chỉ nhận được mã cho Foo và css cho Foo và không có gì nữa.
  • Nhược điểm: Thiết lập này liên quan đến việc người tiêu dùng phải xử lý các câu lệnh node_modules yêu cầu (". Css") trong cấu hình xây dựng của họ với NextJS, việc này được thực hiện với next-transpile-modulesgói npm.
  • Hãy cẩn thận: Chúng tôi sử dụng plugin babel của riêng mình mà bạn có thể tìm thấy ở đây: https://www.npmjs.com/package/babel-plugin-qubic để cho phép mọi người import { Foo,Bar } from "library"và sau đó với babel chuyển đổi nó thành ...
import { Foo } from "library/dist/export/foo"
import { Bar } from "library/dist/export/bar"

Chúng tôi có nhiều cấu hình rollup trong đó chúng tôi thực sự sử dụng cả hai phương pháp; vì vậy, đối với những người tiêu dùng thư viện không quan tâm đến việc rung cây, chỉ có thể thực hiện "Foo from "library"và nhập tệp css duy nhất; và đối với những người tiêu dùng thư viện chăm sóc cây lắc và chỉ sử dụng css quan trọng, họ có thể bật plugin babel của chúng tôi.

Hướng dẫn cuộn cho thực hành tốt nhất:

cho dù bạn đang sử dụng bản in hay không LUÔN LUÔN xây dựng với "rollup-plugin-babel": "5.0.0-alpha.1" Đảm bảo .babelrc của bạn trông như thế này.

{
  "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
    }]
  ]
}

Và với plugin babel trong rollup trông như thế này ...

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

Và gói.json của bạn trông ATLEAST như thế này:

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

Và cuối cùng, phần bên ngoài của bạn trong rollup trông ATLEAST như thế này.

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.

Tại sao?

  • Điều này sẽ bó shit của bạn để tự động kế thừa Reac / Reac-dom và các phụ thuộc ngang hàng / bên ngoài khác của bạn từ dự án tiêu dùng có nghĩa là chúng sẽ không bị trùng lặp trong gói của bạn.
  • Điều này sẽ đi kèm với ES5
  • Điều này sẽ tự động yêu cầu ("..") trong tất cả các hàm của trình trợ giúp babel cho objectS rải, các lớp, v.v ... TỪ dự án tiêu dùng sẽ xóa 15-25KB khác từ kích thước gói của bạn và có nghĩa là các hàm trợ giúp cho objectS Lan sẽ không được sao chép trong thư viện của bạn đầu ra + các dự án tiêu thụ đi kèm đầu ra.
  • Các chức năng Async vẫn sẽ hoạt động
  • bên ngoài sẽ phù hợp với bất cứ điều gì bắt đầu với hậu tố phụ thuộc ngang hàng đó, ví dụ như người trợ giúp babel sẽ phù hợp với bên ngoài cho người trợ giúp / người giúp đỡ / người truyền bá đối tượng

Cuối cùng, đây là một ý chính cho một ví dụ về tệp cấu hình cuộn lên của tệp index.js. https://gist.github.com/ShanonJackson/deb65ebf5b2094b3eac6141b9c25a0e3 Trường hợp src / export / index.ts mục tiêu trông như thế này ...

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

Hãy cho tôi biết nếu bạn gặp bất kỳ vấn đề nào với babel, rollup hoặc có bất kỳ câu hỏi nào về gói / thư viện.


3

Khi bạn gói mã bằng Webpack (hoặc Parcel hoặc Rollup), nó sẽ tạo một tệp duy nhất với tất cả mã.

Vì lý do hiệu suất, tôi không muốn tất cả mã đó được trình duyệt tải xuống trừ khi nó thực sự được sử dụng

Có thể có các tệp riêng biệt được tạo cho từng thành phần. Webpack có khả năng như vậy bằng cách xác định nhiều mục và đầu ra. Giả sử bạn có cấu trúc sau của một dự án

- 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
    - ...

Tệp Webpack sẽ trông giống như thế này

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'),
  },
};

Thông tin thêm về "tách mã" có ở đây trong tài liệu Webpack

Nếu cùng một repo chứa nhiều thành phần khác nhau, thì nên có gì trong main.js?

Có một trường duy nhất trong package.jsontệp có tên main, thật tốt khi đặt giá trị của nó lib/index.jstheo cấu trúc dự án ở trên. Và trong index.jstập tin có tất cả các thành phần xuất khẩu. Trong trường hợp người tiêu dùng muốn sử dụng một thành phần duy nhất có thể truy cập bằng cách thực hiện đơn giản

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

Tôi có đúng khi nghĩ rằng tôi không nên bó các thành phần? Có nên để lại cho người tiêu dùng của các thành phần? Tôi có để lại bất cứ điều gì khác cho người tiêu dùng của các thành phần? Tôi có chỉ cần dịch mã JSX không?

Tốt thôi bạn muốn sao cũng được. Tôi đã thấy rằng một số thư viện React được xuất bản theo cách ban đầu, một số khác - theo cách được gói. Nếu bạn cần một số quy trình xây dựng, sau đó xác định nó và xuất phiên bản đi kèm.

Hy vọng, tất cả các câu hỏi của bạn đã được trả lời :)


Cảm ơn vì sự trả lời. Tôi không muốn phải cập nhật cấu hình Webpack của mình mỗi lần tôi thêm một thành phần mới, như trong ví dụ của bạn. "tùy bạn. Tôi thấy rằng một số thư viện React được xuất bản theo cách ban đầu, một số khác - theo cách được gói." Điều này được chứng minh là không phải là trường hợp. Tạo Ứng dụng React hoạt động với các thành phần chưa được xử lý của tôi OK, nhưng Next JS đang gặp lỗi và rõ ràng chỉ hoạt động với các thành phần được đóng gói, đưa ra quyết định từ tay tôi.
OTW

Tôi đã cố gắng hết sức để nghiên cứu :) "Tôi không muốn phải cập nhật cấu hình Webpack của mình mỗi lần tôi thêm một thành phần mới" - có thể sử dụng một số thẻ đại diện toàn cầu để không liệt kê tất cả các thành phần, nó giải quyết vấn đề của cập nhật cấu hình webpack cho mọi thành phần mới. "Tiếp theo JS đang xảy ra lỗi" - tốt, sau đó gói gói của bạn :) rõ ràng gói thô sẽ hoạt động nếu chỉ được đưa vào gói từ dự án tiêu dùng. Phiên bản đi kèm sẽ hoạt động 100%.
Rashad Ibrahimov

1

Bạn có thể phân chia các thành phần của mình như lodash đang thực hiện cho các phương thức của chúng.

Những gì bạn có thể có là các thành phần riêng biệt mà bạn có thể cho phép nhập riêng hoặc thông qua thành phần chính.

Sau đó, người tiêu dùng có thể nhập toàn bộ gói

import {MyComponent} from 'my-components';

hoặc các bộ phận riêng lẻ của nó

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

Người tiêu dùng sẽ tạo ra các gói riêng dựa trên các thành phần họ nhập. Điều đó sẽ ngăn chặn toàn bộ gói của bạn được tải xuống.


1

Bạn nên xem qua Bit , tôi nghĩ đây là một giải pháp tốt để chia sẻ, tái sử dụng và trực quan hóa các thành phần.

Nó rất dễ dàng để thiết lập. Bạn có thể cài đặt thư viện bit của mình hoặc chỉ một thành phần với:

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

Sau đó, bạn có thể nhập thành phần trong ứng dụng của mình với:

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

Phần tốt là bạn không phải lo lắng về việc định cấu hình Webpack và tất cả nhạc jazz đó. Bit thậm chí còn hỗ trợ phiên bản các thành phần của bạn. Ví dụ này cho thấy một thành phần phản ứng danh sách tiêu đề để bạn có thể xem nếu điều này có đáp ứng yêu cầu của bạn hay không


0

Có một cấu hình trong webpack để tạo các tệp chunk. Để bắt đầu, nó sẽ tạo ra gói chính thành nhiều khối và tải nó như khi cần. nếu dự án của bạn có các mô-đun có cấu trúc tốt, nó sẽ không tải bất kỳ mã nào không cần thiết.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.