CÓMO MIGRAR UN PROYECTO JAVASCRIPT A TYPESCRIPT
Introducción a la migración de JavaScript a TypeScript
La transición de un proyecto JavaScript a TypeScript puede transformar la forma en que los desarrolladores abordan la escritura de código, reduciendo errores comunes como excepciones de tipo undefined o cadenas [Object object]. TypeScript, como superconjunto tipado de JavaScript, ofrece herramientas para mejorar la robustez y mantenibilidad del código. Este tutorial está diseñado para programadores que desean integrar TypeScript en proyectos JavaScript, proporcionando un enfoque gradual para añadir tipos, configurar el entorno, gestionar la compilación, implementar linting y convencer a equipos sobre los beneficios de esta tecnología. A lo largo de este artículo, se detallan los pasos necesarios para lograr una migración efectiva, con ejemplos prácticos y estrategias probadas.
¿Qué es TypeScript?
TypeScript es un superconjunto de JavaScript que introduce un sistema de tipos estáticos y se compila a JavaScript puro. Esto significa que cualquier código JavaScript válido es también válido en TypeScript, pero con la ventaja de añadir anotaciones de tipo que ayudan a detectar errores en tiempo de compilación. El compilador de TypeScript, conocido como tsc, transforma archivos .ts en archivos .js ejecutables. Además, TypeScript ofrece características avanzadas como tipos de unión y tipos algebraicos, que enriquecen la experiencia de desarrollo.
Un ejemplo básico de un programa en TypeScript ilustra su simplicidad:
const mensaje: string = "¡Hola, mundo!";
console.log(mensaje);
Este código define una variable con un tipo explícito string y genera una salida en consola. La familiaridad con JavaScript facilita la adopción de TypeScript, ya que comparte su sintaxis básica.
Instalación inicial de TypeScript
El primer paso para integrar TypeScript en un proyecto es instalar el compilador. Esto se logra ejecutando el siguiente comando en la terminal:
npm install typescript
Una vez instalado, se crea un archivo de configuración tsconfig.json utilizando el comando tsc --init. Este archivo define cómo el compilador procesará los archivos del proyecto. A continuación, se muestra un ejemplo de configuración inicial que permite la coexistencia de archivos JavaScript y TypeScript:
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"allowJs": true,
"checkJs": false,
"outDir": "dist",
"rootDir": ".",
"strict": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"strictNullChecks": true,
"resolveJsonModule": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"*": [
"*",
"src/*",
"src/setup/*",
"src/logic/*",
"src/models/*",
"config/*"
]
}
},
"exclude": ["node_modules", "dist"],
"include": ["src", "test", "config"]
}
Esta configuración permite que el compilador acepte archivos JavaScript (allowJs), genere archivos de salida en la carpeta dist (outDir), y excluya directorios como node_modules. La opción strictNullChecks asegura verificaciones rigurosas de valores nulos, mejorando la seguridad del código.
Primeros pasos con TypeScript en un proyecto JavaScript
Para comenzar, se recomienda crear un archivo TypeScript simple o renombrar un archivo JavaScript existente a .ts. Por ejemplo, un archivo app.ts podría contener:
const saludar = (nombre: string): string => {
return `Hola, ${nombre}!`;
};
console.log(saludar("Usuario"));
Este archivo se compila ejecutando:
tsc
El compilador genera un archivo app.js en la carpeta dist, listo para ejecutarse con Node.js:
node dist/app.js
Este enfoque permite experimentar con TypeScript sin alterar significativamente el proyecto existente.
Estrategias para la migración a TypeScript
La migración a TypeScript puede abordarse mediante diferentes estrategias, dependiendo del alcance y los recursos del proyecto. A continuación, se describen las principales estrategias, ordenadas por su complejidad y valor aportado.
Soporte inicial para TypeScript en aplicaciones existentes
La estrategia más sencilla es permitir que los archivos JavaScript y TypeScript coexistan en el proyecto. Esto implica configurar el compilador para que procese archivos .ts y copie archivos .js a la carpeta de salida sin cambios. La ventaja de este enfoque es su baja curva de aprendizaje, ya que los desarrolladores pueden adoptar TypeScript gradualmente.
Para implementar esta estrategia, se ajusta el archivo tsconfig.json con la opción allowJs: true. Luego, se actualiza el archivo package.json para incluir un comando de compilación:
{
"scripts": {
"start": "node ./dist/app.js",
"build-dist": "tsc",
"test": "mocha --recursive --reporter spec ./dist/test/"
}
}
Este enfoque permite a los desarrolladores escribir nuevo código en TypeScript mientras mantienen el código JavaScript existente funcional.
Añadir soporte de TypeScript a bibliotecas internas
Una vez que el equipo está cómodo con TypeScript, se puede extender su uso a bibliotecas internas. Esto puede hacerse de dos maneras: creando archivos de declaración .d.ts o reescribiendo las bibliotecas en TypeScript. Los archivos de declaración son la opción más eficiente, ya que no requieren modificar el código existente, pero proporcionan soporte de tipos para el compilador y autocompletado en entornos de desarrollo.
Por ejemplo, para una biblioteca JavaScript existente, se crea un archivo index.d.ts:
declare module "mi-biblioteca" {
export function miFuncion(param: string): string;
}
Este archivo permite al compilador entender los tipos de la biblioteca sin necesidad de reescribirla.
Crear un esqueleto de proyecto en TypeScript
Para proyectos futuros, se recomienda establecer un esqueleto de proyecto completamente en TypeScript. Este esqueleto puede incluir configuraciones para registro, métricas y otros elementos comunes. Un buen punto de partida es un proyecto como typescript-node-starter, que proporciona una estructura preconfigurada.
La estructura de un esqueleto típico podría ser:
project/
├── src/
│ ├── logic/
│ ├── models/
│ ├── setup/
├── config/
├── test/
├── tsconfig.json
├── package.json
Este enfoque asegura que los nuevos proyectos adopten TypeScript desde el inicio, promoviendo consistencia en el equipo.
Conversión completa del código a TypeScript
La migración completa a TypeScript implica reescribir todo el código JavaScript en TypeScript, un proceso que debe realizarse como paso final debido a su complejidad. Los pasos recomendados son:
- Definir tipos claros para la lógica de negocio, APIs y comunicaciones HTTP.
- Instalar paquetes
@typespara bibliotecas externas enpackage.json:
{
"dependencies": {
"@types/node": "^20.0.0",
"@types/express": "^4.17.0"
}
}
- Convertir los componentes lógicos del proyecto, priorizando aquellos con lógica de negocio única.
- Actualizar las capas de entrada/salida, como bases de datos y colas.
- Adaptar las pruebas existentes a TypeScript.
Herramientas como ts-migrate de Airbnb pueden automatizar partes de este proceso, convirtiendo archivos JavaScript a TypeScript y permitiendo mejoras incrementales.
Configuración de compilación y empaquetado
La integración de TypeScript requiere ajustes en los procesos de compilación y empaquetado, especialmente en entornos de integración continua como Travis CI. A continuación, se muestra un ejemplo de un archivo de configuración de Travis antes y después de la migración:
Antes:
jobs:
include:
- before_script:
- npm install --no-optional --production
- npm prune --production
before_deploy:
- XZ_OPT=-0 tar --exclude=.git --exclude=reports.xml -cJf "app.tar.xz" *
Después:
jobs:
include:
- before_script:
- npm install --no-optional --production
- npm run build-dist
- npm prune --production
before_deploy:
- cp -rf config/env-templates ./dist/config/
- cp -rf node_modules ./dist/
- cd dist
- XZ_OPT=-0 tar --exclude=.git --exclude=reports.xml -cJf "app.tar.xz" *
- mv app.tar.xz "../artifacts/"
- cd ..
Los cambios principales incluyen la adición del comando build-dist para compilar el código TypeScript y la copia de dependencias a la carpeta dist.
Soporte para mapas de origen
Un desafío al usar TypeScript es que los errores en producción hacen referencia a archivos .js generados, no a los archivos .ts originales. Para solucionar esto, se utiliza la biblioteca source-map-support o la bandera nativa de Node.js --enable-source-maps.
Instalación de source-map-support:
npm install source-map-support
Luego, se añade al inicio del código:
require("source-map-support").install();
const mensaje: string = "Error crítico";
throw new Error(mensaje);
Compilar con mapas de origen:
tsc --sourceMap
Esto asegura que los stack traces en producción apunten a los archivos .ts originales, facilitando la depuración.
Implementación de linting con TypeScript
El linting es esencial para mantener la calidad del código. Aunque existen herramientas como tsfmt, la opción más recomendada es usar ESLint con el plugin @typescript-eslint. La configuración se realiza en un archivo .eslintrc.js:
module.exports = {
rules: {
indent: [2, 2, { SwitchCase: 1 }],
"no-multi-spaces": 2,
"no-trailing-spaces": 2,
"space-before-blocks": 2,
},
overrides: [
{
files: ["**/*.ts"],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
extends: [
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
],
},
],
};
Se agrega un comando en package.json para ejecutar el linting:
{
"scripts": {
"lint-fix": "eslint . --fix"
}
}
Esta configuración asegura que el código TypeScript cumpla con las reglas de estilo y detecte errores potenciales.
Convencer al equipo sobre TypeScript
Introducir TypeScript en una organización requiere una estrategia clara para obtener la aceptación del equipo. Los puntos clave para una presentación efectiva incluyen:
- Explicar los beneficios de TypeScript, como la reducción de errores en producción.
- Mostrar ejemplos simples de código TypeScript para demostrar su accesibilidad.
- Presentar características avanzadas, como tipos de unión, que aportan valor significativo.
- Realizar una demostración en vivo de cómo el compilador detecta errores usando
//@ts-checken archivos JavaScript. - Explicar el uso de archivos
ts.dy paquetes@typespara una integración sencilla. - Mostrar ejemplos reales de pull requests que implementen TypeScript.
- Crear un pull request público para fomentar la colaboración del equipo.
Por ejemplo, una demostración de //@ts-check en un archivo JavaScript puede ser:
//@ts-check
const datos = { nombre: "Usuario" };
console.log(datos.apellido); // Error detectado por TypeScript
Esta demostración muestra cómo TypeScript identifica errores sin necesidad de reescribir el código.
Conclusiones
La migración de un proyecto JavaScript a TypeScript es un proceso que mejora la calidad y mantenibilidad del código al introducir tipos estáticos y herramientas avanzadas de desarrollo. Comenzar con un enfoque gradual, como permitir la coexistencia de JavaScript y TypeScript, facilita la adopción sin interrumpir los flujos de trabajo existentes. La configuración de tsconfig.json, la integración con herramientas de compilación, el soporte para mapas de origen y el linting con ESLint son pasos esenciales para una migración exitosa. Además, convencer al equipo mediante demostraciones prácticas y ejemplos claros es clave para garantizar el apoyo organizacional. Al implementar estas estrategias, los desarrolladores pueden reducir errores, mejorar la experiencia de desarrollo y preparar sus proyectos para un futuro más robusto.