skip to content
dz

Daniel Zenzes

3 min read EN

Eleventy: PostCSS und Tailwind CSS integrieren

In meinem Eleventy-Tutorial habe ich gezeigt, wie man mit Eleventy einen Blog aufsetzt und wie CSS in die Seiten kommt. Hier geht es einen Schritt weiter: Tailwind CSS im selben Build.

Der verbreitete Weg ist, beides nebeneinander laufen zu lassen. Die Tailwind-CLI beobachtet das Stylesheet, Eleventy beobachtet die Templates, und in der package.json hängen zwei Skripte an npm-run-all oder concurrently. Das funktioniert zuverlässig, bedeutet aber zwei Prozesse, die dasselbe Ausgabeverzeichnis befüllen, und Eleventy weiß nichts vom fertigen Stylesheet.

Ich lasse stattdessen Eleventy das CSS selbst bauen. Ein Hook erzeugt das Stylesheet, bevor das erste Template gerendert wird, und legt den fertigen Pfad als globale Daten ab. Ein Befehl, ein Prozess, und das Layout kennt den Dateinamen.

1 min read

Der Feed spricht jetzt Atom

Ich wollte einen alten Beitrag wieder nach vorne holen: PostCSS und Tailwind CSS in Eleventy integrieren. Der ist von 2022, zieht bis heute den meisten Suchtraffic und beschrieb ein Setup, das mit dem heutigen Eleventy nichts mehr zu tun hat. Also neu geschrieben. Nur: Wie bekommt das jemand mit, der ihn vor vier Jahren gelesen hat?

Ein frisches Veröffentlichungsdatum wäre gelogen, eine neue URL wirft die Verlinkungen von vier Jahren weg. Was ich brauche, ist ein zweites Datum: veröffentlicht 2022, überarbeitet 2026. Und genau da ist bei RSS Schluss.

RSS 2.0 wird seit 2009 nicht weiterentwickelt und kennt als Datum nur <pubDate>, mehr nicht. Reader identifizieren den Beitrag anhand seiner <guid> und zeigen ihn danach nie wieder als neu. Meistens will man genau das. Hier eben nicht.

Atom trennt beides: <published> fürs Erscheinen, <updated> für die letzte Änderung, dazu eine stabile <id>. Kein Duplikat im Reader, aber wer <updated> auswertet, schiebt den Beitrag wieder nach oben. Ein Feld, das seit 2005 im Standard steht und mir jetzt die ganze Arbeit abnimmt.

Die Entscheidung hatte mir ohnehin jemand abgenommen, nämlich ich selbst vor Jahren. Im <head> dieser Seite stand die ganze Zeit:

<link rel="alternate" type="application/atom+xml" href="/feed.xml" />

Ausgeliefert wurde darunter aber RSS 2.0. Jetzt stimmt es: /feed.xml liefert Atom 1.0, die alten <guid>-Werte sind unverändert als <id> übernommen, und der aufgefrischte Tailwind-Beitrag trägt ein <updated> von heute.

Wer den RSS Atom-Feed abonniert hat, sollte keinen Unterschied bemerken. Und falls meine Änderung schief geht und der ganze Blog als ungelesen aufpoppt: Formatwechsel, einmalig, dieser Beitrag ist die Erklärung.

crescentro.se

Europäische Cloud-Anbieter 2026

Heute Morgen habe ich erst meinen toten Netlify-Account gelöscht. Nachmittags lese ich dann diesen Artikel, wie passend.

crescentrose hat bei zwölf europäischen Anbietern nicht nur die Preisliste gelesen, sondern jeweils versucht eine Maschine hochzuziehen und hat dann nachgeschaut, welche CPU da eigentlich drinsteckt. Denn vCPU ist eben nicht vCPU, und wer 2026 einen Xeon von 2015 zugeteilt bekommt, möchte das sicher vorher wissen.

Sein Fazit nach der Runde:

I was expecting a heated competition.

Wurde es nicht. Ein gutes Drittel der Anbieter hat es nicht einmal geschafft, ihm einen Server zu verkaufen. Anmeldung kaputt, Bestätigungscode kommt nie, oder man darf als Privatperson gar nicht erst Kunde werden. Von einer souveränen Cloud reden wir in der EU seit Jahren, und dann scheitert es am Registrierungsformular.

Hetzner ist bei mir seit einer Weile die Standardantwort, die Domains liegen bei Hostinger und tun da genau das, was sie sollen. Den AI-Kram, den inzwischen jeder Hoster oben draufklebt, brauche ich nicht.

marijkeluttekes.dev

Frontend ist kein Backend

HTML und CSS kann jeder irgendwie zusammenschustern, und genau deshalb sieht das Internet aus, wie es aussieht. Marijke Luttekes beschreibt den Unterschied gut: Ein Backend folgt Logik, ein Frontend folgt "kommt drauf an". Daten korrekt einzusammeln und abzulegen ist messbar und testbar. Dieselben Daten so auszuspielen, dass ein Mensch sie versteht und da findet, wo er sie sucht, hängt an vielen Faktoren wie Psychologie, Barrierefreiheit, Sprache und dreihundert Displaygrößen. Ein kaputtes Backend wirft Fehler.

Ein kaputtes Frontend sieht gut aus und funktioniert trotzdem nicht.

1 min read

Wer Omarchy bezahlt

Ein Nachtrag zu meinem Post 1Password finanziert Omarchy. Letzte Woche ging es mir um ein Tool, das ich seit Jahren nutze und für das ich wirklich gerne gezahlt habe. Jetzt bin ich eher neugierig, wer da noch alles mitmischt.

Fairerweise muss man sagen, dass die Omacom Foundation hier jeden Namen und jeden Betrag selbst offenlegt: Zwölf Gründungspatrons mit je einer Million Dollar. Vier weitere mit je hunderttausend. Firmengeld kommt bisher von zwei Seiten, 1Password und 37signals, je hunderttausend Dollar im Jahr über drei Jahre. Dazu Anfang September Token-Zusagen von Meta, Anthropic, OpenAI und Fireworks: Rechenzeit, kein Geld. Und seit dieser Woche kann sich jeder beteiligen.

Die Foundation beziffert das Ganze aktuell auf knapp fünfzehn Millionen Dollar, aufgebracht von einer kleinen Gruppe. Bei 37signals ist es im Grunde DHHs Geld. Bei 1Password finanzieren es die Kundenabos.

Genauso interessant wie die Summe ist, wohin sie fließt. Die Foundation ist exklusiver Sponsor von Hyprland, Hauptsponsor von Quickshell und mise und hat gerade einen Kernel-Entwickler fest angestellt. Diese Projekte haben eigene Nutzer und mit Omarchy sonst nichts zu tun. Wer mise benutzt, um seine Toolchain zu verwalten, hat sich für mise entschieden, nicht für die Omacom Foundation. Über den Umweg Omarchy redet sie jetzt trotzdem mit.

Die Foundation führt ihre Geldgeber zusätzlich auf einer eigenen Seite, oligarchy.fyi. Der Seitentitel lautet schlicht „Oligarchy“, die Beschreibung „Elite capital. Public code.“ Darunter läuft eine Zeile, die alle paar Sekunden umspringt: mal „Redistributing billionaire wealth, one ISO at a time.“, mal „An operating system with patrons, not customers.“, mal „Zero dollars. Several billionaires.“

Ist das ein Modell für Open-Source-Finanzierung? Für mich nicht. Es bringt schnell echtes Geld, aber es macht einen kleinen Kreis sehr reicher Leute zu der Instanz, die entscheidet, welche freie Software gefördert wird. Dass die Foundation das selbst oligarchy.fyi nennt, macht es nicht besser.

andadinosaur.com

Litterbox

Litterbox ist eine Safari-Erweiterung für iOS und macOS, kostenlos, ohne Abo, ohne Tracking. Trifft man auf einen Link zu X, zeigt die Erweiterung ein kleines Zeichen an und öffnet den Post auf Klick in einem Popup. Dafür nutzt sie dieselbe Embed-API, mit der X seine Einbettungen auf fremden Seiten ausliefert, und schickt dabei keine Cookies mit.

Für mich ist genau das der Punkt. Ich will ab und zu einen verlinkten Tweet lesen und dafür weder eingeloggt sein noch x.com meine Aufmerksamkeit und meine Daten geben. Nitter und xcancel haben das jahrelang geleistet und sich Ende August dem Druck von X gebeugt.

Bleibt die Frage, wie lange sich so eine App im App Store hält. Der Entwickler rechnet selbst damit, dass X die Embed-API abklemmen könnte. Bis dahin nutze ich Litterbox fleißig.

via Daring Fireball

thoughtbot.com

Is AI ruining my brain?

Früher war es die schnelle Suche bei Google, heute der Assistent, der auf alles eine Antwort weiß. Nur muss ich jetzt nicht mehr mehrere Seiten lesen oder meine Suche mehrfach anpassen. Nutzer erhalten eine plausibel wirkende Antwort in Sekunden. Da kann man Sally Hall nicht vorwerfen, dass sie beim Stricken plötzlich zu ChatGPT greifen will.

testdouble.com

Functionally Zen

Kyle Adams hat sieben Regeln für einfacheren Code aufgeschrieben, hergeleitet aus dem Zen of Python. Besonders eine Geschichte daraus: Er sollte für eine Medizin-App eine Dosisberechnung implementieren und wollte das als Funktion lösen. Ein Kollege wies ihn darauf hin, dass im zugrundeliegenden Lehrbuch längst Tabellen mit den gesuchten Werten standen, weil Ärzte früher ja auch nicht selbst gerechnet haben. Am Ende war es ein Dictionary-Zugriff statt eines Funktionsaufrufs, und damit gab es auch keine eigene Logik mehr, die man hätte testen müssen. Ich freue mich immer, wenn ich solche Details sehe und meinen Code dadurch einfacher machen kann.

Der Artikel dreht sich um Python, aber Adams begründet das zum Schluss mit JavaScript. Guido van Rossum hielt funktionale Primitive 2013 noch für zu unlesbar, heute gehören sie für viele zum Alltag.

1 min read

Eleventy: Markup statt Shortcode

Seit Juni 2022 nutze ich @11ty/eleventy-img für diesen Blog und habe es bisher immer nur brav aktualisiert, aber nie in die Doku geblickt. Mein Shortcode von damals, grob aus der damaligen Doku kopiert, hatte acht Argumente: Quelle, Alt, Klassen, Breiten, Formate, sizes.

async function imageshortcode(
	src,
	alt,
	className = undefined,
	imgClassName = undefined,
	widths = [400, 800, 1280],
	formats = ['webp', 'jpeg'],
	sizes = '100vw',
	includeOriginal = true
) {

Und baute daraus von Hand das passende <picture>/<source>-HTML zusammen:

const picture = `<picture ${pictureAttributes}>
	${sourceHtmlString}
	${imgHtmlString}
</picture>`;

Im Template sah der Aufruf dann so aus:

{% image "src/blog/bild.png", "Beschreibung", "", "" %}

Das aktuelle Plugin macht das überflüssig. Ein addPlugin, mehr ist nicht notwendig:

eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
	widths: [400, 800, 1280],
	formats: ['webp', 'jpeg', 'svg'],
	svgShortCircuit: true
});

Beim Build transformiert das Plugin jedes <img> im Output automatisch, inklusive loading="lazy" und decoding="async". Meine Blog-Bilder wurden zu normalem Markdown, mit normalen relativen Pfaden:

![Prototype Chain](prototype-1.svg)

Den Shortcode samt Helfer habe ich gelöscht. Artikel zu schreiben oder mal ein Bild einzubauen wird wesentlich einfacher.

Beim Umstieg ist mir bei ein paar Bildern aufgefallen, was der alte Shortcode nebenbei falsch gemacht hat. Er rasterisierte auch SVGs. Aus drei Diagramm-SVGs in meinem Post zu Prototypen in JavaScript und TypeScript wurden zwölf PNG/WebP-Versionen. Mit svgShortCircuit: true werden SVGs jetzt unverändert durchgereicht, und bei diesen Bildern macht das den Unterschied zwischen 2,8 MB und 236 KB. Kein Effekt über die ganze Seite, aber weniger Daten, die Besucher laden müssen.

Wer nur alle <img>-Elemente im bestehenden Code automatisch optimieren will: Der Code oben reicht schon, ganz ohne Shortcode oder Umschreiben bestehender Templates. Details zu weiteren Optionen findet ihr in der Doku.

brennan.day

Normalisierter Faschismus in Open Source

Brennan Kenneth Brown zieht die Linie, die ich gestern bewusst eng gezogen habe, konsequent weiter - von DHH über Ladybird bis Brave. Sein Kernpunkt, in einem Satz:

"Apolitical" has never meant a lack of politics. It's meant politics that current power doesn't have to defend.

Zwölf Millionäre, die genau wissen, wofür DHH öffentlich steht, haben trotzdem eine Million Dollar pro Kopf unterschrieben. Lest den ganzen Text. Er ist unbequemer als meiner. Das ist Absicht.

pnpm.io

pnpm 12 setzt auf Rust

Für mich bleibt pnpm die Paketverwaltung der Wahl im JavaScript-Ökosystem. Schon vorher war es deutlich schneller als die Konkurrenz, und dieser Umbau unter der Haube kann sicher nicht schaden.

Ich bin selbst ganz frisch auf der neuen Version unterwegs. Der Wechsel erfordert momentan auch noch einen manuellen Schritt. Gefühlt ist pnpm aber nochmal schneller geworden.

Das Turborepo-Projekt misst eine Verbesserung der Installationszeiten von 64 bis 90 Prozent unter Linux im Vergleich zu pnpm 10.28 - heise hat die Zahlen zusammengefasst.

Ich komme aus der 11er-Linie und habe deshalb selbst nachgemessen, auf einem MacBook Air mit M1: Eine Installation mit gelöschtem node_modules und gefülltem Store dauert unter pnpm 11.25.0 2,37 Sekunden und unter 12.1.0 nur noch 0,79 - rund dreimal so schnell. Ein Lauf, bei dem es nichts zu tun gibt, braucht statt 0,41 nur 0,06 Sekunden. Median aus je fünf Durchläufen mit --frozen-lockfile, beide Versionen auf demselben Store.

blog.stephaniestimac.com

On AI in coding

"But is the code correct?" He asked. And this, dear reader, is where the hard truth hit me. Jhey looked at the ChatGPT output. It had gifted me 5 lines of completely unnecessary code, and another bit was "fine" but not the most efficient way to write JavaScript. He made a few edits for me and left me to continue on with my CSS overhaul. -- The Web Witch's Blog

A few weeks ago, it took my colleague and me hours to find all the bugs that AI introduced into an application simply because someone had written code in a language and framework they were unfamiliar with.

I often use AI for development tasks, and it can be very useful - for example, when you need to apply similar changes to multiple files or when you want to improve your code style ("Tell me how this code can be written in a more readable way..."). However, AI can also produce a mess when you use it for something with which you have no experience. It's like working with a a new colleague who can be brilliant at times but would rather say something than admit that they can’t solve a problem. If AI only supports you on a topic you are familiar with, it will improve your speed and perhaps even the quality of your work. Maybe you can even learn how to improve the structure of your code or write that one specific test. If you have no experience with the topic you're working on, it can produce bugs and poor code; in the end, just copying and pasting code you don't understand won't make you a better programmer.

via Stephanie Stimac

1 min read

TIL: One file to rule them all: PEP-723 and uv

It's impressive what’s possible with Python nowadays! PEP-723 introduces a way to define metadata directly inside a Python file — including dependencies.

"This PEP specifies a metadata format that can be embedded in single-file Python scripts to assist launchers, IDEs and other external tools which may need to interact with such scripts."
PEP 723 – Inline script metadata

uv takes this a step further by handling virtual environments and dependencies seamlessly when executing such scripts.

For example, if you want to spin up a Flask server in one file, it looks like this:

# /// script
# requires-python = ">=3.12"
# dependencies = [
#   "flask",
# ]
# ///
from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return "Hello, World! 🚀"

if __name__ == "__main__":
    app.run(debug=True)

Save this file as server.py and run it with:

uv run server.py

Within seconds, the server is up and running. This makes it easy to execute Python scripts with external dependencies — without worrying about setting up a virtual environment manually.

Even better, you can make it a standalone executable script by adding this shebang at the top:

#!/usr/bin/env -S uv run --script

Now, the script will be executed using uv whenever it’s run (just ensure it's executable with chmod +x).

This approach offers a useful way to define and run Python scripts while handling dependencies — all within a single file.

Thanks to Rob Allen for writing about PEP-723 and how to use uv as a shebang line.