The TemplateStyles extension allows complex CSS (including selectors and media queries) to be used in templates. The Wikimedia Reading Infrastructure team plans to deploy it to the Wikimedia cluster to fulfill our Q3 goal: to empower editors to create mobile-friendly templates. That requires some changes to the extension; it is not entirely clear what those changes should be, though.
When considering how that CSS will be stored, processed and delivered to the client, there are three design choices to be made:
- Where should the CSS for a given template be stored?
- When rendering a page which includes some template (possibly indirectly), how should the parser find out that the CSS for that template needs to be included?
- How should the CSS be delivered to the client?
The first question has big UX and workflow impact for template maintainers; the second affects how the extension can integrate with various things that use the parser(s); the third has important performance implications. They are not fully independent; most combinations are possible but some are more natural than others. At least for the first choice, changing our mind later will be pretty hard.
The goal of this RfC is to find out what the hard constraints are (do user expectations, performance requirements, Parsoid limitations etc. force our hand in any of the questions?) and then agree on what seems like the most promising combination of choices within those limitations. (There is a separate consultation to get input from editors.)
Some initial thoughts on what the choices are and what benefits/drawbacks they have:
- Where should the CSS be stored?
- On the template page, via a parser tag (this is what the current implementation of the extension does - you just put the CSS code together with the template code, inside a <templatestyles>...</templatestyles> block).
- Pro: atomic changes
- Mixed: flexible - template authors can chose to include CSS from a subpage, or use template parameters in the CSS code, or even generate dynamic CSS via #tag or a Lua module. That enables all kinds of interesting use cases; OTOH in the past we have sometimes regretted making templates too flexible, which resulted in problems that should have been handled in software being handled in template language (e.g. we probably don't want users to reimplement LESS in Lua).
- Con: template wikitext becomes even more cluttered than it already is
- Con: the wikitext editor is unlikely to offer CSS syntax highlighting and other feature-specific support.
- Use a separate page (e.g. a /styles.css subpage); migrate to a dedicated revision slot when multi-content revisions (MCR) become available.
- Pro: controlling who can edit the template and who can edit the styles can be detached (especially important if we plan to extend this mechanism to template-specific JS eventually) - can be protected separately, or we can require a specific user right for one but not the other
- Pro: more granular history (possible to subscribe for CSS changes only)
- Pro: easy to provide a better UI (e.g. use CodeEditor to edit the CSS, or highlight syntax errors).
- Pro: easier to search templates by style (e.g. to find templates with non-mobile-friendly styles).
- Con: until MCR gets implemented, atomic changes to both template code and CSS will not be possible.
- Con: until MCR gets implemented, won't work so well with watchlists and other change tracking mechanisms (e.g. won't show up on RecentChangesLinked), or requires extra work to make sure it does.
- Con: initially for protected templates the (not yet created) subpage unprotected and an easy vandalism target; might need extra code to inherit protection status of the template page
- Con: might need a way to turn it on for templates not in the Template namespace
- Use a <templatestyles>...</templatestyles> parser tag, like in the A option, but save the contents of the tag in the database when the template is saved (initially as a custom table; with MCR probably as a persistent virtual slot).
- Pro/Con: largely the same as the A option, except for the flexibility.
- Con: problematic with extensions which require an older revision or an unsaved edited version of the template to be used (FlaggedRevs, TemplateSandbox)
- On the template page, via a parser tag (this is what the current implementation of the extension does - you just put the CSS code together with the template code, inside a <templatestyles>...</templatestyles> block).
- How should the parser fetch the CSS styles that need to be included when rendering a page?
- Hook into the template transclusion mechanism, let the parser pull in the source code and only process it once all templates have been transcluded.
- This requires 1A; with 1B and 1C, there is no straightforward way to do it (in the PHP parser it could be done via ParserFetchTemplate hook but there doesn't seem to be any equivalent in Parsoid)
- Pro: Takes almost no effort to implement - it would just happen naturally.
- Pro: Since transclusion is understood by a lot of tools, all kinds of things would also just work - Parsoid (since it relies on the PHP parser to process parser tags), TemplateSandbox, maybe scary transclusion.
- Con: all included CSS would probably need to be parsed/minified again every time a page is edited (at least until the parser starts supporting partial invalidation for DOM subtree replacements), or cached manually
- Con: when rendering and article, not really possible to tell where a piece of CSS came from. (Although for debugging it could just be included as a CSS or HTML comment.)
- Hook into the end of parsing (e.g. OutputPageParserOutput), get list of included templates, get the corresponding CSS.
- Pro: probably easier to support debugging/bundling/other clever behavior as we know where each piece of CSS came from.
- Con: hard to make it interact properly with ParserFetchTemplate/BeforeParserFetchTemplateAndtitle extensions (incl. FlaggedRevs) - again, MCR would probably help
- Con: even if 1A is chosen in the previous question, the flexibility advantage would be lost.
- Con: Parsoid would probably require its own plugin. Making it work with TemplateSandbox would probably require extra code.
- Require the location of the CSS styles to be included as a parameter to the parser tag (this would mean an empty parser tag would be required even if we go with 1B, i.e. something like <templatestyles src="Template:Foo/styles.css" /> would have to be appended to the template), rely on transclusion to find out which CSS pages can be included.
- This makes no sense for 1A (just use 2A instead).
- Pro: relies on transclusion so a lot of things would just work (much like with 2A)
- Pro: probably easier to support debugging/bundling/other clever behavior as we know where each piece of CSS came from.
- Pro: with 1B, could play nicely with ParserFetchTemplate/BeforeParserFetchTemplateAndtitle
- Con: a bit more cumbersome/verbose wikisyntax (although the pre-save transform could be used to automatically convert <templatestyles /> into <templatestyles src="[pagename]/styles.css" />)
- Hook into the template transclusion mechanism, let the parser pull in the source code and only process it once all templates have been transcluded.
- How should the CSS be delivered to the client?
- A <style> tag in the HTML page, within the DOM subtree of the template.
- Pro: no extra effort to add/remove from the page when using VisualEditor or live preview
- Pro: CSS for templates lower in the body do not block rendering of the first paragraph
- Con: no deduplication; some templates (flags etc.) might be used hundreds of times on the same page (not a bandwidth concern due to compression but might affect browser memory usage / rendering speed?)
- Con: FOUC issues if the template is at the end of the page and the styles affect content outside it.
- Con: if templates have conflicting styles, the end result might depend on the order in which the templates are used in the page.
- Con: could cause extra repaints/reflows?
- <style> tags in the document head
- Pro: deduplicated.
- Pro: can be ordered (although supporting that in various clients would require extra work)
- Mixed: CSS loaded before content (no FOUC, but blocks rendering)
- Con: VisualEditor and other applications that do "piecewise composing" of previews will need some sort of API to tell which stylesheets need to be added/removed (and possibly deal with ordering them).
- Con: applications which fetch page content and then discard parts of it (e.g. Mobile Content Service stripping out collapsed sections and certain templates) have no way of discarding unneeded styles
- External stylesheets loaded via ResourceLoader (see T155813#3047635 for more details)
- Pro: deduplicated.
- Pro: can be ordered (although supporting that in various clients would require extra work)
- Pro: can be properly cached in the client.
- Pro: if we want to allow templates to depend on RL modules to share code, that would be easier to do this way.
- Mixed: CSS loaded before content (no FOUC, but blocks rendering)
- Con: on non-HTTP/2-aware clients (do those still exist?) less performant than inlining
- Con: some browsers (Chrome?) are bad at prioritizing HTTP/2 multiplexing so end up downloading CSS slower this way
- Con: if RL bundles modules, cache fragmentation; if it does not, compression gets less effective
- Con: VisualEditor and other applications that do "piecewise composing" of previews will need some sort of API to tell which stylesheets need to be added/removed (and possibly deal with ordering them).
- Con: applications which fetch page content and then discard parts of it (e.g. Mobile Content Service stripping out collapsed sections and certain templates) have no way of discarding unneeded styles
- Use ResourceLoader modules to tell OutputPage what styles are needed, but make it inline the styles in the head instead of linking to them. This would have the frontend characteristics of 3B and the backend characteristics of 3C.
- Like 3A, but somehow remove the duplicate style blocks.
- The logic for this should probably live in core, since the reasons Performance likes it apply to several other extensions too. There's a straw proposal for that in T155813#3095844.
- Any tool manipulating the HTML would have to be aware of 3E so it could avoid removing styles when removing the HTML that contains the <style> tag.
- A <style> tag in the HTML page, within the DOM subtree of the template.