Mejores prácticas de la biblioteca de componentes compartidos


12

Estoy creando una biblioteca de componentes React que se puede compartir.

La biblioteca contiene muchos componentes, pero es posible que el usuario final solo necesite usar algunos de ellos.

Cuando agrupa el código con Webpack (o Parcel o Rollup), crea un solo archivo con todo el código .

Por razones de rendimiento, no quiero que todo el código sea descargado por el navegador a menos que se use realmente. ¿Estoy en lo cierto al pensar que no debo agrupar los componentes? ¿Debería dejarse la agrupación al consumidor de los componentes? ¿Dejo algo más al consumidor de los componentes? ¿Acabo de transpilar el JSX y listo?

Si el mismo repositorio contiene muchos componentes diferentes, ¿qué debería estar en main.js?


1
Si he entendido bien su pregunta que busca un enfoque como éste uno echar un vistazo a su código fuente y verá que exportan todos los componentes, así como los individuales y cuando una aplicación cliente utiliza sus componentes individuales (e importaciones componentes en lugar de todo el módulo) webpack extraerá solo aquellos archivos que estaban importeden el código, disminuyendo así el tamaño del paquete.
Edward Chopuryan

Respuestas:


5

Esta es una respuesta extremadamente larga porque esta pregunta merece una respuesta extremadamente larga y detallada ya que la forma de "mejor práctica" es más complicada que solo una respuesta de pocas líneas.

Mantuve nuestras bibliotecas internas durante más de 3.5 años en ese tiempo. Me decidí por dos maneras, creo que las bibliotecas deberían agruparse. consumidores

Método 1: Cree un archivo index.ts con todo lo que desea exportar expuesto y el paquete acumulativo de destino en este archivo como entrada. Agrupe toda su biblioteca en un solo archivo index.js e index.css; Con dependencias externas heredadas del proyecto del consumidor para evitar la duplicación del código de la biblioteca. (esencia incluida en la parte inferior de la configuración de ejemplo)

  • Pros: fácil de consumir ya que los consumidores del proyecto pueden importar todo desde la ruta de la biblioteca relativa a la raíz import { Foo, Bar } from "library"
  • Contras: Esto nunca será sacudible del árbol; y antes de que la gente diga, haga esto con ESM y será sacudible. NextJS no admite ESM en esta etapa actual y tampoco lo hacen muchas configuraciones de proyecto, por eso sigue siendo una buena idea compilar esta compilación solo para CJS. Si alguien importa 1 de sus componentes, obtendrá todo el CSS y todo el JavaScript para todos sus componentes.

Método 2: Esto es para usuarios avanzados: cree un nuevo archivo para cada exportación y use rollup-plugin-multi-input con la opción "preserveModules: true" dependiendo de cómo el sistema css que esté usando también necesite asegurarse de que su css NO se fusiona en un solo archivo, pero que cada archivo css requiere una declaración (". css") se deja dentro del archivo de salida después del paquete y ese archivo css existe.

  • Pros: Cuando los usuarios importen {Foo} de "library / dist / foo", solo obtendrán el código para Foo, y el CSS para Foo y nada más.
  • Contras: Esta configuración implica que el consumidor que tiene que manejar node_modules requiere declaraciones (". Css") en su configuración de compilación con NextJS, esto se hace con el next-transpile-modulespaquete npm.
  • Advertencia: utilizamos nuestro propio complemento de babel que puede encontrar aquí: https://www.npmjs.com/package/babel-plugin-qubic para permitir que las personas import { Foo,Bar } from "library"y luego con babel lo transformen en ...
import { Foo } from "library/dist/export/foo"
import { Bar } from "library/dist/export/bar"

Tenemos múltiples configuraciones de resumen donde realmente utilizamos ambos métodos; por lo tanto, los consumidores de la biblioteca que no se preocupan por el movimiento de los árboles pueden simplemente hacer "Foo from "library"e importar el archivo CSS único; y para los consumidores de la biblioteca que se preocupan por la sacudida de los árboles y que solo usan CSS crítico, pueden activar nuestro complemento babel.

Guía de resumen para las mejores prácticas:

ya sea que esté utilizando mecanografiado o no SIEMPRE compile con "rollup-plugin-babel": "5.0.0-alpha.1" Asegúrese de que su .babelrc tenga este aspecto.

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

Y con el plugin de babel en rollup con este aspecto ...

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

Y su package.json parece ATLEAST así:

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

Y, finalmente, sus elementos externos en el rollup se ven ATLEAST así.

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.

¿Por qué?

  • Esto agrupará su mierda automáticamente para heredar react / react-dom y sus otras dependencias pares / externas del proyecto del consumidor, lo que significa que no se duplicarán en su paquete.
  • Esto se incluirá en ES5
  • Esto requerirá automáticamente ("..") en todas las funciones auxiliares de babel para objectSpread, clases, etc. DEL proyecto del consumidor que borrará otros 15-25 KB del tamaño de su paquete y significa que las funciones auxiliares para objectSpread no se duplicarán en su biblioteca salida + la salida agrupada de los proyectos consumidores.
  • Las funciones asincrónicas seguirán funcionando
  • los externos coincidirán con todo lo que comience con ese sufijo de dependencia de pares, es decir, babel-helpers coincidirá con los externos para babel-helpers / helpers / object-spread

Finalmente, aquí hay un resumen de un archivo de configuración de resumen de salida de archivo index.js de ejemplo único. https://gist.github.com/ShanonJackson/deb65ebf5b2094b3eac6141b9c25a0e3 Donde el objetivo src / export / index.ts se ve así ...

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

Avíseme si tiene algún problema con babel, rollup o si tiene alguna pregunta sobre agrupación / bibliotecas.


3

Cuando agrupa el código con Webpack (o Parcel o Rollup), crea un solo archivo con todo el código.

Por razones de rendimiento, no quiero que todo el código sea descargado por el navegador a menos que se use realmente

Es posible tener archivos separados generados para cada componente. Webpack tiene esa capacidad al definir múltiples entradas y salidas. Digamos que tiene la siguiente estructura de un proyecto

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

El archivo de paquete web se vería así

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

Más información sobre "división de código" está aquí en los documentos de Webpack

Si el mismo repositorio contiene muchos componentes diferentes, ¿qué debería estar en main.js?

Hay un solo campo en el package.jsonarchivo llamado main, es bueno poner su valor de lib/index.jsacuerdo con la estructura del proyecto anterior. Y en index.jsarchivo tienen todos los componentes exportados. En caso de que el consumidor quiera usar un solo componente, es accesible simplemente haciendo

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

¿Estoy en lo cierto al pensar que no debo agrupar los componentes? ¿Debería dejarse la agrupación al consumidor de los componentes? ¿Dejo algo más al consumidor de los componentes? ¿Acabo de transpilar el JSX y listo?

Bueno, es tu desición. Descubrí que algunas bibliotecas de React se publican de forma original, otras están agrupadas. Si necesita algún proceso de compilación, defínalo y exporte la versión incluida.

Espero que todas tus preguntas sean respondidas :)


Gracias por la respuesta. No quiero tener que actualizar mi configuración de Webpack cada vez que agrego un nuevo componente, como en su ejemplo. "Depende de usted. He descubierto que algunas bibliotecas React se publican de manera original, otras, están agrupadas". Esto está demostrando que no es el caso. La aplicación Create React funcionó con mis componentes desagregados OK, pero Next JS está arrojando un error y claramente solo funciona con componentes empaquetados, quitando la decisión de mis manos.
otw

He hecho todo lo posible para investigar :) "No quiero tener que actualizar mi configuración de Webpack cada vez que agrego un nuevo componente" - es posible usar algún comodín glob para no enumerar todos los componentes, resuelve el problema de actualizar la configuración del paquete web para cada nuevo componente. "El siguiente JS está arrojando un error" - bueno, entonces empaquete su paquete :) obviamente el paquete en bruto funcionaría si solo se incluyera en la agrupación del proyecto del consumidor. La versión incluida funcionará al 100%.
Rashad Ibrahimov

1

Puede dividir sus componentes como lo está haciendo lodash por sus métodos.

Lo que probablemente tenga son componentes separados que podría permitir importar por separado oa través del componente principal.

Entonces el consumidor podría importar el paquete completo

import {MyComponent} from 'my-components';

o sus partes individuales

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

Los consumidores crearán sus propios paquetes basados ​​en los componentes que importen. Eso debería evitar que se descargue todo el paquete.


1

Debería echar un vistazo a Bit , creo que esta es una buena solución para compartir, reutilizar y visualizar componentes.

Es muy fácil de configurar. Puede instalar su biblioteca de bits o simplemente un componente con:

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

Luego puede importar el componente en su aplicación con:

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

Lo bueno es que no tiene que preocuparse por configurar Webpack y todo ese jazz. Bit incluso admite el control de versiones de sus componentes. Este ejemplo muestra un componente de reacción de la lista de títulos para que pueda ver si cumple con sus requisitos o no


0

Hay una configuración en webpack para crear archivos de fragmentos. Para comenzar, creará el paquete principal en varios fragmentos y lo cargará como se requiera. Si su proyecto tiene módulos bien estructurados, no cargará ningún código que no sea necesario.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.