Twee is a WordPress theme framework for large, fast sites. It is built around Advanced Custom Fields (ACF). It stores ACF data in fewer database rows, reads metadata in bulk and from the object cache, and loads only the styles that a page uses.
The theme in the theme folder is a starting point. Copy it, rename it, and build the site on it. Its markup and styles
follow the CSS_BOX approach of the CSS_BOX Starter Kit, and its AGENTS.md
holds the rules for markup, styles and scripts.
- A Repeater, Group, Flexible Content or Clone field is stored in one metadata row, not in a row for each subfield.
- The value of such a field can be read as an array with
get_post_meta(). - The field keys of a record are stored in one row, not in a row for each field.
- All of this works through ACF filters, so the ACF API, ACF add-ons and revisions keep working.
- The metadata API is compatible with WordPress, but faster. It preloads metadata into memory and uses the WordPress metadata cache.
- An update writes to the database only when the value has changed.
- Metadata for many records is read in one query, split into partitions, and searched with a binary search.
- The post functions fetch the data of many posts at once, to avoid N+1 queries. They fetch the terms of posts and the posts of terms in one request.
- The term functions build and cache term trees, flatten them, and find the parents and children of a term.
- Scripts and styles are registered in one place, with their dependencies.
- Each box, a section with a class that ends in
_box, has a stylesheet of its own. A page loads the stylesheet of a box only when its HTML has that class. - The custom properties and the fonts are put inline into the head, so the page renders without waiting for them.
- The version of a file follows its content, so you never bump it by hand.
- Hidden image sizes are made on the first request, so the uploads folder holds only the sizes that are used.
srcsetandsizesare built from the layout.- SVG uploads get correct metadata.
- WebP conversion is built in, and the popular image optimization plugins are supported.
- Blocks for pages, built with the ACF Flexible Content field, with previews in the admin.
- Pagination for custom queries.
- Breadcrumbs with JSON-LD structured data.
- A Load More button for lists of posts.
- Accessibility fixes for menus.
- A base class for widgets.
- A logger that is compatible with WooCommerce.
- Unit tests for the core libraries.
- WordPress, with PHP 8.0 or newer.
- Advanced Custom Fields Pro.
- Advanced Custom Fields: Extended, for the previews of blocks in the admin.
- Node.js 16 or newer, to build the assets.
- An object cache, such as Redis, is recommended.
-
Copy the
themefolder intowp-content/themes, and rename it. -
Change the theme name in
style.css, and add ascreenshot.jpgto the theme folder. -
Install and activate ACF Pro, and ACF Extended if you want block previews.
-
Activate the theme.
-
Sync the field groups: WP Admin → ACF → Field Groups → Sync available → Sync. The groups are stored as JSON in
includes/acf. See Synchronized JSON. -
Install the packages for the assets:
cd assets npm install
| Path | Holds |
|---|---|
functions.php |
The configuration of the theme: assets, image sizes, menus, post types and taxonomies. |
includes/core/ |
The core libraries: app, asset, content, image, logger, meta, post, term and widget. |
includes/theme/ |
The features of the theme: ACF, blocks, breadcrumbs, gallery, the loader, pagination, previews and others. |
includes/acf/ |
The ACF field groups, as JSON. |
includes/widgets/ |
The widgets of the theme. |
blocks/ |
One template for each layout of the Flexible Content field, such as media.php. |
parts/ |
The parts that templates share: the header, the footer, buttons, contents and posts. |
assets/styles/ |
The styles: properties, base styles, elements and one file for each box in blocks/. |
assets/scripts/ |
The modules that every page loads. |
assets/plugins/ |
Libraries and the modules that only some pages load, each with an index.php. |
assets/build/ |
The built styles and scripts. Do not edit them by hand. |
assets/preview/ |
The previews of blocks, <layout>.webp, shown in the admin. |
Everything is configured in functions.php.
tw_asset_register() registers one or more assets. An asset is a set of scripts, styles and the data they need:
tw_asset_register([
'base' => [
'style' => 'base.css',
'footer' => false,
'display' => true,
'directory' => 'build'
],
'scripts' => [
'footer' => true,
'display' => true,
'deps' => ['jquery', 'app'],
'script' => 'scripts.js',
'object' => 'tw_template',
'directory' => 'build',
'localize' => function() {
return [
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('ajax-nonce')
];
}
]
]);The keys of an asset:
scriptandstylename a file, or a list of files, indirectory.depslists the assets or WordPress scripts that load first.displayset totrueloads the asset on every page. Any other asset is loaded withtw_asset_enqueue('<name>').footerloads the asset at the end of the page.localizeis an array, or a function that returns one. Its data is available to scripts as the object named inobject.inlineholds styles, or a function that returns them, that are put inline when the asset is loaded.
The theme also registers by itself:
- each folder in
assets/plugins, as an asset of the same name. Itsindex.phpreturns the keys above; - each stylesheet in
assets/build/blocks, as the asset<name>_box. It loads when a page has that class.
tw_image_sizes() adds an image size, or changes an existing one. A size with 'hidden' => true is not registered in
WordPress. It is made and cached the first time tw_image(), tw_image_link() or tw_image_attribute() asks for it.
Use it for a size that only a few sections need.
tw_app_type()andtw_app_taxonomy()register post types and taxonomies. They take the same arguments as register_post_type() and register_taxonomy(), and register them at the right time.tw_app_menus()registers menus, andtw_app_sidebar()registers sidebars.tw_app_widget()registers a widget by its class name. Seeincludes/widgets/Posts.php.
A page is built from blocks: the layouts of an ACF Flexible Content field.
tw_block_render($blocks)renders the blocks. Each layout has its template,blocks/<layout>.php.- A template writes its section with
tw_block_attributes('<name>_box', $block). This adds the classes that the settings of the block choose:- the background:
is_lightoris_dark; - the top spacing:
has_top,has_top_halforhas_no_top; - the bottom spacing:
has_bottom,has_bottom_halforhas_no_bottom; - the border:
has_border.
- the background:
tw_block_contents()renders the title, the text and the buttons of a block.tw_block_buttons()renders the buttons alone.- A block marked as hidden is not shown on the site.
ACF Extended shows a preview image for each layout, assets/preview/<layout>.webp. screens.js makes them with
Puppeteer from a page of the site that shows the blocks:
node screens.js http://project.test/?previewThe URL must end in .test. The script finds the project in Z:/ by its host name. A second argument sets the
selector of the sections, and a third sets the theme folder when its name differs from the host.
Run these in theme/assets:
| Command | Does |
|---|---|
npm run watch |
Builds again on each change. |
npm run build |
Builds all the styles and scripts. |
npm run theme |
Builds the base styles, build/base.css and build/other.css. |
npm run blocks |
Builds a stylesheet for each box in build/blocks. |
npm run woo |
Builds the WooCommerce styles, build/woo.css. |
npm run plugins |
Builds the styles in plugins. |
npm run scripts |
Joins the modules in scripts into build/scripts.js. |
npm run imagemin |
Compresses the images. It needs imagemin-cli, installed globally. |
npm run svgo |
Minifies the SVG icons. It needs svgo, installed globally. |
The build of a box stylesheet adds the imports it needs by itself: the mixins, and the elements that it extends.
Behavior is written in modules with Twee.addModule(). The loader is the plugin assets/plugins/app:
Twee.addModule('faq', '.faq_box', function($, wrapper) {
wrapper.on('click', '.title', function() {
$(this).closest('.item').toggleClass('opened');
});
});A module runs once for each element that matches its selector, including the elements that a script adds later with
Twee.runModules(). The AGENTS.md of the starter kit describes the rules in full.
The functions are grouped by subject in includes/core and includes/theme. The most used ones:
- Asset: registers and loads assets, resolves their dependencies, and puts styles inline.
tw_asset_enqueue()loads an asset on the page. - Content: works with titles, text, links and dates.
- Image:
tw_image()returns an image tag with its wrappers andsrcset. It supports hidden sizes, and is much faster than the WordPress functions.tw_image_link()returns only the link of an image.tw_image_attribute()returns an image as a custom property, such as a background or a mask.
- ACF: stores Repeater, Group and Flexible Content fields in one row each, and the field keys in one row.
- ACFE: customizes ACF and integrates ACF Extended.
- Block: renders the blocks of the Flexible Content field and their common fields.
- Meta: reads, updates and deletes metadata through the cache, and maps the metadata of many records at once.
- Post: maps post data, the terms of posts and the threads of terms.
- Term: builds and flattens term trees, and finds parent and child terms, the posts of terms and term links.
The tests use the WordPress test library, and run against a database that they wipe. Set its connection in
tests/config.php or phpunit.xml.dist. Then install the packages and run the tests in this folder:
composer install
composer testcomposer test loads the pcov extension for the coverage report. Without it, run vendor/bin/phpunit --no-coverage.
Author: Andrii Toniievych
Contact: toniyevych@gmail.com
Feel free to contact me if you have any questions.
- Fork this repository
- Commit your changes
- Push it to the branch
- Create the new pull request
Twee is released under the MIT Public License.
Note: The "About" section in README.md and the author (@author) notice in the file-headers shall not be edited or deleted without permission. Thank you!