sources($withFonts); if ([] === $sources) { return null; } $name = 'kant-'.substr(hash('xxh128', implode('|', array_map( static fn (string $file): string => $file.':'.filemtime($file).':'.filesize($file), $sources, ))), 0, 16).'.css'; $path = $this->assetDirectory.'/build/'.$name; if (!is_file($path) && !$this->write($path, $sources)) { return null; } return '/assets/build/'.$name; } /** @return list */ private function sources(bool $withFonts): array { $found = []; foreach (self::SOURCES as $source) { if ('fonts/fonts.css' === $source && !$withFonts) { continue; } $path = $this->assetDirectory.'/'.$source; if (is_file($path)) { $found[] = $path; } } // Ohne das Grundgerüst kein Bündel: dann stimmt etwas anderes nicht. return in_array($this->assetDirectory.'/site.css', $found, true) ? $found : []; } /** @param list $sources */ private function write(string $path, array $sources): bool { $directory = dirname($path); if (!is_dir($directory) && !@mkdir($directory, 0775, true) && !is_dir($directory)) { return false; } $css = ''; foreach ($sources as $source) { $css .= $this->minify((string) file_get_contents($source))."\n"; } // Erst daneben schreiben, dann umbenennen: ein zweiter Aufruf im selben Moment soll nie // eine halb geschriebene Datei ausliefern. $temporary = $path.'.'.bin2hex(random_bytes(4)); if (false === @file_put_contents($temporary, $css) || !@rename($temporary, $path)) { @unlink($temporary); return false; } $this->clean($directory, basename($path)); return true; } /** * Kommentare raus, Einrückung raus - sonst nichts. * * Kein Umbau von Selektoren, kein Zusammenfassen von Regeln: ein Minifier, der die Bedeutung * ändert, spart ein paar Zeichen und kostet einen Abend Fehlersuche. Leerzeichen innerhalb * einer Zeile bleiben deshalb stehen – in `calc(100% - 20px)` und in `.a :hover` tragen sie * Bedeutung. */ private function minify(string $css): string { $withoutComments = preg_replace('#/\*(?!!).*?\*/#s', '', $css) ?? $css; $lines = []; foreach (preg_split('/\R/', $withoutComments) ?: [] as $line) { $line = trim($line); if ('' !== $line) { $lines[] = $line; } } return implode("\n", $lines); } /** Ältere Bündel wegräumen; sie werden von niemandem mehr angefragt. */ private function clean(string $directory, string $keep): void { foreach (glob($directory.'/kant-*.css') ?: [] as $old) { if (basename($old) !== $keep && filemtime($old) < time() - 3600) { @unlink($old); } } } }