{"id":20895947,"url":"https://github.com/dan-q/wp-htmltidy-hack-demo","last_synced_at":"2026-05-12T23:36:30.904Z","repository":{"id":250774275,"uuid":"835434173","full_name":"Dan-Q/wp-htmltidy-hack-demo","owner":"Dan-Q","description":"A demonstration of how to run your entie WordPress output through HTMLTidy from within your theme.","archived":false,"fork":false,"pushed_at":"2024-07-29T20:51:30.000Z","size":23,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-06T02:54:19.449Z","etag":null,"topics":["demo","demonstration","hack","htmltidy","tidy","wordpress"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Dan-Q.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-29T20:42:18.000Z","updated_at":"2024-07-29T20:51:32.000Z","dependencies_parsed_at":null,"dependency_job_id":"c2d3fe01-8076-4183-8bbf-825430ab4b42","html_url":"https://github.com/Dan-Q/wp-htmltidy-hack-demo","commit_stats":null,"previous_names":["dan-q/wp-htmltidy-hack-demo"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dan-Q%2Fwp-htmltidy-hack-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dan-Q%2Fwp-htmltidy-hack-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dan-Q%2Fwp-htmltidy-hack-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dan-Q%2Fwp-htmltidy-hack-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Dan-Q","download_url":"https://codeload.github.com/Dan-Q/wp-htmltidy-hack-demo/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243280208,"owners_count":20265973,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["demo","demonstration","hack","htmltidy","tidy","wordpress"],"created_at":"2024-11-18T10:31:32.892Z","updated_at":"2026-05-12T23:36:30.875Z","avatar_url":"https://github.com/Dan-Q.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"## TidyPress\n\nA hacky demonstration of how you can, with just a few lines in the `header.php` of your (non-FSE) WordPress theme,\nconfigure [HTMLTidy](https://www.html-tidy.org/) to run across all of your output files, resulting in:\n\n- Cleaner, tidier, more human-readable HTML code\n- Code repaired and formatted to the standard you prefer (e.g. HTML5 `\u003ctags\u003e` without XHTML-styles self-closing `\u003ctags/\u003e` where they're not required)\n- Updating logical emphasis tags e.g. `\u003cb\u003e` ➡️ `\u003cstrong\u003e`, `\u003ci\u003e` ➡️ `\u003cem\u003e`\n- Hoisting any inline `\u003cstyle\u003e` blocks to the `\u003chead\u003e`, and re-writing any repeated inline styles as classes\n- Suppressing comments\n- Repairing invalid HTML to save the browser from having to do it every time\n\nPlease don't just use this as-is without understanding the implications. Read this entire README and make sure you grok it first. [I'm happy to take questions](https://danq.me/contact/).\n\n### Demonstration\n\nTo see this working:\n\n1. Check out this codebase\n2. Run `docker compose up` to create a new development WordPress site at https://localhost:8890/\n3. Visit https://localhost:8890/ and go through the install (pick a site name, username, password, etc.)\n4. Use your new credentials to install and activate the **TwentyTwentyOne** theme (http://localhost:8890/wp-admin/theme-install.php?search=twentytwentyone)\n5. Visit http://localhost:8890/ and View Source; see that the code looks like regular old WordPress\n6. Activate the **HTMLTidy21** theme (http://localhost:8890/wp-admin/themes.php), which is a child theme of **TwentyTwentyOne**.\n7. Refresh http://localhost:8890/ and View Source again: note that the code has been tidied for consistency, readability, and standards! 🎉\n\n#### Before\n\n![HTML source, untidy](https://github.com/user-attachments/assets/fcb66529-7bca-4ddc-b34f-0564068175ea)\n\n#### After\n\n![HTML source, tidied](https://github.com/user-attachments/assets/dd34d8ba-655b-42bf-b470-62117c12b7e8)\n\n#### How does this work?\n\n**HTMLTidy21** is a child theme of **TwentyTwentyOne**. `src/header.php`, in this repo, overrides TwentyTwentyOne's `header.php` only.\n\nIt adds a new output buffer with a callback function: `ob_start( 'tidy_entire_page' );`. Because it's never explicitly finished, that output buffer is automatically closed when PHP finishes executing i.e. the page content has been produced. Then the function `tidy_entire_page` is called and passed a string containing all the HTML code of the resulting page.\n\n`function tidy_entire_page($buffer) { ... }` is where the magic happens. It instantiates the PHP library to HTMLTidy and runs it over the HTML code, producing \"tidied\" code (following the rules defined in `src/header.php`).\n\nNote that PHP's bindings to HTMLTidy aren't enabled as-standard, _but they can sometimes look as if they are_. PHP's a deceptive beast. Try to run this code without the 'tidy' extension properly configured and working and it won't throw an error... and it might even try to re-indent your code! But it won't run the full HTMLTidy suite. This will cause you some frustration, because it just won't seem to be working properly. If you examine `Dockerfile` you'll see the steps I took to ensure that the PHP-to-HTMLTidy bindings were properly set up in this container, but your real-life hosting may be different (see below).\n\n(Fun fact: PHP has this same strange \"Don't have the extension? I'mma try my hardest anyway!\" behaviour with ZIP files. If you don't have the 'zip' extension installed, PHP _can still make ZIP files_, it's just they can't be _compressed_ ZIP files. PHP is a weird language.)\n\n### Doing It For Real\n\nTo implement this in your own theme, on your own website:\n\n1. Install a recent version of `libtidy`. If you're running a relatively up-to-date distro you can probably get this from your favourite package manager with e.g. `apt update \u0026\u0026 apt install -y libtidy-dev`. If you're not, then you might have to compile your own. If you get problems like HTMLTidy not believing that certain \"modern\" elements like `\u003cvideo\u003e` exist, check your `libtidy` version.\n2. Install the PHP extension for HTMLTidy. Depending on how your PHP is built this might be as simple as `apt update \u0026\u0026 apt install -y php-tidy` or uncommenting a line in `php.ini`... or it might as complicated as having to recompile PHP but with the `--with-tidy` flag.\n3. Test that HTMLTidy is installed by running `php -i | grep tidy`: if you get any output, that's probably great!\n4. Edit your theme's `header.php` to include the code from `src/header.php` in this repository. Note that the `ob_start(...)` command needs to come before _any_ output, even the `\u003c!DOCTYPE html\u003e` declaration.\n  - Alternatively, you might be able to get away with hooking into `init` from `functions.php` or even a plugin, but you run the risk of a higher-priority hook making you have a Very Bad Day. Up to you.\n5. Reconfigure the HTMLTidy settings according to your preferences. You might find the [full list of options](https://api.html-tidy.org/tidy/quickref_next.html) helpful.\n6. Implement any _exception_ rules you want to. E.g. you might opt to allow logged-in-users to _not_ tidy HTML content by sending a GET parameter `?skip-tidy=true`, e.g. with some code like this: `if( isset( $_GET['skip-tidy'] ) \u0026\u0026 is_user_logged_in() ) return $buffer;`\n7. Make sure you've got some kind of caching set up. Seriously.\n\n#### Caching?\n\nHTMLTidy isn't the _most_ expensive operation, but you don't want to be doing it for every single load of every single page. Make sure your webserver's configured to microcache content, or else use a plugin like [WP Super Cache](https://wordpress.org/plugins/wp-super-cache/) _with the \"advanced\" configuration that allows your webserver to send pages to anonymous users without hitting PHP at all_.\n\nAn alternative approach might be to implement something that runs HTMLTidy on posts _as you save them_ (and then tidy up your `header.php`/`footer.php` etc. manually). That's not for me, but maybe that's the direction you want to go in.\n\n#### Future\n\nI do all kinds of other hacks in my `tidy_entire_page` function. Some of them... like running `preg_replace` on a HTML page... I'm not proud of (there's nothing like running an irregular language through a regular expression to make you feel bad about your life choices).\n\nOthers are fine, like stripping out `\u003clink rel=\"stylesheet\u003e` elements and replacing them with inline `\u003cstyle\u003e...\u003c/style\u003e` blocks (my HTML + CSS is compact enough that for 95%+ of pages [I can fit both into a single \u003c12kb block](https://danq.me/2023/11/04/fast-wordpress-the-hard-way/#21658-12kb), at which point it becomes more-efficient to bundle them into a single file and avoid the round-trip!). Your mileage may vary.\n\nBut personally, I'm really excited for the upcoming release of PHP 8.4's [modern `\\Dom\\Document` implementation](https://wiki.php.net/rfc/dom_additions_84), which will allow me to dynamically manipulate my output content using XPath rather than the hackiest-of-hacks. Until then, though, HTMLTidy's still a great step-up (if you're the kind of person who cares about how pretty their HTML is!).\n\n## Thanks\n\nThanks to [@edent](https://github.com/edent) for [encouraging](https://mastodon.social/@Edent/112871192888419993) me to share this.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdan-q%2Fwp-htmltidy-hack-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdan-q%2Fwp-htmltidy-hack-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdan-q%2Fwp-htmltidy-hack-demo/lists"}