Astro es muy popular ahora mismo, y con razón si me preguntas. El framework permite a los desarrolladores crear sitios web estáticos con gran flexibilidad y potencia.
La capacidad de integrar múltiples bibliotecas de renderizado por sí sola ayuda con la adopción cuando se está migrando desde otro lugar.
Y mientras que la creación de un sitio de documentación es probablemente trivial en este punto, quería cubrir un problema que se presentó para resolver recientemente: la adición de soporte multi-versión para tu sitio de documentación.
Así que vamos a empezar.
El problema
Construir un sitio de documentación no es más que construir un sitio estático con muchas páginas.
De nuevo, trivial si usas Astro.
Sin embargo, ¿qué pasa si esas páginas cambian cuando cambia la versión de tu producto?
Hay dos problemas principales que resolver cuando eso ocurre:
- El más fácil: añadir la versión al proceso de enrutamiento. Es necesario poder pasar de una versión a otra y, dado que Astro utiliza un enrutamiento basado en rutas de archivos, tendrás que crear carpetas para cada versión.
- La difícil: manejar todos los enlaces relativos para adaptarlos a la versión actual y mantener la coherencia de la navegación interna (el mismo enlace debe redirigir a diferentes versiones, en función de la versión seleccionada en ese momento).
Aunque no parezcan gran cosa, resolver estas cuestiones no es trivial. Echemos un vistazo a cómo se podrían implementar ambas características.
Resolver el problema
Tenemos dos problemas que abordar, así que vayamos uno por uno.
Añadir la versión a su proceso de enrutamiento
La parte fácil es asegurarse de que pasamos de rutas como /docs/getting-started
a /docs/1.1.0/getting-started .
De esta forma, podemos seguir haciendo referencia al archivo getting-started.mdx
que usaremos para el contenido de esa página, y para llegar a él, tendremos que ponerlo dentro de una carpeta llamada 1.1.0 .
Eso parece simple, todo lo que tenemos que hacer es cambiar nuestra estructura de carpetas, por lo que va desde:
/
|_ docs
|_ getting-started.mdx
A algo como esto:
/
|_ docs
|_ v1.0.0
| |_ getting-started.mdx
|
|_ v2.0.0
|_ getting-started.mdx
Con ese cambio, ahora estamos dejando que Astro sepa dónde puede encontrar nuestro contenido, y lo hará. Si ejecutas npm run build
ahora, construirá todo correctamente, pero bajo la nueva carpeta.
Pero no hemos terminado, esto es sólo la mitad del paso 1, todavía nos falta una gran parte de nuestro soporte de versiones: el cambiador de versiones.
Necesitamos añadir una forma para que el usuario cambie de versión, así que veamos ese código:
---
const VERSIONS = [
{title: "2.6.0", url: "/v2.6.0"},
{title: "2.7.0", url: "/v2.7.0"},
{title: "3.0.0", url: "/v3.0.0"},
{title: "3.1.1", url: ""}
]
---
<div class="container">
<select id="version-selector">
{VERSIONS.map( v => {
return <option value={ v.url } >v{v.title}</option>
})}
</select>
</div>
<script is:inline>
let currentVersion = ""
if(typeof window != "undefined") {
currentVersion = window.location.pathname.split("/")[1]
}
if(currentVersion && currentVersion.indexOf("v") != 0) currentVersion = "";
function updateLocation(evt) {
let selectedVersion = evt.target.value
let versionRegExp = /v[0-9]+.[0-9]+.[0-9]+/
let pathname = window.location.pathname.replace(/\/$/, "") //remove the trailing "/" if there is any
let pathParts = pathname.split("/")
if(pathname.match(versionRegExp)) {
pathParts[1] = selectedVersion.replace("/", "")
} else {
pathParts = [pathParts[0], selectedVersion.replace("/", ""), ...pathParts.slice(2)]
}
//when we're visiting the root of the default version, we ignore the "index" part
//but if we're inside a specific version coming from the default version, we need to add it
if(pathParts[pathParts.length - 1] == 'index' && selectedVersion == "") {
pathParts[pathParts.length - 1] = ""
} else {
if(pathParts[pathParts.length - 1] == '' && pathname === "") {
pathParts[pathParts.length - 1] = "/index"
}
}
let newPath = `${window.location.protocol}//${window.location.host}${pathParts.join("/")}`
window.location.href = newPath
}
function selectCurrentVersion(dropDown) {
if(typeof currentVersion != "undefined") {
dropDown.querySelector("option[value*='" + currentVersion + "']").setAttribute("selected", true)
} else {
dropDown.querySelector("option[value='']").setAttribute("selected", true)
}
}
const dropDown = document.getElementById("version-selector")
selectCurrentVersion(dropDown)
dropDown.addEventListener("change", updateLocation)
</script>
Este es un componente sencillo que puedes añadir donde quieras. Yo personalmente lo añadiría en la cabecera, ya que es donde la mayoría de los sitios de documentación lo añaden. Pero puedes hacer lo que quieras con él.
Como puedes ver en el código, el componente dibujará un desplegable de versiones basado en las versiones listadas dentro del array VERSIONS
. De ahí obtendrá la etiqueta a mostrar, y la ruta.
Fíjate en que la última versión no tiene ruta, de esta forma puedes mantener tu última versión en la raíz bajo el directorio docs
y sólo utilizará carpetas de versiones cuando se especifique una versión.
Entonces el selectCurrentVersion
se ejecuta como parte del código JavaScript, esta función toma el número de versión de la URL y encuentra la versión correcta option
del elemento a seleccionar.
Por último, con la onChange
del desplegable, llamamos al evento updateLocation
. Esta función se encarga de sobrescribir la versión actual con la seleccionada dentro de la ruta actual. Luego simplemente redirige al usuario a la nueva URL.
Pero espera un segundo, ¿significa esto ahora que cada vez que lanzas una nueva versión, tienes que revisar todos los enlaces de tu documentación y actualizar los enlaces internos para evitar que desplacen al usuario entre versiones?
Eso sería un golpe bajo, sobre todo si tu documentación es bastante grande, así que vamos a intentar hacer algo al respecto.
Manejo de todos los enlaces relativos
Tenemos que encontrar una manera de evitar mantener la versión real de la documentación dentro de las rutas hard-coded
que añadimos a los docs.
Esto se debe a que si lo hacemos así, cada vez que publiquemos una nueva versión y dupliquemos la última versión de los documentos, tendremos que ejecutar un proceso de búsqueda y reemplazo.
Y si nos olvidamos de hacerlo, o si por alguna razón, nuestro patrón de búsqueda no capta todas las URLs, entonces publicaremos un sitio de documentación defectuoso.
Tenemos que encontrar una manera de asegurarnos de que escribimos nuestra documentación pensando siempre en la versión actual, y enlazando a otras secciones de la misma como si no hubiera versiones alternativas de la documentación.
La solución, fue añadir un script para arreglar las URLs directamente en el navegador:
<script >
import { getVersionFromURL } from "~/util";
//code to update the links inside the documents to make sure they link to the right
//version
let links = document.getElementsByTagName("a")
let version = getVersionFromURL(window.location.href)
const host = import.meta.env.PUBLIC_SITE_URL ? import.meta.env.PUBLIC_SITE_URL : ""
let urlParts = ["docs"]
if(version != "") { //if we're not on the default version (the latest)
version = "v" + version
urlParts.push(version)
}
for(let l of links){
if(l.href.indexOf(host) != -1) {
let uri = l.href.replace(host, "")
uri = uri.replace(`/docs`, "").replace(/\/v[0-9]+\.[0-9]+\.[0-9]+\//, "")
urlParts.push(uri)
l.setAttribute("href", urlParts.join("/").replace("//", "/"))
urlParts.pop()
}
}
</script>
Pongo ese script en la parte inferior de mi archivo de diseño principal. De esta forma, el script se ejecuta cuando se carga el contenido.
Ese código se ejecutará a través de todos los enlaces, y buscará los que redirigen a un lugar dentro del dominio del sitio (idealmente, algo así como docs.sudominio.com
) y sólo para esos enlaces, los recreará con la versión actual en ellos.
En otras palabras, tomará /docs/getting-started
y lo transformará dinámicamente en /docs/v1.0.0/getting-started
sin que tengas que hacer nada.
De este modo, todos los enlaces relativos (los que se utilizan para la navegación) se fijan automáticamente para la versión seleccionada en ese momento.
Y eso es todo, Astro permite una gran flexibilidad a la hora de crear componentes y añadir JavaScript a un sitio que de otro modo sería estático.
Estos ejemplos muestran dos casos de uso diferentes, uno donde el componente creado tiene una parte dinámica, por lo que a pesar de que el desplegable se renderiza en el servidor, todavía tenemos que añadir código JS extra para que funcione.
Y por otro lado, todos los enlaces renderizados en el servidor necesitan ser actualizados dinámicamente (en realidad, sólo los de navegación interna), así que añadimos un script a la página que hace precisamente eso. Y no necesitamos trabajar con ninguna otra librería de interfaz de usuario.
¿Te habías encontrado antes con este tipo de problema? ¿Cómo lo resolviste?
Gracias por llegar hasta el final de este blog quiero recordarte que todo esto es gratis y posible gracias a que tu compartes. Un fuerte abrazo y recuerda que el conocimiento es poder.
Invertir en conocimientos produce siempre los mejores beneficios. (Benjamín Franklin)