Documentación · Referencia · El formato de un proyecto
9. Ficheros, CSS y JavaScript
Lo que se copia tal cual, y el CSS y el JavaScript de cada página.
Versión 0.16
publico/ se copia tal cual a la raíz de la salida, incluidos los ficheros
que empiezan por punto, como un .htaccess, salvo los que dejan el sistema o
el control de versiones: .DS_Store, .gitkeep, Thumbs.db, desktop.ini y
cualquier carpeta .git, que no se copian y no avisan (decisión 138). La lista
es corta y cerrada. En contenido/ pasa igual con Thumbs.db y desktop.ini;
lo demás que empieza por punto ya se ignora (§4). Sin pipeline: el CSS y el JS
llegan ya escritos y, si hace falta, minificados fuera del motor.
Lo que no se copia. Ni de publico/ ni de contenido/ se copia un
fichero PHP: uno cuyo nombre tenga como extensión .php, de .php3 a
.php8, .pht, .phtml o .phar, en mayúsculas o en minúsculas, y aunque no
sea la última (foto.php.jpg: un servidor con AddHandler la ejecuta igual).
El build se detiene con un error que nombra el fichero. Un sitio de Ehundu es
estático y ese fichero no lo ha escrito el motor: lo normal es que sea un
resto de otro gestor de contenidos, o algo que alguien dejó en una carpeta de
imágenes, y publicarlo lo pondría a ejecutarse en el servidor. Hoy no hay
forma de permitirlo.
Las imágenes no se procesan durante el build. Si hacen falta varios tamaños,
los genera otra herramienta (por ejemplo, un editor al subir la imagen, §11.6)
y deja los ficheros en publico/, con el nombre que dice la sección imagenes
de sitio.yml (§2). El motor solo los lee: variantes() (§7.3), el atajo
imagen y las figuras del Markdown (§8) escriben el srcset con lo que
encuentran.
9.1 CSS y JavaScript de cada página¶
El CSS y el JavaScript de una página se incrustan en ella, reunidos en un
<style> y un <script>. Es el equivalente del plugin de bundle de
Eleventy: cada plantilla declara lo que necesita donde lo usa, y todo se
escribe en un solo sitio.
{{ css('css/cabecera.css') }}, en cualquier plantilla, parcial o atajo, declara uno o varios ficheros depublico/que necesita la página. No escribe nada donde está.{{ css() }}, sin argumentos, marca dónde va todo el CSS de la página:<style>{{ css() }}</style>en el layout base. Se rellena cuando la página está terminada, así que cuentan también los parciales que van después, como el pie.- El campo
cssdel front matter añade los ficheros de la página. Se suma a lo largo de la cascada (§3): un_datos.ymlpuede añadir el CSS de una sección entera.
Cada fichero sale una sola vez por página, en el orden en que se declara al
construirla, de arriba abajo; los del campo css van al final, para que
puedan sobrescribir a los demás, y uno ya declarado no cambia de sitio (si el
layout declara css/a.css y el front matter lo repite, sigue donde lo puso el
layout). Se insertan con los saltos de línea normalizados (§1), unidos por un
salto de línea. Un fichero que no existe, o que no es un .css de dentro de
publico/, se avisa y se salta; uno que no es UTF-8 detiene el build. Si
una página declara CSS y ninguna plantilla escribe {{ css() }} (ni
{{ css_fichero() }}, más abajo), se avisa.
El JavaScript funciona igual, con js('…'), <script>{{ js() }}</script> y
el campo js.
{# plantillas/base.twig #}
<head>
{{ css('css/reset.css', 'css/estilos.css') }}
<style>{{ css() }}</style>
</head>
<body>
{% include 'parciales/cabecera.twig' %} {# declara css/cabecera.css #}
{% block cuerpo %}{% endblock %}
<script>{{ js() }}</script>
</body>
Como fichero, en lugar de incrustados. Incrustar es lo mejor con poco CSS:
la página llega con todo y no hace falta otra petición. Con mucho, repite el
mismo CSS en cada página y el navegador no puede guardarlo. css_fichero() y
js_fichero() hacen lo mismo que css() y js() con lo que la página ha
declarado (los mismos ficheros, en el mismo orden, una vez cada uno), pero en
lugar de su contenido escriben la etiqueta que enlaza un fichero que contiene
ese contenido, el paquete de esa página (el CSS o el JavaScript que declara,
reunido en uno):
{{ css_fichero() }}escribe<link rel="stylesheet" href="/css/3fa9c1d2ab.css">.{{ js_fichero() }}escribe<script src="/js/7c01e4b9d3.js" defer></script>.
Las dos funciones solo marcan el sitio, como css() y js() sin argumentos:
los ficheros se declaran con css('…') y js('…'), y si se los das a
css_fichero() se avisa y se ignoran.
El fichero lo escribe el motor en salida/css/ o salida/js/, y su nombre es
la huella del contenido: los 10 primeros caracteres hexadecimales de su SHA-256.
Las páginas que declaran lo mismo comparten el mismo fichero, y si cambia
cualquiera de los ficheros de publico/ que lo forman, cambia el nombre: ni el
navegador ni una caché intermedia pueden servir uno viejo. Los ficheros que
dejan de usarse desaparecen de salida/ como cualquier otro (§10). Si una
página no declara nada, o no se puede leer nada de lo que declara, no se
escribe ninguna etiqueta. Si dos cosas van al mismo fichero, por ejemplo un
publico/css/3fa9c1d2ab.css, el build se detiene (§10).
Como el nombre cambia con el contenido, el fichero se puede servir con una
caducidad de un año. En Apache, con un .htaccess en publico/:
<FilesMatch "^[0-9a-f]{10}\.(css|js)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
Una plantilla puede usar la forma que le convenga: css() para un CSS corto
que quiere en la propia página, css_fichero() para uno largo. Lo declarado
es lo mismo en las dos. Dentro de un CSS que se enlaza, las direcciones de
url(…) tienen que empezar por /, como ya pasa en uno incrustado: las
relativas se resolverían desde donde esté el fichero o la página, y un
paquete reúne ficheros de carpetas distintas. En la plantilla de un PDF
(§10.3) solo vale css().
{# plantillas/base.twig #}
<head>
{{ css('css/base.css') }}
{{ css_fichero() }}
</head>
<body>
{% block cuerpo %}{% endblock %}
{{ js_fichero() }}
</body>