Espacios de nombres y módulos ES
Existen dos maneras en las que podemos dividir nuestro código fuente en múltiples ficheros. La primera de ellas es el uso de espacios de nombres y la segunda es el uso de módulos ES. En este apartado ilustraremos el uso de ambos mecanismos, aunque cabe mencionar que los espacios de nombres no suelen utilizarse.
Espacios de nombres
TypeScript permite hacer uso de espacios de nombres. Un espacio de nombres es un formato de módulos específico que el lenguaje proporciona desde antes de la aparición de los módulos ES. En la actualidad, los espacios de nombres no se encuentran en desuso, pero también es cierto que la mayoría de características que proporcionan también son proporcionadas por los módulos ES. Es por ello que se recomienda el uso de módulos ES, en lugar de espacios de nombres, con el objetivo de que TypeScript y JavaScript se encuentren alineados en lo que a la gestión de módulos se refiere.
Para ilustrar su funcionamiento, supongamos el siguiente ejemplo de código fuente TypeScript que hemos
utilizado anteriormente, el cual está incluido en el fichero index.ts dentro del directorio src
de nuestro proyecto:
type ColorType = 'red' | 'yellow' | 'blue' | 'orange' | 'green';
abstract class ThreeDimensionalFigure {
constructor(private readonly name: string, private color: ColorType) {
}
getName() {
return this.name;
}
getColor() {
return this.color;
}
setColor(color: ColorType) {
this.color = color;
}
abstract getVolume(): number;
abstract print(): void;
}
class Cube extends ThreeDimensionalFigure {
private readonly faces = 6;
constructor(name: string, color: ColorType, private base: number = 1,
private height: number = 1, private depth: number = 1) {
super(name, color);
}
getFaces() {
return this.faces;
}
getVolume() {
return this.base * this.height * this.depth;
}
print() {
console.log(`I am a ${this.getName()}, I have ${this.getFaces()} faces, ` +
`and my volume is ${this.getVolume()}`);
}
}
abstract class TwoDimensionalFigure {
constructor(private readonly name: string, private color: ColorType) {
}
getName() {
return this.name;
}
getColor() {
return this.color;
}
setColor(color: ColorType) {
this.color = color;
}
abstract getArea(): number;
abstract print(): void;
}
class Rectangle extends TwoDimensionalFigure {
private readonly sides = 4;
constructor(name: string, color: ColorType,
private base: number = 1, private height: number = 1) {
super(name, color);
}
getSides() {
return this.sides;
}
getArea() {
return this.base * this.height;
}
print() {
console.log(`I am a ${this.getName()}, I have ${this.getSides()} sides, ` +
`and my area is ${this.getArea()}`);
}
}
class FigureCollection<T extends TwoDimensionalFigure | ThreeDimensionalFigure> {
constructor(private figures: T[]) {
}
addFigure(newFigure: T) {
this.figures.push(newFigure);
}
getNumberOfFigures() {
return this.figures.length;
}
getFigure(index: number) {
return this.figures[index];
}
print() {
this.figures.forEach((figure) => {
figure.print();
});
}
}
const myTwoDimensionalFigureCollection = new FigureCollection<TwoDimensionalFigure>([
new Rectangle('RedRectangle', 'red', 10, 5),
new Rectangle('GreenRectangle', 'green', 5, 30),
]);
const myThreeDimensionalFigureCollection = new FigureCollection<ThreeDimensionalFigure>([
new Cube('RedCube', 'red', 10, 5, 4),
new Cube('GreenCube', 'green', 5, 30, 7),
]);
myTwoDimensionalFigureCollection.print();
myThreeDimensionalFigureCollection.print();
También supongamos que las opciones habilitadas para el compilador de TypeScript son las siguientes:
{
"exclude": [
"./tests",
"./node_modules",
"./dist"
],
"compilerOptions": {
"target": "ES2024",
"module": "CommonJS",
"rootDir": "./src",
"declaration": true,
"outDir": "./dist",
"strict": true,
}
}
Vamos a dividir el código fuente del ejemplo anterior en múltiples ficheros utilizando, para ello,
un espacio de nombres. Lo primero que haremos es crear un nuevo fichero denominado types.ts en el
directorio src, el cual contendrá el siguiente fragmento de código que hemos extraído del fichero
index.ts:
namespace App {
type ColorType = 'red' | 'yellow' | 'blue' | 'orange' | 'green';
}
Tal y como puede observarse, dicho fragmento de código ha sido incluido en un espacio de nombres
denominado App. Al intentar compilar el código fuente, el compilador de TypeScript emite
numerosos errores relacionados con el tipo de datos ColorType:
src/index.ts:2:61 - error TS2304: Cannot find name 'ColorType'.
2 constructor(private readonly name: string, private color: ColorType) {
~~~~~~~~~
src/index.ts:11:19 - error TS2304: Cannot find name 'ColorType'.
11 setColor(color: ColorType) {
~~~~~~~~~
src/index.ts:22:36 - error TS2304: Cannot find name 'ColorType'.
22 constructor(name: string, color: ColorType, private base: number = 1,
~~~~~~~~~
src/index.ts:42:61 - error TS2304: Cannot find name 'ColorType'.
42 constructor(private readonly name: string, private color: ColorType) {
~~~~~~~~~
src/index.ts:51:19 - error TS2304: Cannot find name 'ColorType'.
51 setColor(color: ColorType) {
~~~~~~~~~
src/index.ts:62:36 - error TS2304: Cannot find name 'ColorType'.
62 constructor(name: string, color: ColorType,
~~~~~~~~~
Found 6 errors in the same file, starting at: src/index.ts:2
El tipo de datos propio ColorType ha pasado a formar parte del espacio de nombres App, que es diferente al
espacio de nombres global, por defecto, donde se encuentra alojado el resto de nuestro código fuente.
También podría darse el caso de que al ejecutar el linter, obtengamos errores como los siguientes:
$eslint .
/home/usuario/DSI/theory-examples/src/types.ts
1:1 error ES2015 module syntax is preferred over namespaces @typescript-eslint/no-namespace
1:11 error 'App' is defined but never used @typescript-eslint/no-unused-vars
2:8 error 'ColorType' is defined but never used @typescript-eslint/no-unused-vars
✖ 3 problems (3 errors, 0 warnings)
Se puede observar como el linter, teniendo en cuenta el primer error, indica que es preferible utilizar
módulos ES a utilizar espacios de nombres. Si le molesta lo anterior, recuerde que puede desactivar
la regla correspondiente (@typescript-eslint/no-namespace) en el fichero de configuración
eslint.config.mjs.
A continuación, observe como también ha cambiado el contenido del directorio dist:
[~/theory-examples(main)]$ls -lrtha dist/
total 20K
drwxrwxr-x 6 usuario usuario 4,0K abr 6 07:59 ..
-rw-rw-r-- 1 usuario usuario 2,4K abr 6 07:59 index.js
-rw-rw-r-- 1 usuario usuario 14 abr 6 07:59 types.js
drwxrwxr-x 2 usuario usuario 4,0K abr 6 07:59 .
-rw-rw-r-- 1 usuario usuario 28 abr 6 07:59 types.d.ts
Ahora, tal y como puede observarse, hemos pasado a tener dos ficheros con código fuente JavaScript.
Es curioso el contenido que se ha generado en el fichero types.js alojado en dist:
"use strict";
No se ha generado código JavaScript porque, realmente, la definición de un tipo de datos propio es una característica de TypeScript y no de JavaScript.
El código fuente del fichero types.ts se encuentra disponible dentro del espacio de nombres App.
Si, por ejemplo, quisiéramos utilizar el tipo de datos ColorType en otro fichero diferente al de su
definición, deberemos exportarlo:
namespace App {
export type ColorType = 'red' | 'yellow' | 'blue' | 'orange' | 'green';
}
A continuación, tenemos dos opciones para hacer uso de ColorType en el código
del fichero index.ts. La primera consiste en preceder a todas las ocurrencias
de ColorType con el nombre del espacio de nombres App seguido de un punto,
además de importar de algún modo el código del fichero types.ts, para
que pueda ser utilizado desde el fichero index.ts:
/// <reference path="types.ts"/>
abstract class ThreeDimensionalFigure {
constructor(private readonly name: string, private color: App.ColorType) {
}
getName() {
return this.name;
}
getColor() {
return this.color;
}
setColor(color: App.ColorType) {
this.color = color;
}
abstract getVolume(): number;
abstract print(): void;
}
// Code goes on here
La segunda opción consistiría en introducir todo el código fuente de index.ts
en el mismo espacio de nombres App:
/// <reference path="types.ts"/>
namespace App {
abstract class ThreeDimensionalFigure {
constructor(private readonly name: string, private color: ColorType) {
}
getName() {
return this.name;
}
getColor() {
return this.color;
}
setColor(color: ColorType) {
this.color = color;
}
abstract getVolume(): number;
abstract print(): void;
}
// Code goes on here
Gracias a los anteriores cambios, ahora, el código compila y ejecuta correctamente, mostrando lo siguiente por la consola:
I am a RedRectangle, I have 4 sides, and my area is 50
I am a GreenRectangle, I have 4 sides, and my area is 150
I am a RedCube, I have 6 faces, and my volume is 200
I am a GreenCube, I have 6 faces, and my volume is 1050
El hecho de que el programa se haya ejecutado correctamente ha sido pura casualidad. En realidad,
es importante recordar que todo el código JavaScript necesario para que nuestro programa funcione
se ha generado en el fichero index.js (el fichero types.js se encuentra vacío).
De hecho, podríamos eliminar la siguiente línea del fichero index.ts y el programa seguiría
ejecutándose correctamente:
/// <reference path="types.ts"/>
Cabe hacer un pequeño inciso en este punto, para indicar que si utiliza directivas como
la anterior, al ejecutar el linter obtendrá un error indicándole que es preferible utilizar
un estilo basado en sentencias import, en lugar de hacer uso de dichas directivas. En este
caso, la regla que emite este tipo de errores es @typescript-eslint/triple-slash-reference.
Este tipo de directiva del compilador de TypeScript permite establecer dependencias entre
ficheros en tiempo de compilación, además de establecer un orden en el que el compilador
procesa los diferentes ficheros. Podemos eliminar la directiva porque, realmente, todo
el código fuente se encuentra alojado en el directorio src, algo que hemos indicado
a través de la opción rootDir del fichero de configuración del compilador de TypeScript.
Además, el orden en el que se procesan los ficheros, en este caso, es el adecuado, aunque
esto último podría no ser así dependiendo del caso particular al que nos enfrentemos.
Por último, el compilador de TypeScript sabe lo que se incluye y no se incluye en un
espacio de nombres concreto. Es por ello que no se produce ningún error en tiempo de
compilación. En cualquier caso, el uso de directivas como la anterior solo se recomienda
en aquellos casos en los que sea estrictamente necesario, es decir, cuando se obtengan
errores por parte del compilador de TypeScript a la hora de no ser capaz de resolver una
referencia concreta o de que no esté procesando los ficheros involucrados en la compilación
en el orden correcto.
Gracias a los espacios de nombres, ahora podremos dividir nuestro código fuente en múltiples ficheros a lo largo de nuestro proyecto. Por ejemplo, creemos algunos ficheros más para incluir las clases abstractas, las clases concretas y las colecciones.
El contenido del fichero abstracts.ts almacenado en el directorio src será el siguiente:
namespace App {
export abstract class ThreeDimensionalFigure {
constructor(private readonly name: string, private color: ColorType) {
}
getName() {
return this.name;
}
getColor() {
return this.color;
}
setColor(color: ColorType) {
this.color = color;
}
abstract getVolume(): number;
abstract print(): void;
}
export abstract class TwoDimensionalFigure {
constructor(private readonly name: string, private color: ColorType) {
}
getName() {
return this.name;
}
getColor() {
return this.color;
}
setColor(color: ColorType) {
this.color = color;
}
abstract getArea(): number;
abstract print(): void;
}
}
El contenido del fichero figures.ts almacenado en el directorio src será el siguiente:
namespace App {
export class Cube extends ThreeDimensionalFigure {
private readonly faces = 6;
constructor(name: string, color: ColorType, private base: number = 1,
private height: number = 1, private depth: number = 1) {
super(name, color);
}
getFaces() {
return this.faces;
}
getVolume() {
return this.base * this.height * this.depth;
}
print() {
console.log(`I am a ${this.getName()}, I have ${this.getFaces()} faces, ` +
`and my volume is ${this.getVolume()}`);
}
}
export class Rectangle extends TwoDimensionalFigure {
private readonly sides = 4;
constructor(name: string, color: ColorType,
private base: number = 1, private height: number = 1) {
super(name, color);
}
getSides() {
return this.sides;
}
getArea() {
return this.base * this.height;
}
print() {
console.log(`I am a ${this.getName()}, I have ${this.getSides()} sides, ` +
`and my area is ${this.getArea()}`);
}
}
}
El contenido del fichero collections.ts, también almacenado en el directorio src,
será tal y como se muestra a continuación:
namespace App {
export class FigureCollection<T extends TwoDimensionalFigure | ThreeDimensionalFigure> {
constructor(private figures: T[]) {
}
addFigure(newFigure: T) {
this.figures.push(newFigure);
}
getNumberOfFigures() {
return this.figures.length;
}
getFigure(index: number) {
return this.figures[index];
}
print() {
this.figures.forEach((figure) => {
figure.print();
});
}
}
}
Por último, el contenido del fichero index.ts será tal y como sigue:
namespace App {
const myTwoDimensionalFigureCollection = new FigureCollection<TwoDimensionalFigure>([
new Rectangle('RedRectangle', 'red', 10, 5),
new Rectangle('GreenRectangle', 'green', 5, 30),
]);
const myThreeDimensionalFigureCollection = new FigureCollection<ThreeDimensionalFigure>([
new Cube('RedCube', 'red', 10, 5, 4),
new Cube('GreenCube', 'green', 5, 30, 7),
]);
myTwoDimensionalFigureCollection.print();
myThreeDimensionalFigureCollection.print();
}
Tras haber llevado a cambio todos los cambios anteriores, el contenido de nuestro directorio
src debería ser algo como lo siguiente:
[~/theory-examples(main)]$ls -lrtha src
total 28K
drwxrwxr-x 6 usuario usuario 4,0K abr 6 07:59 ..
drwxrwxr-x 2 usuario usuario 4,0K abr 6 08:50 .
-rw-rw-r-- 1 usuario usuario 729 abr 6 08:58 abstracts.ts
-rw-rw-r-- 1 usuario usuario 468 abr 6 08:58 collections.ts
-rw-rw-r-- 1 usuario usuario 1,1K abr 6 08:59 figures.ts
-rw-rw-r-- 1 usuario usuario 92 abr 6 09:06 types.ts
-rw-rw-r-- 1 usuario usuario 492 abr 6 09:11 index.ts
Al compilar el código fuente, el compilador de TypeScript no emite ningún error.
Además, el contenido del directorio dist debería ser algo como lo siguiente:
[~/theory-examples(main)]$ls -lrtha dist/
total 48K
drwxrwxr-x 6 usuario usuario 4,0K abr 6 09:13 ..
-rw-rw-r-- 1 usuario usuario 848 abr 6 09:13 abstracts.js
-rw-rw-r-- 1 usuario usuario 676 abr 6 09:13 abstracts.d.ts
-rw-rw-r-- 1 usuario usuario 585 abr 6 09:13 collections.js
-rw-rw-r-- 1 usuario usuario 315 abr 6 09:13 collections.d.ts
-rw-rw-r-- 1 usuario usuario 1,3K abr 6 09:13 figures.js
-rw-rw-r-- 1 usuario usuario 660 abr 6 09:13 figures.d.ts
-rw-rw-r-- 1 usuario usuario 14 abr 6 09:13 types.js
-rw-rw-r-- 1 usuario usuario 542 abr 6 09:13 index.js
-rw-rw-r-- 1 usuario usuario 26 abr 6 09:13 index.d.ts
-rw-rw-r-- 1 usuario usuario 95 abr 6 09:13 types.d.ts
drwxrwxr-x 2 usuario usuario 4,0K abr 6 09:13 .
No obstante, veamos si nuestro programa ejecuta correctamente:
$node dist/index.js
/home/usuario/DSI/theory-examples/dist/index.js:5
new App.Rectangle('RedRectangle', 'red', 10, 5),
^
TypeError: App.Rectangle is not a constructor
at /home/usuario/DSI/theory-examples/dist/index.js:5:9
at Object.<anonymous> (/home/usuario/DSI/theory-examples/dist/index.js:14:3)
at Module._compile (node:internal/modules/cjs/loader:1734:14)
at Object..js (node:internal/modules/cjs/loader:1899:10)
at Module.load (node:internal/modules/cjs/loader:1469:32)
at Function._load (node:internal/modules/cjs/loader:1286:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:235:24)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:151:5)
at node:internal/main/run_main_module:33:47
Node.js v23.9.0
Se puede observar como se produce un error en tiempo de ejecución. ¿Por qué sucede lo anterior? Los espacios de nombres son una funcionalidad exclusiva de TypeScript y, desde el punto de vista de la compilación, no existe ningún problema porque el compilador de TypeScript es capaz de resolver todas las referencias necesarias.
En JavaScript los espacios de nombres no existen y el código JavaScript generado se encuentra
disponible en múltiples ficheros independientes. El error anterior se produce cuando se intenta
instanciar un objeto de la clase Rectangle. JavaScript no es capaz de encontrar la clase
Rectangle ni tampoco su constructor en el fichero index.js que es el que se ha ejecutado.
Para resolver lo anterior, debemos modificar la opción outFile del compilador de TypeScript,
para que tome el valor ./dist/app.js, de modo que todo el código fuente JavaScript generado se
encuentre en un único fichero, y no en varios.
De este modo, si elminamos todo el directorio dist y volvemos a compilar el código fuente
TypeScript, el contenido del dicho directorio será el siguiente:
[~/theory-examples(main)]$ls -lrtha dist/
total 16K
drwxrwxr-x 6 usuario usuario 4,0K abr 6 09:29 ..
-rw-rw-r-- 1 usuario usuario 3,2K abr 6 09:29 app.js
-rw-rw-r-- 1 usuario usuario 1,8K abr 6 09:29 app.d.ts
drwxrwxr-x 2 usuario usuario 4,0K abr 6 09:29 .
Con las últimas versiones, el compilador de TypeScript podría emitir los siguientes errores:
tsconfig.json:33:5 - error TS6082: Only 'amd' and 'system' modules are supported alongside --outFile.
33 "module": "CommonJS", /* Specify what module code is generated. */
~~~~~~~~
tsconfig.json:65:5 - error TS6082: Only 'amd' and 'system' modules are supported alongside --outFile.
65 "outFile": "./dist/app.js", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
~~~~~~~~~
Found 2 errors in the same file, starting at: tsconfig.json:33
Esto se debe a que, en versiones recientes, el compilador de TypeScript no permite utilizar la opción
outFile, a no ser que tengamos AMD o System como valor en la opción module del fichero de
configuración. Esto es un problema porque nuestro código se ejecuta sobre Node.js, por lo que hemos
estado utilizando el valor CommonJS para la opción module. Lo anterior hace que el uso de espacios
de nombres tampoco sea adecuado cuando nuestro código JavaScript generado a partir de código
TypeScript va a ejecutarse sobre Node.js.
A pesar de los errores anteriores, si solo utilizamos en nuestro código fuente espacios de nombres (y no una
mezcla de espacios de nombres con módulos ES, algo que tampoco se recomienda), tras el proceso de compilación,
el fichero app.js contendrá todo el código fuente JavaScript resultante del proceso de compilación de todos
los ficheros con código fuente TypeScript. Es por ello que podremos ejecutar nuestro programa de la
siguiente manera:
$node dist/app.js
I am a RedRectangle, I have 4 sides, and my area is 50
I am a GreenRectangle, I have 4 sides, and my area is 150
I am a RedCube, I have 6 faces, and my volume is 200
I am a GreenCube, I have 6 faces, and my volume is 1050
Espacios de nombres y múltiples directorios
Supongamos que queremos dividir nuestro código fuente aún más y, además,
organizarlo en múltiples directorios ubicados en el directorio src.
Partamos de la configuración del compilador utilizada en el apartado anterior. Además, se
pueden eliminar todos los ficheros *.ts del directorio src, a excepción del fichero index.ts.
A continuación, vamos a crear los subdirectorios abstracts, collections, figures y types
dentro del directorio src de nuestro proyecto:
[~/theory-examples(main)]$ls -l src/
total 20
drwxrwxr-x 2 usuario usuario 4096 abr 6 16:57 abstracts
drwxrwxr-x 2 usuario usuario 4096 abr 6 16:57 collections
drwxrwxr-x 2 usuario usuario 4096 abr 6 16:57 figures
-rw-rw-r-- 1 usuario usuario 786 abr 6 16:57 index.ts
drwxrwxr-x 2 usuario usuario 4096 abr 6 16:57 types
Dentro del directorio abstracts creamos los ficheros TwoDimensionalFigure.ts y ThreeDimensionalFigure.ts.
El contenido del primer fichero será el siguiente:
namespace App {
export abstract class TwoDimensionalFigure {
constructor(private readonly name: string, private color: ColorType) {
}
getName() {
return this.name;
}
getColor() {
return this.color;
}
setColor(color: ColorType) {
this.color = color;
}
abstract getArea(): number;
abstract print(): void;
}
}
El contenido del fichero ThreeDimensionalFigure.ts será el siguiente:
namespace App {
export abstract class ThreeDimensionalFigure {
constructor(private readonly name: string, private color: ColorType) {
}
getName() {
return this.name;
}
getColor() {
return this.color;
}
setColor(color: ColorType) {
this.color = color;
}
abstract getVolume(): number;
abstract print(): void;
}
}
A continuación, en el directorio collections, creamos un fichero FigureCollection.ts,
al cual añadimos el siguiente código fuente:
namespace App {
export class FigureCollection<T extends TwoDimensionalFigure | ThreeDimensionalFigure> {
constructor(private figures: T[]) {
}
addFigure(newFigure: T) {
this.figures.push(newFigure);
}
getNumberOfFigures() {
return this.figures.length;
}
getFigure(index: number) {
return this.figures[index];
}
print() {
this.figures.forEach((figure) => {
figure.print();
});
}
}
}
Seguidamente, en el directorio figures, creamos los ficheros Rectangle.ts y Cube.ts.
El contenido de Rectangle.ts será tal y como sigue:
namespace App {
export class Rectangle extends TwoDimensionalFigure {
private readonly sides = 4;
constructor(name: string, color: ColorType,
private base: number = 1, private height: number = 1) {
super(name, color);
}
getSides() {
return this.sides;
}
getArea() {
return this.base * this.height;
}
print() {
console.log(`I am a ${this.getName()}, I have ${this.getSides()} sides, ` +
`and my area is ${this.getArea()}`);
}
}
}
En el caso del fichero Cube.ts, el contenido será el siguiente:
namespace App {
export class Cube extends ThreeDimensionalFigure {
private readonly faces = 6;
constructor(name: string, color: ColorType, private base: number = 1,
private height: number = 1, private depth: number = 1) {
super(name, color);
}
getFaces() {
return this.faces;
}
getVolume() {
return this.base * this.height * this.depth;
}
print() {
console.log(`I am a ${this.getName()}, I have ${this.getFaces()} faces, ` +
`and my volume is ${this.getVolume()}`);
}
}
}
En el directorio types, existirá un único fichero denominado ColorType.ts:
namespace App {
export type ColorType = 'red' | 'yellow' | 'blue' | 'orange' | 'green';
}
Finalmente, el contenido del fichero index.ts ubicado en el directorio
src ya debía contener lo siguiente:
namespace App {
const myTwoDimensionalFigureCollection =
new FigureCollection<TwoDimensionalFigure>([
new Rectangle('RedRectangle', 'red', 10, 5),
new Rectangle('GreenRectangle', 'green', 5, 30),
]);
const myThreeDimensionalFigureCollection =
new FigureCollection<ThreeDimensionalFigure>([
new Cube('RedCube', 'red', 10, 5, 4),
new Cube('GreenCube', 'green', 5, 30, 7),
]);
myTwoDimensionalFigureCollection.print();
myThreeDimensionalFigureCollection.print();
}
Tras compilar el ejemplo anterior, al ejecutar se produce el siguiente error:
$node dist/app.js
/home/usuario/DSI/theory-examples/dist/app.js:5
new App.Rectangle('RedRectangle', 'red', 10, 5),
^
TypeError: App.Rectangle is not a constructor
at /home/usuario/DSI/theory-examples/dist/app.js:5:9
at Object.<anonymous> (/home/usuario/DSI/theory-examples/dist/app.js:14:3)
at Module._compile (node:internal/modules/cjs/loader:1734:14)
at Object..js (node:internal/modules/cjs/loader:1899:10)
at Module.load (node:internal/modules/cjs/loader:1469:32)
at Function._load (node:internal/modules/cjs/loader:1286:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:235:24)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:151:5)
at node:internal/main/run_main_module:33:47
Node.js v23.9.0
El error se debe a que no se ha procesado el fichero Rectangle.ts.
Para solucionar lo anterior, debemos incluir varias directivas reference-path al principio del
fichero index.ts:
/// <reference path="abstracts/TwoDimensionalFigure.ts"/>
/// <reference path="abstracts/ThreeDimensionalFigure.ts"/>
/// <reference path="collections/FigureCollection.ts"/>
/// <reference path="figures/Rectangle.ts"/>
/// <reference path="figures/Cube.ts"/>
namespace App {
const myTwoDimensionalFigureCollection =
new FigureCollection<TwoDimensionalFigure>([
new Rectangle('RedRectangle', 'red', 10, 5),
new Rectangle('GreenRectangle', 'green', 5, 30),
]);
const myThreeDimensionalFigureCollection =
new FigureCollection<ThreeDimensionalFigure>([
new Cube('RedCube', 'red', 10, 5, 4),
new Cube('GreenCube', 'green', 5, 30, 7),
]);
myTwoDimensionalFigureCollection.print();
myThreeDimensionalFigureCollection.print();
}
Una vez hecho lo anterior y tras volver a compilar, el programa se ejecutará correctamente.
Es importante mencionar en este punto que el orden en el que se han indicado las directivas
reference-path es significativo. Por ejemplo, considere el caso en el que el contenido
de index.ts fuera el siguiente:
/// <reference path="figures/Rectangle.ts"/>
/// <reference path="figures/Cube.ts"/>
/// <reference path="abstracts/TwoDimensionalFigure.ts"/>
/// <reference path="abstracts/ThreeDimensionalFigure.ts"/>
/// <reference path="collections/FigureCollection.ts"/>
namespace App {
const myTwoDimensionalFigureCollection =
new FigureCollection<TwoDimensionalFigure>([
new Rectangle('RedRectangle', 'red', 10, 5),
new Rectangle('GreenRectangle', 'green', 5, 30),
]);
const myThreeDimensionalFigureCollection =
new FigureCollection<ThreeDimensionalFigure>([
new Cube('RedCube', 'red', 10, 5, 4),
new Cube('GreenCube', 'green', 5, 30, 7),
]);
myTwoDimensionalFigureCollection.print();
myThreeDimensionalFigureCollection.print();
}
En este caso, tras compilar el código, al intentar ejecutar el programa surge el siguiente error:
$node dist/app.js
/home/usuario/DSI/theory-examples/dist/app.js:4
class Rectangle extends App.TwoDimensionalFigure {
^
TypeError: Class extends value undefined is not a constructor or null
at /home/usuario/DSI/theory-examples/dist/app.js:4:33
at Object.<anonymous> (/home/usuario/DSI/theory-examples/dist/app.js:25:3)
at Module._compile (node:internal/modules/cjs/loader:1734:14)
at Object..js (node:internal/modules/cjs/loader:1899:10)
at Module.load (node:internal/modules/cjs/loader:1469:32)
at Function._load (node:internal/modules/cjs/loader:1286:12)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:235:24)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:151:5)
at node:internal/main/run_main_module:33:47
Node.js v23.9.0
El código de los ficheros Rectangle.ts y Cube.ts es procesado por el compilador de TypeScript
con anterioridad al código de los ficheros TwoDimensionalFigure.ts y ThreeDimensionalFigure.ts.
Los dos primeros ficheros hacen referencia a código incluido en los dos segundos ficheros,
respectivamente. Es por ello que se produce el error, al no haberse procesado TwoDimensionalFigure.ts
con anterioridad a Rectangle.ts, en este caso concreto.
Módulos ES
A lo largo de su historia, JavaScript ha gestionado los módulos haciendo uso de diferentes formatos. Al mismo tiempo, desde su nacimiento, TypeScript ha soportado muchos de esos formatos. Durante todo este tiempo, el estándar de JavaScript ha adoptado un formato denominado módulos ES (o ES6, dado que se incluyeron a partir de la sexta versión del estándar ECMAScript en 2015).
En TypeScript, al igual que en JavaScript (a partir de ES6), cualquier fichero que contenga
una primera sentencia import o una sentencia export se considera un módulo. Un fichero sin
lo anterior se trata como un script cuyo contenido se encuentra disponible en el ámbito global.
Un módulo, sin embargo, tiene su propio ámbito local de ejecución. Aquellas variables, funciones,
clases, etc., definidas en un módulo no serán visibles desde fuera del mismo, a menos que sean
exportadas de manera explícita. Igualmente, para utilizar una variable, función, clase, interfaz,
etc., exportada desde un módulo diferente, la misma tendrá que importarse explícitamente allá donde
quiera ser utilizada.
Hay tres aspectos fundamentales que se deben tener en cuenta a la hora de escribir código modular en TypeScript:
- Sintaxis. ¿Qué sintaxis puedo utilizar para importar/exportar?
- Resolución de módulos. ¿Qué relación existe entre el nombre y directorio que utilizo en el código fuente para referirme a un módulo y el fichero correspondiente en disco?
- Carga y ejecución de módulos. ¿Cómo va a ser cargado y ejecutado el código JavaScript generado perteneciente a un módulo? Este aspecto tiene que ver con la apariencia del código JavaScript generado por el compilador de TypeScript.
Respondiendo a la primera pregunta, en nuestro caso, utilizaremos la sintaxis de módulos ES,
esto es, hacer uso de import y export. También se podría utilizar sintaxis CommonJS
(es la que utilizan, al menos en versiones antiguas, la gran mayoría de los módulos
disponibles a través de npm), la cual se basa en require() y module.exports = {}.
En lo que respecta a la segunda pregunta, la resolución de módulos es el proceso por el cual,
a partir de una cadena de caracteres que acompaña, por ejemplo, a una sentencia import, se
determina a qué fichero en disco se refiere dicha cadena. TypeScript incluye dos estrategias
de resolución de módulos configurables a partir de la opción moduleResolution del compilador:
- Clásica. La opción
moduleResolutiontoma por defecto el valorclassiccuando la opciónmodulees diferente al valorcommonjs. Se incluye para proporcionar retrocompatibilidad. - Node. La opción
moduleResolutiontoma por defecto el valornodecuando la opciónmoduletoma el valorcommonjs. Esta estrategia de resolución (en tiempo de compilación) replica la manera en la que Node.js utiliza CommonJS como resolutor y cargador de módulos (en tiempo de ejecución).
Para responder a la tercera y última pregunta, se deben tener en cuenta dos opciones del compilador de TypeScript que afectan a la apariencia del código JavaScript generado:
target. Determina para qué versión del estándar ECMAScript se va a generar el código JavaScript. Generalmente, el valor que tome esta propiedad dependerá del entorno de ejecución donde se va a ejecutar el código JavaScript generado, para lo que se deberá tener en cuenta, por ejemplo, aspectos como el navegador o versión de Node.js más antigua donde debería poder ejecutarse.module. Determina el código JavaScript que debe generarse para que un módulo pueda interactuar con otro en tiempo de ejecución. Toda comunicación entre módulos se establece a través de un cargador de módulos que, en tiempo de ejecución, se encarga de localizar y ejecutar el código de un módulo.
En el caso que nos ocupa, hemos estado generando código para uno de los últimos estándares ECMAScript (ES2024),
el cual está soportado por las últimas versiones de Node.js, además de hacer uso de CommonJS como cargador de
módulos, que es el utilizado por Node.js. Lo anterior se corresponde con fijar el valor ES2024 a la
propiedad target y el valor CommonJS a la propiedad module.
Una vez dicho todo lo anterior, continuemos con el ejemplo de la sección anterior. Para ello, en primer lugar,
modifiquemos todos los ficheros de código fuente incluidos en el directorio src para eliminar el espacio de
nombres App. También, eliminemos todas las directivas reference-path del fichero index.ts. Por último,
deberemos tener las siguientes opciones del compilador habilitadas:
{
"exclude": [
"tests"
"node_modules",
"dist",
],
"compilerOptions": {
"target": "ES2024",
"module": "CommonJS",
"rootDir": "./src",
"declaration": true,
"outDir": "./dist",
"strict": true,
}
}
Lo anterior hará que el compilador de TypeScript informe de múltiples errores al tratar de recompilar el código fuente:
src/abstracts/ThreeDimensionalFigure.ts:4:20 - error TS2304: Cannot find name 'ColorType'.
4 private color: ColorType,
~~~~~~~~~
src/abstracts/ThreeDimensionalFigure.ts:13:19 - error TS2304: Cannot find name 'ColorType'.
13 setColor(color: ColorType) {
~~~~~~~~~
src/abstracts/TwoDimensionalFigure.ts:4:20 - error TS2304: Cannot find name 'ColorType'.
4 private color: ColorType,
~~~~~~~~~
src/abstracts/TwoDimensionalFigure.ts:13:19 - error TS2304: Cannot find name 'ColorType'.
13 setColor(color: ColorType) {
~~~~~~~~~
src/collections/FigureCollection.ts:2:13 - error TS2304: Cannot find name 'TwoDimensionalFigure'.
2 T extends TwoDimensionalFigure | ThreeDimensionalFigure,
~~~~~~~~~~~~~~~~~~~~
src/collections/FigureCollection.ts:2:36 - error TS2304: Cannot find name 'ThreeDimensionalFigure'.
...
Found 20 errors in 6 files.
Errors Files
2 src/abstracts/ThreeDimensionalFigure.ts:4
2 src/abstracts/TwoDimensionalFigure.ts:4
2 src/collections/FigureCollection.ts:2
3 src/figures/Cube.ts:1
3 src/figures/Rectangle.ts:1
8 src/index.ts:2
Para solucionar estos errores, lo que debemos hacer es usar sentencias import para importar todas
aquellas entidades de código exportadas que necesitemos utilizar en cada uno de los ficheros
del directorio src. Por ejemplo, comencemos con el fichero ThreeDimensionalFigure.ts ubicado
en el subdirectorio abstracts de src:
import { ColorType } from "../types/ColorType";
export abstract class ThreeDimensionalFigure {
constructor(
private readonly name: string,
private color: ColorType,
) {}
getName() {
return this.name;
}
getColor() {
return this.color;
}
setColor(color: ColorType) {
this.color = color;
}
abstract getVolume(): number;
abstract print(): void;
}
Se puede observar como hemos importado el tipo de datos propio ColorType que se
encuentra definido en el fichero ColorType.ts del subdirectorio types de
src. También es importante mencionar que debemos mantener la palabra reservada
export en todas aquellas entidades de código que queramos utilizar fuera del fichero
en el que se encuentran definidas.
De un modo similar, el contenido del fichero TwoDimensionalFigure.ts del directorio
abstracts quedaría como sigue:
import { ColorType } from "../types/ColorType";
export abstract class TwoDimensionalFigure {
constructor(
private readonly name: string,
private color: ColorType,
) {}
getName() {
return this.name;
}
getColor() {
return this.color;
}
setColor(color: ColorType) {
this.color = color;
}
abstract getArea(): number;
abstract print(): void;
}
En el caso del fichero src/collections/FigureCollections.ts, su contenido quedará tal y como sigue:
import { TwoDimensionalFigure } from "../abstracts/TwoDimensionalFigure";
import { ThreeDimensionalFigure } from "../abstracts/ThreeDimensionalFigure";
export class FigureCollection<
T extends TwoDimensionalFigure | ThreeDimensionalFigure,
> {
constructor(private figures: T[]) {}
addFigure(newFigure: T) {
this.figures.push(newFigure);
}
getNumberOfFigures() {
return this.figures.length;
}
getFigure(index: number) {
return this.figures[index];
}
print() {
this.figures.forEach((figure) => {
figure.print();
});
}
}
El contenido del fichero src/figures/Cube.ts debería ser el siguiente:
import { ThreeDimensionalFigure } from "../abstracts/ThreeDimensionalFigure";
import { ColorType } from "../types/ColorType";
export class Cube extends ThreeDimensionalFigure {
private readonly faces = 6;
constructor(
name: string,
color: ColorType,
private base: number = 1,
private height: number = 1,
private depth: number = 1,
) {
super(name, color);
}
getFaces() {
return this.faces;
}
getVolume() {
return this.base * this.height * this.depth;
}
print() {
console.log(
`I am a ${this.getName()}, I have ${this.getFaces()} faces, ` +
`and my volume is ${this.getVolume()}`,
);
}
}
De un modo similar, el contenido del fichero src/figures/Rectangle.ts debería quedar tal y como sigue:
import { TwoDimensionalFigure } from "../abstracts/TwoDimensionalFigure";
import { ColorType } from "../types/ColorType";
export class Rectangle extends TwoDimensionalFigure {
private readonly sides = 4;
constructor(
name: string,
color: ColorType,
private base: number = 1,
private height: number = 1,
) {
super(name, color);
}
getSides() {
return this.sides;
}
getArea() {
return this.base * this.height;
}
print() {
console.log(
`I am a ${this.getName()}, I have ${this.getSides()} sides, ` +
`and my area is ${this.getArea()}`,
);
}
}
El contenido del fichero src/index.ts deberá ser el siguiente:
import { FigureCollection } from "./collections/FigureCollection";
import { TwoDimensionalFigure } from "./abstracts/TwoDimensionalFigure";
import { Rectangle } from "./figures/Rectangle";
import { ThreeDimensionalFigure } from "./abstracts/ThreeDimensionalFigure";
import { Cube } from "./figures/Cube";
const myTwoDimensionalFigureCollection =
new FigureCollection<TwoDimensionalFigure>([
new Rectangle("RedRectangle", "red", 10, 5),
new Rectangle("GreenRectangle", "green", 5, 30),
]);
const myThreeDimensionalFigureCollection =
new FigureCollection<ThreeDimensionalFigure>([
new Cube("RedCube", "red", 10, 5, 4),
new Cube("GreenCube", "green", 5, 30, 7),
]);
myTwoDimensionalFigureCollection.print();
myThreeDimensionalFigureCollection.print();
Si recompilamos el código fuente, el directorio dist debería contener una estructura
similar al directorio src:
[~/theory-examples(main)]$ls -l dist/
total 24
drwxrwxr-x 2 usuario usuario 4096 abr 6 20:30 abstracts
drwxrwxr-x 2 usuario usuario 4096 abr 6 20:30 collections
drwxrwxr-x 2 usuario usuario 4096 abr 6 20:30 figures
-rw-rw-r-- 1 usuario usuario 11 abr 6 20:30 index.d.ts
-rw-rw-r-- 1 usuario usuario 569 abr 6 20:30 index.js
drwxrwxr-x 2 usuario usuario 4096 abr 6 20:30 types
Además, la ejecución del programa muestra lo siguiente por la consola:
[~/theory-examples(main)]$node dist/index.js
I am a RedRectangle, I have 4 sides, and my area is 50
I am a GreenRectangle, I have 4 sides, and my area is 150
I am a RedCube, I have 6 faces, and my volume is 200
I am a GreenCube, I have 6 faces, and my volume is 1050
El contenido del fichero index.js generado debería ser algo como lo que sigue:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const FigureCollection_1 = require("./collections/FigureCollection");
const Rectangle_1 = require("./figures/Rectangle");
const Cube_1 = require("./figures/Cube");
const myTwoDimensionalFigureCollection = new FigureCollection_1.FigureCollection([
new Rectangle_1.Rectangle("RedRectangle", "red", 10, 5),
new Rectangle_1.Rectangle("GreenRectangle", "green", 5, 30),
]);
const myThreeDimensionalFigureCollection = new FigureCollection_1.FigureCollection([
new Cube_1.Cube("RedCube", "red", 10, 5, 4),
new Cube_1.Cube("GreenCube", "green", 5, 30, 7),
]);
myTwoDimensionalFigureCollection.print();
myThreeDimensionalFigureCollection.print();
Si modificamos la opción module del compilador para asignarle el valor AMD,
el código generado en index.js será diferente:
define(["require", "exports", "./collections/FigureCollection", "./figures/Rectangle", "./figures/Cube"], function (require, exports, FigureCollection_1, Rectangle_1, Cube_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const myTwoDimensionalFigureCollection = new FigureCollection_1.FigureCollection([
new Rectangle_1.Rectangle("RedRectangle", "red", 10, 5),
new Rectangle_1.Rectangle("GreenRectangle", "green", 5, 30),
]);
const myThreeDimensionalFigureCollection = new FigureCollection_1.FigureCollection([
new Cube_1.Cube("RedCube", "red", 10, 5, 4),
new Cube_1.Cube("GreenCube", "green", 5, 30, 7),
]);
myTwoDimensionalFigureCollection.print();
myThreeDimensionalFigureCollection.print();
});
Si ahora modificamos dicha opción para que tome el valor ES2022, esta vez el código
generado en index.js tendrá la siguiente apariencia (el cual es muy similar al propio
código a partir del cual se ha generado, esto es, el contenido en index.ts):
import { FigureCollection } from "./collections/FigureCollection";
import { Rectangle } from "./figures/Rectangle";
import { Cube } from "./figures/Cube";
const myTwoDimensionalFigureCollection = new FigureCollection([
new Rectangle("RedRectangle", "red", 10, 5),
new Rectangle("GreenRectangle", "green", 5, 30),
]);
const myThreeDimensionalFigureCollection = new FigureCollection([
new Cube("RedCube", "red", 10, 5, 4),
new Cube("GreenCube", "green", 5, 30, 7),
]);
myTwoDimensionalFigureCollection.print();
myThreeDimensionalFigureCollection.print();
Tal y como comentamos, dependiendo del entorno donde queramos ejecutar el código generado,
tendremos que modificar la anterior propiedad. Por ejemplo, al intentar ejecutar el código
generado en Node.js haciendo uso de los dos últimos valores de la propiedad module, el
programa falla, dado que el cargador de módulos de Node.js (CommonJS) no es capaz de resolver
y ejecutar los módulos correspondientes.
Sintaxis alternativa en la importación y exportación de módulos
Respecto a la importación de módulos, se puede utilizar una sintaxis alternativa, tal y como
muestra el siguiente ejemplo de contenido del fichero index.ts:
import * as TwoDim from "./abstracts/TwoDimensionalFigure";
import { ThreeDimensionalFigure } from "./abstracts/ThreeDimensionalFigure";
import { Rectangle as Rect } from "./figures/Rectangle";
import { Cube } from "./figures/Cube";
import { FigureCollection } from "./collections/FigureCollection";
const myTwoDimensionalFigureCollection =
new FigureCollection<TwoDim.TwoDimensionalFigure>([
new Rect("RedRectangle", "red", 10, 5),
new Rect("GreenRectangle", "green", 5, 30),
]);
const myThreeDimensionalFigureCollection =
new FigureCollection<ThreeDimensionalFigure>([
new Cube("RedCube", "red", 10, 5, 4),
new Cube("GreenCube", "green", 5, 30, 7),
]);
myTwoDimensionalFigureCollection.print();
myThreeDimensionalFigureCollection.print();
Hemos modificado la sintaxis del primer import del anterior ejemplo:
import * as TwoDim from "./abstracts/TwoDimensionalFigure";
La anterior sintaxis indica que, todas las entidades de código exportadas pertenecientes
al fichero ./abstracts/TwoDimensionalFigure.ts deben importarse en conjunto. Luego,
para referirnos a alguna de dichas entidades, deberemos prefijar el alias TwoDim seguido
de un punto y el identificador de dicha entidad:
new FigureCollection<TwoDim.TwoDimensionalFigure>([
Los alias se pueden utilizar de otro modo, tal y como se muestra en la siguiente línea:
import { Rectangle as Rect } from "./figures/Rectangle";
En este caso, en lugar de utilizar Rectangle para referirnos a dicha clase, podremos
hacerlo utilizando Rect, dentro del fichero donde la hemos importado:
new Rect("RedRectangle", "red", 10, 5),
new Rect("GreenRectangle", "green", 5, 30),
Por último, es importante mencionar que, por cada fichero, se puede definir una exportación
por defecto. Por ejemplo, podemos modificar el fichero src/figures/Cube.ts para incluir
dicha exportación por defecto:
import { ThreeDimensionalFigure } from "../abstracts/ThreeDimensionalFigure";
import { ColorType } from "../types/ColorType";
export default class Cube extends ThreeDimensionalFigure {
private readonly faces = 6;
constructor(
name: string,
color: ColorType,
private base: number = 1,
private height: number = 1,
private depth: number = 1,
) {
super(name, color);
}
getFaces() {
return this.faces;
}
getVolume() {
return this.base * this.height * this.depth;
}
print() {
console.log(
`I am a ${this.getName()}, I have ${this.getFaces()} faces, ` +
`and my volume is ${this.getVolume()}`,
);
}
}
Ahora, a la hora de importar la clase Cube en otro fichero diferente, por ejemplo,
en el fichero src/index.ts, podremos hacer algo como lo siguiente:
import * as TwoDim from "./abstracts/TwoDimensionalFigure";
import { ThreeDimensionalFigure } from "./abstracts/ThreeDimensionalFigure";
import { Rectangle as Rect } from "./figures/Rectangle";
import MyCube from "./figures/Cube";
import { FigureCollection } from "./collections/FigureCollection";
const myTwoDimensionalFigureCollection =
new FigureCollection<TwoDim.TwoDimensionalFigure>([
new Rect("RedRectangle", "red", 10, 5),
new Rect("GreenRectangle", "green", 5, 30),
]);
const myThreeDimensionalFigureCollection =
new FigureCollection<ThreeDimensionalFigure>([
new MyCube("RedCube", "red", 10, 5, 4),
new MyCube("GreenCube", "green", 5, 30, 7),
]);
myTwoDimensionalFigureCollection.print();
myThreeDimensionalFigureCollection.print();
En concreto, hemos modificado la siguiente línea:
import MyCube from "./figures/Cube";
En este caso, al no usar {} estamos indicando que queremos importar la exportación
por defecto y, además, le estamos dando el alias MyCube. Es por ello que, en el código,
debemos hacer lo siguiente para referirnos a la clase Cube:
new MyCube("RedCube", "red", 10, 5, 4),
new MyCube("GreenCube", "green", 5, 30, 7),
Módulos ECMAScript (ESM) en Node.js
Durante los últimos años, se ha intentado que Node.js proporcione soporte para ejecutar módulos ESM, lo cual ha sido bastante complicado dado que, como ya se ha indicado con anterioridad, tradicionalmente, Node.js siempre ha utilizado el sistema CommonJS (CJS).
En la sección anterior, al hacer uso del valor CommonJS en la opción module del
compilador, realmente, hemos estado generando módulos CJS.
No obstante, ahora que Node.js proporciona la posibilidad de ejecutar directamente
módulos ESM, las opciones moduleResolution y module del compilador de TypeScript
también pueden tomar los valores Node16 y NodeNext.
Al mismo tiempo, Node.js también soporta una nueva opción en el fichero package.json,
denominada type que puede tomar los valores module o commonjs. La opción permite
establecer si los ficheros de código JavaScript (.js) generados deben interpretarse
como módulos ESM o como módulos CJS en Node.js. Por defecto, si no está establecida,
los ficheros se interpretarán como módulos CJS.
Existen diferencias en la gestión de un módulo ESM y un módulo CJS, siendo una de las más importantes la forma en la que se deben importar artefactos en nuestro código escrito en TypeScript.
Por ejemplo, cuando TypeScript encuentra un fichero con extensión .ts, va a intentar
averiguar, a través del fichero package.json, si ese fichero debe interpretarse como
un módulo ESM y, por lo tanto, cómo poder encontrar otros módulos que ese fichero importe,
además de cómo generar adecuadamente el código JavaScript correspondiente.
Cuando un fichero .ts se compila como un módulo ESM, la sintaxis import/export se dejará
tal cual en el fichero .js correspondiente. Sin embargo, cuando se compila como un módulo
CJS, el código generado será muy similar a aquel que se obtiene fijando el valor CommonJS
a la opción module de la configuración del compilador.
Continuando con el ejemplo de secciones anteriores, modifiquemos nuestro fichero package.json
para que contenga la opción type fijada al valor commonjs. También, modifiquemos la opción
module del fichero de configuración del compilador para que tome el valor Node16.
Al intentar compilar, TypeScript interpretará todos los ficheros .ts como módulos CJS
(debido a la opción type del fichero package.json). Por lo tanto, el código JavaScript
generado será muy similar al generado utilizando el valor CommonJS para la opción module de
la configuración del compilador (aunque realmente dicha opción tenga asignado el valor Node16).
Lo anterior hará que el código fuente generado se siga ejecutando correctamente en Node.js.
Véase, por ejemplo, el código generado en el fichero index.js:
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const Rectangle_1 = require("./figures/Rectangle");
const Cube_1 = __importDefault(require("./figures/Cube"));
const FigureCollection_1 = require("./collections/FigureCollection");
const myTwoDimensionalFigureCollection = new FigureCollection_1.FigureCollection([
new Rectangle_1.Rectangle("RedRectangle", "red", 10, 5),
new Rectangle_1.Rectangle("GreenRectangle", "green", 5, 30),
]);
const myThreeDimensionalFigureCollection = new FigureCollection_1.FigureCollection([
new Cube_1.default("RedCube", "red", 10, 5, 4),
new Cube_1.default("GreenCube", "green", 5, 30, 7),
]);
myTwoDimensionalFigureCollection.print();
myThreeDimensionalFigureCollection.print();
Ahora bien, modifiquemos la opción type del fichero package.json para que tome el valor module.
En este caso, el compilador emite los siguientes errores:
src/abstracts/ThreeDimensionalFigure.ts:1:27 - error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean '../types/ColorType.js'?
1 import { ColorType } from "../types/ColorType";
~~~~~~~~~~~~~~~~~~~~
src/abstracts/TwoDimensionalFigure.ts:1:27 - error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean '../types/ColorType.js'?
1 import { ColorType } from "../types/ColorType";
~~~~~~~~~~~~~~~~~~~~
src/collections/FigureCollection.ts:1:38 - error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean '../abstracts/TwoDimensionalFigure.js'?
1 import { TwoDimensionalFigure } from "../abstracts/TwoDimensionalFigure";
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/collections/FigureCollection.ts:2:40 - error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean '../abstracts/ThreeDimensionalFigure.js'?
...
Found 15 errors in 6 files.
Errors Files
1 src/abstracts/ThreeDimensionalFigure.ts:1
1 src/abstracts/TwoDimensionalFigure.ts:1
2 src/collections/FigureCollection.ts:1
3 src/figures/Cube.ts:1
3 src/figures/Rectangle.ts:1
5 src/index.ts:1
Ahora TypeScript interpreta que todos los ficheros .ts se tratan de módulos ESM. Además, al haber
fijado module al valor Node16, la opción moduleResolution ha tomado el mismo valor implícitamente.
La resolución de módulos ESM es diferente a la resolución de módulos CJS y, por lo tanto, el compilador
emite los errores anteriores, indicando que en las sentencias import se debe hacer uso de la extensión
.js. Añadiendo dichas extensiones en todas y cada una de las sentencias import de los ficheros
ubicados en el directorio src, el compilador dejará de emitir dichos mensajes de error y, además,
el código fuente generado se ejecutará correctamente en Node.js. Además, obsérvese el código generado
para el fichero index.js, el cual difiere del generado con anterioridad:
import { Rectangle as Rect } from "./figures/Rectangle.js";
import MyCube from "./figures/Cube.js";
import { FigureCollection } from "./collections/FigureCollection.js";
const myTwoDimensionalFigureCollection = new FigureCollection([
new Rect("RedRectangle", "red", 10, 5),
new Rect("GreenRectangle", "green", 5, 30),
]);
const myThreeDimensionalFigureCollection = new FigureCollection([
new MyCube("RedCube", "red", 10, 5, 4),
new MyCube("GreenCube", "green", 5, 30, 7),
]);
myTwoDimensionalFigureCollection.print();
myThreeDimensionalFigureCollection.print();
Por último, cabe mencionar que Node.js ahora soporta dos extensiones nuevas: .mjs y .cjs.
Node.js siempre interpretará los ficheros con la primera extensión como módulos ESM, mientras
que los interpretará como módulos CJS haciendo uso de la segunda extensión, con independencia
de lo que se haya establecido en la opción type del fichero package.json.
Del mismo modo, TypeScript ahora soporta dos nuevas extensiones para los ficheros con código
fuente: .mts y .cts. Al compilar ficheros con estas extensiones, el compilador generará
ficheros con extensiones .mjs y .cjs, respectivamente, y cuyo contenido también será
diferente según el caso.