Requests for comment/Skin templating

Problem Statement
The current skinning system is difficult to use for many use-cases, and could therefore use improvement.

Goals
There are numerous competing goals for a skinning system in MediaWiki, which are described below. Any system we ship should ideally cater to all of these goals. These goals are broken out by which stakeholders they apply to.
 * As a wiki user, I want to:
 * Choose between a list of enabled skins to help personalise my browsing and editing experience.
 * Customise a skin to modify its appearance or otherwise add or remove functionality, either for only myself or for everyone on the wiki, for varied reasons.
 * As a system administrator, I want to:
 * Decide which skins are enabled for wiki users to choose, so that they can receive the best set of features available to them or otherwise ensure that the wiki stays on-brand.
 * Make simple modifications to skins, to better make them match the unique needs of the wiki.
 * Have fast loading times for pages, so that users are not driven away by long load times.
 * As a skin developer, I want to:
 * Develop skins very similar to existing ones with minimal fuss, to customise various aspects of the skin.
 * Develop brand-new skins unlike anything seen before, to provide a unique experience for users of my skin.
 * As a PHP extension developer, I want to:
 * Inject new elements into defined places of a skin to expose the functionality of my extension.
 * Re-arrange or outright replace parts of a skin to expose the functionality of my extension.
 * As a javascript author, I want to:
 * Have a consistent DOM where I can hook my js into to expose its functionality.

Why Mustache?
Mustache is already being used as a templating engine elsewhere within core and extensions. It will serve this purpose and there is therefore no need to introduce yet another engine when an existing one will work. Avoiding ad-hoc engines reduces friction in developing new skins, and having an engine means we don't need to mix PHP (code) with presentation logic. We currently have such mixing, and it makes some of the above goals nearly impossible to achieve.

What's Wrong With the Current System?
The current skinning system does not adequately meet the above Goals.
 * System administrators cannot make simple modifications to skins without resorting to either coding extensions (requiring PHP knowledge they may not have, and there may not be existing hook points for the modifications they wish to make), or by directly modifying the core skin files (which is fragile in the face of upgrades). At best, they can copy the entire skin and make their couple of simple tweaks, and push that out as a new skin which they must then maintain across upgrades.
 * Skin developers cannot easily make new skins that function as expected. Many extensions rely on a DOM structure similar to vector (and will break if such a DOM structure is not found), and any attempt to make a new skin requires duplicating everything, even if only small changes are desired from a base skin such as vector.

Wiki users are adequately served by the existing system, and will continue to be adequately served by the new system, as the customisation points in Special:Preferences and sitewide and user css/js pages will remain as-is for this proposal.

Past Work
Requests for comment/Redo skin framework seems like a previous attempt, however it is still in draft status and is nebulous as to how it will actually be implemented. This RFC contains a concrete implementation strategy, and achieves the same goals as the older draft RFC.

Design
To meet the Goals above, I propose a system where a skin is broken into numerous templates, each responsible for one small piece of the overall rendered page. A hierarchy of skins will allow for skins to inherit templates from parent skins if they wish to use them as-is, or otherwise customise those templates to provide their own functionality. In Core, this inheritance will look somewhat like the following:
 * Fallback: Base templates that provide a consistent DOM that all skins can use. It contains no CSS.
 * Vector
 * Monobook
 * Timeless
 * Modern

When a mustache template is being resolved, it traverses up the hierarchy, using the first defined template it finds. In other words, child templates completely replace parent templates. This search order is defined as follows:
 * 1) System administrator customisations of a template (cannot be modified by Extensions)
 * 2) The template for the selected skin (possibly modified by Extensions)
 * 3) The template of that skin's parent (possibly modified by Extensions)
 * 4) ... and so on, up the hierarchy, until we reach the root

When a LESS template is being resolved, the hierarchy is traversed from top-down, so that each child element cascades and replaces things defined in the parent. Unlike mustache templates, every part of these is rendered:
 * 1) The template of the topmost skin in the hierarchy
 * 2) Children of that skin which are parents of our selected skin, in order
 * 3) Our selected skin
 * 4) Extension customisations
 * 5) system administrator customisations

LESS templates are resolved before transforming it to CSS, meaning that the existing extension points like MediaWiki:Common.css are irrelevant to the above discussion. The goal is that system administrators can customise variables such as @primaryColour to easily theme the site without needing to make their own skin, or change the color in 500 different places once the LESS is expanded to CSS and variables therefore no longer apply.

Caching
The above lookups will likely be quite expensive in terms of file I/O, and possibly database calls (depending on where system admin customisations live). As such, this design will expand all templates during install or upgrade, and cache the results. system admin customisations will need to trigger a re-cache for the modified templates via some mechanism (if via Special page, do it as part of that. Otherwise, provide a maintenance script)

Mustache itself only resolves "partials" at runtime, but this would play havoc with the caching system. We would need to extend the mustache parser to expand partials immediately and cache the full result.

Extensibility
Extensions can use a hook that receives the text of the template as a string reference, and can manipulate that string to modify template contents. This hook is fired during the upgrade process, and the resulting template is cached.

System admins can customise templates as well, however the means of this customisation is not yet defined. There are two options, which each have security and convenience tradeoffs:
 * 1) A special page on the wiki which lets privileged users modify templates. There are two issues with this, as this gives on-wiki users full control of rendered HTML (something that has typically not happened before), and it is possible for a wiki admin to break a template in a way that they render the UI unusable, and therefore are unable to revert the break.
 * 2) A defined path on the filesystem for template overrides, which lets people with filesystem access to override templates by placing files there. This keeps control over HTML outside of the wiki, and is more easily reverted in case things break, but it is harder to provide good tooling or good UX when someone is just messing directly with files in a directory. Furthermore, this poses challenges for load-balanced environments: a customisation would need to be applied to every web server in the farm so each local PHP install can find it; not insurmountable using tools such as ansible, but still an extra caveat to keep in mind.

Results
If the above design is put in place, we meet our Goals in the following ways:
 * Wiki users can still use Special:Preferences to choose their skin, and can customise skins via sitewide and user css/js.
 * system admins can still choose the list of allowed skins and default skins via the same mechanisms as now (wfLoadSkin, $wg variables to control skin selection)
 * system admins can use the Extensibility points to customise mustache templates (to manipulate HTML) or LESS templates (to manipulate styles) in a way that is currently not achievable without creating a brand-new skin.
 * Skin developers can inherit from any skin if they want to make simple or minor tweaks, they can inherit from Fallback if they want to provide something wholly custom while still having a consistent DOM with other skins, or they could make their new skin as a root if a consistent DOM is not a concern for them.
 * PHP extension developers can use the Extensibility points to make changes to templates that are consistent across every skin.
 * JS authors can rely on a (more) consistent DOM across skins than what we currently have, making things like Gadgets more robust.

What About the Current System?
The current skinning system and infrastructure will continue to work, but will be deprecated. After a period of at least 4 releases where it is deprecated, it will subsequently be removed.

For example, if this system makes it into 1.34, the old system will be soft deprecated in 1.34 and 1.35, hard deprecated in 1.36 and 1.37, and removed entirely in 1.38. Critical issues raised by 3rd party users where the new system is not capable of achieving something the old system is capable of doing may merit prolonging the deprecation period.

Questions and Comments

 * Since both Drupal & Symfony use Twig, maybe that is a better option than Mustache? --DBarratt (WMF) (talk) 18:12, 26 February 2019 (UTC)