Capítulo 1. Configuración del proyecto
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
Quieres empezar con TypeScript, ¡fantástico! La gran pregunta es: ¿cómo empiezas? Puedes integrar TypeScript en tus proyectos de muchas maneras, y todas son ligeramente diferentes en función de las necesidades de tu proyecto. Al igual que JavaScript se ejecuta en muchos tiempos de ejecución, hay muchas formas de configurar TypeScript para que satisfaga las necesidades de tu objetivo.
Este capítulo cubre todas las posibilidades de introducir TypeScript en tu proyecto, como una extensión junto a JavaScript que te proporciona autocompletado básico e indicación de errores, hasta configuraciones completas para aplicaciones full-stack en Node.js y el navegador.
Dado que las herramientas de JavaScript son un campo con infinitas posibilidades -algunos dicen que cada semana se publica una nueva cadena de compilación de JavaScript, casi tanto como nuevos frameworks-, este capítulo se centra más en lo que puedes hacer sólo con el compilador de TypeScript, sin ninguna herramienta adicional.
TypeScript ofrece todo lo que necesitas para tus necesidades de transpilación, excepto la capacidad de crear paquetes minificados y optimizados para su distribución web. Bundlers como ESBuild o Webpack se encargan de esta tarea. Además, hay configuraciones que incluyen otros transpiladores como Babel.js que pueden funcionar bien con TypeScript.
Los bundlers y otros transpiladores no entran en el ámbito de este capítulo. Consulta su documentación para la inclusión de TypeScript y utiliza los conocimientos de este capítulo para obtener la configuración adecuada.
TypeScript, al ser un proyecto con más de una década de historia, arrastra algunos restos de épocas anteriores de los que, en aras de la compatibilidad, TypeScript no puede deshacerse sin más. Por lo tanto, este capítulo se centrará en la sintaxis moderna de JavaScript y en los desarrollos recientes de los estándares web.
Si todavía tienes que dirigirte a Internet Explorer 8 o Node.js 10, primero: lo siento, estas plataformas son realmente difíciles para las que desarrollar. Sin embargo, segundo: podrás unir las piezas para plataformas más antiguas con los conocimientos de este capítulo y la documentación oficial de TypeScript.
1.1 Comprobación de tipos de JavaScript
Debate
TypeScript ha sido diseñado como un superconjunto de JavaScript, y todo JavaScript válido es también TypeScript válido. Esto significa que TypeScript también es muy bueno detectando posibles errores en el código JavaScript normal.
Podemos utilizarlo si no queremos una configuración completa de TypeScript, sino algunas sugerencias básicas y comprobaciones de tipo para facilitar nuestro flujo de trabajo de desarrollo.
Un buen requisito previo si sólo quieres comprobar el tipo de JavaScript es un buen editor o IDE. Un editor que va muy bien con TypeScript es Visual Studio Code. Visual Studio Code -o VSCode para abreviar- fue el primer gran proyecto en utilizar TypeScript, incluso antes del lanzamiento de TypeScript.
Mucha gente recomienda VSCode si quieres escribir JavaScript o TypeScript. Pero en realidad, cualquier editor es bueno siempre que sea compatible con TypeScript. Y hoy en día la mayoría lo hacen.
Con Visual Studio Code obtenemos algo muy importante para la comprobación tipográfica de JavaScript: líneas rojas garabateadas cuando algo no cuadra del todo, como puedes ver en la Figura 1-1. Esta es la barrera de entrada más baja. El sistema de tipos de TypeScript tiene diferentes niveles de rigor cuando se trabaja con un código base.
En primer lugar, el sistema de tipos intentará inferir tipos del código JavaScript a través del uso. Si tienes una línea como ésta en tu código
let
a_number
=
1000
;
TypeScript inferirá correctamente number
como tipo de a_number
.
Una dificultad de JavaScript es que los tipos son dinámicos. Los enlaces a través de let
, var
, o const
pueden cambiar de tipo en función del uso.1 Echa un vistazo al siguiente ejemplo:
let
a_number
=
1000
;
if
(
Math
.
random
()
<
0.5
)
{
a_number
=
"Hello, World!"
;
}
console
.
log
(
a_number
*
10
);
Asignamos un número a a_number
y cambiamos la ligadura a string
si la condición de la línea siguiente se evalúa como verdadera. Esto no sería un gran problema si no intentáramos multiplicar a_number
en la última línea. En aproximadamente el 50% de los casos, este ejemplo producirá un comportamiento no deseado.
TypeScript puede ayudar aquí. Con la adición de un comentario de una sola línea con @ts-check
en la parte superior de nuestro archivoJavaScript, TypeScript activa el siguiente nivel de rigor: la comprobación de tipo de los archivos JavaScript basada en la información de tipo disponible en el archivoJavaScript.
En nuestro ejemplo, TypeScript se dará cuenta de que hemos intentado asignar una cadena a un enlace que TypeScript ha deducido que es un número. Obtendremos un error en nuestro editor:
// @ts-check
let
a_number
=
1000
;
if
(
Math
.
random
()
<
0.5
)
{
a_number
=
"Hello, World!"
;
// ^-- Type 'string' is not assignable to type 'number'.ts(2322)
}
console
.
log
(
a_number
*
10
);
Ahora podemos empezar a arreglar nuestro código, y TypeScript nos guiará.
La inferencia de tipos en JavaScript va muy lejos. En el siguiente ejemplo, TypeScript infiere tipos fijándose en operaciones como la multiplicación y la suma, así como en los valores por defecto:
function
addVAT
(
price
,
vat
=
0.2
)
{
return
price
*
(
1
+
vat
);
}
La función addVat
toma dos argumentos. El segundo argumento es opcional, ya que se ha establecido un valor por defecto de 0.2
. TypeScript te avisará si intentas pasar un valor que no funciona:
addVAT
(
1000
,
"a string"
);
// ^-- Argument of type 'string' is not assignable
// to parameter of type 'number'.ts(2345)
Además, como utilizamos operaciones de multiplicación y suma dentro del cuerpo de la función, TypeScript entiende que devolveremos un número desde esta función:
addVAT
(
1000
).
toUpperCase
();
// ^-- Property 'toUpperCase' does not
// exist on type 'number'.ts(2339)
En algunas situaciones necesitas algo más que la inferencia de tipos. En los archivos JavaScript, puedes anotar argumentos de función y enlaces mediante anotaciones de tipo JSDoc. JSDoc es una convención de comentarios que te permite describir tus variables einterfaces de función de forma que no sólo sea legible para los humanos, sino también interpretable por las máquinas. TypeScript recogerá tus anotaciones y las utilizará como tipos para el sistema de tipos:
/** @type {number} */
let
amount
;
amount
=
'12'
;
// ^-- Argument of type 'string' is not assignable
// to parameter of type 'number'.ts(2345)
/**
* Adds VAT to a price
*
* @param {number} price The price without VAT
* @param {number} vat The VAT [0-1]
*
* @returns {number}
*/
function
addVAT
(
price
,
vat
=
0.2
)
{
return
price
*
(
1
+
vat
);
}
JSDoc también te permite definir nuevos tipos complejos para los objetos:
/**
* @typedef {Object} Article
* @property {number} price
* @property {number} vat
* @property {string} string
* @property {boolean=} sold
*/
/**
* Now we can use Article as a proper type
* @param {[Article]} articles
*/
function
totalAmount
(
articles
)
{
return
articles
.
reduce
((
total
,
article
)
=>
{
return
total
+
addVAT
(
article
);
},
0
);
}
Sin embargo, la sintaxis puede parecer un poco tosca; encontraremos mejores formas de anotar objetos en la Receta 1.3.
Dado que tienes una base de código JavaScript que está bien documentada mediante JSDoc, añadir una sola línea encima de tus archivos te permitirá comprender muy bien si algo va mal en tu código.
1.2 Instalar TypeScript
Solución
Instala TypeScript a través del principal registro de paquetes de Node: NPM.
Debate
TypeScript está escrito en TypeScript, compilado a JavaScript, y utiliza el tiempo de ejecución JavaScript de Node.js como entorno de ejecución principal.2 Aunque no estés escribiendo una aplicación Node.js, las herramientas para tus aplicaciones JavaScript se ejecutarán en Node. Por tanto, asegúrate de obtener Node.js del sitio web oficial y familiarizarte con sus herramientas de línea de comandos.
Para un proyecto nuevo, asegúrate de inicializar la carpeta de tu proyecto con unpackage.json nuevo. Este archivo contiene toda la información para que Node y su gestor de paquetes NPM averigüen el contenido de tu proyecto. Genera un nuevo archivo package.json con contenidos predeterminados en la carpeta de tu proyecto con la herramienta de línea de comandos NPM:
$npm
init
-y
Nota
A lo largo de este libro, verás comandos que deben ejecutarse en tu terminal. Por comodidad, mostramos estos comandos tal y como aparecerían en BASH o en shells similares disponibles para Linux, macOS o el subsistema Windows para Linux. El signo $
al principio es una convención para indicar un comando, pero no está pensado para que lo escribas tú. Ten en cuenta que todos los comandos también funcionan en la interfaz de línea de comandos normalde Windows, así como en PowerShell.
NPM es el gestor de paquetes de Node. Viene con una CLI, un registro y otras herramientas que te permiten instalar dependencias. Una vez inicializado tu package.json, instalaTypeScript desde NPM. Lo instalamos como una dependencia de desarrollo, lo que significa que TypeScript no se incluirá si pretendes publicar tu proyecto como una biblioteca enel propio NPM:
$npm
install
-D
typescript
Puedes instalar TypeScript globalmente para tener el compilador de TypeScript disponible en todas partes, pero te recomiendo encarecidamente que instales TypeScript por separado para cada proyecto. Dependiendo de la frecuencia con la que visites tus proyectos, acabarás teniendo diferentes versiones de TypeScript sincronizadas con el código de tu proyecto. Instalar (y actualizar) TypeScript globalmente podría romper proyectos que no has tocado en un tiempo.
Nota
Si instalas dependencias frontales mediante NPM, necesitarás una herramienta adicional para asegurarte de que tu código también se ejecuta en tu navegador: un bundler. TypeScript no incluye un bundler que funcione con los sistemas de módulos soportados, por lo que necesitas configurar las herramientas adecuadas. Herramientas como Webpack son comunes, y también lo es ESBuild. Todas las herramientas están diseñadas para ejecutar TypeScript también. O puedes hacerlo completamente nativo, como se describe en la Receta 1.8.
Ahora que TypeScript está instalado, inicializa un nuevo proyecto TypeScript. Utiliza NPX para ello: te permite ejecutar una utilidad de línea de comandos que hayas instalado en relación con tu proyecto.
Con:
$npx
tsc
--init
puedes ejecutar la versión local del compilador TypeScript de tu proyecto y pasar la bandera init
para crear un nuevo tsconfig.json.
El tsconfig.json es el archivo de configuración principal de tu proyecto TypeScript. Contiene toda la configuración necesaria para que TypeScript entienda cómo interpretar tu código, cómo hacer que los tipos estén disponibles para las dependencias, y si necesitas activar o desactivar ciertas características.
Por defecto, TypeScript establece estas opciones por ti:
{
"compilerOptions"
:
{
"target"
:
"es2016"
,
"module"
:
"commonjs"
,
"esModuleInterop"
:
true
,
"forceConsistentCasingInFileNames"
:
true
,
"strict"
:
true
,
"skipLibCheck"
:
true
}
}
Veámoslos en detalle.
target
es es2016
, lo que significa que si ejecutas el compilador de TypeScript, compilará tus archivos TypeScript con una sintaxis compatible con ECMAScript 2016. Dependiendo de los navegadores o entornos que utilices, puedes establecerla en algo más reciente (las versiones de ECMAScript se denominan según el año de publicación) o en algo más antiguo, como es5
, para quienes tengan que utilizar versiones muy antiguas de Internet Explorer. Por supuesto, espero que no tengas que hacerlo.
module
es commonjs
. Esto te permite escribir la sintaxis del módulo ECMAScript, pero en lugar de llevar esta sintaxis a la salida, TypeScript la compilará al formato CommonJS. Esto significa que
import
{
name
}
from
"./my-module"
;
console
.
log
(
name
);
//...
se convierte:
const
my_module_1
=
require
(
"./my-module"
);
console
.
log
(
my_module_1
.
name
);
una vez que compilas. CommonJS era el sistema de módulos de Node.js y se ha hecho muy común debido a la popularidad de Node. Desde entonces, Node.js ha adoptado también módulos ECMAScript, algo que abordaremos en la Receta 1.9.
esModuleInterop
garantiza que los módulos que no son módulos ECMAScript se alineen con el estándar una vez importados. ayuda a las personas que utilizan sistemas de archivos que distinguen entre mayúsculas y minúsculas a cooperar con las personas que utilizan sistemas de archivos que no distinguen entre mayúsculas y minúsculas. Y asume que tus archivos de definición de tipos instalados (más sobre esto más adelante) no tienen errores. Así que tu compilador no los comprobará y será un poco más rápido. forceConsistentCasingInFileNames
skipLibCheck
Una de las características más interesantes es el modo estricto de TypeScript. Si se establece en true
, TypeScript se comportará de forma diferente en ciertas áreas. Es una forma de que el equipo de TypeScript defina su punto de vista sobre cómo debe comportarse el sistema de tipos.
Si TypeScript introduce un cambio de ruptura porque cambia la visión sobre el sistema de tipos, se incorporará en modo estricto. Esto significa, en última instancia, que tu código podría romperse si actualizas TypeScript y lo ejecutas siempre en modo estricto.
Para darte tiempo a adaptarte a los cambios, TypeScript también te permite activar o desactivar ciertas características del modo estricto, característica por característica.
Además de los ajustes por defecto, te recomiendo encarecidamente otros dos:
{
"compilerOptions"
:
{
//...
"rootDir"
:
"./src"
,
"outDir"
:
"./dist"
}
}
Esto le dice a TypeScript que recoja los archivos fuente de una carpeta src y ponga los archivos compilados en una carpeta dist. Esta configuración te permite separar los archivos compilados de los de autor. Tendrás que crear la carpeta src, por supuesto; la carpeta dist se creará después de compilar.
Compilación. Una vez que tengas configurado tu proyecto, crea un archivo index.ts en src
:
console
.
log
(
"Hello World"
);
La extensión . ts indica que es un archivo TypeScript. Ahora ejecútalo:
$npx
tsc
1.3 Mantener los tipos al margen
Problema
Quieres escribir JavaScript normal sin ningún paso de compilación adicional, pero seguir teniendo soporte para el editor e información de tipos adecuada para tus funciones. Sin embargo, no quieres definir tus tipos de objetos complejos con JSDoc como se muestra en la Receta 1.1.
Solución
Mantén los archivos de definición de tipos "al margen" y ejecuta el compilador de TypeScript en el modo "comprobar JavaScript".
Debate
La adopción gradual siempre ha sido un objetivo dedicado para TypeScript. Con esta técnica, que he bautizado como "tipos aparte", puedes escribir la sintaxis de TypeScript para tipos de objetos y funciones avanzadas como los genéricos y los condicionales (véase el Capítulo 5) en lugar de los engorrosos comentarios JSDoc, pero sigues escribiendo JavaScript para tu aplicación real.
En algún lugar de tu proyecto, quizá en una carpeta @tipos, crea un archivo de definición de tipos. Su terminación es .d.ts, y a diferencia de los archivos .ts normales, su propósito es contener declaraciones pero no código real.
Aquí es donde puedes escribir tus interfaces, alias de tipos y tipos complejos:
// @types/person.d.ts
// An interface for objects of this shape
export
interface
Person
{
name
:
string
;
age
:
number
;
}
// An interface that extends the original one
// this is tough to write with JSDoc comments alone.
export
interface
Student
extends
Person
{
semester
:
number
;
}
Ten en cuenta que exportas las interfaces desde los archivos de declaración. Así podrás importarlas en tus archivos JavaScript:
// index.js
/** @typedef { import ("../@types/person").Person } Person */
El comentario de la primera línea indica a TypeScript que importe el tipo Person
de @types/persona y lo haga disponible con el nombre Person
.
Ahora puedes utilizar este identificador para anotar parámetros de funciones u objetos, igual que harías con tipos primitivos como string
:
// index.js, continued
/**
* @param {Person} person
*/
function
printPerson
(
person
)
{
console
.
log
(
person
.
name
);
}
Para asegurarte de que obtienes respuesta del editor, todavía tienes que poner @ts-check
al principio de tus archivos JavaScript, como se describe en la Receta 1.1. O bien, puedes configurar tu proyecto para que compruebe siempre JavaScript.
Abre tsconfig.json y establece la bandera checkJs
en true
. Esto recogerá todos los archivos JavaScript de tu carpeta src y te dará información constante sobre los errores de tipo en tu editor. También puedes ejecutar npx tsc
para ver si tienes errores en tu línea de comandos.
Si no quieres que TypeScript transpile tus archivos JavaScript a versiones antiguas de JavaScript, asegúrate de configurar noEmit
a true
:
{
"compilerOptions"
:
{
"checkJs"
:
true
,
"noEmit"
:
true
,
}
}
Con eso, TypeScript mirará tus archivos fuente y te dará toda la información de tipos que necesites, pero no tocará tu código.
También se sabe que esta técnica es escalable. Destacadas bibliotecas JavaScript como Preact funcionan así y proporcionan fantásticas herramientas tanto a sus usuarios como a sus colaboradores.
1.4 Migrar un proyecto a TypeScript
Solución
Cambia el nombre de tus módulos archivo por archivo de .js a .ts. Utiliza varias opciones y funciones del compilador que te ayuden a subsanar errores.
Debate
La ventaja de tener archivos TypeScript en lugar de archivos JavaScript con tipos es que tus tipos e implementaciones están en un solo archivo, lo que te proporciona una mejor compatibilidad con el editor y acceso a más funciones de TypeScript, y aumenta la compatibilidad con otras herramientas.
Sin embargo, lo más probable es que cambiar el nombre de todos los archivos de .js a .ts provoque toneladas de errores. Por eso debes ir archivo por archivo y aumentar gradualmente la seguridad tipográfica a medida que avanzas.
El mayor problema al migrar es que de repente estás tratando con un proyecto TypeScript, no con JavaScript. Aún así, muchos de tus módulos serán JavaScript y, sin información de tipo, fallarán en el paso de comprobación de tipo.
Facilítate las cosas a ti mismo y a TypeScript desactivando la comprobación de tipos para JavaScript, pero permite que los módulos TypeScript carguen y hagan referencia a archivos JavaScript:
{
"compilerOptions"
:
{
"checkJs"
:
false
,
"allowJs"
:
true
}
}
Si ejecutas ahora npx tsc
, verás que TypeScript recoge todos los archivos JavaScript y TypeScript de tu carpeta de origen y crea los respectivos archivos JavaScript en tu carpeta de destino. TypeScript también transpilará tu código para que sea compatible con la versión de destino especificada.
Si trabajas con dependencias, verás que algunas de ellas no vienen con información de tipo. Esto también producirá errores de TypeScript:
import
_
from
"lodash"
;
// ^- Could not find a declaration
// file for module 'lodash'.
Instala definiciones de tipo de terceros para librarte de este error. Consulta la Receta 1.5.
Una vez que migres archivo por archivo, puede que te des cuenta de que no podrás conseguir todas las tipificaciones de un archivo de una sola vez. Hay dependencias, y rápidamente te adentrarás en la madriguera del conejo de tener demasiados archivos que ajustar antes de poder abordar el que realmente necesitas.
Siempre puedes decidir vivir con el error. Por defecto, TypeScript establece la opción del compilador noEmitOnError
en false
:
{
"compilerOptions"
:
{
"noEmitOnError"
:
false
}
}
Esto significa que no importa cuántos errores tengas en tu proyecto, TypeScript generará archivos de resultados, intentando no bloquearte. Esta puede ser una configuración que quieras activar cuando termines de migrar.
En modo estricto, la bandera de función de TypeScript noImplicitAny
se establece en true
. Esta bandera se asegurará de que no olvides asignar un tipo a una variable, constante o parámetro de función. Aunque sólo sea any
:
function
printPerson
(
person
:
any
)
{
// This doesn't make sense, but is ok with any
console
.
log
(
person
.
gobbleydegook
);
}
// This also doesn't make sense, but any allows it
printPerson
(
123
);
any
es el tipo comodín de TypeScript. Todos los valores son compatibles con , y te permite acceder a todas las propiedades o llamar a todos los métodos. desactiva efectivamente la comprobación de tipos, dándote algo de espacio para respirar durante tu proceso de migración. any
any
any
Alternativamente, puedes anotar tus parámetros con unknown
. Esto también te permite pasar todo a una función, pero no te permitirá hacer nada con ello hasta que sepas más sobre el tipo.
También puedes decidir ignorar los errores añadiendo un comentario @ts-ignore
antes de la línea que quieras excluir de la comprobación de tipo. Un comentario @ts-nocheck
al principio de tu archivo desactiva por completo la comprobación de tipo para este módulo en particular.
Una directiva de comentario que es fantástica para la migración es @ts-expect-error
. Funciona como @ts-ignore
, ya que se tragará los errores del progreso de la comprobación de tipo, pero producirá líneas garabateadas rojas si no se encuentra ningún error de tipo.
Al migrar, esto te ayuda a encontrar los puntos en los que has pasado con éxito a TypeScript. Cuando no queden directivas @ts-expect-error
, habrás terminado:
function
printPerson
(
person
:
Person
)
{
console
.
log
(
person
.
name
);
}
// This error will be swallowed
// @ts-expect-error
printPerson
(
123
);
function
printNumber
(
nr
:
number
)
{
console
.
log
(
nr
);
}
// v- Unused '@ts-expect-error' directive.ts(2578)
// @ts-expect-error
printNumber
(
123
);
Lo bueno de esta técnica es que inviertes las responsabilidades. Normalmente, tienes que asegurarte de que pasas los valores correctos a una función; ahora puedes asegurarte de que la función es capaz de manejar la entrada correcta.
Todas las posibilidades de librarte de errores a lo largo de tu proceso de migración tienen algo en común: son explícitas. Tienes que establecer explícitamente los comentarios de @ts-expect-error
, anotar los parámetros de las funciones como any
, o ignorar por completo los archivos de la comprobación de tipos. Con ello, siempre podrás buscar esas trampillas de escape durante el proceso de migración y asegurarte de que, con el tiempo, te has librado de todas.
1.5 Cargar tipos desde Definitivamente tipado
Solución
Desde Definitivamente Tipado, instala definiciones de tipos mantenidas por la comunidad.
Debate
Definitely Typed es uno de los repositorios más grandes y activos de GitHub y recopila definiciones de tipos TypeScript de alta calidad desarrolladas y mantenidas porla comunidad.
El número de definiciones de tipos mantenidas se acerca a las 10.000, y rara vez hay una biblioteca JavaScript que no esté disponible.
Todas las definiciones de tipos se lintan, comprueban e implementan en el registro de paquetes Node.js NPM bajo el espacio de nombres @types
. NPM tiene un indicador en el sitio de información de cada paquete que muestra si las definiciones de tipo Definitely Typed están disponibles, como puedes ver en la Figura 1-2.
Hacer clic en este logotipo te lleva al sitio real de las definiciones de tipo. Si un paquete ya tiene disponibles definiciones de tipos de origen, muestra un pequeño logotipo TS junto al nombre del paquete, como se muestra en la Figura 1-3.
Para instalar, por ejemplo, tipologías para el popular framework de JavaScript React, instala el paquete @types/react
a tus dependencias locales:
# Installing React
$npm
install
--save
react
# Installing Type Definitions
$npm
install
--save-dev
@types/react
Nota
En este ejemplo instalamos tipos a dependencias de desarrollo, ya que los consumimos mientras desarrollamos la aplicación, y el resultado compilado no utiliza los tipos de todos modos.
Por defecto, TypeScript recogerá las definiciones de tipos que encuentre en las carpetas @types visibles relativas a la carpeta raíz de tu proyecto. También recogerá todas las definiciones de tipos de node_modules/@types; ten en cuenta que aquí es donde NPM instala, porejemplo, @types/react
.
Hacemos esto porque la opción de compilador typeRoots
en tsconfig.json está establecida en @types
y ./node_modules/@types
. Si necesitas anular esta configuración, asegúrate de incluir las carpetas originales si quieres recoger definiciones de tipos de Definitely Typed:
{
"compilerOptions"
:
{
"typeRoots"
:
[
"./typings"
,
"./node_modules/@types"
]
}
}
Ten en cuenta que con sólo instalar las definiciones de tipos en node_modules/@types, TypeScript las cargará durante la compilación. Esto significa que si algunos tipos declaran globales, TypeScript los recogerá.
Tal vez quieras indicar explícitamente qué paquetes pueden contribuir al ámbito global, especificándolos en la opción types
de las opciones del compilador:
{
"compilerOptions"
:
{
"types"
:
[
"node"
,
"jest"
]
}
}
Ten en cuenta que esta configuración sólo afectará a las contribuciones al ámbito global. Si cargas módulos de nodos mediante sentencias import, TypeScript seguirá recogiendo los tipos correctos de @types:
// If `@types/lodash` is installed, we get proper
// type defintions for this NPM package
import
_
from
"lodash"
const
result
=
_
.
flattenDeep
([
1
,
[
2
,
[
3
,
[
4
]],
5
]]);
Volveremos sobre este ajuste en la Receta 1.7.
1.6 Configurar un proyecto Full-Stack
Solución
Crea dos archivos tsconfig para cada frontend y backend, y cargalas dependencias compartidas como compuestos.
Debate
Tanto Node.js como el navegador ejecutan JavaScript, pero entienden de forma muy distinta lo que los desarrolladores deben hacer con el entorno. Node.js está pensado para servidores, herramientas de línea de comandos y todo lo que se ejecuta sin interfaz de usuario. Tiene su propio conjunto de API y biblioteca estándar. Este pequeño script inicia un servidor HTTP:
const
http
=
require
(
'http'
)
;
const
hostname
=
'127.0.0.1'
;
const
port
=
process
.
env
.
PORT
||
3000
;
const
server
=
http
.
createServer
(
(
req
,
res
)
=>
{
res
.
statusCode
=
200
;
res
.
setHeader
(
'Content-Type'
,
'text/plain'
)
;
res
.
end
(
'Hello World'
)
;
}
)
;
server
.
listen
(
port
,
hostname
,
(
)
=>
{
console
.
log
(
`
Server running at http://
${
hostname
}
:
${
port
}
/
`
)
;
}
)
;
Aunque sin duda es JavaScript, algunas cosas son exclusivas de Node.js:
"http"
es un módulo incorporado en Node.js para todo lo relacionado con HTTP. Se carga a través derequire
, que es un indicador del sistema de módulos de Node llamado CommonJS. Hay otras formas de cargar módulos en Node.js, como vemos en la Receta 1.9, pero últimamente CommonJS es la más común.El objeto
process
es un objeto global que contiene información sobre las variables de entorno y el proceso actual de Node.js en general. También es exclusivo de Node.js.El
console
y sus funciones están disponibles en casi todos los tiempos de ejecución de JavaScript, pero lo que hace en Node es diferente de lo que hace en el navegador. En Node, imprime en STDOUT; en el navegador, imprimirá una línea en las herramientas de desarrollo.
Por supuesto, hay muchas más API exclusivas para Node.js. Pero lo mismo ocurre con JavaScript en el navegador:
import
{
msg
}
from
`
./msg.js
`
;
document
.
querySelector
(
'button'
)
?
.
addEventListener
(
"click"
,
(
)
=>
{
console
.
log
(
msg
)
;
}
)
;
Después de años sin una forma de cargar módulos, los módulos ECMAScript han encontrado su camino en JavaScript y en los navegadores. Esta línea carga un objeto de otro módulo de JavaScript. Esto se ejecuta en el navegador de forma nativa y es un segundo sistema de módulos para Node.js (ver Receta 1.9).
JavaScript en el navegador está pensado para interactuar con los eventos de la interfaz de usuario. El objeto
document
y la idea de unquerySelector
que apunte a elementos del Modelo de Objetos del Documento (DOM) son exclusivos del navegador. También lo es añadir un oyente de eventos y escuchar los eventos "clic". Esto no lo tienes en Node.js.Y de nuevo,
console
. Tiene la misma API que Node.js, pero el resultado es un pocodiferente.
Las diferencias son tan grandes que es difícil crear un proyecto TypeScript que se ocupe de ambas. Si estás escribiendo una aplicación de pila completa, necesitas crear dos archivos de configuración TypeScript que se ocupen de cada parte de tu pila.
Trabajemos primero en el backend. Supongamos que quieres escribir un servidor Express.js en Node.js (Express es un popular framework de servidor para Node). Primero, crea un nuevo proyecto NPM como se muestra en la Receta 1.1. A continuación, instala Express como dependencia:
$npm
install
--save
express
E instala definiciones de tipos para Node.js y Express desde Definitely Typed:
$npm
install
-D
@types/express
@types/node
Crea una nueva carpeta llamada servidor. Aquí es donde va tu código Node.js. En lugar de crear un nuevo tsconfig.json a través de tsc
, crea un nuevo tsconfig.json en la carpeta servidor de tu proyecto. Aquí tienes el contenido:
// server/tsconfig.json
{
"compilerOptions"
:
{
"target"
:
"ESNext"
,
"lib"
:
[
"ESNext"
],
"module"
:
"commonjs"
,
"rootDir"
:
"./"
,
"moduleResolution"
:
"node"
,
"types"
:
[
"node"
],
"outDir"
:
"../dist/server"
,
"esModuleInterop"
:
true
,
"forceConsistentCasingInFileNames"
:
true
,
"strict"
:
true
,
"skipLibCheck"
:
true
}
}
Ya deberías saber mucho de esto, pero algunas cosas llaman la atención:
-
La propiedad
module
se establece encommonjs
, el sistema de módulos original de Node.js. Todas las declaracionesimport
yexport
se transpilarán a su homólogo CommonJS. -
La propiedad
types
se establece en["node"]
. Esta propiedad incluye todas las bibliotecas que quieras tener disponibles globalmente. Si"node"
está en el ámbito global, obtendrás información de tipo pararequire
,process
, y otros específicos de Node.js que estén en el espacio global.
Para compilar tu código del lado del servidor, ejecuta
$npx
tsc
-p
server/tsconfig.json
Ahora para el cliente:
// client/tsconfig.json
{
"compilerOptions"
:
{
"target"
:
"ESNext"
,
"lib"
:
[
"DOM"
,
"ESNext"
],
"module"
:
"ESNext"
,
"rootDir"
:
"./"
,
"moduleResolution"
:
"node"
,
"types"
:
[],
"outDir"
:
"../dist/client"
,
"esModuleInterop"
:
true
,
"forceConsistentCasingInFileNames"
:
true
,
"strict"
:
true
,
"skipLibCheck"
:
true
}
}
Hay algunas similitudes, pero de nuevo destacan algunas cosas:
-
Añades
DOM
a la propiedadlib
. Esto te proporciona definiciones de tipos para todo lo relacionado con el navegador. Cuando necesitabas instalar las tipografías de Node.js a través de Definitely Typed, TypeScript envía las definiciones de tipos más recientes para el navegador con el compilador. -
La matriz
types
está vacía. Esto eliminará"node"
de nuestras tipologías globales. Como sólo puedes instalar definiciones de tipos por package.json, las definiciones de tipos"node"
que instalamos antes estarían disponibles en toda la base de código. Para la parteclient
parte, sin embargo, querrás deshacerte de ellas.
Para compilar tu código frontend, ejecuta
$npx
tsc
-p
client/tsconfig.json
Ten en cuenta que has configurado dos archivos tsconfig.json distintos. Los editores como Visual Studio Code sólo recogen la información de configuración de los archivos tsconfig.json por carpeta. También podrías llamarlos tsconfig.server.json y tsconfig.client.json y tenerlos en la carpeta raíz de tu proyecto (y ajustar todas las propiedades del directorio). tsc
utilizará las configuraciones correctas y arrojará errores si encuentra alguna, pero el editor permanecerá en silencio en la mayoría de los casos o trabajará con una configuración por defecto.
Las cosas se ponen un poco más peliagudas si quieres tener dependencias compartidas. Una forma de conseguir dependencias compartidas es utilizar referencias a proyectos y proyectos compuestos. Esto significa que extraes tu código compartido en su propia carpeta, pero le dices a TypeScript que debe ser un proyecto dependiente de otro.
Crea una carpeta compartida en el mismo nivel que el cliente y el servidor. Crea un tsconfig.json en compartida con estos contenidos:
// shared/tsconfig.json
{
"compilerOptions"
:
{
"composite"
:
true
,
"target"
:
"ESNext"
,
"module"
:
"ESNext"
,
"rootDir"
:
"../shared/"
,
"moduleResolution"
:
"Node"
,
"types"
:
[],
"declaration"
:
true
,
"outDir"
:
"../dist/shared"
,
"esModuleInterop"
:
true
,
"forceConsistentCasingInFileNames"
:
true
,
"strict"
:
true
,
"skipLibCheck"
:
true
},
}
Vuelven a destacar dos cosas:
-
La bandera
composite
se establece entrue
. Esto permite que otros proyectos hagan referencia a éste. -
La bandera
declaration
también se establece entrue
. Esto generará archivos d.ts de tu código para que otros proyectos puedan consumir información de tipos.
Para incluirlos en tu código de cliente y servidor, añade esta línea a client/tsconfig.json y server/tsconfig.json:
// server/tsconfig.json
// client/tsconfig.json
{
"compilerOptions"
:
{
// Same as before
},
"references"
:
[
{
"path"
:
"../shared/tsconfig.json"
}
]
}
Y ya está todo listo. Puedes escribir dependencias compartidas e incluirlas en tu código decliente y servidor.
Sin embargo, hay una advertencia. Esto funciona muy bien si compartes, por ejemplo, sólo modelos e información de tipos, pero en el momento en que compartas funcionalidad real, verás que los dos sistemas de módulos diferentes (CommonJS en Node, módulos ECMAScript en el navegador) no pueden unificarse en un archivo compilado. O bien creas un módulo ESNext y no puedes importarlo en código CommonJS, o bien creas código CommonJS y no puedes importarlo en el navegador.
Hay dos cosas que puedes hacer:
-
Compila a CommonJS y deja que un bundler se encargue del trabajo de resolución de módulos para el navegador.
-
Compila a módulos ECMAScript y escribe aplicaciones Node.js modernas basadas en módulos ECMAScript. Consulta la Receta 1.9 para obtener más información.
Puesto que empiezas de cero, te recomiendo encarecidamente la segunda opción.
1.7 Configurar las pruebas
Solución
Crea una tsconfig distinta para el desarrollo y la compilación, y excluye todos los archivos de prueba en esta última.
Debate
En el ecosistema de JavaScript y Node.js, hay muchos marcos de pruebas unitarias y ejecutores de pruebas. Varían en detalles, tienen opiniones diferentes o están adaptados a determinadas necesidades. Algunos simplemente son más bonitos que otros.
Mientras que los ejecutores de pruebas como Ava se basan en la importación de módulos para introducir el framework en el ámbito, otros proporcionan un conjunto de globales. Por ejemplo, Mocha:
import
assert
from
"assert"
;
import
{
add
}
from
".."
;
describe
(
"Adding numbers"
,
()
=>
{
it
(
"should add two numbers"
,
()
=>
{
assert
.
equal
(
add
(
2
,
3
),
5
);
});
});
assert
proviene de la biblioteca de aserciones incorporada en Node.js, pero describe
, it
, y muchos más son globales proporcionados por Mocha. Además, sólo existen cuando se ejecuta la CLI de Mocha.
Esto supone un pequeño reto para tu configuración de tipos, ya que esas funciones son necesarias para escribir pruebas, pero no están disponibles cuando ejecutas tu aplicación real.
La solución es crear dos archivos de configuración diferentes: un tsconfig.json normal para el desarrollo que pueda recoger tu editor (recuerda la Receta 1.6) y otro tsconfig.build.json que utilices cuando quieras compilar tu aplicación.
El primero incluye todos los globales que necesitas, incluidos los tipos para Mocha; el segundo se asegura de que no se incluya ningún archivo de prueba en tu compilación.
Vayamos paso a paso. Nos fijamos en Mocha como ejemplo, pero otros ejecutores de pruebas que proporcionan globales, como Jest, funcionan de la misma manera.
Primero, instala Mocha y sus tipos:
$npm
install
--save-dev
mocha
@types/mocha
@types/node
Crea un nuevo tsconfig.base.json. Dado que las únicas diferencias entre desarrollo y compilación son el conjunto de archivos que deben incluirse y las bibliotecas activadas, te conviene tener el resto de ajustes del compilador ubicados en un archivo que puedas reutilizar para ambos. Un archivo de ejemplo para una aplicación Node.js tendría este aspecto:
// tsconfig.base.json
{
"compilerOptions"
:
{
"target"
:
"esnext"
,
"module"
:
"commonjs"
,
"esModuleInterop"
:
true
,
"forceConsistentCasingInFileNames"
:
true
,
"strict"
:
true
,
"outDir"
:
"./dist"
,
"skipLibCheck"
:
true
}
}
Los archivos fuente deben ubicarse en src; los archivos de prueba deben ubicarse en una carpeta adyacente test. La configuración que crees en esta receta también te permitirá crear archivos que terminen en .test.ts en cualquier parte de tu proyecto.
Crea un nuevo tsconfig.json con tu configuración base de desarrollo. Éste se utiliza para la retroalimentación del editor y para ejecutar pruebas con Mocha. Amplía la configuración básica de tsconfig.base.json e informa a TypeScript de qué carpetas debe recogerpara la compilación:
// tsconfig.json
{
"extends"
:
"./tsconfig.base.json"
,
"compilerOptions"
:
{
"types"
:
[
"node"
,
"mocha"
],
"rootDirs"
:
[
"test"
,
"src"
]
}
}
Ten en cuenta que añades types
para Node y Mocha. La propiedad types
define qué globales están disponibles y, en la configuración de desarrollo, tienes ambas.
Además, puede que compilar tus pruebas antes de ejecutarlas te resulte engorroso. Existen atajos para ayudarte. Por ejemplo, ts-node
ejecuta tu instalación local de Node.js y realiza primero una compilación de TypeScript en memoria:
$npm
install
--save-dev
ts-node
$
npx
mocha
-r
ts-node/register
tests/*.ts
Con el entorno de desarrollo configurado, es hora del entorno de compilación. Crea un tsconfig.build.json. Tiene un aspecto similar a tsconfig.json, pero enseguida notarás la diferencia:
// tsconfig.build.json
{
"extends"
:
"./tsconfig.base.json"
,
"compilerOptions"
:
{
"types"
:
[
"node"
],
"rootDirs"
:
[
"src"
]
},
"exclude"
:
[
"**/*.test.ts"
,
"**/test/**"
]
}
Además de cambiar types
y rootDirs
, defines qué archivos excluir de la comprobación de tipos y de la compilación. Utiliza patrones comodín que excluyan todos los archivos terminados en .test.ts que se encuentren en carpetas de prueba. Según tu gusto, también puedes añadir .spec.ts o carpetas spec a esta matriz.
Compila tu proyecto haciendo referencia al archivo JSON correcto:
$npx
tsc
-p
tsconfig.build.json
Verás que en los archivos de resultados (ubicados en dist
), no verás ningún archivo de prueba. Además, aunque sigas pudiendo acceder a describe
y it
al editar tus archivos fuente, obtendrás un error si intentas compilar:
$ npx tsc -p tsconfig.build.json src/index.ts:5:1 - error TS2593: Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha` and then add 'jest' or 'mocha' to the types field in your tsconfig. 5 describe("this does not work", () => {}) ~~~~~~~~ Found 1 error in src/index.ts:5
Si no te gusta contaminar tus globales durante el modo de desarrollo, puedes elegir una configuración similar a la de la Receta 1.6, pero no te permitirá escribir pruebas adyacentes a tus archivos fuente.
Por último, siempre puedes optar por un ejecutor de pruebas que prefiera el sistema de módulos.
1.8 Escribir módulos ECMAScript a partir de URLs
Solución
Establece target
y module
en las opciones de compilador de tu tsconfiga esnext
y apunta a tus módulos con una extensión .js. Además, instala tipos en las dependencias a través de NPM, y utiliza la propiedad path
en tu tsconfig para indicar a TypeScript dónde buscar los tipos:
// tsconfig.json
{
"compilerOptions"
:
{
"target"
:
"esnext"
,
"module"
:
"esnext"
,
"paths"
:
{
"https://esm.sh/lodash@4.17.21"
:
[
"node_modules/@types/lodash/index.d.ts"
]
}
}
}
Debate
Los navegadores modernos admiten la carga de módulos desde el principio. En lugar de agrupar tu aplicación en un conjunto más pequeño de archivos, puedes utilizar directamente los archivos JavaScript sin procesar.
Las redes de distribución de contenidos (CDN), como esm.sh, unpkg y otras, están diseñadas para distribuir módulos de nodos y dependencias de JavaScript como URL, consumibles mediante la carga nativa de módulos ECMAScript.
Con un almacenamiento en caché adecuado y HTTP de última generación, los módulos ECMAScript se convierten en una alternativa real para las aplicaciones.
TypeScript no incluye un bundler moderno, por lo que necesitarías instalar una herramienta adicional de todos modos. Pero si decides optar primero por el módulo, hay algunas cosas que debes tener en cuenta al trabajar con TypeScript.
Lo que quieres conseguir es escribir las sentencias import
y export
en TypeScript, pero conservando la sintaxis de carga de módulos y dejando que el navegador se encargue de la resolución de módulos:
// File module.ts
export
const
obj
=
{
name
:
"Stefan"
,
};
// File index.ts
import
{
obj
}
from
"./module"
;
console
.
log
(
obj
.
name
);
Para conseguirlo, dile a TypeScript que
-
Compilar a una versión de ECMAScript que entienda los módulos
-
Utiliza la sintaxis de módulo ECMAScript para la generación de código de módulo
Actualiza dos propiedades en tu tsconfig.json:
// tsconfig.json
{
"compilerOptions"
:
{
"target"
:
"esnext"
,
"module"
:
"esnext"
}
}
module
indica a TypeScript cómo transformar las sentencias import y export. El valor predeterminado convierte la carga de módulos a CommonJS, como se ve en la Receta 1.2. Si estableces module
en esnext
, utilizarás la carga de módulos ECMAScript y, por tanto, conservarás la sintaxis.
target
indica a TypeScript la versión de ECMAScript a la que quieres transpilar tu código. Una vez al año, hay una nueva versión de ECMAScript con nuevas características. Si configuras target
como esnext
, siempre se utilizará la última versión de ECMAScript.
Dependiendo de tus objetivos de compatibilidad, es posible que quieras establecer esta propiedad en la versión de ECMAScript compatible con los navegadores que quieras admitir. Normalmente se trata de una versión con un año (por ejemplo, es2015
, es2016
, es2017
, etc). Los módulos ECMAScript funcionan con todas las versiones a partir de es2015
. Si eliges una versión anterior, no podrás cargar módulos ECMAScript de forma nativa en el navegador.
Cambiar estas opciones del compilador ya hace una cosa importante: deja intacta la sintaxis. El problema surge cuando quieres ejecutar tu código.
Normalmente, las sentencias import en TypeScript apuntan a archivos sin extensión. Escribes import { obj } from "./module"
, omitiendo .ts. Una vez que compilas, esta extensión sigue faltando. Pero el navegador necesita una extensión para apuntar realmente al archivo JavaScript correspondiente.
La solución: Añade una extensión .js, aunque estés apuntando a un archivo .ts cuando desarrolles. TypeScript es lo suficientemente inteligente como para captarlo:
// index.ts
// This still loads types from 'module.ts', but keeps
// the reference intact once we compile it.
import
{
obj
}
from
'./module.js'
;
console
.
log
(
obj
.
name
);
Para los módulos de tu proyecto, ¡es todo lo que necesitas!
La cosa se pone mucho más interesante cuando quieres utilizar dependencias. Si te vuelves nativo, puede que quieras cargar módulos desde una CDN, como esm.sh:
import
_
from
"https://esm.sh/lodash@4.17.21"
// ^- Error 2307
const
result
=
_
.
flattenDeep
([
1
,
[
2
,
[
3
,
[
4
]],
5
]]);
console
.
log
(
result
);
TypeScript dará error con el siguiente mensaje "No se puede encontrar el módulo ... o sus correspondientes declaraciones de tipo. (2307)"
La resolución de módulos de TypeScript funciona cuando los archivos están en tu disco, no en un servidor a través de HTTP. Para obtener la información que necesitamos, tenemos que proporcionar a TypeScript una resolución propia.
Aunque estemos cargando dependencias desde URLs, la información de tipo para estas dependencias vive con NPM. Para lodash
, puedes instalar la información de tipos desde Definitely Typed:
$npm
install
-D
@types/lodash
Para las dependencias que vienen con sus propios tipos, puedes instalarlas directamente:
$npm
install
-D
preact
Una vez instalados los tipos, utiliza la propiedad path
en las opciones de tu compilador para indicar a TypeScript cómo resolver tu URL:
// tsconfig.json
{
"compilerOptions"
:
{
// ...
"paths"
:
{
"https://esm.sh/lodash@4.17.21"
:
[
"node_modules/@types/lodash/index.d.ts"
]
}
}
}
¡Asegúrate de apuntar al archivo correcto!
También existe una vía de escape si no quieres utilizar tipados, o si simplemente no encuentras tipados. Dentro de TypeScript, podemos utilizar any
para desactivar intencionadamente la comprobación de tipos. Para los módulos, podemos hacer algo muy parecido: ignorar el error TypeScript:
// @ts-ignore
import
_
from
"https://esm.sh/lodash@4.17.21"
ts-ignore
elimina la línea siguiente de la comprobación de tipos y puede utilizarse en todos los casos en que quieras ignorar los errores de tipo (véase la Receta 1.4). Esto significa efectivamente que no obtendrás ninguna información de tipo para tus dependencias y podrías encontrarte con errores, pero puede ser la solución definitiva para dependencias antiguas y sin mantener que sólo necesitas pero para las que no encontrarás ningún tipo.
1.9 Cargar diferentes tipos de módulos en el Nodo
Solución
Establece la resolución del módulo TypeScript en "nodeNext"
y nombra tus archivos .mts o .cts.
Debate
Con la llegada de Node.js, el sistema de módulos CommonJS se ha convertido en uno de los más populares del ecosistema JavaScript.
La idea es sencilla y eficaz: define exportaciones en un módulo y exígelasen otro:
// person.js
function
printPerson
(
person
)
{
console
.
log
(
person
.
name
);
}
exports
=
{
printPerson
,
};
// index.js
const
person
=
require
(
"./person"
);
person
.
printPerson
({
name
:
"Stefan"
,
age
:
40
});
Este sistema ha tenido una gran influencia en los módulos ECMAScript y también ha sido el predeterminado para la resolución de módulos y el transpilador de TypeScript. Si observas la sintaxis de los módulos ECMAScript en el Ejemplo 1-1, puedes ver que las palabras clave permiten diferentes transpilaciones. Esto significa que con la configuración del módulo commonjs
, tus sentencias import
y export
se transpilan a require
y exports
.
Ejemplo 1-1. Utilizar el sistema de módulos ECMAScript
// person.ts
type
Person
=
{
name
:
string
;
age
:
number
;
};
export
function
printPerson
(
person
)
{
console
.
log
(
person
.
name
);
}
// index.ts
import
*
as
person
from
"./person"
;
person
.
printPerson
({
name
:
"Stefan"
,
age
:
40
});
Con la estabilización de los módulos ECMAScript, Node.js también ha empezado a adoptarlos. Aunque los fundamentos de ambos sistemas de módulos parecen muy similares, existen algunas diferencias en los detalles, como el manejo de las exportaciones por defecto o la carga de módulos ECMAScript de forma asíncrona.
Como no hay forma de tratar ambos sistemas de módulos igual pero con diferente sintaxis, los mantenedores de Node.js decidieron dar espacio a ambos sistemas y les asignaron diferentes terminaciones de archivo para indicar el tipo de módulo preferido. La Tabla 1-1 muestra las diferentes terminaciones, cómo se nombran en TypeScript, a qué los compila TypeScript y qué pueden importar. Gracias a la interoperabilidad de CommonJS, está bien importar módulos CommonJS desde módulos ECMAScript, pero no al revés.
Finaliza | TypeScript | Se compila en | Puede importar |
---|---|---|---|
.js |
.ts |
CommonJS |
.js, .cjs |
.cjs |
.cts |
CommonJS |
.js, .cjs |
.mjs |
.mts |
Módulos ES |
.js, .cjs, .mjs |
Los desarrolladores de bibliotecas que publican en NPM obtienen información extra en su archivo package.json para indicar el tipo principal de un paquete (module
o commonjs
), y para apuntar a una lista de archivos principales o fallbacks para que los cargadores de módulos puedan recoger el archivo correcto:
// package.json
{
"name"
:
"dependency"
,
"type"
:
"module"
,
"exports"
:
{
"."
:
{
// Entry-point for `import "dependency"` in ES Modules
"import"
:
"./esm/index.js"
,
// Entry-point for `require("dependency") in CommonJS
"require"
:
"./commonjs/index.cjs"
,
},
},
// CommonJS Fallback
"main"
:
"./commonjs/index.cjs"
}
En TypeScript, escribes principalmente sintaxis de módulo ECMAScript y dejas que el compilador decida qué formato de módulo crear al final. Ahora posiblemente haya dos: CommonJS y módulos ECMAScript.
Para permitir ambas cosas, puedes establecer la resolución del módulo en tu tsconfig.json en NodeNext
:
{
"compilerOptions"
:
{
"module"
:
"NodeNext"
// ...
}
}
Con esa bandera, TypeScript recogerá los módulos correctos tal y como se describen en tu paquete de dependencias .json, reconocerá las terminaciones .mts y .cts, y seguirá la Tabla 1-1 para las importaciones de módulos.
Para ti, como desarrollador, existen diferencias en la importación de archivos. Dado que CommonJS no requería terminaciones al importar, TypeScript aún admite importaciones sin terminaciones. El ejemplo del Ejemplo 1-1 sigue funcionando, si todo lo que utilizas es CommonJS.
La importación con terminaciones de archivo, como en la Receta 1.8, permite importar módulos tanto en módulos ECMAScript como en módulos CommonJS:
// index.mts
import
*
as
person
from
"./person.js"
;
// works in both
person
.
printPerson
({
name
:
"Stefan"
,
age
:
40
});
Si la interoperabilidad CommonJS no funciona, siempre puedes recurrir a una declaración require
. Añade "node"
como tipos globales a las opciones de tu compilador:
// tsconfig.json
{
"compilerOptions"
:
{
"module"
:
"NodeNext"
,
"types"
:
[
"node"
],
}
}
A continuación, importa con esta sintaxis específica de TypeScript:
// index.mts
import
person
=
require
(
"./person.cjs"
);
person
.
printPerson
({
name
:
"Stefan"
,
age
:
40
});
En un módulo CommonJS, será una llamada más a require
; en los módulos ECMAScript, incluirá funciones de ayuda de Node.js:
// compiled index.mts
import
{
createRequire
as
_createRequire
}
from
"module"
;
const
__require
=
_createRequire
(
import
.
meta
.
url
);
const
person
=
__require
(
"./person.cjs"
);
person
.
printPerson
({
name
:
"Stefan"
,
age
:
40
});
Ten en cuenta que esto reducirá la compatibilidad con entornos que no sean Nodo.js, como el navegador, pero con el tiempo podría solucionar problemas de interoperabilidad.
1.10 Trabajar con Deno y Dependencias
Solución
Eso es fácil; TypeScript está integrado.
Debate
Deno es un moderno tiempo de ejecución de JavaScript creado por las mismas personas que desarrollaron Node.js. Deno es similar a Node.js en muchos aspectos, pero con diferencias significativas:
-
Deno adopta estándares de plataforma web para sus principales API, lo que significa que te resultará más fácil portar código del navegador al servidor.
-
Sólo permite el acceso al sistema de archivos o a la red si lo activas explícitamente.
-
No gestiona las dependencias a través de un registro centralizado, sino -de nuevo adoptando características del navegador- a través de URL.
Ah, ¡y viene con herramientas de desarrollo integradas y TypeScript!
Deno es la herramienta con la barrera más baja si quieres probar TypeScript. No necesitas descargar ninguna otra herramienta (el compilador tsc
ya está incorporado), ni configuraciones de TypeScript. Escribes archivos .ts, y Deno se encarga del resto:
// main.ts
function
sayHello
(
name
:
string
)
{
console
.
log
(
`Hello
${
name
}
`
);
}
sayHello
(
"Stefan"
);
$deno
run
main.ts
TypeScript de Deno puede hacer todo lo que puede hacer tsc
, y se actualiza con cada actualización de Deno. Sin embargo, hay algunas diferencias a la hora de configurarlo.
En primer lugar, la configuración por defecto tiene diferencias respecto a la configuración por defecto publicada por tsc --init
. Las banderas de características del modo estricto se establecen de forma diferente, e incluye soporte para React (¡en el lado del servidor!).
Para realizar cambios en la configuración, debes crear un archivo deno.json en tu carpeta raíz. Deno lo recogerá automáticamente, a menos que le digas que no lo haga. deno.json incluye varias configuraciones para el tiempo de ejecución de Deno, incluidas las opciones del compilador de TypeScript:
{
"compilerOptions"
:
{
// Your TSC compiler options
},
"fmt"
:
{
// Options for the auto-formatter
},
"lint"
:
{
// Options for the linter
}
}
Puedes ver más posibilidades en el sitio web de Deno.
Las bibliotecas por defecto también son diferentes. Aunque Deno soporta los estándares de la plataforma web y tiene API compatibles con los navegadores, necesita hacer algunos recortes porque no hay interfaz gráfica de usuario. Por eso algunos tipos -por ejemplo, la biblioteca DOM- chocan con lo que proporciona Deno.
Algunas bibliotecas de interés son:
-
deno.ns, el espacio de nombres predeterminado de Deno
-
deno.window, el objeto global para Deno
-
deno.worker, el equivalente para Web Workers en el tiempo de ejecución de Deno
DOM y los subconjuntos están incluidos en Deno, pero no están activados por defecto. Si tu aplicación se dirige tanto al navegador como a Deno, configura Deno para que incluya todas las bibliotecas del navegador y de Deno:
// deno.json
{
"compilerOptions"
:
{
"target"
:
"esnext"
,
"lib"
:
[
"dom"
,
"dom.iterable"
,
"dom.asynciterable"
,
"deno.ns"
]
}
}
Aleph.js es un ejemplo de framework que se dirige tanto a Deno como al navegador.
Otra diferencia con Deno es cómo se distribuye la información de tipo para las dependencias. Las dependencias externas en Deno se cargan mediante URLs desde una CDN. El propio Deno aloja su biblioteca estándar en https://deno.land/std.
Pero también puedes utilizar CDNs como esm.sh o unpkg, como en la Receta 1.8. Estas CDN distribuyen tipos enviando una cabecera X-TypeScript-Types
con la solicitud HTTP, mostrando que Deno iba a cargar declaraciones de tipos. Esto también se aplica a las dependencias que no tienen declaraciones de tipos de origen, sino que dependen deDefinitely Typed.
Así, en el momento en que instales tu dependencia, Deno obtendrá no sólo los archivos fuente, sino también toda la información de tipo.
Si no cargas una dependencia desde una CDN, sino que la tienes localmente, puedes apuntar a un archivo de declaración de tipos en el momento de importar la dependencia:
// @deno-types="./charting.d.ts"
import
*
as
charting
from
"./charting.js"
;
o incluir una referencia a las tipificaciones en la propia biblioteca:
// charting.js
/// <reference types="./charting.d.ts" />
Esta referencia también se denomina directiva de triple barra y es una característica de TypeScript, no de Deno. Existen varias directivas de triple barra, la mayoría utilizadas en sistemas de dependencia de módulos anteriores a ECMAScript. La documentación ofrece una buena visión general. Sin embargo, si te limitas a los módulos ECMAScript, lo más probable es que no utilices las directivas de triple barra.
1.11 Utilizar configuraciones predefinidas
Solución
Utiliza una configuración predefinida de tsconfig/bases y amplíala a partir de ahí.
Debate
Al igual que Definitely Typed alberga definiciones de tipos mantenidas por la comunidad para bibliotecas populares, tsconfig/bases alberga un conjunto de recomendaciones mantenidas por la comunidad para configuraciones de TypeScript que puedes utilizar como punto de partida para tu propio proyecto. Esto incluye marcos como Ember.js, Svelte o Next.js, así como tiempos de ejecución de JavaScript como Node.js y Deno.
Los archivos de configuración se reducen al mínimo, ocupándose sobre todo de las bibliotecas, módulos y ajustes de destino recomendados, y de un montón de banderas de modo estricto que tienen sentido para el entorno respectivo.
Por ejemplo, ésta es la configuración recomendada para Node.js 18, con una configuración recomendada de modo estricto y con módulos ECMAScript:
{
"$schema"
:
"https://json.schemastore.org/tsconfig"
,
"display"
:
"Node 18 + ESM + Strictest"
,
"compilerOptions"
:
{
"lib"
:
[
"es2022"
],
"module"
:
"es2022"
,
"target"
:
"es2022"
,
"strict"
:
true
,
"esModuleInterop"
:
true
,
"skipLibCheck"
:
true
,
"forceConsistentCasingInFileNames"
:
true
,
"moduleResolution"
:
"node"
,
"allowUnusedLabels"
:
false
,
"allowUnreachableCode"
:
false
,
"exactOptionalPropertyTypes"
:
true
,
"noFallthroughCasesInSwitch"
:
true
,
"noImplicitOverride"
:
true
,
"noImplicitReturns"
:
true
,
"noPropertyAccessFromIndexSignature"
:
true
,
"noUncheckedIndexedAccess"
:
true
,
"noUnusedLocals"
:
true
,
"noUnusedParameters"
:
true
,
"importsNotUsedAsValues"
:
"error"
,
"checkJs"
:
true
}
}
Para utilizar esta configuración, instálala a través de NPM:
$npm
install
--save-dev
@tsconfig/node18-strictest-esm
y conéctalo a tu propia configuración de TypeScript:
{
"extends"
:
"@tsconfig/node18-strictest-esm/tsconfig.json"
,
"compilerOptions"
:
{
// ...
}
}
Esto recogerá todos los ajustes de la configuración predefinida. Ahora puedes empezar a configurar tus propias propiedades, por ejemplo, los directorios raíz y de salida.
Get Libro de cocina de TypeScript now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.