Integración de TypeScript con Svelte | Programar Plus

Svelte es uno de los marcos de JavaScript más nuevos y su popularidad está aumentando rápidamente. Es un marco basado en plantillas, pero que permite JavaScript arbitrario dentro de los enlaces de la plantilla; tiene una excelente historia de reactividad que es simple, flexible y eficaz; y como marco compilado con anticipación (AOT), tiene un rendimiento y tamaños de paquete increíblemente impresionantes. Esta publicación se centrará en la configuración de TypeScript dentro de las plantillas de Svelte. Si es nuevo en Svelte, le recomiendo que consulte el tutorial introductorio y los documentos.

Si desea seguir el código (o si desea depurar lo que podría faltar en su propio proyecto), puede clonar el repositorio. Tengo sucursales configuradas para demostrar las diversas piezas que revisaré.

Nota: Si bien vamos a integrar manualmente Svelte y Typescript, podría considerar usar la plantilla oficial de Svelte que hace lo mismo si está comenzando un proyecto nuevo. De cualquier manera, esta publicación cubre una configuración de TypeScript que aún es relevante, incluso si usa la plantilla.

Configuración básica de TypeScript y Svelte

Veamos una configuración de línea de base. Si vas al initial-setup branch en el repositorio, hay un proyecto Svelte simple configurado, con TypeScript. Para ser claros, TypeScript solo funciona de forma independiente .ts archivos. No está integrado de ninguna manera en Svelte. Lograr la integración de TypeScript es el propósito de esta publicación.

Revisaré algunas piezas que hacen que Svelte y TypeScript funcionen, principalmente porque las cambiaré en un momento, para agregar compatibilidad con TypeScript a las plantillas Svelte.

Primero, tengo un tsconfig.json Archivo:

{
  "compilerOptions": {
    "module": "esNext",
    "target": "esnext",
    "moduleResolution": "node"
  },
  "exclude": ["./node_modules"]
}

Este archivo le dice a TypeScript que quiero usar JavaScript moderno, usar resolución de nodo y excluir un node_modules de la compilación.

Entonces, en typings/index.d.ts Tengo esto:

declare module "*.svelte" {
  const value: any;
  export default value;
}

Esto permite que TypeScript coexista con Svelte. Sin esto, TypeScript generaría errores cada vez que se carga un archivo Svelte con una declaración de importación. Por último, necesitamos decirle a webpack que procese nuestros archivos Svelte, lo cual hacemos con esta regla en webpack.config.js:

{
  test: /.(html|svelte)$/,
  use: [
    { loader: "babel-loader" },
    {
      loader: "svelte-loader",
      options: {
        emitCss: true,
      },
    },
  ],
}

Todo eso es la configuración básica para un proyecto que utiliza componentes Svelte y archivos TypeScript. Para confirmar que todo se compila, abra un par de terminales y ejecute npm start en uno, que iniciará un reloj de paquete web, y npm run tscw en el otro, para iniciar una tarea de observación de TypeScript. Con suerte, ambos se ejecutarán sin errores. Para verificar realmente que se está ejecutando la verificación de TypeScript, puede cambiar:

let x: number = 12;

…en index.ts a:

let x: number = "12";

… y vea aparecer el error en el reloj de TypeScript. Si realmente desea ejecutar esto, puede ejecutar node server en una tercera terminal (recomiendo iTerm2, que le permite ejecutar estas terminales dentro de pestañas en la misma ventana) y luego presione localhost:3001.

Agregar TypeScript a Svelte

Agreguemos TypeScript directamente a nuestro componente Svelte, luego veamos qué cambios de configuración necesitamos para que funcione. Primero ve a Helper.svelte, y añadir lang="ts" a la etiqueta de secuencia de comandos. Eso le dice a Svelte que hay TypeScript dentro del script. Ahora agreguemos algo de TypeScript. Cambiemos el val prop que se verifica como un número, a través de export let val: number;. El componente completo ahora se ve así:

<script lang="ts">
  export let val: number;
</script>

<h1>Value is: {val}</h1>

Nuestra ventana de paquete web ahora debería tener un error, pero eso es lo esperado.

Mostrando el terminal con una salida de error.

Necesitamos decirle al cargador Svelte cómo manejar TypeScript. Instalemos lo siguiente:

npm i svelte-preprocess svelte-check --save

Ahora, vayamos a nuestro archivo de configuración de paquete web y tomemos svelte-preprocess:

const sveltePreprocess = require("svelte-preprocess");

… y agréguelo a nuestro svelte-loader:

{
  test: /.(html|svelte)$/,
  use: [
    { loader: "babel-loader" },
    {
      loader: "svelte-loader",
      options: {
        emitCss: true,
        preprocess: sveltePreprocess({})
      },
    },
  ],
}

Bien, reiniciemos el proceso del paquete web y debería compilarse.

Agregar cuenta de cheques

Hasta ahora, lo que tenemos se construye, pero no funciona. Si tenemos un código inválido en un componente de Svelte, queremos que genere un error. Entonces, vayamos a App.svelte, agrega lo mismo lang="ts" a la etiqueta de secuencia de comandos y luego pasar un valor no válido para la val prop, así:

<Helper val={"3"} />

Si miramos en nuestra ventana de TypeScript, no hay errores, pero debería haberlos. Resulta que no escribimos check nuestra plantilla Svelte con el compilador tsc normal, sino con la utilidad svelte-check que instalamos anteriormente. Detengamos nuestro reloj TypeScript y, en esa terminal, ejecutemos npm run svelte-check. Eso iniciará el proceso de verificación esbelta en modo reloj, y deberíamos ver el error que esperábamos.

Mostrando el terminal con un error detectado.

Ahora, elimine las comillas alrededor del 3, y el error debería desaparecer:

Mostrando la misma ventana de terminal, pero sin errores.

¡Limpio!

En la práctica, querríamos que tanto svelte-check como tsc se ejecutaran al mismo tiempo, por lo que detectamos ambos errores en nuestros archivos TypeScript y plantillas Svelte. Hay un montón de utilidades en npm que permiten hacer esto, o podemos usar iTerm2, es capaz de dividir múltiples terminales en la misma ventana. Lo estoy usando aquí para ejecutar el servidor, la compilación del paquete web, la compilación tsc y la compilación svelte-check.

Se muestra la ventana iTerm2 con cuatro terminales abiertos en una cuadrícula de dos por dos.

Esta configuración está en el basic-checking rama del repositorio.

Atrapando accesorios faltantes

Todavía hay un problema que debemos resolver. Si omitimos un accesorio obligatorio, como el val prop que acabamos de ver, todavía no obtendremos un error, pero deberíamos, ya que no le asignamos un valor predeterminado en Helper.svelte, y por lo tanto es obligatorio.

<Helper /> // missing `val` prop

Para decirle a TypeScript que informe esto como un error, volvamos a nuestro tsconfigy agregue dos nuevos valores

"strict": true,
"noImplicitAny": false 

El primero habilita un montón de comprobaciones de TypeScript que están deshabilitadas de forma predeterminada. El segundo, noImplicitAny, desactiva uno de esos controles estrictos. Sin esa segunda línea, cualquier variable que carezca de un tipo, que se escribe implícitamente como any– ahora se informa como un error (no implícito, ¿entendido?)

Las opiniones difieren ampliamente de si noImplicitAny debe establecerse en true. Creo que es demasiado estricto, pero mucha gente no está de acuerdo. Experimente y llegue a su propia conclusión.

De todos modos, con esa nueva configuración en su lugar, deberíamos poder reiniciar nuestra tarea de verificación svelte y ver el error que estábamos esperando.

Mostrando terminal con un error detectado.

Esta configuración está en el better-checking rama del repositorio.

Retazos

Una cosa a tener en cuenta es que el mecanismo de TypeScript para detectar propiedades incorrectas se apaga de forma inmediata e irreversible para un componente si ese componente alguna vez hace referencia $$props o $$restProps. Por ejemplo, si pasara un accesorio no declarado de, digamos, junk en el componente Helper, obtendría un error, como se esperaba, ya que ese componente no tiene junk propiedad. Pero este error desaparecería inmediatamente si el Helper componente referenciado $$props o $$restProps. El primero le permite acceder dinámicamente a cualquier accesorio sin tener una declaración explícita, mientras que $$restProps es para acceder dinámicamente a accesorios no declarados.

Esto tiene sentido cuando lo piensas. El propósito de estas construcciones es acceder dinámicamente a una propiedad sobre la marcha, generalmente para algún tipo de metaprogramación, o pasar atributos arbitrariamente a un elemento html, que es común en las bibliotecas de UI. La existencia de cualquiera de ellos implica el acceso arbitrario a un componente que puede no haber sido declarado.

Hay otro uso común de $$props, y eso es para acceder a los accesorios declarados como palabra reservada. class es un ejemplo común de esto. Por ejemplo:

const className = $$props.class;

…ya que:

export let class = "";

…no es válido. class es una palabra reservada en JavaScript, pero hay una solución en este caso específico. La siguiente es también una forma válida de declarar ese mismo apoyo: gracias a Rich Harris por ayudar con esto.

let className;
export { className as class };

Si su único uso de $$props es acceder a un accesorio cuyo nombre está reservado, puede usar esta alternativa y mantener una mejor verificación de tipos para su componente.

Pensamientos de despedida

Svelte es uno de los frameworks JavaScript más prometedores, productivos y francamente divertidos con los que he trabajado. La relativa facilidad con la que se puede agregar TypeScript es como una cereza en la parte superior. Hacer que TypeScript detecte los errores temprano puede ser un verdadero impulso de productividad. Espero que esta publicación haya sido de alguna ayuda para lograrlo.