Saltearse al contenido

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:

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

  2. 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 y export, 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 objeto module.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.

1
import { defineConfig } from "vite";
2
import react from "@vitejs/plugin-react-swc";
3
import path from "path";
4
5
export 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.

    1
    export { default as Button } from "./components/Button";
    2
    export { default as useCounter } from "./hooks/useCounter";
    3
    export { 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 nombre React. 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 es username, 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 comando npm version para incrementar la versión automáticamente mediante el subcomando patch, minor o major.

    • patch: incrementa el número de versión de la corrección de errores. Por ejemplo, de 1.0.0 a 1.0.1.
    • minor: incrementa el número de versión de las nuevas características compatibles con versiones anteriores. Por ejemplo, de 1.0.0 a 1.1.0.
    • major: incrementa el número de versión de los cambios incompatibles con versiones anteriores. Por ejemplo, de 1.0.0 a 2.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 campo main sería dist/<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 campo module sería dist/<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:import from "". Pudiendo ser un componente, hook, utilidad, etc. Para configurar este punto de entrada, se debe especificar el archivo de salida del paquete en el campoexportsdel archivopackage.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 mediante import y ./dist/<package-name>.umd.js cuando el paquete es importado mediante require.

    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 para import y/o require según la configuración del paquete realizado en el archivo vite.config.js.

  • files: por último, es importante hablar del campo files. 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 directorio dist 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 archivo package.json. Esto se debe a que el archivo .npmignore es más flexible y permite excluir archivos y directorios de forma más detallada.

    1
    node_modules
    2
    src

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.

Terminal window
1
npm 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.

Bibliografía