Наш модуль для ProcessWire правит уже собранную страницу: убирает отступы и переносы, нужные только в шаблонах, расставляет типографику и делает замены. В 0.4.0 очистка переписана так, чтобы она не могла изменить значение атрибута — и чтобы при любой ошибке страница уходила такой, какой пришла.
OutputTransformer висит на Page:render и работает с готовым HTML, а не с шаблонами. Шаблоны остаются читаемыми: отступы, переносы, комментарии — всё на месте. В браузер уходит то, что нужно браузеру.
Мы держим модуль на своих сайтах с 2025 года, включая этот. Теперь код открыт: github.com/theoretic/OutputTransformer. Вопросы и обсуждение — в теме на форуме ProcessWire.
Три преобразования
Очистка. В шаблоне HTML написан для человека. Браузеру из этого нужен минимум: очистка убирает лишние пробелы, переносы и комментарии, не меняя того, что страница значит.
Типографика. Тире, «ёлочки» с вложенными «лапками», неразрывные пробелы. Для русского — atispro/emt-php8, для остальных языков — mundschenk-at/php-typography.
Замены. Список «что» и «на что», по строке на пару, через mb_ereg_replace. Для мелочей, ради которых не станешь править шаблон.
Каждое преобразование — отдельный хук на Page:render со своим приоритетом и своим списком шаблонов-исключений. Любое выключается отдельно от остальных: достаточно стереть приоритет.
Почему 0.4.0 переписан
До 0.3.0 включительно очистка обращалась со страницей как с плоским текстом: одни и те же правила применялись к тегам, тексту и скриптам. На большинстве страниц это незаметно — пока не попадётся место, где пробел и есть данные.
Так оно и вскрылось. Форма в админке отдавала value=" | «, а обратно приходило «| «: разделитель заголовков менялся сам собой при каждом сохранении. Тем же способом x < y в тексте превращалось в начало тега, а строки скрипта склеивались со своими //-комментариями — и всё, что было дальше, оказывалось закомментировано.
В 0.4.0 страница сначала разбирается на куски — комментарии, теги, текст, CDATA и «сырые» элементы, — и у каждого вида свои правила. Значение атрибута не меняется никогда: только class, rel, srcset и sizes подрезаются как списки, а у style отпадает хвостовая точка с запятой. Содержимое script, pre, textarea и CDATA не трогается вообще.
Что остаётся от разметки
<!-- before -->
<ul class=" nav main ">
<li>
<a href="/about/" title=" About us ">About</a>
</li>
</ul>
<!-- after -->
<ul class="nav main"><li><a href=/about/ title=" About us ">About</a></li></ul> Список классов подрезан и схлопнут, кавычки сняты там, где это разрешает HTML, пробелы между тегами убраны. Значение title осталось байт в байт: пробелы по краям — это данные. Комментарии в примере оставлены для читателя: очистка удаляет их, кроме условных и noindex.
Сколько это весит
Честно о цифрах
Замеры сделаны на этом сайте 18 сентября 2026 года: каждая страница запрошена дважды — с включённым и выключенным модулем, — только HTML, gzip -9 для сжатой цифры.
Выигрыш в байтах небольшой: 4−10% до сжатия и около 4% после. Ради одного этого модуль ставить незачем — gzip и brotli уже сделали основное. Смысл в другом: страница уходит без мусора, её удобно читать в «Просмотре кода», а типографика и замены доезжают до браузера сами, без правок в каждом шаблоне.
Как оставить кусок нетронутым
Обёртки. Теги <no-cleanup>, <no-typografy> и <no-replace> выводят содержимое из-под соответствующего преобразования. Сами теги из вывода исчезают.
Шаблоны-исключения. У каждого преобразования свой список шаблонов, страницы которых оно не трогает.
Свои выражения. «Keep untouched by…» — по одному PCRE на строку, с делимитерами. Что совпало, остаётся как есть. Выражение, которое не компилируется, пропускается, пишется в лог и показывается при сохранении настроек.
Всё защищённое на время преобразования подменяется символами из частной области Unicode (U+F8E0-U+F8F1). Если что-то пошло не так — ошибка в регулярном выражении, исключение, потерянная или задвоенная подстановка, страница, где эти символы уже есть, — преобразование для этой страницы пропускается целиком, страница уходит такой, какой пришла, а причина пишется в лог outputtransformer. Испорченной или пустой страницы не будет.
Две страховки
<!-- in a template: cleanup would collapse the newlines inside -->
<no-cleanup><pre><code><?=htmlspecialchars($block->code)?></code></pre></no-cleanup>
<!-- in the settings, Keep untouched by cleanup: one PCRE per line -->
~<input\b[^>]*name="?title_separator[^>]*>~i Первое — для своей разметки, второе — для того, что обёрткой не достать: чужой модуль, поле админки, сторонний виджет.