Best practice per la libreria di componenti condivisi


12

Sto creando una libreria di componenti React condivisibile.

La libreria contiene molti componenti ma l'utente finale potrebbe averne solo bisogno per utilizzarne alcuni.

Quando si raggruppa il codice con Webpack (o Parcel o Rollup), viene creato un singolo file con tutto il codice .

Per motivi di prestazioni, non voglio che tutto quel codice venga scaricato dal browser a meno che non sia effettivamente utilizzato. Ho ragione nel pensare che non dovrei raggruppare i componenti? Il raggruppamento dovrebbe essere lasciato al consumatore dei componenti? Lascio qualcos'altro al consumatore dei componenti? Devo solo traspilare JSX e basta?

Se lo stesso repository contiene molti componenti diversi, quale dovrebbe essere in main.js?


1
Se ho capito bene la tua domanda siete alla ricerca di un approccio come questo uno dare un'occhiata al loro codice sorgente e vedrete che esportano tutti i componenti, oltre a quelli individuali e quando un'applicazione client utilizza componentistica (e importazioni individuali componenti anziché l'intero modulo) webpack estrarrà solo i file presenti importednel codice, riducendo così la dimensione del bundle.
Edward Chopuryan,

Risposte:


5

Questa è una risposta estremamente lunga perché questa domanda merita una risposta estremamente lunga e dettagliata in quanto il modo "best practice" è più complicato di una risposta di poche righe.

Ho mantenuto le nostre librerie in-house per oltre 3,5 anni in quel momento, ho optato per due modi in cui penso che le biblioteche dovrebbero essere raggruppate, i compromessi dipendono da quanto è grande la tua biblioteca e personalmente compiliamo entrambi i modi per soddisfare entrambi i sottogruppi di consumatori.

Metodo 1: Creare un file index.ts con tutto ciò che si desidera venga esportato esposto e destinazione rollup in questo file come input. Raggruppa l'intera libreria in un singolo file index.js e file index.css; Con la dipendenza esterna ereditata dal progetto del consumatore per evitare la duplicazione del codice della libreria. (sintesi inclusa nella parte inferiore della configurazione di esempio)

  • Pro: facile da consumare poiché i consumatori del progetto possono importare tutto dal percorso della libreria relativa radice import { Foo, Bar } from "library"
  • Contro: questo non sarà mai agitabile; e prima che la gente dica di farlo con ESM e sarà alberabile. NextJS non supporta ESM in questa fase attuale e nemmeno molte configurazioni del progetto, ecco perché è comunque una buona idea compilare questa build solo per CJS. Se qualcuno importa 1 dei tuoi componenti otterrà tutti i CSS e tutti i JavaScript per tutti i tuoi componenti.

Metodo 2: Questo è per utenti esperti: crea un nuovo file per ogni esportazione e usa rollup-plugin-multi-input con l'opzione "preservModules: true" a seconda di quale sistema css stai usando devi anche assicurarti che il tuo css NON viene unito in un singolo file ma ogni file css richiede che l'istruzione (". css") venga lasciata nel file di output dopo il rollup e che il file css esista.

  • Pro: Quando gli utenti importano {Foo} da "library / dist / foo" otterranno solo il codice per Foo, i CSS per Foo e nient'altro.
  • Contro: Questa configurazione prevede che il consumatore debba gestire le istruzioni (". Css") di node_modules nella configurazione di build con NextJS, questo viene fatto con il next-transpile-modulespacchetto npm.
  • Avvertenza: utilizziamo il nostro plug-in babel che puoi trovare qui: https://www.npmjs.com/package/babel-plugin-qubic per consentire alle persone di import { Foo,Bar } from "library"trasformarlo in ...
import { Foo } from "library/dist/export/foo"
import { Bar } from "library/dist/export/bar"

Abbiamo diverse configurazioni di rollup in cui utilizziamo effettivamente entrambi i metodi; così per i consumatori di biblioteche che non si preoccupano di scuotere l'albero possono semplicemente fare "Foo from "library"e importare il singolo file CSS; e per i consumatori di biblioteche che si preoccupano di scuotere gli alberi e di usare solo i CSS critici possono semplicemente attivare il nostro plug-in babel.

Guida al rollup per le migliori pratiche:

indipendentemente dal fatto che tu stia usando dattiloscritto o meno SEMPRE build con "rollup-plugin-babel": "5.0.0-alpha.1" Assicurati che il tuo .babelrc assomigli a questo.

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

E con il plugin babel in rollup che assomiglia a questo ...

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

E il tuo package.json sembra ATLEAST in questo modo:

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

E infine i tuoi esterni nel rollup sembrano ATLEAST così.

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.

Perché?

  • Questo raggrupperà la tua merda per ereditare automaticamente la reazione / reazione-dom e le altre dipendenze peer / esterne dal progetto del consumatore, il che significa che non saranno duplicati nel tuo bundle.
  • Questo verrà raggruppato in ES5
  • Ciò richiederà automaticamente ("..") in tutte le funzioni di babel helper per objectSpread, classi ecc. DAL progetto del consumatore che eliminerà altri 15-25 KB dalle dimensioni del pacchetto e significherà che le funzioni di helper per objectSpread non verranno duplicate nella libreria output + output dei progetti di consumo in bundle.
  • Le funzioni asincrone continueranno a funzionare
  • gli esterni corrisponderanno a tutto ciò che inizia con quel suffisso di dipendenza tra pari, cioè gli babel-helper corrisponderanno a quelli esterni per babel-helper / helper / object-spread

Infine ecco un esempio per un esempio di file di configurazione di rollup di output del file index.js singolo. https://gist.github.com/ShanonJackson/deb65ebf5b2094b3eac6141b9c25a0e3 Dove il target src / export / index.ts si presenta così ...

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

Fammi sapere se riscontri problemi con babel, rollup o hai domande sul raggruppamento / sulle librerie.


3

Quando si raggruppa il codice con Webpack (o Parcel o Rollup), viene creato un singolo file con tutto il codice.

Per motivi di prestazioni, non voglio che tutto quel codice venga scaricato dal browser a meno che non sia effettivamente utilizzato

È possibile generare file separati per ciascun componente. Webpack ha tale capacità definendo più voci e output. Supponiamo che tu abbia la seguente struttura di un progetto

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

Il file Webpack sarebbe simile a questo

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

Ulteriori informazioni sulla "suddivisione del codice" sono disponibili nei documenti Webpack

Se lo stesso repository contiene molti componenti diversi, quale dovrebbe essere in main.js?

C'è un singolo campo nel package.jsonfile chiamato main, è bene mettere il suo valore in lib/index.jsbase alla struttura del progetto sopra. E nel index.jsfile sono stati esportati tutti i componenti. Nel caso in cui il consumatore desideri utilizzare un singolo componente è raggiungibile semplicemente

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

Ho ragione nel pensare che non dovrei raggruppare i componenti? Il raggruppamento dovrebbe essere lasciato al consumatore dei componenti? Lascio qualcos'altro al consumatore dei componenti? Devo solo traspilare JSX e basta?

Bene, dipende da te. Ho scoperto che alcune librerie di React sono pubblicate in modo originale, altre - sono in bundle. Se hai bisogno di un processo di compilazione, definiscilo ed esporta la versione in bundle.

Spero che tutte le tue domande abbiano una risposta :)


Grazie per la risposta. Non voglio aggiornare la mia configurazione Webpack ogni volta che aggiungo un nuovo componente, come nel tuo esempio. "Dipende da te. Ho scoperto che alcune librerie di React sono pubblicate in modo originale, altre - sono in bundle." Questo sta dimostrando di non essere il caso. Crea React App ha funzionato con i miei componenti non raggruppati OK, ma Next JS sta generando un errore e chiaramente funziona solo con i componenti raggruppati, prendendo la decisione dalle mie mani.
otw

Ho fatto del mio meglio per ricercare :) "Non voglio aggiornare la mia configurazione di Webpack ogni volta che aggiungo un nuovo componente" - è possibile utilizzare alcuni caratteri jolly per non elencare tutti i componenti, risolve il problema di aggiornamento della configurazione del webpack per ogni nuovo componente. "Il prossimo JS sta generando un errore" - beh, quindi raggruppa il tuo pacchetto :) ovviamente il pacchetto grezzo funzionerebbe se incluso solo nel raggruppamento dal progetto del consumatore. La versione in bundle funzionerà al 100%.
Rashad Ibrahimov,

1

Puoi dividere i tuoi componenti come sta facendo lodash per i loro metodi.

Quello che probabilmente hai sono componenti separati che potresti consentire di importare separatamente o tramite il componente principale.

Quindi il consumatore potrebbe importare l'intero pacchetto

import {MyComponent} from 'my-components';

o le sue singole parti

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

I consumatori creeranno i propri pacchetti in base ai componenti che importano. Ciò dovrebbe impedire il download dell'intero pacchetto.


1

Dovresti dare un'occhiata a Bit , penso che questa sia una buona soluzione per condividere, riutilizzare e visualizzare i componenti.

È molto facile da configurare. Puoi installare la tua libreria di bit o solo un componente con:

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

Quindi puoi importare il componente nella tua app con:

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

La parte buona è che non devi preoccuparti di configurare Webpack e tutto quel jazz. Bit supporta anche il controllo delle versioni dei componenti. Questo esempio mostra un componente di reazione dell'elenco titoli in modo da poter dare un'occhiata se questo soddisfa i tuoi requisiti o meno


0

Esiste una configurazione nel webpack per creare file di blocchi. Per cominciare, creerà il pacchetto principale in più blocchi e lo caricherà come richiesto. se il tuo progetto ha moduli ben strutturati, non caricherà alcun codice che non è richiesto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.