Publicación de paquetes en npm
Hasta el momento, hemos trabajado en un entorno de desarrollo local y hemos compilado y empaquetado los archivos de la aplicación en modo de producción. Sin embargo, esto no permite compartir los elementos definidos en el proyecto de manera directa con otros desarrolladores o, inclusive, con uno mismo en un futuro.
Es decir, los componentes, hooks, utilidades o cualquier otro elemento definido en el proyecto no son accesibles por otros proyectos. Para solucionar esto es que contamos con un gestor de paquetes como npm. Si bien hemos utilizado npm únicamente para instalar dependencias, también podemos utilizarlo en el sentido inverso: publicando paquetes.
¿Qué es un paquete?
Antes de continuar, es importante definir qué es un paquete. En el contexto de npm, un paquete es un conjunto de archivos que contienen código, recursos y metadatos que se pueden compartir y reutilizar en otros proyectos. Los paquetes pueden contener cualquier tipo de recurso, como JavaScript, CSS, imágenes, fuentes, etc.
Para publicar un paquete en npm, podemos optar por dos enfoques:
-
Publicar un paquete privado: Los paquetes privados son paquetes que solo pueden ser instalados por los propietarios del paquete o por aquellos que tengan permisos para acceder al paquete. Para publicar un paquete privado, es necesario tener una suscripción a npm.
-
Publicar un paquete público: Los paquetes públicos son paquetes que están disponibles para cualquier persona en el registro de npm. Cualquier desarrollador puede instalar y utilizar un paquete público sin necesidad de permisos especiales.
ES Modules y CommonJS
Antes de publicar un paquete en npm, tenermos la libertad de elegir el nivel de compatibilidad y alcance que deseamos para el paquete. Podemos optar por publicar el código fuente del proyecto, el código transpilado o ambos.
Si transpilamos el código fuente, es importante tener en cuenta la existencia de los módulos en JavaScript. Los módulos son unidades de código que encapsulan la lógica y los recursos de una aplicación. Los módulos permiten organizar y reutilizar el código de forma eficiente, facilitando el mantenimiento y la escalabilidad de la aplicación.
Es importante tener en cuenta que existen dos sistemas de módulos principales en el ecosistema de JavaScript: ES Modules (ESM) y CommonJS (CJS). Además, existe un tercer sistema de módulos llamado UMD (Universal Module Definition).
- ES Modules (ESM): ESM o ECMAScript Modules es un sistema de módulos nativo de JavaScript que permite importar y exportar módulos de forma declarativa. Los módulos ESM se importan y exportan mediante las palabras clave
import
yexport
, respectivamente. Este sistema de módulos es compatible con navegadores modernos y Node.js a partir de la versión 12. - CommonJS (CJS): CommonJS es un sistema de módulos utilizado en Node.js y en entornos de desarrollo de JavaScript. Los módulos CommonJS se importan y exportan mediante la función
require
y el objetomodule.exports
. Este sistema de módulos es compatible con Node.js y navegadores antiguos. - UMD: UMD es un sistema de módulos que combina los sistemas de módulos ESM y CommonJS en un solo archivo. Los módulos UMD se pueden utilizar en navegadores modernos y en Node.js. Este sistema de módulos es compatible con navegadores modernos y Node.js.
Módulos en Vite
Vite utiliza los módulos ESM de forma predeterminada. Esto significa que los archivos de la aplicación se importan y exportan mediante las palabras clave import
y export
. Este proceso es transparente para el desarrollador, ya que Vite se encarga de manejar la resolución de módulos y la transpilación del código. Aún así, es importante tener en cuenta que podemos personalizar la configuración de Vite para ajustar el comportamiento de los módulos.
Si buscamos publicar únicamente el módulo ESM, podemos hacerlo sin realizar ninguna configuración adicional ya que, por defecto, al emplear el comando npm run build
, Vite transpila el código fuente a módulos ESM. Sin embargo, la configuración por defecto de Vite genera archivos de salida con nombres aleatorios, lo cual puede dificultar la importación de los módulos en otros proyectos.
Entonces, sería conveniente o incluso necesario personalizar la configuración de Vite para generar archivos de salida con nombres predecibles y compatibles con los sistemas de módulos ESM y CommonJS. Para esto, podemos utilizar la opción build.rollupOptions
y build.lib
en el archivo vite.config.js
.
1import { defineConfig } from "vite";2import react from "@vitejs/plugin-react-swc";3import path from "path";4
5export default defineConfig({6 plugins: [react()],7 build: {8 lib: {9 entry: path.resolve(__dirname, "src/index.js"),10 name: "<package-name>",11 fileName: (format) => `<package-name>.${format}.js`,12 },13 rollupOptions: {14 external: [<external-dependencies>],15 output: {16 globals: {17 <external-dependencies>: "<global-variable>",18 },19 },20 },21 },22});
Analizando el código anterior, podemos observar que la configuración de Vite se divide en dos secciones principales: build.lib
y build.rollupOptions
. Adicionalmente, pueden configurarse otros aspectos del proyecto, como los plugins, las dependencias externas y las opciones de salida.
Opciones de build.lib
Esta sección permite configurar la generación de un paquete de biblioteca (library) en Vite, que es justamente lo que buscamos al publicar un paquete en npm. Las opciones disponibles son las siguientes:
-
entry
: ruta del archivo de entrada del paquete. Este archivo debe exportar los elementos que se desean publicar en el paquete. Por ejemplo, los componentes, hooks, utilidades, etc.1export { default as Button } from "./components/Button";2export { default as useCounter } from "./hooks/useCounter";3export { default as formatCurrency } from "./utils/formatCurrency"; -
name
: nombre de la biblioteca. Este nombre se utiliza para generar el archivo de salida y se puede personalizar según las necesidades del proyecto. -
fileName
: función que genera el nombre del archivo de salida en función del formato de módulo. Por ejemplo, si el formato es ESM, el nombre del archivo será<package-name>.esm.js
y si el formato es UMD, el nombre del archivo será<package-name>.umd.js
.
Opciones de build.rollupOptions
Esta sección permite configurar las opciones de Rollup, el empaquetador de módulos utilizado por Vite. Las opciones disponibles son las siguientes:
external
: lista de dependencias externas que no deben incluirse en el paquete. Estas dependencias se consideran como dependencias de tiempo de ejecución y deben ser importadas por el consumidor del paquete. Si definimos un componente que utiliza React, por ejemplo, React sería una dependencia externa y el consumidor del paquete debería importar React en su proyecto.output.globals
: objeto que mapea las dependencias externas a las variables globales utilizadas por el consumidor del paquete. Este mapeo es necesario para que el consumidor del paquete pueda acceder a las dependencias externas de forma correcta. Por ejemplo, si React es una dependencia externa, el mapeo sería{ react: "React" }
. De esta forma, el paquete asume que React está disponible en el ámbito global con el nombreReact
. Si React no está disponible en el ámbito global, se presentarán errores al momento de importar el paquete.
Publicación de un paquete
Una vez que hemos configurado el proyecto para generar un paquete de biblioteca, podemos proceder a publicar el paquete en npm. Para esto, es necesario seguir los siguientes pasos.
Crear una cuenta en npm
Para publicar paquetes en npm, es necesario tener una cuenta en la plataforma. Podemos dirigirnos a npm y crear una cuenta de forma gratuita.
Alternativamente, podemos usar el comando npm adduser
o npm register
para crear una cuenta desde la terminal. Sin embargo, se recomienda utilizar la interfaz web para crear la cuenta, ya que es más sencillo.
Iniciar sesión en npm
Una vez creada la cuenta, es necesario iniciar sesión en la plataforma. Para esto, se puede ejecutar el comando npm login
en la terminal y proporcionar las credenciales de la cuenta. El mecanismo de autenticación empleado por npm es el mismo que el de GitHub, por lo que se puede utilizar la autenticación de dos factores (2FA) si se tiene habilitada en la cuenta de GitHub.
Configurar el paquete
El archivo package.json
es el archivo de configuración principal de un paquete en npm. En este archivo, se definen los metadatos del paquete, como el nombre, la versión, la descripción, las dependencias, las palabras clave, los scripts, entre otros.
Es importante asegurarse de que el archivo package.json
contiene la información correcta antes de publicar el paquete. Algunos campos importantes a tener en cuenta son:
-
name
: nombre del paquete. Este campo debe ser único en el registro de npm y se recomienda utilizar un namespace o alcance para evitar conflictos con otros paquetes. Por ejemplo, si el nombre de usuario esusername
, el nombre del paquete sería@username/package-name
. -
version
: versión del paquete. Este campo debe seguir la convención de versionado semántico (SemVer) y se debe incrementar cada vez que se realicen cambios en el paquete. Este proceso se puede realizar de forma manual, para lo cual debemos tener especial cuidado en seguir la convención de versionado semántico. O bien, se puede utilizar el comandonpm version
para incrementar la versión automáticamente mediante el subcomandopatch
,minor
omajor
.patch
: incrementa el número de versión de la corrección de errores. Por ejemplo, de1.0.0
a1.0.1
.minor
: incrementa el número de versión de las nuevas características compatibles con versiones anteriores. Por ejemplo, de1.0.0
a1.1.0
.major
: incrementa el número de versión de los cambios incompatibles con versiones anteriores. Por ejemplo, de1.0.0
a2.0.0
.
Para incrementar la versión del paquete es necesario que el proyecto esté en un repositorio Git y que no tenga cambios sin confirmar. De otra forma, el comando
npm version
no funcionará. -
description
: descripción del paquete. Este campo proporciona información sobre el propósito y las características del paquete. -
main
: archivo principal del paquete. Este campo indica el archivo que se importará cuando se requiera el paquete. Si el paquete es<package-name>.umd.js
, el campomain
seríadist/<package-name>.umd.js
. -
module
: archivo de módulo del paquete. Este campo indica el archivo de módulo que se importará cuando se requiera el paquete. Si el paquete es<package-name>.esm.js
, el campomodule
seríadist/<package-name>.esm.js
. -
exports
: esta propiedad permite especificar los puntos de entrada del paquete cuando es consumido por otros proyectos. Es una forma de controlar exáctamente qué archivos pueden ser importados por los consumidores del paquete.".": este es el punto de entrada por defecto del paquete. El uso del punto
.indica que este es el punto de entrada principal del paquete. Por lo cual, los consumidores del paquete podrán importar el paquete de la siguiente forma:
importfrom " " . Pudiendo ser
un componente, hook, utilidad, etc. Para configurar este punto de entrada, se debe especificar el archivo de salida del paquete en el campo
exportsdel archivo
package.json`.
1"exports": {2".": {3"import": "./dist/<package-name>.esm.js",4"require": "./dist/<package-name>.umd.js"5}6}Basándonos en el ejemplo anterior, el campo
exports
indica que el punto de entrada principal del paquete es./dist/<package-name>.esm.js
cuando el paquete es importado medianteimport
y./dist/<package-name>.umd.js
cuando el paquete es importado medianterequire
.Lógicamente, el campo
exports
puede contener más puntos de entrada si el paquete exporta múltiples elementos. Y cada punto de entrada puede tener configuraciones específicas paraimport
y/orequire
según la configuración del paquete realizado en el archivovite.config.js
. -
files
: por último, es importante hablar del campofiles
. Este campo permite especificar los archivos y directorios que se incluirán en el paquete al publicarlo en npm. Por defecto, npm incluye todos los archivos y directorios del proyecto, lo cual puede no ser deseado. Sin embargo, esta propiedad permite controlar qué archivos y directorios se incluyen en el paquete.1"files": [2"./dist"3]En el ejemplo anterior, el campo
files
indica que solo se incluirá el directoriodist
en el paquete al publicarlo en npm. Esto es útil para evitar incluir archivos innecesarios en el paquete, como los archivos de configuración, los archivos de desarrollo, los archivos de prueba, etc.Alternativamente, se puede utilizar un archivo
.npmignore
para especificar los archivos y directorios que no se incluirán en el paquete. Este archivo es similar al archivo.gitignore
de un repositorio Git y permite controlar qué archivos y directorios se excluyen del paquete.Como recomendación, utilizar este archivo suele ser una mejor práctica que el campo
files
en el archivopackage.json
. Esto se debe a que el archivo.npmignore
es más flexible y permite excluir archivos y directorios de forma más detallada.1node_modules2src
Publicación del paquete
Una vez que hemos configurado el paquete y hemos incrementado la versión del paquete en el archivo package.json
, podemos proceder a publicar el paquete en npm. Para esto, es necesario ejecutar el comando npm publish
en la terminal. Este comando publica el paquete en el registro de npm y lo hace accesible para otros desarrolladores.
Por otro lado, solo se puede publicar un paquete si es no es privado, a menos que se tenga una suscripción a npm (lo cual no es gratuito).
Por defecto, los proyectos creados con Vite son privados, por lo que es necesario cambiar la visibilidad del paquete en el archivo package.json
eliminando el campo private
. Además, debemos indicar mediante una bandera que el paquete es público. Esto se debe a que los paquetes privados requieren una suscripción a npm. La bandera en cuestión es --access public
.
1npm publish --access public
Es importante tener en cuenta que, al publicar una actualización del paquete, se debe incrementar la versión del paquete en el archivo package.json
. De lo contrario, npm mostrará un error al intentar publicar el paquete.