Background
There are multiple ways we could share styles to support CSS-only components. We need to determine what's best for our users and ecosystem.
Exploration
CSS class approach
Other frameworks typically distribute CSS styles with pre-defined classes (Bootstrap's .btn, .btn-primary, etc). This approach offers good ease-of-use, but creates some coupling between the markup and the framework styles.]
Considerations:
- Consistent with the approach used in other systems
- Especially for simple components, quick and easy to use
- Users must stick with the pre-defined markup and selectors, meaning less flexibility and potential difficulty for components with a lot of markup
- Ease of use might lead to lots of use, which could make breaking changes become disruptive and difficult to manage
- Ease of use and ubiquity might lead to misuse, which happened with mediawiki.ui. However, once Codex is fairly ubiquitous in MediaWiki, this could happen anyway (e.g. someone could slap the .cdx-button class on something as long as the Button component is loaded on the page somewhere)
- Requires some creative problem solving in terms of class names. Right now, Codex Vue components follow the structure .cdx-component-name, which should be the base component selector. We may need to repurpose these names for the CSS-only version, then add .cdx-component-name--vue or .cdx-component-name--hydrated or some other variation for styles that only apply to the Vue version (e.g. interactivity). This could get especially hairy for components where the CSS-only version is very different than the Vue version (like Select)
- We would need to figure out how to export these styles for use (possibly in a new package)
Less mixin approach
An alternative approach would be to distribute Codex styles as pure LESS mixins, where the consumer decides where the styles should ultimately be applied. This approach is described more in Codex ADR 04.
Pros and cons:
- For more complex components, dev users wouldn't be beholden to a specific markup structure. Instead, they could apply mixins to whichever elements they choose.
- This is in line with how we chose to share styles for base elements (see the ADR linked above)
- Avoiding double-loading styles would be difficult. If the mixins are applied to different selectors throughout different problems (not to mention within Codex Vue components that are then loaded on the page), would it be possible to consolidate the styles so they're not loaded multiple times?
- Not as straightforward to use
Open questions
- Which approach has the fewest or the most easily mitigated performance issues? See T323180.
- Which approach will result in the lowest maintenance burden?
- Which approach will prevent misuse?
- Which approach is preferred by our developer users?
- Is there room for implementing both approaches, e.g. creating and releasing mixins, but then also applying those mixins to standard classes?
Proposal: Class-based approach plus OOUI-style sub-packages
- Use cdx- classes for styling ("Bootstrap-style" approach). We are already writing BEM-style CSS inside our Vue components, so we don't need to make any changes here.
- Continue writing styles within Vue single-file components (SFCs). Where necessary, component styles will start making distinctions between no-JS styles and JS-enabled ones by using a modifier class like --js-active. Components can add this class in the mounted() lifecycle hook. We can write a composition function that takes a component’s base class name as an argument and applies this modifier class on mount automatically. Grouping some styles under a --js-active class may also prove useful for SSR in the future. Example patch here: https://gerrit.wikimedia.org/r/c/design/codex/+/866509/
- Define a series of new Codex sub-packages which are intended for use within MediaWiki. Each new package will group together a set of related components (maybe corresponding to the component groups we were considering adding to the documentation site). This will work similar to how we are handling codex-search currently. Each new package will get built in Vite’s library-mode, so a single JS and CSS file gets built with only the data for the relevant subset of components. An example of this can be seen in this patch, which creates a demo codex-buttons package: https://gerrit.wikimedia.org/r/c/design/codex/+/865840
- Each Codex sub-package gets its own ResourceLoader module containing only its CSS and JS files. Users only need to load the packages corresponding to the components they need in their feature.
- Load CSS and JS separately: There are two ways we can do this.
- We could update ResourceLoader so that a user can get only the styles, or only the scripts, or both, when using a module that contains both CSS and JS. Currently this is only possible through an annoying work-around.
- Alternatively, we will have to do this work manually. Each Codex sub-package would need to have two modules in MW – one containing only the CSS, i.e. codex-buttons-styles, and one containing only the JS files, i.e. codex-buttons. The latter package would depend on the former, but the former can be used without the latter.
- Interaction with SSR: In the future, we expect that a more robust SSR system will be available in at least some situations. SSR-based features would basically create their own bundle instead of relying on the pre-split ones that we’d be providing here. Such builds will depend on the full Codex package (which contains all CSS and JS code). The SSR tooling will be able to tree-shake out all unnecessary code for these features, so the regular Codex modules will not be needed at all.
- We still have some open questions about this (@AnneT feel free to share any here), but I don't think they are blockers to this work moving forward. For example, there may be some components like <CdxSelect> or <CdxIcon> that may need to look pretty different in a CSS-only implementation vs a SSR-one (even pre-hydration).
Future work:
In the system described here, users of CSS-only components would still be responsible for authoring the proper markup for their UI. This will likely be somewhat tedious for all but the simplest features. We could make this experience a lot better by providing one or both of the following:
- Better templating language support on the server; it would be great if template authors could just include a {% cdx-button %} partial that can take arguments corresponding to component properties
- Alternatively, a PHP interface could be provided similar to how OOUI-PHP works currently
Feasibility of this approach
Aside from the potential (small-ish) changes to ResourceLoader (see point 4 above), we can do all of this now. And actually most of the styles we need already exist in the Vue components themselves. Most of the work would just be a matter of reorganizing some of our current code so that it can be used in multiple ways going forward.
Risks/Trade-offs
- This implementation strategy will allow us to keep writing our component styles within the Vue SFCs, which is nice from a familiarity and developer productivity approach.
- We would need to create some additional packages inside the Codex repo, but this will mostly be a one-time task. As we add new components to Codex we'll need to include them in one of the existing sub-packages or (more rarely) create a new one. But little additional code is required here and once a sub-package has been set up it will rarely change. These will be included in each release just like codex-search is (though we may not need to publish them on NPM since they are intended only for use in MW). See https://gerrit.wikimedia.org/r/c/design/codex/+/866509/ for an example of what this would look like.
- CSS-only implementations of components will need to be tested; I think that VueTest is a good place to do this (maybe a new sub-page of that extension can be created for the CSS-only components)
- We'll need to create several additional ResourceLoader modules (especially if we cannot come up with a way to get just the CSS or JS from a module that contains both). But like in the case of the additional Codex packages, most of the work here is a one-time effort.
- We might want to include some easily copy-pastable markup snippets for users in the absence of a good way to package up CSS-only components in mustache templates or PHP.
Overall I think this approach is pretty low-risk, and doesn't rely on any speculative or complex new technical work.
Even after a full SSR solution is available (which may take some time), I think that there will always be valid use-cases for a simple, no-build-step approach to server-rendering Codex-consistent UIs.
Acceptance criteria
- DST completes proofs of concept if necessary
- DST engineers discuss and agree on an approach
- DST gets and applies feedback from developer stakeholders
- DST agrees on the implementation path and documents the decision via an ADR