Najważniejsze wskazówki dotyczące biblioteki współdzielonych komponentów

12

Tworzę udostępnioną bibliotekę komponentów React.

Biblioteka zawiera wiele komponentów, ale użytkownik końcowy może potrzebować tylko kilku z nich.

Gdy pakiet jest pakowany w pakiet Webpack (lub Paczkę lub Pakiet), tworzy on jeden plik z całym kodem .

Ze względu na wydajność nie chcę, aby cały ten kod był pobierany przez przeglądarkę, chyba że jest faktycznie używany. Czy mam rację, myśląc, że nie powinienem wiązać komponentów? Czy pakiet należy pozostawić konsumentowi komponentów? Czy pozostawiam coś konsumentowi komponentów? Czy po prostu transponuję JSX i to wszystko?

Jeśli to samo repozytorium zawiera wiele różnych komponentów, co powinno znajdować się w main.js?

otw
źródło
1
Gdybym zrozumiał swoje pytanie poprawnie szukasz podejścia jak ten jeden spojrzeć na ich kodu źródłowego, a zobaczysz, że wywożą one wszystkie komponenty jak również poszczególne z nich i kiedy aplikacja klient używa ich komponenty (i importu poszczególnych komponentów zamiast całego modułu) webpack pobierze tylko te pliki, które były importedw kodzie, zmniejszając w ten sposób rozmiar pakietu.
Edward Chopuryan

Odpowiedzi:

5

To jest bardzo długa odpowiedź, ponieważ to pytanie zasługuje na niezwykle długą i szczegółową odpowiedź, ponieważ metoda „najlepszych praktyk” jest bardziej skomplikowana niż tylko odpowiedź liniowa.

Iv'e utrzymywał nasze biblioteki wewnętrzne przez ponad 3,5 roku w tym czasie iv'e osiedlił się na dwa sposoby. Myślę, że biblioteki powinny być powiązane, kompromisy zależą od tego, jak duża jest twoja biblioteka i osobiście opracowujemy oba sposoby, aby zadowolić oba podzbiory konsumenci.

Metoda 1: Utwórz plik index.ts ze wszystkim, co chcesz wyeksportować, i docelowym pakietem zbiorczym dla tego pliku jako jego danych wejściowych. Spakuj całą bibliotekę w jeden plik index.js i plik index.css; Z zewnętrzną zależnością odziedziczoną po projekcie konsumenckim, aby uniknąć powielania kodu biblioteki. (Gist znajduje się na dole przykładowej konfiguracji)

  • Zalety: Łatwy w użyciu, ponieważ klienci projektu mogą importować wszystko ze ścieżki względnej biblioteki głównej import { Foo, Bar } from "library"
  • Minusy: To nigdy nie będzie wstrząsać drzewem; i zanim ludzie powiedzą, zrób to z ESM, a będzie to możliwe. NextJS nie obsługuje ESM na obecnym etapie i nie ma wielu ustawień projektu, dlatego nadal dobrym pomysłem jest skompilowanie tej kompilacji tylko do CJS. Jeśli ktoś zaimportuje 1 z twoich komponentów, otrzyma wszystkie css i cały javascript dla wszystkich twoich komponentów.

Metoda 2: dla zaawansowanych użytkowników: utwórz nowy plik dla każdego eksportu i użyj wtyczki rollup-plugin-multi-input z opcją „preserveModules: true”, w zależności od tego, jakiego systemu css używasz, aby upewnić się, że Twój plik css NIE jest scalony w jeden plik, ale że każdy plik css wymaga instrukcji („. css”) pozostawia się w pliku wyjściowym po zrolowaniu i ten plik css istnieje.

  • Plusy: gdy użytkownicy zaimportują {Foo} z „biblioteki / dist / foo”, dostaną tylko kod dla Foo i css dla Foo i nic więcej.
  • Wady: Ta konfiguracja wymaga od konsumenta obsługi instrukcji node_modules Require (". Css") w konfiguracji kompilacji z NextJS, odbywa się to za pomocą next-transpile-modulespakietu npm.
  • Uwaga: Używamy naszej własnej wtyczki Babel, którą można znaleźć tutaj: https://www.npmjs.com/package/babel-plugin-qubic, aby umożliwić ludziom, import { Foo,Bar } from "library"a następnie wraz z Babel przekształcić ją w ...
import { Foo } from "library/dist/export/foo"
import { Bar } from "library/dist/export/bar"

Mamy wiele konfiguracji zestawień, w których faktycznie używamy obu metod; więc konsumenci bibliotek, którym nie zależy na wstrząsaniu drzewem, mogą po prostu "Foo from "library"zaimportować pojedynczy plik css; a dla użytkowników bibliotek, którzy troszczą się o drżenie drzew i używają tylko krytycznego css, mogą po prostu włączyć naszą wtyczkę Babel.

Przewodnik po najlepszych praktykach:

czy używasz maszynopisu czy nie ZAWSZE kompiluj z "rollup-plugin-babel": "5.0.0-alpha.1" Upewnij się, że Twój .babelrc wygląda tak.

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

A dzięki wtyczce babel w pakiecie wygląda to tak ...

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

A Twój pakiet.json wygląda ATLEAST tak:

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

I w końcu twoje zewnętrzne wyglądające tak, jakby wyglądały jak KASETA.

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.

Dlaczego?

  • Spowoduje to spakowanie twojego gówna do automatycznego dziedziczenia reagowania / reagowania i innych zależności równorzędnych / zewnętrznych od projektu konsumenckiego, co oznacza, że ​​nie będą duplikowane w twoim pakiecie.
  • Spowoduje to dołączenie do ES5
  • Będzie to automatycznie wymagało („..”) we wszystkich funkcjach pomocniczych babel dla ObjectSpread, klas itp. Z projektu konsumenckiego, który usunie kolejne 15-25 KB z rozmiaru pakietu i oznacza, że ​​funkcje pomocnicze dla ObjectSpread nie będą duplikowane w bibliotece produkcja + projekty konsumenckie w pakiecie produkcja.
  • Funkcje asynchroniczne będą nadal działać
  • zewnętrzne będą pasować do wszystkiego, co zaczyna się od sufiksu zależności rówieśniczej, tzn. pomocnicy-babel będą pasować do zewnętrznych dla pomocników / pomocników / rozprzestrzeniania się obiektów

Na koniec znajduje się streszczenie przykładowego pojedynczego pliku konfiguracyjnego pakietu zbiorczego pliku index.js. https://gist.github.com/ShanonJackson/deb65ebf5b2094b3eac6141b9c25a0e3 Gdzie docelowy plik src / export / index.ts wygląda tak ...

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

Daj mi znać, jeśli napotkasz jakiekolwiek problemy z babel, rollupem lub masz pytania dotyczące tworzenia pakietów / bibliotek.

Shanon Jackson
źródło
3

Gdy pakiet jest pakowany w pakiet Webpack (lub Paczkę lub Pakiet), tworzy on jeden plik z całym kodem.

Ze względu na wydajność nie chcę, aby cały ten kod był pobierany przez przeglądarkę, chyba że jest faktycznie używany

Dla każdego komponentu można wygenerować osobne pliki. Webpack ma taką możliwość, definiując wiele wpisów i wyjść. Załóżmy, że masz następującą strukturę projektu

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

Plik Webpack wyglądałby mniej więcej tak

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

Więcej informacji na temat „dzielenia kodu” znajduje się tutaj w dokumentacji pakietu Webpack

Jeśli to samo repozytorium zawiera wiele różnych komponentów, co powinno znajdować się w main.js?

W package.jsonpliku znajduje się jedno pole main, dlatego warto umieścić jego wartość lib/index.jszgodnie z powyższą strukturą projektu. I w index.jspliku mają wszystkie komponenty wyeksportowane. W przypadku, gdy konsument chce użyć jednego komponentu, jest to możliwe po prostu przez zrobienie tego

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

Czy mam rację, myśląc, że nie powinienem wiązać komponentów? Czy pakiet należy pozostawić konsumentowi komponentów? Czy pozostawiam coś konsumentowi komponentów? Czy po prostu transponuję JSX i to wszystko?

Cóż, to zależy od ciebie. Przekonałem się, że niektóre biblioteki React są publikowane w oryginalny sposób, inne - w pakiecie. Jeśli potrzebujesz procesu kompilacji, zdefiniuj go i wyeksportuj dołączoną wersję.

Mam nadzieję, że na wszystkie twoje pytania odpowiedziano :)

Rashad Ibrahimov
źródło
Dzięki za odpowiedzi. Nie chcę aktualizować konfiguracji pakietu WebPack za każdym razem, gdy dodam nowy komponent, jak w twoim przykładzie. „to zależy od ciebie. Przekonałem się, że niektóre biblioteki React są publikowane w oryginalny sposób, inne - w pakiecie”. Okazuje się, że tak nie jest. Aplikacja Create React działała z moimi uwolnionymi komponentami OK, ale Next JS zgłasza błąd i najwyraźniej działa tylko z komponentami dołączonymi, podejmując decyzję z moich rąk.
otw
Starałem się jak najlepiej zbadać :) „Nie chcę aktualizować konfiguracji pakietu WebPack za każdym razem, gdy dodam nowy komponent” - możliwe jest użycie niektórych symboli glob-wild, aby nie wyświetlać wszystkich składników, to rozwiązuje problem aktualizowanie konfiguracji webpacka dla każdego nowego komponentu. „Następny JS zgłasza błąd” - cóż, a następnie spakuj swój pakiet :) oczywiście pakiet surowy zadziałałby, gdyby został włączony do pakietu z projektu konsumenckiego. Dołączona wersja będzie działać w 100%.
Rashad Ibrahimov
1

Możesz podzielić swoje komponenty tak, jak lodash robi dla swoich metod.

To, co prawdopodobnie posiadasz, to osobne komponenty, które możesz zezwolić na importowanie osobno lub przez główny komponent.

Wtedy konsument mógłby zaimportować cały pakiet

import {MyComponent} from 'my-components';

lub jego poszczególne części

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

Konsumenci będą tworzyć własne pakiety na podstawie importowanych komponentów. To powinno zapobiec pobieraniu całego pakietu.

Bojan Bedrač
źródło
1

Powinieneś spojrzeć na Bit , myślę, że to dobre rozwiązanie do udostępniania, ponownego wykorzystania i wizualizacji komponentów.

Jest bardzo łatwy w konfiguracji. Możesz zainstalować swoją bibliotekę bitów lub tylko komponent z:

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

Następnie możesz zaimportować komponent do swojej aplikacji za pomocą:

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

Zaletą jest to, że nie musisz się martwić konfiguracją Webpacka i całym tym jazzem. Bit obsługuje nawet wersjonowanie twoich komponentów. Ten przykład pokazuje komponent reagowania na listę tytułów, dzięki czemu możesz sprawdzić, czy spełnia twoje wymagania, czy nie

Vicente
źródło
0

W pakiecie internetowym znajduje się konfiguracja do tworzenia plików porcji. Na początek utworzy główny pakiet w wiele porcji i załaduje go tak, jak to konieczne. jeśli twój projekt ma dobrze ustrukturyzowane moduły, nie załaduje żadnego kodu, który nie jest wymagany.

Ireal23
źródło