NPM#

npm (Node Package Manager) es una herramienta esencial en el desarrollo web moderno, especialmente para la gestión de paquetes y módulos JavaScript. Comprender sus componentes y cómo se integra en el flujo de trabajo del frontend es crucial.

La página web: El sitio oficial de npm (https://www.npmjs.com/) permite descubrir nuevos paquetes, colaborar y reportar errores. Es similar a cómo funciona GitHub, proporcionando una plataforma centralizada para la comunidad de desarrolladores.

El cliente por CLI: El cliente de línea de comandos de npm permite instalar, actualizar y gestionar paquetes y programas JavaScript de manera eficiente, similar a cómo apt gestiona paquetes en sistemas basados en Debian.

El registro: npm mantiene un registro de paquetes que se pueden instalar y actualizar, facilitando la colaboración y la gestión de dependencias.

Instalación de npm#

Para comenzar a usar npm, es necesario tener Node.js instalado, ya que npm se incluye con Node.js. Aquí hay dos formas comunes de instalar Node.js y npm.

Desde los repositorios de Ubuntu:

node -v
npm -v
sudo apt install nodejs
sudo apt install npm

Desde el control de versiones de Node:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.36.0/install.sh | bash
nvm list-remote
nvm install v13.6.0

Mantener Node.js actualizado es importante para aprovechar las últimas características y mejoras de seguridad. Para actualizar Node.js, puedes usar npm:

sudo npm install -g n
sudo n stable
sudo npm install -g npm

Administración de Paquetes con npm#

npm facilita la instalación, actualización y gestión de paquetes en tus proyectos. Aquí tienes algunos comandos básicos:

  • Inicializar un nuevo proyecto:

    npm init
    
  • Instalar un paquete:

    npm install <nombre-paquete> (o npm i <nombre-paquete>)
    
  • Instalar un paquete globalmente:

    npm install -g <nombre-paquete>
    
  • Desinstalar un paquete:

    npm uninstall <nombre-paquete>
    
  • Actualizar un paquete:

    npm update <nombre-paquete>
    
  • Listar paquetes instalados:

    npm list
    

El Archivo package.json#

El package.json es el corazón de cualquier proyecto npm. Declara las bibliotecas instaladas y sus versiones, así como scripts que se pueden ejecutar con npm run.

Ejemplo de package.json:

{
 "name": "webpackinicial",
 "version": "1.0.0",
 "description": "Projecte inicial npm",
 "main": "index.js",
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1"
 },
 "author": "",
 "license": "ISC"
}

Ejemplo: Instalación de jQuery#

Para instalar y utilizar jQuery en un proyecto, sigue estos pasos:

  1. Inicializa un nuevo proyecto npm:

    npm init
    
  2. Instala jQuery:

    npm install jquery
    
  3. Incluye jQuery en tu HTML:

    <!DOCTYPE html>
    <html lang="en">
    <head>
       <meta charset="UTF-8">
       <meta name="viewport" content="width=device-width, initial-scale=1.0">
       <title>Document</title>
       <script src="./node_modules/jquery/dist/jquery.js"></script>
       <script>
         $(function() {
           console.log($);
         });
       </script>
    </head>
    <body>
    </body>
    </html>
    

Motivaciones para Usar npm#

La programación del frontend hoy en día puede ser tan compleja como la del backend. Mantener todo el código y las dependencias de terceros en archivos .html y .js puede ser complicado. Aquí es donde npm resulta invaluable:

  • Automatización de tareas:

    • Recarga en vivo de los cambios.

    • Minificación y ofuscación del código.

    • Incrementar la compatibilidad entre navegadores.

    • Compilación de SASS, TypeScript, etc.

  • Gestión de dependencias:

    • Mantener y actualizar bibliotecas de terceros

Creación de un Nuevo Proyecto Node#

Para crear un nuevo proyecto Node, asegúrate de tener versiones recientes de Node.js y npm:

node --version    # Debe ser superior a la 8
npm --version  # Debe ser superior a la 6
npm init 

Esto crea un archivo package.json en tu proyecto.

Integración con Git#

El directorio node_modules es muy grande y no debe subirse al repositorio. Asegúrate de incluirlo en tu .gitignore:

echo "node_modules" >> .gitignore

Cuando otros clonen tu repositorio, solo necesitan ejecutar npm install para instalar las dependencias listadas en package.json.

Creación de Librerías con NPM#

Una librería de Javascript puede ser creada y utilizada de muchas maneras. Actualmente podemos aconsejar que esté preparada para ser usada con módulos ESM. NodeJS ya permite usar ESM, pero si nuestra librería es para Nodejs también deberíamos habilitar la posibilidad de importar con CommonJS. Aunque la librería debería poder ser usada en Javascript, también será usada en Typescript, así que hay que informar del tipado mediante archivos .d.ts. Puesto que será importada en muchos proyectos, interesa que sea muy compatible y ligera, esto lo conseguimos fácilmente con Vite. Y por último la debemos publicar. La publicación es también muy diversa, muchas librerías permiten descargar el .js o enlazarlo con un CDN, aunque la manera más cómoda para programar es mediante servicios como npm o yarn.

Para crear una librería con npm se necesita crear una estructura de ficheros y configuraciones que permita al sistema de npm y a otros desarrolladores usarla sin problemas. Hay muchas formas de enfocarlo, pero aquí vamos a ser un poco coherentes con las tecnologías explicadas a lo largo de este libro.

Este apartado corresponde a este capítulo pero necesita de conocimientos explicados en capítulos posteriores, así que no se recomienda leer secuencialmente.

Antes de empezar necesitaremos configurar un proyecto de Vite un poco distinto, ya que no servirá para una web completa, sino para una librería. Además, usaremos Typescript y otras herramientas como tests o linters que nos permitan un mejor control del código. Podemos usar una plantilla existente o hacerlo a mano. Un ejemplo de plantilla puede ser este: kbysiec/vite-vanilla-ts-lib-starter

Los paquetes preparados para Typescript necesitan declarar los tipos y el código. Para facilitar el uso hay que preparar el package.json con exports y types. Por ejemplo:

{
  "name": "chessmarro-board",
  "private": true,
  "version": "0.0.5",
  "module": "./dist/chessmarro-board.js",
  "type": "module",
  "exports": {
    ".":  "./dist/chessmarro-board.js"
  },
  "types": "./dist/index.d.ts",
  "scripts": {
 ...
  },
  "devDependencies": {
...
  }
}

En este caso, se podrán importar las funciones que exporte chessmarro-board.js y sus tipos estará en index.d.ts.

Hay que asegurarse de que se generarán esos archivos en la carpeta dist. Para ello hay que configurar si es necesario el vite.config.ts.

/// <reference types="vitest" />
import path from "path";
import { defineConfig } from "vite";
import packageJson from "./package.json";

const getPackageName = () => {
  return packageJson.name;
};

const getPackageNameCamelCase = () => {
  try {
    return getPackageName().replace(/-./g, char => char[1].toUpperCase());
  } catch (err) {
    throw new Error("Name property in package.json is missing.");
  }
};

const fileName = {
  es: `${getPackageName()}.js`,
  iife: `${getPackageName()}.iife.js`,
};

const formats = Object.keys(fileName) as Array<keyof typeof fileName>;

export default defineConfig({
  base: "./",
  build: {
    outDir: "./build/dist",
    lib: {
      entry: path.resolve(__dirname, "src/index.ts"),
      name: getPackageNameCamelCase(),
      formats,
      fileName: format => fileName[format],
    },
  },
  test: {
    watch: true,
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src"),
      "@@": path.resolve(__dirname),
    },
  },
});

En vite hemos configurado para crear una librería: https://vite.dev/guide/build#library-mode, por eso usamos build.lib (https://vite.dev/config/build-options#build-lib). En Vite se simplifica el desarrollo porque podemos usar al mismo tiempo el index.html para ir probando la librería.

También hay que configurar el package.json:

{
  "name": "chessmarro-board",
  "private": false,
  "version": "0.0.6",
  "module": "./dist/chessmarro-board.js",
  "type": "module",
  "exports": {
    ".": "./build/dist/chessmarro-board.js"
  },
  "types": "./build/dist/index.d.ts",
  "files": ["build/dist"] 

Veamos estas líneas:

  • name: Es el nombre del proyecto y el nombre que tendrá en npm.

  • private: Los usuarios gratuitos de npm no pueden publicar librerías privadas.

  • version: En npm necesitan que cada nueva versión modifique este número. Tenemos libertad para elegir números, pero se considera que el primero cambia a 1 cuando es la primera versión estable. el segundo es para cambios importantes que no afectan a la retrocompatibilidad y el tercero para parches y cambios menores.

  • module: Aquí indicamos la ruta del primer módulo de la librería.

  • type: quí siempre pondremos module porque lo recomendable es hacer módulos ESM.

  • exports: Esta parte es específica de las librerías, aquí ponemos que al importar, la raíz es ese fichero. Esto afecta también a cómo se importa. En este caso importamos todo el primer módulo, pero si no lo queremos importar todo siempre, podemos poner más rutas a más archivos.

  • types: Para que funcione bien en typescript hay que definir los tipos ya que el resultado es Javascript (no tipado). Este fichero se genera en el proceso de build, como veremos a continuación.

  • files: Solo se publicará la carpeta dist/, evitando archivos innecesarios.

Las siguientes líneas:

  "scripts": {
    "dev": "vite --host",
    "build": "rimraf build/**/*  && vite build && dts-bundle-generator --config ./dts-bundle-generator.config.ts && copyfiles ./package.json build",

Nos interesa la línea de build:

  • rimraf build: Elimina todo el contenido de la carpeta build para no tener problemas de ficheros antiguos no regenerados.

  • vite build: Este genera la librería con Vite

  • dts-bundle-generator –config ./dts-bundle-generator.config.ts: genera un único archivo .d.ts que contiene todas las declaraciones de tipos necesarias para la librería. En lugar de tener múltiples archivos .d.ts dispersos, dts-bundle-generator crea un solo archivo de definiciones de tipos.

  • copyfiles ./package.json build copyfiles copia el package.json al directorio build/. Si se va a publicar la librería en npm desde la carpeta build/, es necesario que contenga un package.json. Copiarlo garantiza que los metadatos de la librería se mantengan en la versión empaquetada.

En el ejemplo que podemos usar como plantilla: kbysiec/vite-vanilla-ts-lib-starter podemos ver otras cosas ya no imprescindibles pero muy interesantes como Eslint, Prettier o Husky esas herramientas las estudiamos en el capítulo de proyectos y de CI/CD.

Publicar#

Publicar en npm es muy sencillo, solo hay que darse de alta y ejecutar en el directorio del proyecto:

npm login
npm publish --access public

Se añade un archivo .npmignore en la raíz del proyecto con el siguiente contenido:

node_modules
src
test
.vite
.vscode
.git
tsconfig.json
vite.config.ts
dts-bundle-generator.config.ts
...

Esto evitará que se suban archivos irrelevantes a npm.

Lecturas:#

https://2ality.com/2025/02/typescript-esm-packages.html kbysiec/vite-vanilla-ts-lib-starter