Cómo utilizar módulos en TypeScript

El autor seleccionó el Fondo de Ayuda COVID-19 para recibir una donación como parte del programa Write for DOnations .
Introducción
Los módulos son una forma de organizar el código en piezas más pequeñas y manejables, lo que permite que los programas importen código desde diferentes partes de la aplicación. Ha habido algunas estrategias para implementar la modularidad en el código JavaScript a lo largo de los años. TypeScript evolucionó junto con las especificaciones ECMAScript para proporcionar un sistema de módulos estándar para programas JavaScript que tenga la flexibilidad de trabajar con estos diferentes formatos. TypeScript ofrece soporte para crear y usar módulos con una sintaxis unificada que es similar a la sintaxis del módulo ES , al tiempo que permite al desarrollador generar código que se dirige a diferentes cargadores de módulos, como Node.js ( CommonJS ), require.js ( AMD ), UMD , SystemJS o módulos nativos ECMAScript 2015 (ES6).
En este tutorial, creará y utilizará módulos en TypeScript. Seguirá diferentes ejemplos de código en su propio entorno TypeScript, mostrando cómo usar la palabra clave import
and export
, cómo establecer exportaciones predeterminadas y cómo hacer que los archivos con exports
objetos sobrescritos sean compatibles con su código.
Prerrequisitos
Para seguir este tutorial, necesitarás:
- Un entorno en el que puede ejecutar programas TypeScript para seguir los ejemplos. Para configurarlo en su máquina local, necesitará lo siguiente:
- Se instalaron Node y npm (o yarn ) para ejecutar un entorno de desarrollo que maneje paquetes relacionados con TypeScript. Este tutorial se probó con la versión 14.3.0 de Node.js y la versión 6.14.5 de npm. Para instalar en macOS o Ubuntu 18.04, siga los pasos de Cómo instalar Node.js y crear un entorno de desarrollo local en macOS o la sección Instalación mediante un PPA de Cómo instalar Node.js en Ubuntu 18.04 . Esto también funciona si está utilizando el Subsistema de Windows para Linux (WSL) .
- Necesitará conocimientos suficientes de JavaScript, especialmente de sintaxis ES6+, como desestructuración, operadores rest e importaciones/exportaciones . Si necesita más información sobre estos temas, le recomendamos leer nuestra serie Cómo codificar en JavaScript .
- Este tutorial hará referencia a aspectos de los editores de texto que admiten TypeScript y muestran errores en línea. Esto no es necesario para usar TypeScript, pero aprovecha más las características de TypeScript. Para aprovecharlas, puede usar un editor de texto como Visual Studio Code , que tiene compatibilidad total con TypeScript de fábrica.
Todos los ejemplos que se muestran en este tutorial se crearon utilizando TypeScript versión 4.2.2.
Configuración del proyecto
En este paso, creará un proyecto de muestra que contiene dos clases pequeñas para manejar operaciones vectoriales: Vector2
y Vector3
. En este caso, un vector se refiere a una medida matemática de magnitud y distancia, que se utiliza a menudo en programas de gráficos visuales.
Las clases que cree tendrán una única operación en cada una: la suma de vectores. Más adelante, utilizará estas clases de ejemplo para probar la importación y exportación de código de un programa a otro.
Primero, crea un directorio que albergará tu código de muestra:
- mkdir vector_project
Una vez creado el directorio, conviértalo en su directorio de trabajo:
- cd vector_project
Ahora que estás en la raíz de tu proyecto, crea tu aplicación Node.js con npm
:
- npm init
Esto creará un package.json
archivo para su proyecto.
A continuación, agregue TypeScript como una dependencia de desarrollo:
- npm install typescript@4.2.2 --save-dev
Esto instalará TypeScript en su proyecto, con el compilador TypeScript configurado con su configuración predeterminada. Para crear sus propias configuraciones personalizadas, deberá crear un archivo de configuración específico.
Cree y abra un archivo con el nombre tsconfig.json
en la raíz de su proyecto. Para que su proyecto funcione con los ejercicios de este tutorial, agregue el siguiente contenido al archivo:
tsconfig.json
{ "compilerOptions": { "target": "ES6", "module": "CommonJS", "outDir": "./out", "rootDir": "./src", "strict": true }}
En este código, se establecen varias configuraciones para el compilador TypeScript. "target": "ES6"
determina el entorno para el que se compilará el código y "outDir": "./out"
especifica "rootDir": "./src"
qué directorios contendrán la salida y la entrada del compilador, respectivamente. "strict": true
establece un nivel de verificación de tipos estricta. Por último, "module": "CommonJS"
especifica el módulo system como CommonJS
. Lo usará para simular el trabajo con una aplicación Node.js.
Una vez configurado el proyecto, ahora puedes pasar a crear módulos con sintaxis básica.
Creación de módulos en TypeScript conexport
En esta sección, creará módulos en TypeScript utilizando la sintaxis de módulo de TypeScript.
De manera predeterminada, los archivos en TypeScript se tratan como scripts globales. Esto significa que cualquier variable , clase , función u otra construcción declarada en el archivo está disponible globalmente. Tan pronto como comience a usar módulos en un archivo, este archivo pasa a tener alcance de módulo y ya no se ejecuta globalmente.
Para mostrar esto en acción, creará su primera clase: Vector2
. Cree un nuevo directorio llamado src/
en la raíz del proyecto:
- mkdir src
Este es el directorio que usted establece como directorio raíz ( rootDir
) en su tsconfig.json
archivo.
Dentro de esta carpeta, crea un nuevo archivo llamado vector2.ts
. Abre este archivo en tu editor de texto favorito y luego escribe tu Vector2
clase:
proyecto_vector/src/vector2.ts
class Vector2 { constructor(public x: number, public y: number) {} add(otherVector2: Vector2) { return new Vector2(this.x + otherVector2.x, this.y + otherVector2.y); }}
En este código, estás declarando una clase llamada Vector2
, que se crea al pasar dos números como parámetros, establecidos como propiedades x
y y
. Esta clase tiene un método que agrega un vector a sí misma al combinar los valores respectivos x
de y y
.
Como su archivo actualmente no utiliza módulos, su Vector2
alcance es global. Para convertir su archivo en un módulo, solo tiene que exportar su Vector2
clase:
proyecto_vector/src/vector2.ts
export class Vector2 { constructor(public x: number, public y: number) {} add(otherVector2: Vector2) { return new Vector2(this.x + otherVector2.x, this.y + otherVector2.y); }}
El archivo src/vector2.ts
ahora es un módulo que tiene una única exportación: la Vector2
clase. Guarde y cierre el archivo.
A continuación, puedes crear tu Vector3
clase. Crea el vector3.ts
archivo dentro del src/
directorio, luego abre el archivo en tu editor de texto favorito y escribe el siguiente código:
proyecto_vector/src/vector3.ts
export class Vector3 { constructor(public x: number, public y: number, public z: number) {} add(otherVector3: Vector3) { return new Vector3( this.x + otherVector3.x, this.y + otherVector3.y, this.z + otherVector3.z ); }}
Este código crea una clase similar a Vector2
, pero con una z
propiedad adicional que almacena el vector en tres dimensiones. Observe que la export
palabra clave ya ha convertido a este archivo en su propio módulo. Guarde y cierre este archivo.
Ahora tienes dos archivos vector2.ts
y vector3.ts
, ambos son módulos. Cada archivo tiene una única exportación, que es la clase del vector que representan. En la siguiente sección, incorporarás estos módulos a otros archivos con import
instrucciones.
Uso de módulos en TypeScript conimport
En la sección anterior, viste cómo crear módulos. En esta sección, importarás estos módulos para usarlos en otras partes de tu código.
Un escenario común al trabajar con módulos en TypeScript es tener un solo archivo que recopila varios módulos y los reexporta como un solo módulo. Para demostrar esto, cree un archivo llamado vectors.ts
dentro de su src/
directorio, luego abra este archivo en su editor favorito y escriba lo siguiente:
proyecto_vectorial/src/vectors.ts
import { Vector2 } from "./vector2";import { Vector3 } from "./vector3";
Para importar otro módulo disponible en su proyecto, utilice la ruta relativa al archivo en una import
declaración. En este caso, está importando ambos módulos desde ./vector2
y ./vector3
, que son las rutas relativas desde el archivo actual a los archivos src/vector2.ts
y src/vector3.ts
.
Ahora que ha importado los vectores, puede volver a exportarlos en un solo módulo con la siguiente sintaxis resaltada:
proyecto_vectorial/src/vectors.ts
import { Vector2 } from "./vector2";import { Vector3 } from "./vector3";export { Vector2, Vector3 };
La export {}
sintaxis permite exportar varios identificadores. En este caso, se exportan las clases Vector2
y Vector3
mediante una única export
declaración.
También puedes utilizar dos export
declaraciones separadas, como ésta:
proyecto_vectorial/src/vectors.ts
import { Vector2 } from "./vector2";import { Vector3 } from "./vector3";export { Vector2 };export { Vector3 };
Esto tiene el mismo significado que el fragmento de código anterior.
Dado que src/vectors.ts
solo está importando dos clases para reexportarlas más tarde, puede utilizar una forma aún más breve de la sintaxis:
proyecto_vectorial/src/vectors.ts
export { Vector2 } from "./vector2";export { Vector3 } from "./vector3";
La import
declaración está implícita aquí y el compilador TypeScript la incluirá automáticamente. Luego, los archivos se exportarán inmediatamente con el mismo nombre. Guarde este archivo.
Ahora que está exportando sus dos clases vectoriales desde el src/vectors.ts
archivo, cree un nuevo archivo llamado src/index.ts
, luego abra el archivo y escriba el siguiente código:
proyecto_vector/src/index.ts
import { Vector2, Vector3 } from "./vectors";const vec2a = new Vector2(1, 2);const vec2b = new Vector2(2, 1);console.log(vec2a.add(vec2b));const vec3a = new Vector3(1, 2, 3);const vec3b = new Vector3(3, 2, 1);console.log(vec3a.add(vec3b));
En este código, estás importando ambas clases de vector desde el src/vectors.ts
archivo, que utiliza la ruta relativa ./vectors
. Luego, estás creando algunas instancias de vector, utilizando el add
método para sumarlas y luego registrando los resultados.
Al importar exportaciones con nombre, también puede utilizar un alias diferente, lo que puede resultar útil para evitar conflictos de nombres dentro de un archivo. Para probar esto, realice los siguientes cambios resaltados en su archivo:
proyecto_vector/src/index.ts
import { Vector2 as Vec2, Vector3 as Vec3 } from "./vectors";const vec2a = new Vec2(1, 2);const vec2b = new Vec2(2, 1);console.log(vec2a.add(vec2b));const vec3a = new Vec3(1, 2, 3);const vec3b = new Vec3(3, 2, 1);console.log(vec3a.add(vec3b));
Aquí se utiliza la as
palabra clave para establecer los alias Vec2
y Vec3
para las clases importadas.
Observa cómo estás importando todo lo que está disponible dentro de ./vectors
. Este archivo solo exporta esas dos clases, por lo que puedes usar la siguiente sintaxis para importar todo en una sola variable:
proyecto_vector/src/index.ts
import * as vectors from "./vectors";const vec2a = new vectors.Vector2(1, 2);const vec2b = new vectors.Vector2(2, 1);console.log(vec2a.add(vec2b));const vec3a = new vectors.Vector3(1, 2, 3);const vec3b = new vectors.Vector3(3, 2, 1);console.log(vec3a.add(vec3b));
En el código resaltado arriba, se utiliza la import * as
sintaxis para importar todo lo que exporta un módulo en una única variable. También se tuvo que cambiar la forma en que se utilizaban las clases Vector2
y Vector3
, ya que ahora están disponibles dentro del vectors
objeto, que se crea durante la importación.
Si guarda el archivo y compila el proyecto usando tsc
:
- npx tsc
El compilador TypeScript creará el out/
directorio (dada la compileOptions.outDir
opción que configure en el tsconfig.json
archivo) y luego completará el directorio con archivos JavaScript.
Abra el archivo compilado disponible out/index.js
en su editor de texto favorito. Se verá así:
proyecto_vector/out/index.js
"use strict";Object.defineProperty(exports, "__esModule", { value: true });const vectors = require("./vectors");const vec2a = new vectors.Vector2(1, 2);const vec2b = new vectors.Vector2(2, 1);console.log(vec2a.add(vec2b));const vec3a = new vectors.Vector3(1, 2, 3);const vec3b = new vectors.Vector3(3, 2, 1);console.log(vec3a.add(vec3b));
Como la compilerOptions.module
opción en el tsconfig.json
archivo está configurada en CommonJS
, el compilador TypeScript crea código compatible con el sistema de módulos Node.js. Esto utiliza la require
función para cargar otros archivos como módulos.
A continuación, eche un vistazo al src/vectors.ts
archivo compilado, que está disponible en out/vectors.js
:
proyecto_vectorial/out/vectors.js
"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.Vector3 = exports.Vector2 = void 0;var vector2_1 = require("./vector2");Object.defineProperty(exports, "Vector2", { enumerable: true, get: function () { return vector2_1.Vector2; } });var vector3_1 = require("./vector3");Object.defineProperty(exports, "Vector3", { enumerable: true, get: function () { return vector3_1.Vector3; } });
Aquí, el compilador TypeScript creó un código compatible con la forma en que se exportan los módulos cuando se usa CommonJS, que es asignar los valores exportados al exports
objeto.
Ahora que ha probado la sintaxis para importar y exportar archivos y luego ha visto cómo se compilan en JavaScript, puede pasar a declarar exportaciones predeterminadas en su archivo.
Uso de exportaciones predeterminadas
En esta sección, examinará otra forma de exportar un valor desde un módulo denominada exportación predeterminada, que establece una exportación específica como la importación asumida desde un módulo. Esto puede simplificar el código cuando importe los archivos.
Abra el src/vector2.ts
archivo nuevamente:
proyecto_vector/src/vector2.ts
export class Vector2 { constructor(public x: number, public y: number) {} add(otherVector2: Vector2) { return new Vector2(this.x + otherVector2.x, this.y + otherVector2.y); }}
Observa cómo estás exportando un único valor desde tu archivo/módulo. Otra forma en la que podrías haber escrito tu exportación era utilizando una exportación predeterminada. Cada archivo puede tener como máximo una única exportación predeterminada, por lo que esto podría resultar útil en este caso.
Para cambiar su exportación a una exportación predeterminada, agregue el siguiente código resaltado:
proyecto_vector/src/vector2.ts
export default class Vector2 { constructor(public x: number, public y: number) {} add(otherVector2: Vector2) { return new Vector2(this.x + otherVector2.x, this.y + otherVector2.y); }}
Guarde el archivo y luego haga lo mismo en el src/vector3.ts
archivo:
proyecto_vector/src/vector3.ts
export default class Vector3 { constructor(public x: number, public y: number, public z: number) {} add(otherVector3: Vector3) { return new Vector3( this.x + otherVector3.x, this.y + otherVector3.y, this.z + otherVector3.z ); }}
Para importar sus exportaciones predeterminadas, guarde sus archivos, luego abra el src/vectors.ts
archivo y cambie su contenido a lo siguiente:
proyecto_vectorial/src/vectors.ts
import Vector2 from "./vector2";import Vector3 from "./vector3";export { Vector2, Vector3 };
Tenga en cuenta que, en ambas importaciones, solo le está dando un nombre a su importación, en lugar de tener que usar la desestructuración para importar un valor específico. Esto importará automáticamente la exportación predeterminada de cada módulo.
Cada módulo que tiene una exportación predeterminada también tiene una exportación especial llamada default
, que se puede usar para acceder al valor exportado predeterminado. Para usar la export ... from
sintaxis abreviada que usaba antes, puede usar esa exportación denominada:
proyecto_vectorial/src/vectors.ts
export { default as Vector2 } from "./vector2";export { default as Vector3 } from "./vector3";
Ahora estás reexportando la exportación predeterminada de cada módulo con un nombre específico.
Uso export =y import = require()compatibilidad
Algunos cargadores de módulos, como AMD y CommonJS, tienen un objeto llamado exports
que contiene todos los valores exportados por un módulo. Al utilizar cualquiera de esos cargadores de módulos, es posible sobrescribir el objeto exportado modificando el valor del exports
objeto. Esto es similar a las exportaciones predeterminadas disponibles en los módulos ES y, por lo tanto, también en TypeScript. Sin embargo, estas dos sintaxis son incompatibles. En esta sección, veremos cómo TypeScript maneja este comportamiento de una manera compatible con las exportaciones predeterminadas.
En TypeScript, cuando se utiliza un cargador de módulos que admite la sobrescritura del objeto exportado, se puede cambiar el valor del objeto exportado mediante la export =
sintaxis. Para ello, se asigna el valor del objeto exportado al export
identificador. Si se ha utilizado Node.js en el pasado, esto es lo mismo que utilizar exports =
.
Nota: asegúrese de que la opción compilerOptions.module
en su tsconfig.json
archivo esté configurada CommonJS
antes de realizar cualquiera de los siguientes cambios.
Imagina que quieres cambiar el objeto exportado en cada uno de tus archivos vectoriales para que apunte a la clase vectorial en sí. Abre el archivo src/vector2.ts
. Para cambiar el valor del objeto exportado en sí, realiza el siguiente cambio resaltado:
proyecto_vector/src/vector2.ts
export = class Vector2 { constructor(public x: number, public y: number) {} add(otherVector2: Vector2) { return new Vector2(this.x + otherVector2.x, this.y + otherVector2.y); }};
Guarde esto y luego haga lo mismo con el archivo src/vector3.ts
:
proyecto_vector/src/vector3.ts
export = class Vector3 { constructor(public x: number, public y: number, public z: number) {} add(otherVector3: Vector3) { return new Vector3( this.x + otherVector3.x, this.y + otherVector3.y, this.z + otherVector3.z ); }};
Por último, cambia tu vectors.ts
espalda a la siguiente forma:
proyecto_vectorial/src/vectors.ts
import Vector2 from "./vector2";import Vector3 from "./vector3";export { Vector2, Vector3 };
Guarde estos archivos y luego ejecute el compilador TypeScript:
- npx tsc
El compilador de TypeScript le generará varios errores, incluidos los siguientes:
Outputsrc/vectors.ts:1:8 - error TS1259: Module '"~/project/src/vector2"' can only be default-imported using the 'esModuleInterop' flag1 import Vector2 from "./vector2"; ~~~~~~~ src/vector2.ts:1:1 1 export = class Vector2 { ~~~~~~~~~~~~~~~~~~~~~~~~ 2 constructor(public x: number, public y: number) {} ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ... 6 } ~~~ 7 } ~ This module is declared with using 'export =', and can only be used with a default import when using the 'esModuleInterop' flag.src/vectors.ts:2:8 - error TS1259: Module '"~/project/src/vector3"' can only be default-imported using the 'esModuleInterop' flag2 import Vector3 from "./vector3"; ~~~~~~~ src/vector3.ts:1:1 1 export = class Vector3 { ~~~~~~~~~~~~~~~~~~~~~~~~ 2 constructor(public x: number, public y: number, public z: number) {} ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ... 10 } ~~~ 11 } ~ This module is declared with using 'export =', and can only be used with a default import when using the 'esModuleInterop' flag.
Este error se debe a la forma en que estás importando tus archivos vectoriales dentro de src/vectors.ts
. Este archivo aún usa la sintaxis para importar la exportación predeterminada de un módulo, pero ahora estás sobrescribiendo el objeto exportado, por lo que ya no tienes una exportación predeterminada. Usar ambas sintaxis juntas es incompatible.
Hay dos formas de resolver esto: usando import = require()
y configurando la esModuleInterop
propiedad true
en el archivo de configuración del compilador TypeScript.
Primero, probará la sintaxis correcta para importar este tipo de módulo realizando el siguiente cambio resaltado en su código en el src/vectors.ts
archivo:
proyecto_vectorial/src/vectors.ts
import Vector2 = require("./vector2");import Vector3 = require("./vector3");export { Vector2, Vector3 };
La import = require()
sintaxis utiliza el exports
objeto como valor para la importación en sí, lo que le permite utilizar cada clase de vector. La compilación de su código funcionará ahora como se esperaba.
Otro enfoque para resolver el error de TypeScript 1259
es establecer la opción compilerOptions.esModuleInterop
en true
en el tsconfig.json
archivo. De manera predeterminada, este valor es false
. Cuando se establece en true
, el compilador de TypeScript emitirá JavaScript adicional que verifica el objeto exportado para detectar si es una exportación predeterminada o un exports
objeto que se sobrescribió y luego lo usa en consecuencia.
Esto funciona como se espera y te permite mantener tu código dentro src/vectors.ts
como antes. Para obtener más información sobre cómo esModuleInterop
funciona y qué cambios se realizan en el código JavaScript emitido, consulta la documentación de TypeScript para obtener información sobre estaesModuleInterop
opción.
Nota: Todos los cambios realizados en esta sección suponen que está utilizando un sistema de módulos que admite la sobrescritura del objeto exportado de un módulo. Actualmente, esos módulos son AMD y CommonJS. Si estuviera utilizando un sistema de módulos diferente, el compilador TypeScript le daría un error durante la compilación.
Por ejemplo, si la compilerOptions.module
configuración se estableciera para ES6
apuntar al sistema de módulos ES, el compilador TypeScript nos daría múltiples errores, lo que se reduce a solo dos errores repetidos, 1202
y 1203
:
"Output"src/vector2.ts:1:1 - error TS1203: Export assignment cannot be used when targeting ECMAScript modules. Consider using 'export default' or another module format instead. 1 export = class Vector2 { ~~~~~~~~~~~~~~~~~~~~~~~~ 2 constructor(public x: number, public y: number) {} ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~... 6 } ~~~ 7 }; ~~src/vectors.ts:1:1 - error TS1202: Import assignment cannot be used when targeting ECMAScript modules. Consider using 'import * as ns from "mod"', 'import {a} from "mod"', 'import d from "mod"', or another module format instead.1 import Vector2 = require("./vector2");
Para obtener más información sobre cómo orientar el sistema de módulos ES, consulte la documentación del compilador TypeScript para la module
propiedad .
Conclusión
TypeScript ofrece un sistema de módulos con todas las funciones y una sintaxis inspirada en la especificación de módulos ES, al tiempo que permite al desarrollador utilizar una variedad de otros sistemas de módulos en el código JavaScript emitido, como CommonJS , AMD , UMD , SystemJS y ES6 . Al utilizar las opciones import
y export
disponibles en TypeScript, puede asegurarse de que su código sea modular y compatible con el entorno más amplio de JavaScript. Saber cómo utilizar los módulos le permitirá organizar el código de su aplicación de forma concisa y eficiente, ya que los módulos son una parte fundamental para tener una base de código bien estructurada que sea fácil de ampliar y mantener.
Para obtener más tutoriales sobre TypeScript, consulte nuestra página de la serie Cómo codificar en TypeScript .
Deja una respuesta