
# 缓存和压缩

流水线末端有三个生产环境相关事项：避免对相同输入重复编译，避免每次请求都承担构建编译器的开销，以及缩小生成的 CSS。这三者都是围绕编译器的 PHP 端辅助功能。

## 缓存

每次调用 CSS 都会运行完整流水线。对于请求之间内容不变的情况，基于文件的缓存会将结果写入磁盘，并在后续调用时直接提供。

将 `cache` 传给 `tw::generate()`；`true` 表示默认目录，也可以传入自定义路径字符串：

```php
use TailwindPHP\tw;

// Cache to the default directory: sys_get_temp_dir() . '/tailwindphp'
$css = tw::generate([
    'content' => '<div class="flex p-4">Hello</div>',
    'cache' => true,
]);

// Cache to a custom directory
$css = tw::generate([
    'content' => '<div class="flex p-4">Hello</div>',
    'cache' => '/path/to/cache',
]);

// With a time-to-live (seconds)
$css = tw::generate([
    'content' => '<div class="flex p-4">Hello</div>',
    'cache' => true,
    'cacheTtl' => 3600, // expire after 1 hour
]);
```

如果缓存目录不存在，系统会自动创建。

### 缓存键

缓存键由 **内容**、**CSS 配置**和 **minify** 标志生成，并用作文件名 `tailwind_<hash>.css`。标记内容、CSS 输入或是否启用压缩的任何变化，都会生成不同的键，因此对应不同的文件。

键中的 CSS 配置包含内联的 `css` 以及 `importPaths` 中列出文件的内容。两种输入不包含在内：这些文件通过嵌套的 `@import` 引入的文件，以及 TailwindPHP 版本。编辑嵌套导入文件或升级软件包后，请清除缓存，否则系统会继续提供旧输出。

### 生存时间

`cacheTtl` 的单位是秒，表示有效时长。命中缓存时，系统会将文件修改时间与当前时间比较；如果文件的时间早于 `cacheTtl`，则视为未命中并重新编译。若未设置 `cacheTtl`，缓存条目永不过期，需要手动清除。

### 清除缓存

`tw::clearCache()`（或 `clearCache()` 函数）会从缓存目录中删除 `tailwind_*.css` 文件，并返回已删除文件的数量。

```php
use TailwindPHP\tw;
use function TailwindPHP\clearCache;

$deleted = tw::clearCache();                  // default directory
$deleted = tw::clearCache('/path/to/cache');  // custom directory
clearCache('/path/to/cache');                 // function form
```

## 编译器状态缓存

只有相同内容再次出现时，输出缓存才有用。如果每次调用时内容都发生变化，开销较大的是构建编译器本身：解析 `@import "tailwindcss"`、解析打包资源并累积主题。此阶段仅取决于输入 CSS，因此可以存储并重复使用。

将 `stateCacheFile` 传给 `tw::compile()`（或 `new TailwindCompiler($css, $options)`）：

```php
use TailwindPHP\tw;

$compiler = tw::compile($themeCss, [
    'stateCacheFile' => __DIR__ . '/cache/tailwind-state.php',
]);

$css = $compiler->generateExact($html, true);
```

首次构建时，状态会以纯 PHP 文件写入该路径，OPcache 可在请求间共享此文件；后续构建会直接加载该文件，不再重新解析。输出与未使用缓存的编译器逐字节一致。

- 系统会根据引擎指纹（TailwindPHP 源码和资源的大小及修改时间）、由输入 CSS、`base` 和 `importSearchPaths` 选项生成的键，以及输入导入的每个文件的大小和修改时间验证文件。如果不匹配，系统会重新构建并写入文件，因此升级 TailwindPHP 或编辑导入文件时无需手动操作。
- 路径由你负责：针对每种不同的输入 CSS 使用单独文件，并将其放在 Web 服务器可写入的目录中。写入是原子操作（先写入临时文件，再执行 `rename()`）。
- 使用自定义 `importResolver` 的输入会绕过缓存，因为无法为解析器的行为生成指纹。
- 较低层级的 `compile()` 数组 API 也可使用同一缓存，方式为 `TailwindPHP\StateCache\compileCssCached($css, $cacheFile, $options)`。

`tw::generate()` 不接受 `stateCacheFile`；每次调用都会构建新的编译器。若需控制构建开销，请使用编译器实例。

## 压缩

压缩可以缩小生产环境中的输出。`minify` 标志会在序列化期间直接生成压缩后的 CSS，因此速度最快、精度也最高；`tw::minify()` 会对现有的 CSS 字符串运行基于字符串的压缩器（`src/_tailwindphp/CssMinifier.php`）：

```php
use TailwindPHP\tw;

// Minify during generation (serializer path, preferred)
$css = tw::generate([
    'content' => '<div class="flex p-4">Hello</div>',
    'minify' => true,
]);

// Or minify an existing string (string-based post-pass)
$css = tw::generate('<div class="flex p-4">Hello</div>');
$minified = tw::minify($css);
```

两种路径生成的结构等效，但字节并不完全相同。序列化器路径会保留后代 `:not()` 选择器，而字符串处理流程会将其折叠，因此生成输出时优先使用 `minify` 标志；如果 CSS 是以字符串形式传入的，则使用 `tw::minify()`。

### 功能说明

两种路径都会应用相同的一组缩减操作：

| 步骤 | 效果 |
|------|--------|
| 移除注释 | 移除 `/* … */` 块 |
| 合并空白 | 合并连续空白，并移除 `{ } ; : ,` 周围及选择器组合符周围的空白 |
| 缩短十六进制颜色 | `#ffffff` → `#fff`，`#aabbcc` → `#abc` |
| 移除零单位 | `0px` → `0`（保留 `0s`/`0ms` 时间值） |
| 缩短字重 | `font-weight:normal` → `400`，`font-weight:bold` → `700` |
| 移除空规则 | 删除声明块为空的选择器 |

按照设计，压缩器**不会**合并重复选择器或组合简写属性，因为这两种操作都会增加调试难度，并可能影响层叠效果。

### CLI：`--minify` 与 `--optimize`

[CLI](https://tailwindphp.inline0.dev/zh/docs/cli) 通过两个标志开放了序列化器的压缩路径：

```bash
# Optimize and minify (smallest output)
tailwindphp -i ./src/app.css -o ./dist/styles.css --minify   # or -m

# Meant to optimize without minifying
tailwindphp -i ./src/app.css -o ./dist/styles.css --optimize
```

在当前版本中，`--optimize` 也会进行压缩，因此两个命令写入的字节相同（[#9](https://github.com/inline0/tailwindphp/issues/9)）。无论采用哪种方式，每次构建都会运行与 lightningcss 等效的转换，因此不带标志的普通输出已经经过优化且易于阅读。

有关其余公开 API（`tw::generate()`、`tw::compile()`、检查方法和输入格式），请参阅 [API 参考](https://tailwindphp.inline0.dev/zh/docs/api)。
