3 puntos por GN⁺ 2023-09-11 | 1 comentarios | Compartir por WhatsApp
  • Los sitios estáticos son fáciles y rápidos de alojar y requieren poco mantenimiento, así que para un sitio simple puede bastar con unas pocas líneas de Makefile sin tener que aprender un constructor aparte
  • La estructura central consiste en mantener la disposición de archivos de source, añadir header.html a los HTML y copiar tal cual el resto de los archivos a build
  • make build empareja archivos de entrada y salida con find source -type f y patsubst, y aplica reglas de make distintas para .html y para los demás archivos
  • El resaltado de la página actual, la conversión de Markdown, el servidor local, la reconstrucción automática al detectar cambios y el despliegue en GitHub Pages pueden añadirse como targets adicionales cuando haga falta
  • Como tiene pocas dependencias y es claro qué partes modificar, en sitios con requisitos pequeños un flujo de compilación hecho a mano puede ser más rápido que un generador genérico

Sitio estático básico hecho solo con Makefile

  • Un generador de sitios estáticos produce resultados fáciles de alojar y rápidos, con una carga de mantenimiento muy baja
  • Para un sitio simple, puede ser más rápido y satisfactorio escribir un script propio que aprender y adaptar un constructor de sitios existente
  • Si se trata de un sitio común sin marcas de tiempo de actualización automática ni feed RSS, basta con una configuración todavía más simple que un script para blogs
  • Requisitos básicos

    • Todos los archivos de entrada se colocan en el directorio source, y en la salida se mantiene la misma estructura de directorios
    • A todos los archivos HTML se les agrega header.html al inicio
    • Los archivos que no son HTML se copian sin cambios al directorio build
  • Reglas del Makefile

    • El target build tiene dependencias que hacen corresponder todos los archivos bajo source con archivos de salida bajo build
    • No realiza trabajo por sí mismo, pero sirve como punto de entrada para que se apliquen las reglas adecuadas a cada archivo
    • La regla build/%.html depende de source/%.html, header.html y Makefile
    • mkdir -p $(dir $@) crea el directorio de salida, y cat header.html $< > $@ concatena el encabezado con el HTML de entrada para generar la salida
    • La regla build/% copia tal cual los archivos que no son HTML con cp $< $@
    • Con solo este header.html y estas reglas, ejecutar make build crea un directorio build que puede abrirse localmente o subirse a un servidor web

Ejemplos de extensión y targets auxiliares

  • Mostrar la página actual

    • Si se resalta la página actual en la navegación, el visitante puede ver de inmediato en qué parte del sitio está
    • En el ejemplo, se busca el enlace de navegación y se le agrega la clase current
    • sed -E 's|(href="$(subst source,,$<))|class="current" \\1|' header.html | cat - $< > $@
    • La forma exacta de sustitución debe adaptarse a la estructura de marcado que se esté usando
  • Generar HTML desde Markdown

    • Si no quieres escribir HTML directamente o el contenido existente está en formato Markdown, puedes conectar por tubería un convertidor de Markdown a HTML
    • El ejemplo usa smu
    • smu $< | cat header.html - > $@
    • La regla base sigue asumiendo que build/foo.html se genera a partir de source/foo.html
    • Para archivos Markdown, hay que conservar también el sufijo .html o modificar la regla para que busque archivos .md como entrada
  • Vista previa local

    • Algunos sitios no se previsualizan correctamente si se abre el archivo local directamente en el navegador; una razón común es usar enlaces absolutos en lugar de enlaces relativos
    • Python ya está instalado en muchos sistemas e incluye un servidor web adecuado para este uso
    • Target serve: python -m http.server -d build
  • Reconstrucción automática al cambiar archivos

    • Mientras se trabaja en el sitio, volver a compilar manualmente cada vez es incómodo
    • Con entr se puede ejecutar automáticamente make build cuando cambien source, header.html o Makefile
    • Target watch: find source header.html Makefile | entr make build
    • Si quieres evitar dependencias, también puedes usar inotifywait
  • Despliegue en GitHub Pages

    • Si el repositorio está en GitHub, es natural considerar alojar el HTML resultante en GitHub Pages
    • Ajustar el comando de despliegue para no tener que preocuparse por los detalles de git es un poco complicado, pero puede resolverse con git worktree
    • El método se basa en el artículo de Sangsoo Nam
    • Se agrega la rama gh-pages como worktree public_html
    • Se copia build/* a public_html
    • Se ejecuta git add --all, git commit -m "Deploy to github pages", git push origin gh-pages
    • Al final, se elimina el worktree con git worktree remove public_html
  • Ejemplo real

    • Una página creada con este enfoque puede verse en karlb/astridbartel.de
    • Un generador de sitios estáticos completo puede empezar con seis líneas de Makefile y modificarse rápido según las necesidades, sin dependencias extrañas ni carga de mantenimiento

1 comentarios

 
GN⁺ 2023-09-11
Opiniones en Hacker News
  • Antes generaba mi sitio web personal (https://pablo.rauzy.name/) con un Makefile simple.
    Luego agregué funciones como noticias y feed RSS, listados automáticos de artículos de investigación y materiales de clases, y una lista de libros filtrable por etiquetas. Sigue siendo un Makefile, pero el Makefile en sí se volvió incluso un poco más simple; en cambio, llama a scripts Bash que usan las excelentes utilidades xml2 y 2xml para manejar HTML línea por línea. Principalmente aprovecha utilidades básicas como grep y sed.
    También le agregué algunos git hooks que llaman automáticamente a make cuando hace falta y, en particular, en el servidor remoto donde está alojado el sitio, al hacer push al repositorio se reconstruye la versión pública.
    Lleva años funcionando muy bien, y el historial de git se remonta a 2009. Al ver los primeros commits, eran beccad7 (FIRST_VERSION) Initial commit, d1cc6d7 adding link to Google Reader shared items, 6ccfd0c fix typo, d337959 adding link to Identi.ca account, y de verdad ya pasaron 15 años.
  • El problema con este enfoque es que, aunque borres un archivo en source/, no se borra en build/.
    En mis proyectos, reconstruir todo el sitio sigue siendo lo bastante rápido, así que opté por eliminar toda la carpeta build antes de reconstruir.
    https://github.com/jez/jez.github.io/blob/source/Makefile#L1...
    Esto anula en buena medida la compilación incremental, que es una de las grandes razones para usar un sistema de build, pero aun así, si sé qué página quiero regenerar, puedo ejecutar make directamente sobre ese archivo.
    Me gustaría saber si hay algún workaround común para este patrón en Makefiles.
    • No sé si sea un patrón común, pero mi solución fue ejecutar en cada ocasión un comando que borra los archivos “inesperados”.
      Uso la función shell de GNU Make para listar archivos y la función filter-out para filtrar las salidas “esperadas”. Es un hack feo, pero garantiza que se ejecute cada vez al hacer que el comando se ejecute como parte de una expansión de variable mediante la función shell.
      Enlace al Makefile: https://github.com/jaredkrinke/make-blog/blob/main/Makefile
    • El borrado y el cambio de nombre de archivos es un problema común en varios sistemas de control de versiones/build.
      Además de una opción nuclear como make clean, se pueden tener targets específicos de Make para borrar y renombrar. Por ejemplo, hacer que make rm sourcefile o make mv sourcefile newsourcefile se encarguen tanto del borrado/cambio de nombre del origen como del destino generado.
      En la práctica, incluso en blogs o proyectos online bastante grandes, el ciclo make clean / make all suele ser lo bastante rápido, y muchas veces es necesario al modificar plantillas o elementos de diseño del sitio. Si el tamaño llega al punto de preocuparte por el tiempo de reconstrucción, quizá tenga más sentido usar un CMS real que gestione las fuentes en una base de datos y genere dinámicamente al acceder el cliente.
    • ¿No bastaría con make clean?
    • Creo que algo así funcionaría:
      rm/%.html:
      @rm -f source/%.html build/%.html
      Se ejecutaría con $ make rm/page.html.
    • El sistema de build genérico Shake tiene una función prune justamente para este propósito.
      http://neilmitchell.blogspot.com/2015/04/cleaning-stale-file...
      Pero creo que la mejor solución que también funciona con make es tener un target make dist que genere el archivo final .tar.gz de los resultados. Si escribes bien las reglas, no incluirá archivos obsoletos. En proyectos grandes puede ser lento, pero durante el desarrollo de todos modos no hace falta usarlo, solo al momento de lanzar. La compilación incremental sigue siendo posible, y solo el .tar.gz final se crea desde cero.
  • Me inspiré directamente en el trabajo de Karl con el script de shell blog.sh mencionado en este artículo.
    Lo tomé y lo modifiqué para crear barf, mi generador de sitios estáticos minimalista. No existiría si Karl no hubiera publicado ese gran trabajo.
    [0]: https://github.com/karlb/karl.berlin/blob/master/blog.sh
    [1]: https://barf.bt.ht
    • Alguien con gustos parecidos. Al mío lo llamo shite, y con eso construyo mi sitio. El nombre insinúa la calidad del software :)
      Lo que más me gusta es que hasta ahora no he tenido que actualizar nada, y parece que nunca tendré que hacerlo. Lo segundo mejor es que tiene hot reload sin JavaScript.
      [1] https://github.com/adityaathalye/shite
      [2] https://evalapply.org
  • Si se mezcla un poco de m4, se puede mantener el mismo enfoque esquelético y aun así ganar un poco más de flexibilidad.

Hace unos 20 años mantuve un sitio web pequeño hecho de esa forma. Pero, salvo por sitios web personales, no sé si hoy este modelo encaje bien. Esto se debe a que, en esencia, este enfoque obliga a asumir un rol de Web 1.0. Todos los usuarios que contribuyen tienen que dominar HTML, o alguien tiene que encargarse de las tareas ingratas de “webmaster”
[1] https://en.wikipedia.org/wiki/M4_(computer_language)

  • No existe tal cosa como “un poquito de m4
    Empiezas un proyecto limpio y prometes que esta vez no vas a tocar m4. Luego, para reducir código repetido, terminas metiendo una pequeña llamada a m4
    Un año después estás escarbando entre cinco capas de expansión de macros para descubrir por qué la palabra “cat” desaparece silenciosamente del sitio web, y al final encuentras que un desarrollador junior intentó implementar un bucle for por su cuenta, en vez de copiarlo del manual, y arruinó las comillas
    Una vez resuelto el problema inmediato, decides que depurar DSL es demasiado difícil, así que traes el archivo de macros M4 que venías copiando de proyecto en proyecto. Después pasas un día cambiando todos los usos de define por macros generadoras de macros que agregan comentarios a la salida para que funcione el script que genera trazas de pila
    En el próximo proyecto pondrás una regla absoluta. ¡Nada de m4! Claro, quizá se pueda hacer una excepción en ese único lugar
  • En lugar de depender de sustituciones de texto generales como m4 o perl, recomiendo usar SGML, que es la base y el superconjunto común de HTML y XML
    SGML ofrece de forma nativa macros de texto con verificación de tipos sencilla, es decir, expansión de entidades, y también permite expansión de macros parametrizadas con conocimiento de tipos. Aquí, los tipos se refieren al tipo de contenido general de los elementos de marcado, es decir, qué elementos hijos se permiten y en qué orden, y también contempla expandir o escapar según contextos como atributos, CDATA y RCDATA
    Expandir y escapar correctamente comentarios de usuarios potencialmente maliciosos, aplicando reglas de usuario que, por ejemplo, permitan marcado de nivel inline pero prohíban elementos script, es algo que solo SGML puede expandir correctamente. También permite expandir Markdown o sintaxis wiki a HTML, importar contenido HTML externo/de sindicación, crear RSS y outlines para navegación, etc. Encaja bien con trabajos bastante complejos de preparación de sitios estáticos desde la línea de comandos
    [1]: https://sgmljs.net/docs/producing-html-tutorial/producing-ht...
    [2]: https://sgmljs.net/docs/sgmlproc-manual.html
  • En vez de m4 o buscar-y-reemplazar con sed, estaría bueno probar envsubst
    Es un programa que sustituye referencias a variables estilo Bash, por ejemplo $TITLE, por el valor de una variable de entorno
    export CURRENT="..."
    cat page.html | envsubt
  • “Un poquito de m4”, por favor, jamás
    m4 ni siquiera es bueno como lenguaje de programación esotérico
  • Lo hice una vez, y nunca más
    Que haya funcionado en sendmail no alcanza para justificar nada
  • Me gusta que casi todos los blogs de desarrolladores que encuentro en HN tengan feed RSS
    Cada vez que leo algo interesante aquí, me suscribo al feed. Ya sea un sitio de Wordpress, Bear Blog, Micro.blog, un blog de Havenweb o el feed de un sitio hecho a mano, lo agrego al módulo Really Social Sites de Hey Homepage
    En última instancia, me gustaría publicar esta lista de blogs, como hace Kagi con la iniciativa Small Web. Aunque, si se quiere aportar calidad, la curaduría es clave, y cuando uno piensa en curaduría parece natural empezar una especie de revista online
    • Como desarrollador, estoy tratando de entender si soy raro por no querer tener mi propio blog
      ¿De dónde saca la gente esa “sensación de derecho”, en el buen sentido, de que puede compartir cosas con otros? Me pregunto por qué asumen que a otros les va a interesar aquello en lo que están trabajando. A veces se siente como una competencia. Algo como: “para conseguir likes o exposición en el blog, tengo que construir algo lo más genial posible”
      La colaboración obviamente es buena, y para eso todo tiene que ser público. Pero no me queda clara la línea entre “hago esto porque se ve genial” y “me esfuerzo por compartirlo con otros para obtener reacciones”
    • También está https://prose.sh, parecido a Bear Blog
  • Un amigo me explicó una vez cómo generaba artículos científicos con make
    Decía que, aunque cambiara un solo archivo de prueba, con un único comando podía regenerar todo el artículo, incluyendo la ejecución de pruebas y la generación de gráficos
  • Es una idea prolija, pero si ya estás haciendo push a GitHub, puedes subir solo el código fuente y GitHub puede publicar Markdown como páginas alojadas
    https://pages.github.com/
    • Entonces pasas a depender de GitHub para algo más que simple hosting. Es mejor dejar desde el principio la generación del sitio ejecutable en local
  • Me gusta el código
    Lo mío quedó un poco sobrediseñado porque quería hot reload sin JavaScript, y fue un yak shaving divertido
    La idea básica es la misma. Uso heredoc para las plantillas y un compilador que convierte texto plano en HTML; en mi caso es pandoc. Uso un CSV intermedio para generar índices, y también tengo una técnica útil con sed para extraer el front matter. Clásico y bueno
    [1] https://github.com/karlb/karl.berlin/blob/master/blog.sh
    [2] https://github.com/adityaathalye/shite
    [3] Mi forma de hacerlo: https://github.com/adityaathalye/shite/blob/master/bin/templ...
    • Su enfoque GEMINI me dio bastante risa. Quita la mayor parte del formato con expresiones regulares

Aunque tiene algunas limitaciones. Yo organizo los posts por namespace y pongo la fecha en la URL, y make no maneja eso directamente muy bien

  • Al ver scripts de este tipo, queda clarísimo por qué usamos cosas como esbuild o vite en vez de hacerlas nosotros mismos
  • La ventaja de make es que, en programas grandes compilados con compiladores lentos, hace que las recompilaciones incrementales sean mucho más rápidas cuando hay cambios pequeños
    Un trabajo que tardaría 40 minutos en recompilarse completo puede terminar en unos 3 segundos
    Si un sitio estático se genera desde cero en menos de 1 segundo solo haciendo cat de un encabezado común y unos cientos de archivos HTML, no hay ventaja en usar make en lugar de un script. Solo se corre el riesgo de hacer builds incompletos por bugs de dependencias
    • Si las dependencias de archivos en realidad no importan, se pueden marcar los targets como .phony
      Aun así se puede mantener una separación como make build y make push
  • Wow, esto es casi exactamente lo que pensaba hacer para mi sitio
    En otros proyectos pequeños usé un script de shell muy chico como bundler improvisado. Insertaba CSS y JS dentro del HTML, y el objetivo era que también se pudieran servir localmente los archivos sin build