DevTalles · fernando-herrera.com
Guía completa de configuración y despliegue
Stack: React · TypeScript · CSS Modules · Vitest · Rollup
- Crear el proyecto
- Instalar dependencias
- Configurar package.json
- Estructura de carpetas
- Declaraciones de tipos — css-modules.d.ts
- Crear componentes .tsx
- Punto de entrada — src/index.ts
- Configurar tsconfig.json
- Configurar rollup.config.mjs
- Configurar Vitest
- Primer test
- Crear .npmignore
- Build del paquete
- Prueba local con npm link
- Publicar en NPM
- Usar la librería en tu proyecto
Crea la carpeta del proyecto e inicializa el package.json.
mkdir mi-libreria-ui
cd mi-libreria-ui
npm init -yBundler y plugins base
npm install --save-dev rollup @rollup/plugin-node-resolve
npm install --save-dev @rollup/plugin-commonjs rollup-plugin-peer-deps-externalTypeScript
npm install --save-dev typescript @rollup/plugin-typescript tslibCSS Modules
npm install --save-dev rollup-plugin-postcssImágenes y assets (jpg, png, svg, gif)
npm install --save-dev @rollup/plugin-urlTesting
npm install --save-dev vitest jsdomReact como devDependency
npm install --save-dev react react-dom @types/react @types/react-dom{
"name": "@tu-usuario/mi-libreria-ui",
"version": "1.0.0",
"description": "Mi librería de componentes React",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"types": "dist/types/index.d.ts",
"files": ["dist"],
"scripts": {
"test": "vitest run",
"test:watch": "vitest watch",
"build": "rollup -c rollup.config.mjs"
},
"peerDependencies": {
"react": ">=17.0.0",
"react-dom": ">=17.0.0"
}
}
⚠️ React debe ir enpeerDependencies, NO endependencies, para evitar instancias duplicadas.
mi-libreria-ui/
src/
components/
Button/
Button.tsx
Button.module.css
index.ts
Card/
Card.tsx
Card.module.css
index.ts
tests/
math.test.ts
index.ts
css-modules.d.ts
package.json
rollup.config.mjs
vitest.config.ts
tsconfig.json
.npmignore
src/css-modules.d.ts
Le indica a TypeScript cómo interpretar importaciones de CSS Modules e imágenes.
declare module '*.module.css' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.jpg' {
const src: string;
export default src;
}
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.svg' {
const src: string;
export default src;
}src/components/Button/Button.tsx
import React from 'react';
import styles from './Button.module.css';
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
}
const Button: React.FC<ButtonProps> = ({
label, onClick, variant = 'primary'
}) => {
return (
<button onClick={onClick} className={styles[variant]}>
{label}
</button>
);
};
export default Button;src/components/Button/index.ts
export { default } from './Button';src/index.ts
Exporta todos los componentes que el usuario final podrá importar desde la librería.
export { default as Button } from './components/Button';
export { default as Card } from './components/Card';{
"compilerOptions": {
"target": "ES2017",
"module": "ESNext",
"lib": ["ES2017", "DOM"],
"jsx": "react-jsx",
"declaration": true,
"declarationDir": "dist/types",
"emitDeclarationOnly": false,
"strict": true,
"rootDir": "./src",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": ["vitest/globals"],
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}✅
"types": ["vitest/globals"]permite usardescribe,testyexpectsin importarlos explícitamente en cada archivo de test.
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import peerDeps from 'rollup-plugin-peer-deps-external';
import postcss from 'rollup-plugin-postcss';
import url from '@rollup/plugin-url';
export default {
input: 'src/index.ts',
output: [
{ file: 'dist/index.cjs.js', format: 'cjs', sourcemap: true },
{ file: 'dist/index.esm.js', format: 'esm', sourcemap: true },
],
plugins: [
peerDeps(), // maneja dependencias externas
url({ include: ['**/*.jpg', '**/*.png', '**/*.svg', '**/*.gif'] }), // maneja archivos de imagen
postcss({ modules: true, inject: true }), // habilita CSS Modules
resolve(), // resuelve rutas de archivos
commonjs(), // convierte CommonJS a ES6
typescript({
tsconfig: './tsconfig.json',
declaration: true,
declarationDir: 'dist/types',
}),
],
};
⚠️ El orden de los plugins importa:urlypostcssdeben ir antes deresolve()ytypescript().
vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom', // simula el DOM para tests de componentes
globals: true, // describe/test/expect sin imports explícitos
},
});src/tests/math.test.ts
test('suma basica', () => {
expect(1 + 1).toBe(2);
});Comandos para correr los tests:
npm test # ejecución única — ideal para CI/CD
npm run test:watch # modo watch interactivo — ideal en desarrolloEvita subir archivos innecesarios al registro de NPM.
src/
node_modules/
rollup.config.mjs
vitest.config.ts
tsconfig.json
.env
Genera la carpeta dist/ con los archivos listos para publicar.
npm run build✅ Verifica que
dist/contengaindex.cjs.js,index.esm.jsy la carpetatypes/.
Enlaza la librería a tu proyecto consumidor para probar cambios en tiempo real, sin necesidad de publicar en NPM.
En el proyecto de la librería:
npm linkEn el proyecto consumidor:
npm link @tu-usuario/mi-libreria-uiCada build se refleja automáticamente — sin reinstalar:
npm run build1. Primero en el proyecto consumidor:
npm unlink @tu-usuario/mi-libreria-ui2. Luego en la librería:
npm unlink
⚠️ El nombre ennpm linkdebe coincidir exactamente con el camponamedelpackage.json.
Crea una cuenta en npmjs.com si aún no tienes una, luego ejecuta:
npm login
# Primera publicación
npm publish --access public
# Versiones siguientes
npm version patch # 1.0.0 → 1.0.1
npm version minor # 1.0.0 → 1.1.0
npm version major # 1.0.0 → 2.0.0
npm publishInstala el paquete:
npm install @tu-usuario/mi-libreria-uiImporta y usa tus componentes:
import { Button, Card } from '@tu-usuario/mi-libreria-ui';
function App() {
return (
<div>
<Button label="Hola" onClick={() => alert('click')} />
<Card title="Mi tarjeta" />
</div>
);
}✅ Los tipos
.d.tsse generan automáticamente — tendrás autocompletado completo en VSCode.
| Herramienta | Rol |
|---|---|
rollup |
Bundler principal |
rollup.config.mjs |
Configuración en ES Module nativo |
@rollup/plugin-typescript |
Compila .ts / .tsx |
rollup-plugin-postcss |
Procesa CSS Modules + inject: true |
@rollup/plugin-url |
Incrusta .jpg .png .svg .gif |
rollup-plugin-peer-deps-external |
Excluye React del bundle |
css-modules.d.ts |
Tipado para CSS Modules e imágenes |
tsconfig.json |
rootDir, moduleResolution: bundler, tipos de Vitest |
Vitest + jsdom |
Testing con simulación de DOM |
DevTalles · fernando-herrera.com