Cómo crear una librería de componentes con React y StoryBook [REPO]

En el siguiente post, vamos a realizar un tutorial paso a paso para implementar una librería de componentes para React utilizando Storybook y Rollup. He desplegado el proyecto en GitHub Pages para compartir contigo el resultado final:

https://juannunezblasco.github.io/react-rollup-storybook-library-starter/

Cubriremos desde la configuración inicial hasta la publicación y distribución de la librería. Si estás buscando una guía completa que te permita crear y mantener tu propia librería de componentes de React, estás en el lugar correcto.

Puedes ver el proyecto completo en GitHub:

https://github.com/juannunezblasco/react-rollup-storybook-library-starter

Qué es Storybooks

Storybook es una herramienta que permite a los desarrolladores construir componentes de forma aislada, lo que facilita la evaluación y el testing individual de cada uno de ellos.

También permite la creación de documentación interactiva y visual para componentes, lo que hace que sea más fácil para otros desarrolladores (y para ti mismo más adelante) entender cómo funciona cada componente.

Además, Storybook ofrece una gran cantidad de complementos para ampliar sus funcionalidades, por lo que se puede personalizar para satisfacer las necesidades específicas de tu proyecto.

El objetivo final es tener una librería de componentes que utilicen todos los miembros de tu equipo.

Esto hará aumentar la productividad de tu empresa, al mismo tiempo que mejora la consistencia del diseño de tu aplicación.

Componentes a crear en nuestra librería

Crearemos dos componentes sencillos a modo de prueba y que toda aplicación necesita: un botón y un input de un formulario.

// Button.tsx

import React from "react";
import "./Button.scss";

export interface ButtonProps extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>{
  label: string;
}

const Button = ({label, ...others}: ButtonProps) => {
  return <button {...others}>{label}</button>;
};

export default Button;
// Input.tsx

import React from "react";
import "./Input.scss";
import { Input, InputProps } from "antd";

export interface InputTextProps extends InputProps {
    /**
     * 
     */
}

const InputText:React.FC<InputTextProps> = (props: InputTextProps) => {
  return <Input type="text" {...props} />;
};

export default InputText;

Cada uno de nuestros componentes tendrá la misma estructura de carpetas:

  • /tests : archivos con nuestros tests
  • Button.scss : estilos que aplicaremos
  • Button.stories.tsx : implementación específica de StoryBook
  • Button.tsx: componente nativo de React
  • index.ts: archivo de barril dónde exportamos el componente.

Vamos a analizar el archivo con la implementación de StoryBook porque el resto no tienen mucho misterio.

// Button.stories.tsx

import { StoryFn, Meta } from "@storybook/react";
import React from "react";
import Button, { ButtonProps } from "./Button";

// More on default export: <https://storybook.js.org/docs/react/writing-stories/introduction#default-export>
const ExportDefault: Meta<typeof Button> = {
  title: "ReactComponentLibrary/Button",
  component: Button,
};

// More on component templates: <https://storybook.js.org/docs/react/writing-stories/introduction#using-args>
const Template: StoryFn<typeof Button> = (args) => <Button {...args} />;

export const HelloWorld: StoryFn<ButtonProps> = Template.bind({});
// More on args: <https://storybook.js.org/docs/react/writing-stories/args>
HelloWorld.args = {
  label: "Save",
};

export const ClickMe: StoryFn<ButtonProps> = Template.bind({});
ClickMe.args = {
  label: "Click me!",
};

export default ExportDefault;

La constante ExportDefault es dónde incluimos el título y componente en si mismo. Lo tipamos con la interfaz de StoryBook incluyendo nuestro componente como genérico.

Después creamos un template de nuestros componente pasando las propiedades al componente con el operador spread.

Basándonos en este template construiremos todas las variaciones de nuestros componente modificando sus argumentos. Será estos los que veremos reflejamos en nuestra librería de componentes.

Configuración Rollup.js

Rollup.js es una herramienta de agrupamiento de módulos de JavaScript que compila pequeñas piezas de código en algo más grande y más complejo, como una biblioteca o una aplicación.

Nos permite crear módulos de JavaScript que pueden ser importados y utilizados en otros proyectos.

Rollup.js es especialmente útil para su uso con bibliotecas y frameworks como React debido a su eficacia en la eliminación de código muerto y su eficiencia en la carga de módulos.

// rollup.config.js
import terser from '@rollup/plugin-terser';
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "@rollup/plugin-typescript";
import dts from "rollup-plugin-dts";
import postcss from "rollup-plugin-postcss";
import peerDepsExternal from 'rollup-plugin-peer-deps-external';

import pkg from "./package.json" assert { type: 'json' };

export default [
	{
		input: 'src/index.ts',
		output: [
			{
				file: pkg.main,
				format: 'cjs',
				sourcemap: true,
			},
			{
				file: pkg.module,
				format: "esm",
				sourcemap: true,
			},
			/* {
				file: 'dist/bundle.min.js',
				format: 'iife',
				name: 'version',
				plugins: [terser()]
			} */
		],
		plugins: [
			peerDepsExternal(),
			resolve(),
			commonjs(),
			typescript({ tsconfig: "./tsconfig.json" }),
			postcss(),
			terser()
		]
	},
	{
		input: "dist/esm/types/index.d.ts",
		output: [{ file: "dist/index.d.ts", format: "esm" }],
		plugins: [dts()],
		external: [/\\.(css|less|scss)$/],
	},
];

Por ejemplo, en este caso realizamos una configuración para que la librería soporte la implementación utilizando CommonJS y el estandar de Javascript moderno ES6 y posteriores.

El objeto comentado generaría una versión del paquete que se puede ejecutar directamente en un navegador sin necesidad de un módulo cargado. Lo incluyo para que lo tengas en cuenta, pero para el caso que nos ocupa no nos interesa.

Publicación del paquete en npm para compartirlo con tu equipo

En primer lugar tenemos que configurar el packaje.json.

{
  "name": "@juannunezblasco/react-rollup-storybook-library-starter",
  "version": "0.1.0",
  "description": "Template and quick-starter to create modern React libraries using Rollup.",
  "main": "dist/cjs/bundle.js",
  "module": "dist/esm/bundle.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "rimraf dist && rollup -c",
    "dev": "rollup -c -w",
    "test": "jest",
    "lint": "eslint src",
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build",
    "predeploy-storybook": "npm run build-storybook",
    "deploy-storybook": "gh-pages -d storybook-static"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/juannunezblasco/react-rollup-storybook-library-starter.git"
  },
  "author": "juannunezblasco",
  "license": "MIT",
  "keywords": [
    "react-template",
    "react-library",
    "typescript",
    "react",
    "template"
  ],
  "bugs": {
    "url": "<https://github.com/juannunezblasco/react-rollup-storybook-library-starter/issues>"
  },
  "homepage": "<https://juannunezblasco.github.io/react-rollup-storybook-library-starter>",
  "peerDependencies": {
    "antd": "^5.4.0",
    "react": ">=16.8.0",
    "react-dom": ">=16.8.0"
  },
  "devDependencies": {
    ...
  }
}

Incluimos toda la información relativa a nuestro packete:

  • Nombre
  • Descripción
  • Repositorio
  • Rutas
  • Keywords: relevante si es un paquete público.
  • etc.

Al igual que las peerDependencies, que representan las dependencias que esperamos encontrar en la aplicación que vaya a utilizar nuestro paquete.

Una vez terminada nuestra configuración podemos publicar nuestro paquete en npm:

npm publish

Conclusiones

Te recomiendo seguir las instrucciones que he dejado en el README.dm para clonar el repositorio y probar la implementación.

Si te ha servido de ayuda compártelo en tus redes sociales.

Juan Núñez Blaco

Full Stack Developer