Creación de un proyecto TypeScript en Node (2º parte – Creación de una librería TypeScript)

Publicado el 25 noviembre 2020 por Daniel Rodríguez @analyticslane

Ahora que conocemos las ventajas de TypeScript vamos a ver como crear un proyecto TypeScript en Node. Para lo que es necesario crear y configurar un archivo tsconfig.json además del package.json.

Creación de un proyecto TypeScript en Node

Para la creación de un proyecto TypeScript en Node necesitamos crear una carpeta y dentro de esta crear los archivos package.json, en el que se almacena la configuración de Node como los scripts y los paquetes instalados, y otro tsconfig.json, en el que se almacena la configuración del compilador de TypeScript. Archivos que se pueden crear automáticamente con dos comandos. Así, para inicializar el proyecto, abriremos la terminal y ejecutaremos las siguientes instrucciones

mkdir tslane
cd tslane
npm init -y
tsc -init

Mediante la primera instrucción se creará una carpeta llamada tslane en la que se almacena el proyecto. Posteriormente, con el segundo comando, se accede a esta carpeta. El tercer comando es el que crea el archivo package.json del proyecto Node, en el que la opción -y se usa para confirmar todas las opciones por defecto. Finalmente, el comando tsc -init creará el archivo tsconfig.json con las opciones por defecto. Comando que requiere la instalación previa de TypeScript como dependencia global de Node, lo que se explicó en la entrada anterior.

El archivo tsconfig.json

Sí abrimos el archivo tsconfig.json que se acaba de crear veremos algo como lo siguiente.

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig.json to read more about this file */

    /* Basic Options */
    // "incremental": true,                   /* Enable incremental compilation */
    "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
    // "lib": [],                             /* Specify library files to be included in the compilation. */
    // "allowJs": true,                       /* Allow javascript files to be compiled. */
    // "checkJs": true,                       /* Report errors in .js files. */
    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
    // "sourceMap": true,                     /* Generates corresponding '.map' file. */
    // "outFile": "./",                       /* Concatenate and emit output to single file. */
    // "outDir": "./",                        /* Redirect output structure to the directory. */
    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "composite": true,                     /* Enable project compilation */
    // "tsBuildInfoFile": "./",               /* Specify file to store incremental compilation information */
    // "removeComments": true,                /* Do not emit comments to output. */
    // "noEmit": true,                        /* Do not emit outputs. */
    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */
    "strict": true,                           /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,              /* Enable strict null checks. */
    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
    // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    // "noUnusedLocals": true,                /* Report errors on unused locals. */
    // "noUnusedParameters": true,            /* Report errors on unused parameters. */
    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */

    /* Module Resolution Options */
    // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                       /* List of folders to include type definitions from. */
    // "types": [],                           /* Type declaration files to be included in compilation. */
    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true,                  /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */
    // "allowUmdGlobalAccess": true,          /* Allow accessing UMD globals from modules. */

    /* Source Map Options */
    // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */

    /* Advanced Options */
    "skipLibCheck": true,                     /* Skip type checking of declaration files. */
    "forceConsistentCasingInFileNames": true  /* Disallow inconsistently-cased references to the same file. */
  }
}

Un archivo JSON en el que existen cerca de cincuenta opciones, la mayoría de ellas comentadas. Entre las que se encuentran seleccionadas podemos destacar target y module. Mediante la opción target se indica la versión de ECMAScript a la que se compila el código TypeScript. Por defecto, si no se indica nada el compilador trabaja con ES3, pero en este caso el objetivo es ES5. Un estándar que soportan los navegadores desde Internet Explorer 10, Safari 6 y Chrome 23, lo que indica que el código resultante funcionará incluso en navegadores antiguos. En caso de que necesitemos que el código funcione en navegadores más antiguos se puede cambiar la opción. Por otro lado, mediante la opción module se indica el sistema de resolución de modelos usado a la hora de la compilación. Valor que también dejaremos en la opción que viene "commonjs".

Configuración de las carpetas del proyecto

En el caso de los proyectos TypeScript en Node es aconsejable crear dos carpetas, una para el código TypeScript y otra para el código JavaScript compilado. Las cuales se pueden configurar en el archivo tsconfig.json. La carpeta en la que se almacena el código se indica mediante la opción rootDir, en nuestro caso vamos a usar src. Mientras la opción outDir se usa para indicar la carpeta de salida, carpeta que en nuestro proyecto será src. Por eso en nuestro archivo tsconfig.json descontaremos estas dos opciones las configuraremos como se muestra a continuación:

"outDir": "./dist",
"rootDir": "./src",

Programa "¡Hola Mundo!"

Ahora podemos crear nuestro primer programa para evaluar si hemos configurado correctamente el proyecto. Para lo que podemos crearemos la carpeta src y dentro de esta el archivo hola.ts en el que escribiremos solamente console.log('¡Hola Mundo!');. Una vez hecho esto se puede compilar el proyecto con mediante el comando tsc.

Si todo ha ido bien se creará la carpeta dist y dentro de esta un archivo hola.js con la versión JavaScript del programa. Programa que se podrá ejecutar mediante el comando

node ./dist/hola.js

Indicar los tipos de datos en JavaScript mediante los archivos d.ts

Al compilar el código a JavaScript se pierden las indicaciones de los tipos de datos en el código. Ya que el lenguaje no lo soporta. Si posteriormente queremos usar este código en otro proyecto TypeScript la información se ha perdido. Para evitar esto se pueden generar los archivos d.ts. Archivos en los que se almacena la definición de los tipos del API para el código JavaScript. Si queremos generar estos archivos es necesario cambiar la opción declaration del archivo tsconfig.json a verdadero.

La importancia de estos archivos lo podemos ver si cambiamos el contenido del archivo hola.ts por el siguiente.

function hola(msg: String): void {
    console.log(msg)
}

hola('¡Hola Mundo!');

En este se ha creado una función que admite una variable de tipo String y no devuelve nada ( void). Por lo que si le pasamos un valor de tipo numérico a esta función el compilador de TypeScript nos dará un error. Información que se pierde al compilar.

Sí hemos quitado el comentario de la opción declaration y le asignamos el valor true a la hora de volver a compilar con tsc se generan en este caso dos archivos, no uno. El archivo .js con el código compilado y el archivo .d.ts con las definiciones de los tipos. Archivo que contendrá la siguiente línea.

declare function hola(msg: String): void;

Cómo podemos ver es simplemente la definición de los tipos para la función hola.

Integrar código JavaScript en TypeScript

Al ver el contenido de los archivos .d.ts podemos comprender cómo se puede integrar código JavaScript en TypeScript sin crear las versiones .ts de los archivos .js. Si tenemos una librería en JavaScript solo tenemos que crear un archivo .d.ts con las definiciones de las funciones. Así el compilador de TypeScript sabrá cómo usar el API de la librería.

En Node estas definiciones se pueden importar mediante los paquetes @types que generalmente se importarán como dependencia de desarrollo. Así cuando importemos un paquete escrito en JavaScript, además de este deberemos importar el @types correspondiente. Algo que veremos con ejemplos más adelante.

Archivos .map para depuración de código

Además de los archivos .d.ts también es interesante crear los archivos .map. Archivos que asocian el código JavaScript generado con los archivos originales TypeScript. Los cuales son necesarios a la hora de depurar el código. Ya que de otro modo no se puede saber a cuales de las líneas de código TypeScript se corresponden las generadas en JavaScript.

Estos archivos se crean si en el archivo tsconfig.json se elimina el comentario de la opción sourceMap y se le asigna el valor true. De este modo al compilar con tsc se crearán tres archivos por cada uno de los originales: .js, .d.ts y .js.map.

Conclusiones

Hemos visto cómo crear un proyecto TypeScript en Node, para lo que necesitamos comprender el tsconfig.json. Además de esto hemos visto la importancia de dos archivos de definiciones .d.ts, para la integración con JavaScritp, y mapeo .map para la depuración del código.