From 6341427906741439e1448bc4179c30a55f1504d5 Mon Sep 17 00:00:00 2001 From: Oleg Isonen Date: Mon, 20 Apr 2020 15:44:04 +0200 Subject: [PATCH 1/5] first version of CSSOM RFC --- text/0000-cssom.md | 105 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 text/0000-cssom.md diff --git a/text/0000-cssom.md b/text/0000-cssom.md new file mode 100644 index 00000000..97f74088 --- /dev/null +++ b/text/0000-cssom.md @@ -0,0 +1,105 @@ +- Start Date: 2020-04-20 +- RFC PR: +- React Issue: + +# Summary + +I know it's a long-lasting and controversial topic and probably not the highest priority for the team right now, but let me try to create the first **simplified** vision for a CSS implementation we all could agree on. + +CSS-in-JS like React is on a dual mission, supporting both complex apps and small(ish) sites. This has created a big divide in the community because of the very different needs that these types of projects have. Like React, CSS-in-JS was born out of the need to reduce the complexity of maintaining large applications. + +While there are many options for authoring CSS, it is super tiresome to convince a team to use a particular approach. It creates a big divide and lots of frustration. While there is a huge value in not having a built-in solution and giving the space to 3rd party libs to thrive and experiment, the friction and frustration for not having a good built-in approach that covers 90% of use cases is real. + +## Motivation + +### Goals + +- High-performance rendering of static styles via CSSOM. +- CSS Rules can be reused across components. +- Components have no additional overhead for styling. +- Lists - inline styles have no way to be reused across list elements, where typically styles are repeated. +- Support the full CSS spec. Inline styles are limited to [CSSStyleDeclaration](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration) and we need to support the full spec on the web. +- Adhere to React's component model and the upcoming Suspense architecture. + +### Non-goals + +- Enforcing CSS-in-JS over vanilla CSS or css-modules (you can still use className) +- Implementing every possible idea CSS-in-JS libs came up with + +# Detailed design + +### Tagged templates + +- familiar syntax +- syntax highlighting (already supported) +- linting +- ability to return a data structure that React can identify, not just a string +- interpolations + +```js +import { css } from "react-cssom"; +const buttonStyle = css` + color: red; +`; // {css: '.css-0 { color: red; }'} + +render(); +``` + +### Using style prop for both inline and CSSOM styles + +Since we can return from the tag function any data structure we want, we can identify styles created using our `css` function and we have no problem accepting both types of styles. + +Using the `style` prop allows us to be flexible in the future about how React wants to optimize and treat this interface. + +That being said using a new prop like `css` is also an option: + +```js +render(); +``` + +### Babel plugin + +Babel plugin can be optional and can enable advanced features like: + +- vendor prefixing +- optimizations +- other preprocessing options e.g. using PostCSS + +After babel plugin the call into `css` tag function can be removed and the result of the above example can be compiled to: + +```js +const buttonStyle = { css: ".css-0 { color: red; }" }; +render(); +``` + +# Drawbacks + +- we are adding a new area of responsibility to React, which will require more work to maintain it, but hopefully can be parallelized +- it inevitably will raise the discussions about how React is enforcing CSS-in-JS, so we will have to prepare a good answer +- there are several of unresolved features listed below, which will have to be addressed over time + +# Alternatives + +This is a very simple initial version, so it's not as feature-rich as some other CSS-in-JS solutions, but it has a potential to cover more use cases later. + +It is not a replacement for react-native approach. + +It is not a replacement for any entirely different approaches like SwiftUI. + +# Adoption strategy + +It is not a breaking change. + +# How we teach this + +It is a primitive interface for passing CSS to React to integrate with component's lifecycle. It can be also a way for babel plugins and compile-time processing of CSS for React components. + +# Unresolved questions + +- Unique class names generation algorithm +- Composition +- Overrides +- Dynamic or state-based styling +- Theming +- Alternative object-based syntax +- Compatibility with react-native From f1ea8b613e93def2dc343f9327027274c69ab3b3 Mon Sep 17 00:00:00 2001 From: Oleg Isonen Date: Tue, 21 Apr 2020 11:29:26 +0200 Subject: [PATCH 2/5] compile target --- text/0000-cssom.md | 87 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 4 deletions(-) diff --git a/text/0000-cssom.md b/text/0000-cssom.md index 97f74088..ce689d76 100644 --- a/text/0000-cssom.md +++ b/text/0000-cssom.md @@ -14,6 +14,7 @@ While there are many options for authoring CSS, it is super tiresome to convince ### Goals +- CSS scoping (unique selector) - High-performance rendering of static styles via CSSOM. - CSS Rules can be reused across components. - Components have no additional overhead for styling. @@ -57,12 +58,71 @@ That being said using a new prop like `css` is also an option: render(); ``` -### Babel plugin +### A new primitive -Babel plugin can be optional and can enable advanced features like: +Once style or css prop accepts an object that contains CSS, we are essentially creating a new primitive. This primitive can be passed arround, reused, overriden and React will know what to do with it. This opens a number of new doors in the future. + +### Compile target + +This new primitive can be targeted by compilers (babel plugins and co.). In the simplest scenario it will work without any compilation steps. With compilation it would be possible to compile for example CSS modules into something that React can take and render. + +Example of CSS modules being rendered by the new primitive: + +```js +import styles from "./button.css"; +render(); +``` + +What happens right now with CSS loader: + +```js +// button.js +import styles from "./button.css"; +render(); +``` + +Compiles `button.css` to: + +```js +// button.css +const createStyleNode = (id, content) => { + const style = document.createElement("style"); + style.id = id; + style.textContent = content; + document.head.appendChild(style); +}; + +createStyleNode( + "/src/button.css:-css", + ".button-some-suffix {\n color: red;\n}" +); +``` + +Instead, it could compile to something like this: + +```js +// button.css +export default { + button: { + css: ".button-some-suffix {\n color: red;\n}", + id: "/src/button.css:-css", + }, +}; +``` + +and + +```js +// button.js +import styles from "./button.css"; +render(); +``` + +Optionally many other compiler-based things can be enabled: - vendor prefixing -- optimizations +- optimizations for compression +- linting - other preprocessing options e.g. using PostCSS After babel plugin the call into `css` tag function can be removed and the result of the above example can be compiled to: @@ -72,6 +132,25 @@ const buttonStyle = { css: ".css-0 { color: red; }" }; render(); ``` +### Object based styles syntax + +It is still possible to use object based syntax like in [JSS](https://github.com/cssinjs/jss/blob/master/docs/jss-syntax.md) or [React Native](https://reactnative.dev/docs/stylesheet). It is possible because in the end it's still going to be a CSS string which can be passed to React: + +```js +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, +}); // {container: {css: ".r-d23pfw {flex: 1; justify-content: center; align-items: center;"}} +render( + + Hi + +); +``` + # Drawbacks - we are adding a new area of responsibility to React, which will require more work to maintain it, but hopefully can be parallelized @@ -96,7 +175,7 @@ It is a primitive interface for passing CSS to React to integrate with component # Unresolved questions -- Unique class names generation algorithm +- Specific algorithm for unique class names generation - Composition - Overrides - Dynamic or state-based styling From 7fc5bd61835ae693bbece9c19fcf5c2b766e6d08 Mon Sep 17 00:00:00 2001 From: Oleg Isonen Date: Tue, 21 Apr 2020 12:29:45 +0200 Subject: [PATCH 3/5] add className property to the primitive and html results --- text/0000-cssom.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/text/0000-cssom.md b/text/0000-cssom.md index ce689d76..820729ca 100644 --- a/text/0000-cssom.md +++ b/text/0000-cssom.md @@ -41,11 +41,22 @@ While there are many options for authoring CSS, it is super tiresome to convince import { css } from "react-cssom"; const buttonStyle = css` color: red; -`; // {css: '.css-0 { color: red; }'} +`; // { css: ".css-0 { color: red; }", className: "css-0" }; render(); ``` +Renders to: + +```html + + +``` + ### Using style prop for both inline and CSSOM styles Since we can return from the tag function any data structure we want, we can identify styles created using our `css` function and we have no problem accepting both types of styles. @@ -106,6 +117,7 @@ export default { button: { css: ".button-some-suffix {\n color: red;\n}", id: "/src/button.css:-css", + className: "button-some-suffix", }, }; ``` @@ -118,6 +130,15 @@ import styles from "./button.css"; render(); ``` +and render to: + +```html + + +``` + Optionally many other compiler-based things can be enabled: - vendor prefixing @@ -143,7 +164,7 @@ const styles = StyleSheet.create({ justifyContent: "center", alignItems: "center", }, -}); // {container: {css: ".r-d23pfw {flex: 1; justify-content: center; align-items: center;"}} +}); // {container: {css: ".r-d23pfw {flex: 1; justify-content: center; align-items: center;", className: "r-d23pfw"}} render( Hi From e90160751019ec88dd3ec1c6c58106e273d877fa Mon Sep 17 00:00:00 2001 From: Oleg Isonen Date: Tue, 21 Apr 2020 13:46:38 +0200 Subject: [PATCH 4/5] add Unique identification of the primitive to unresolved questiotns --- text/0000-cssom.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/0000-cssom.md b/text/0000-cssom.md index 820729ca..63bcacc3 100644 --- a/text/0000-cssom.md +++ b/text/0000-cssom.md @@ -196,6 +196,7 @@ It is a primitive interface for passing CSS to React to integrate with component # Unresolved questions +- Unique identification of the primitive e.g. `{'@@css': string}` or `{[React.cssSymbol]: string}` - Specific algorithm for unique class names generation - Composition - Overrides From 94af0ceb358ae94a5a4eb9fcde3234115574a055 Mon Sep 17 00:00:00 2001 From: Oleg Isonen Date: Tue, 21 Apr 2020 13:48:13 +0200 Subject: [PATCH 5/5] add Publishing of components including CSS to a registy like NPM to the goals --- text/0000-cssom.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/0000-cssom.md b/text/0000-cssom.md index 63bcacc3..af42f177 100644 --- a/text/0000-cssom.md +++ b/text/0000-cssom.md @@ -21,6 +21,7 @@ While there are many options for authoring CSS, it is super tiresome to convince - Lists - inline styles have no way to be reused across list elements, where typically styles are repeated. - Support the full CSS spec. Inline styles are limited to [CSSStyleDeclaration](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration) and we need to support the full spec on the web. - Adhere to React's component model and the upcoming Suspense architecture. +- Publishing of components including CSS to a registy like NPM ### Non-goals