
Esta serie ofrece una visión con criterio sobre cómo abordar la construcción de una librería, tanto si se publica dentro de una organización como si es código abierto. La Parte 1 se centra en la especificación: cómo decidir qué construir antes de ahogarte en detalles de implementación.
¿Por dónde empiezo?
Conoce a tu(s) cliente(s)
Antes de escribir componentes, servicios o utilidades, dedica tiempo a quién consumirá el paquete: otros equipos, colaboradores externos o tú en el futuro. Pregúntate:
- ¿Qué runtime y bundler usan?
- ¿Necesitan tree-shaking y builds duales ESM/CJS?
- ¿Cuál es su nivel con el ecosistema (p. ej. Angular, Node)?
Ese contexto condiciona la superficie de API, las peer dependencies y el tono de la documentación.
Documenta cómo te gustaría usarlo
Si construyes una librería—interna o para la comunidad—probablemente ya entiendes el problema. Un acelerador práctico es el desarrollo guiado por README: escribe el documento de uso que te gustaría encontrar antes de que el código lo iguale.
Regla de oro: el primer README no tiene que cubrir todos los casos límite. Necesita un camino feliz creíble que el lector pueda copiar, pegar y adaptar.
Ejemplo — Slothy Client (ficticio)
Slothy Client es un cliente pequeño que facilita almacenar y recuperar tareas. Mantiene tareas pendientes y te permite trabajar con ellas.
Instalación
npm install @slothy/client --save
Configuración
import { createSlothyClient, SlothyClientConfig } from '@slothy/core';
const config: SlothyClientConfig = {
storage: 'in_memory',
debug: false,
};
const client = await createSlothyClient(config);
Integraciones — Angular
npm install @slothy/angular --save
Luego importa SlothyClientModule.forRoot(...) en tu AppModule (el README del paquete Angular detalla los pasos).
SlothyClientConfig
| Propiedad | Tipo | Descripción |
|---|---|---|
storage |
'in_memory' | 'indexed_db' |
Dónde persistir las tareas |
debug |
boolean |
Activa logs detallados |
Esa sola página ya te obliga a nombrar conceptos (SlothyClient, createSlothyClient, forma del config) que acabarán siendo tu contrato público.
MVP antes que fallar
En la primera iteración, mantén el foco en el problema central. Dos trampas habituales:
- Especificaciones demasiado completas — aunque conozcas el dominio, evita documentar cada función futura. Captura el comportamiento mínimo que aporta valor.
- Contratos vagos o demasiado grandes — prefiero interfaces pequeñas y composables que puedas extender después, y documenta solo lo que la v0 garantiza de verdad.
Arquitectura por capas
Mantén el dominio aislado
Los frameworks y las librerías de UI cambian; tu modelo de dominio e invariantes no deberían filtrarse a todos los consumidores. Un layout típico para @slothy/core:
.
└── lib/
├── domain/
│ └── ...
├── core/
│ └── ...
├── utils/
│ └── ...
└── index.ts
index.ts debe exportar solo lo que consideras API pública soportada. El resto queda interno o detrás de puntos de entrada explícitos (@slothy/core/testing, etc.).
export { createSlothyClient, SlothyClient, SlothyClientConfig } from './domain';
Código cliente
import { createSlothyClient, SlothyClientConfig } from '@slothy/core';
const config: SlothyClientConfig = {
storage: 'in_memory',
debug: true,
};
const client = await createSlothyClient(config);
client.doSomething();
Hazlo todo lo enchufable que puedas
Aquí enchufable significa: núcleo estable, bordes intercambiables.
- Prefiere funciones y factories (
createSlothyClient) frente a singletons ocultos. - Expón interfaces acotadas para almacenamiento, logging y transporte para que los equipos puedan sustituirlas sin hacer fork.
- Usa peer dependencies para frameworks (Angular, React) para que el consumidor controle versiones y evite duplicados.
Documenta un camino recomendado por ecosistema principal; mantén adaptadores opcionales en paquetes aparte.
Escribe varios clientes/adaptadores para tu dominio
Si los consumidores deben integrar con Angular, herramientas CLI u otros stacks, publica paquetes adaptadores finos que envuelvan el núcleo y traduzcan la configuración propia del framework a la configuración de dominio.
Ejemplo de estructura para @slothy/angular:
.
└── lib/
├── utils/
│ └── ...
├── ng-slothy.config.ts
├── ng-slothy-client.module.ts
└── index.ts
ng-slothy.config.ts:
import { SlothyClient, SlothyConfig } from '@slothy/core';
export interface NgSlothyClientModuleConfig extends SlothyConfig {}
export class NgSlothyClient extends SlothyClient {}
export function mapToSlothyConfig(
ngConfig: NgSlothyClientModuleConfig
): SlothyConfig {
const config: SlothyConfig = { ...ngConfig };
return config;
}
ng-slothy.tokens.ts (simplificado):
import { InjectionToken } from '@angular/core';
import type { SlothyClient, SlothyConfig } from '@slothy/core';
export const SLOTHY_CONFIG = new InjectionToken<SlothyConfig>('SLOTHY_CONFIG');
export const SLOTHY_CLIENT = new InjectionToken<SlothyClient>('SLOTHY_CLIENT');
ng-slothy-client.module.ts:
import { NgModule, ModuleWithProviders } from '@angular/core';
import { createSlothyClient } from '@slothy/core';
import {
NgSlothyClientModuleConfig,
NgSlothyClient,
mapToSlothyConfig,
} from './ng-slothy.config';
import { SLOTHY_CONFIG, SLOTHY_CLIENT } from './ng-slothy.tokens';
@NgModule()
export class SlothyClientModule {
static forRoot(
config?: NgSlothyClientModuleConfig
): ModuleWithProviders<SlothyClientModule> {
const slothyConfig = mapToSlothyConfig(config ?? {});
return {
ngModule: SlothyClientModule,
providers: [
{ provide: SLOTHY_CONFIG, useValue: slothyConfig },
{
provide: SLOTHY_CLIENT,
useFactory: () => createSlothyClient(slothyConfig),
},
{ provide: NgSlothyClient, useExisting: SLOTHY_CLIENT },
],
};
}
}
Código cliente
import { SlothyClientModule } from '@slothy/angular';
const SLOTHY_MODULE_CONFIG: NgSlothyClientModuleConfig = {
param: 'my-awesome-slothy',
debug: false,
};
@NgModule({
imports: [
SlothyClientModule.forRoot(SLOTHY_MODULE_CONFIG),
],
})
export class AppModule {}
El paquete Angular mapea la configuración del framework a la del núcleo; no reimplementa la lógica de dominio.
Qué entendemos por «especificación» en la Parte 1
- Un contrato en forma de README que puedes iterar con usuarios.
- Un límite MVP que evita la completitud prematura.
- Un mapa de paquetes por capas: dominio primero, adaptadores después.
Sigue en la Parte 2 — Versionado, changelog y pruebas para versionado, disciplina de changelog y estrategias de prueba que mantengan ese contrato honesto en el tiempo, y luego la Parte 3 — Contribuidores y documentación para CONTRIBUTING.md, RFCs y documentación de API.
Etiquetas: código abierto, librería, TypeScript, Angular