Cómo adaptar Astro a TypeScript
Astro usa TypeScript por defecto así que, para crear un blog (o cualquier otro tipo de web) con Astro, puedes seguir los pasos que te conté en “¿Cómo crear un blog con Astro?” teniendo en cuenta los cambios que te comento a continuación:
Paso 1: Crear el proyecto con Astro
Crea un nuevo proyecto Astro utilizando yarn:
yarn create astro
Durante el proceso de configuración, responde a las siguientes preguntas (y fíjate en el cambio en la pregunta de TypeScript):
- ¿Dónde quieres crear tu nuevo proyecto?
./astro-blog-template
(o como quiera que se llame tu proyecto)
- ¿Cómo te gustaría empezar tu nuevo proyecto?
Usar la plantilla para blog
- ¿Planeas escribir en TypeScript?
Sí
- ¿Qué tan estricto debe ser TypeScript?
Estricto (Recomendado)
- ¿Instalar dependencias?
Sí
- ¿Inicializar un nuevo repositorio git?
Sí
Nota 1:
Yo he puesto
astro-blog-template
como nombre de proyecto porque he escrito este artículo a la vez que he creado la plantilla. Sustituye ese nombre por el que más te interese.Además, al final del artículo te digo cómo usar la plantilla y que tengas de serie todo esto (y algunos extras que explico en otros artículos).
Nota 2:
Puedes responder de otras formas (obviamente) pero yo te recomiendo que uses las mismas que yo.
Paso 2: Configurar el repositorio
Igual que en el artículo “¿Cómo crear un blog con Astro? - Paso 2: Configurar el repositorio”.
Paso 3: Comprueba que todo funciona
Puedes hacerlo abriendo una terminal en la carpeta del proyecto y ejecutando:
yarn dev
y accediendo a http://localhost:4321/.
Nota:
Mira la web tras cada paso que completes a partir de ahora para comprobar que todo sigue igual (es decir, que no has roto la web).
yarn dev
se encarga de revisar si hay cambios y actualiza la web con ellos de manera automática.
Paso 4: Mejorar la configuración del proyecto
Igual que en el artículo “¿Cómo crear un blog con Astro? - Paso 4: Mejorar la configuración del proyecto” pero teniendo en cuenta que la configuración en tsconfig.json
extiende astro/tsconfigs/strict
en vez de astro/tsconfigs/base
, por lo que tu archivo de configuración (tras los cambios) debería verse así:
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"baseUrl": "src/",
"paths": {
"@/*": ["./*"],
"@components/*": ["components/*"],
"@layouts/*": ["layouts/*"],
"@styles/*": ["styles/*"]
},
"strictNullChecks": true
}
}
Paso 5: Aplica tus datos sobre el proyecto
Igual que en el artículo “¿Cómo crear un blog con Astro? - Paso 5: Personalizar la estructura del proyecto”.
Paso 6: Corregir los fallos de TypeScript
Para muchos, es un quebradero de cabeza. Para mí, un pequeño incordio que luego me ahorra problemas y tests.
Crea src/types/collections.d.ts
Soy poco amigo de hacer cambios completos pero, en este caso, vamos a usar los tipos en varios sitios así que vamos a cubrirnos a futuro.
Al lío:
- Crea el archivo
src/types/collections.d.ts
(implica crear la carpetasrc/types/
) - Escribe lo siguiente en el archivo
src/types/collections.d.ts
:import { type CollectionEntry } from 'astro:content'; export type Collection = "blog"; export type Post = CollectionEntry<'blog'>;
Nota:
Por convención, los archivos de TypeScript que contienen (solo) definiciones de tipos tienen el sufijo
d
(definition), por eso el archivo que acabas de crear se llamacollections.d.ts
para indicar que contiene las definiciones de tipos de colección (de contenidos de Astro).
¿Por qué no modificamos tsconfig.json
?
¿Recuerdas las rutas que hemos creado en el Paso 4? Seguramente sentirás la tentación de añadir una nueva para @types
.
Seguramente, cambiarás tu archivo tsconfig.ts
para verse así:
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"baseUrl": "src/",
"paths": {
"@/*": ["./*"],
"@components/*": ["components/*"],
"@layouts/*": ["layouts/*"],
"@styles/*": ["styles/*"],
"@types/*": ["types/*"]
},
"strictNullChecks": true
}
}
Y seguramente verás el fallo de importación.
¿Qué ha pasado?
@types
es un prefijo de paquetes de tipo.
Por ejemplo, si vas a usar ReactJS con TypeScript, instalarás @types/react
como dependencia.
Por eso, si creas la ruta en tsconfig.ts
, el proyecto no puede decidir si es un paquete externo (en node_modules
) o si es una definición interna al proyecto.
¿Cómo lo solucionamos?
No necesitas hacer nada.
Si miras tu configuración, verás que creaste la siguiente línea:
"@/*": ["./*"]
Con eso, estás haciendo que @/
apunte a src/
así que puedes hacer la importación de tipos (internos) con @/types/
.
Modifica src/pages/blog/[...slug].astro
Inicialmente, el archivo se ve así:
---
import { type CollectionEntry, getCollection } from 'astro:content';
import BlogPost from '@layouts/BlogPost.astro';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.slug },
props: post,
}));
}
type Props = CollectionEntry<'blog'>;
const post = Astro.props;
const { Content } = await post.render();
---
<BlogPost {...post.data}>
<Content />
</BlogPost>
Y verás que, en la línea 6, post
aparece marcado con el fallo:
Parameter 'post' implicitly has an 'any' type.ts(7006)
.
Y es que a TypeScript estricto (lo que hemos configurado para nuestro proyecto) “no es fan” del tipo any
ya que permite cualquier tipo y, por lo tanto, es suceptible de fallo.
Por fortuna, ya hemos creado los tipos necesarios en el archivo src/types/collections.d.ts
.
Solo queda importar los tipos y sustituir donde sea necesario.
Tras los cambios, el archivo debería verse como sigue:
---
import { getCollection } from 'astro:content';
import BlogPost from '@layouts/BlogPost.astro';
import type { Collection, Post } from '@/types/collections.d';
export async function getStaticPaths() {
const collection: Collection = "blog";
const posts: Post[] = await getCollection(collection);
return posts.map((post) => ({
params: { slug: post.slug },
props: post,
}));
}
type Props = Post;
const post = Astro.props;
const { Content } = await post.render();
---
<BlogPost {...post.data}>
<Content />
</BlogPost>
Con esto, desaparecen los fallos en este archivo.
Nota:
En algunos frameworks basados en JavaScript (como Astro o como NextJS), nos encontramos nombres de carpetas o ficheros entre corchetes.
El caso más cercano ahora mismo es este mismo archivo:
src/pages/blog/[...slug].astro
.Esto implica que, al procesar una URL del tipo
blog/resto-de/la-url
, Astro cogerá todo lo que vaya detrás de la parte que conoce (en este caso/blog
) y lo guardará en un parámetro llamadoslug
para poder acceder a dichos datos desde el propio archivo.Puedes descubrir más detalles y conseguir una mejor comprensión en la documentación oficial de Astro.
Modifica src/pages/blog/index.astro
En el archivo src/pages/blog/index.astro
que nos servirá de página principal del blog (o página de archivo del blog), nos encontramos un par de fallos de TypeScript.
A continuación te dejo cómo se ve el archivo actualmente (por conveniencia he quitado las líneas de CSS) en el que deberías ver fallos marcados en las líneas 10 y 94:
---
import BaseHead from '@components/BaseHead.astro';
import Header from '@components/Header.astro';
import Footer from '@components/Footer.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import { getCollection } from 'astro:content';
import FormattedDate from '@components/FormattedDate.astro';
const posts = (await getCollection('blog')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
<style>
// líneas 19 a 85
</style>
</head>
<body>
<Header />
<main>
<section>
<ul>
{
posts.map((post) => (
<li>
<a href={`/blog/${post.slug}/`}>
<img width={720} height={360} src={post.data.heroImage} alt="" />
<h4 class="title">{post.data.title}</h4>
<p class="date">
<FormattedDate date={post.data.pubDate} />
</p>
</a>
</li>
))
}
</ul>
</section>
</main>
<Footer />
</body>
</html>
Digo fallos… en realidad es el mismo fallo: Parameter '...' implicitly has an 'any' type.ts(7006)
.
Al igual que con el caso anterior (el archivo src/pages/blog/[...slug].astro
), tenemos preparada la solución con los tipos que hemos definido en el archivo src/types/collections.d.ts
.
Tras los cambios, el archivo debería quedarte como puedes ver a continuación:
---
import BaseHead from '@components/BaseHead.astro';
import Header from '@components/Header.astro';
import Footer from '@components/Footer.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '@/consts';
import { getCollection } from 'astro:content';
import FormattedDate from '@components/FormattedDate.astro';
import type { Collection, Post } from '@/types/collections.d';
const collection: Collection = "blog";
const posts: Post[] = (await getCollection(collection)).sort(
(a: Post, b: Post) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
<style>
// líneas 19 a 85
</style>
</head>
<body>
<Header />
<main>
<section>
<ul>
{
posts.map((post) => (
<li>
<a href={`/blog/${post.slug}/`}>
<img width={720} height={360} src={post.data.heroImage} alt="" />
<h4 class="title">{post.data.title}</h4>
<p class="date">
<FormattedDate date={post.data.pubDate} />
</p>
</a>
</li>
))
}
</ul>
</section>
</main>
<Footer />
</body>
</html>
Y ya no debería “quejarse” de ningún fallo.
Una pequeña trampa
Gracias por llegar hasta aquí. En próximos artículos, seguiré contando cómo crear una plantilla personalizada.
Aunque si no quieres crearla, puedes usar la mía (la que tendrás, más o menos, si sigues todos los pasos de todos los artículos de esta serie) con el siguiente comando:
yarn create astro -- --template borjalofe/astro-blog-template