Skip to content

Commit

Permalink
refactor: extract the style functions into styled-engine package
Browse files Browse the repository at this point in the history
  • Loading branch information
juanrgm committed May 29, 2022
1 parent e74745b commit a0d7365
Show file tree
Hide file tree
Showing 25 changed files with 224 additions and 46 deletions.
8 changes: 8 additions & 0 deletions .changeset/nasty-rabbits-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@suid/css": patch
"@suid/material": patch
"@suid/styled-engine": patch
"@suid/system": patch
---

Extract the style functions into `styled-engine` package
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ With this smart target in mind, the project avoids becoming another library that
| [@suid/material](/packages/material) | ![material-npm] | A port of Material-UI (MUI) built with Solid.js. |
| [@suid/icons-material](/packages/icons-material) | ![icons-material-npm] | Material Design icons as SVG Solid.js components. |
| [@suid/codemod](/packages/codemod) | ![codemod-npm] | Tool for migrating MUI React code to SUID SolidJS. |
| [@suid/styled-engine](/packages/styled-engine) | ![styled-engine-npm] | Styled engine used by @suid/system. |
| [@suid/system](/packages/system) | ![system-npm] | Styles system used by suid packages. |
| [@suid/base](/packages/base) | ![base-npm] | Unstyled base components used by @suid/material. |
| [@suid/css](/packages/css) | ![css-npm] | CSS render in JS. |
Expand All @@ -31,6 +32,7 @@ With this smart target in mind, the project avoids becoming another library that
[material-npm]: https://img.shields.io/npm/v/@suid/material
[icons-material-npm]: https://img.shields.io/npm/v/@suid/icons-material
[codemod-npm]: https://img.shields.io/npm/v/@suid/codemod
[styled-engine-npm]: https://img.shields.io/npm/v/@suid/styled-engine
[site-npm]: https://img.shields.io/npm/v/@suid/site
[css-npm]: https://img.shields.io/npm/v/@suid/css
[system-npm]: https://img.shields.io/npm/v/@suid/system
Expand Down
14 changes: 6 additions & 8 deletions packages/css/src/dom/appendStyleElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,22 @@ import setStyleElementText from "./setStyleElementText";

function appendStyleElement(
css: string | string[],
id?: string | false
): {
element: HTMLStyleElement;
id: string | false | undefined;
} {
attributes?: Record<string, any>
) {
if (Array.isArray(css)) css = css.join("\n");
const id: string | undefined = attributes?.["id"];
const head = document.head || document.getElementsByTagName("head")[0];
const prevElement = id && document.getElementById(id);
if (prevElement && prevElement instanceof HTMLStyleElement) {
setStyleElementText(prevElement, css);
registerStyleElementUsage(prevElement);
return { element: prevElement, id };
return prevElement;
} else {
if (prevElement) prevElement.remove();
const element = createStyleElement(css, id);
const element = createStyleElement(css, attributes);
registerStyleElementUsage(element);
head.appendChild(element);
return { element, id };
return element;
}
}

Expand Down
20 changes: 18 additions & 2 deletions packages/css/src/dom/createStyleElement.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
import setStyleElementText from "./setStyleElementText";

function createStyleElement(css: string, id?: string | false) {
function setAttributes(
element: HTMLStyleElement,
attributes: Record<string, any>
) {
for (const name in attributes) {
const value = attributes[name];
if (value !== undefined) {
if (value === null) {
element.removeAttribute(name);
} else {
element.setAttribute(name, value);
}
}
}
}

function createStyleElement(css: string, attributes?: Record<string, any>) {
const element = document.createElement("style");
if (id) element.setAttribute("id", id);
element.type = "text/css";
if (attributes) setAttributes(element, attributes);
setStyleElementText(element, css);
return element;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/material/src/GlobalStyles/GlobalStyles.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { GlobalStylesTypeMap } from "./GlobalStylesProps";
import createComponentFactory from "@suid/base/createComponentFactory";
import createSxClass from "@suid/system/createSxClass";
import createStyle from "@suid/system/createStyle";

const $ = createComponentFactory<GlobalStylesTypeMap>()({
name: "MuiGlobalStyles",
Expand All @@ -18,7 +18,7 @@ const $ = createComponentFactory<GlobalStylesTypeMap>()({
* - [GlobalStyles API](https://mui.com/api/global-styles/)
*/
const GlobalStyles = $.component(function GlobalStyles({ props }) {
createSxClass(() => ({
createStyle(() => ({
"@global": props.styles || {},
}));
return <></>;
Expand Down
1 change: 1 addition & 0 deletions packages/material/src/styles/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { default as useThemeProps } from "./useThemeProps";
export { createTheme } from "./createTheme";
export { default as ThemeProvider } from "./ThemeProvider";
export type { Theme } from "./createTheme";
export { default as StyledEngineProvider } from "@suid/system/StyledEngineProvider";
21 changes: 21 additions & 0 deletions packages/styled-engine/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2022 Juanra GM <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
17 changes: 17 additions & 0 deletions packages/styled-engine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# @suid/styled-engine

![GitHub Workflow Status](https://img.shields.io/github/workflow/status/swordev/suid/CI) ![npm (scoped)](https://img.shields.io/npm/v/@suid/styled-engine?label=@suid/styled-engine)

## Installation

```sh
npm install @suid/styled-engine
```

## Documentation

https://suid.io

## License

Distributed under the MIT License. See LICENSE for more information.
20 changes: 20 additions & 0 deletions packages/styled-engine/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "@suid/styled-engine",
"version": "0.0.1",
"description": "Styled engine used by @suid/system.",
"scripts": {
"build": "tsc --build",
"clean": "tsc --build --clean",
"watch": "tsc --build -w"
},
"dependencies": {
"@suid/css": "workspace:*",
"@suid/utils": "workspace:*"
},
"peerDependencies": {
"solid-js": "^1.3.0"
},
"publishConfig": {
"directory": "lib"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createContext } from "solid-js";

export type StyledEngineContextValue = {};

const StyledEngineContext = createContext<StyledEngineContextValue>({});

export default StyledEngineContext;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import StyledEngineContext, {
StyledEngineContextValue,
} from "./StyledEngineContext";
import { JSXElement } from "solid-js";

export default function StyledEngineProvider(inProps: {
children: JSXElement;
value?: StyledEngineContextValue;
}) {
return (
<StyledEngineContext.Provider value={inProps.value || {}}>
{inProps.children}
</StyledEngineContext.Provider>
);
}
1 change: 1 addition & 0 deletions packages/styled-engine/src/StyledEngineProvider/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./StyledEngineProvider";
Original file line number Diff line number Diff line change
@@ -1,53 +1,60 @@
import mergeSxObjects from "./mergeSxObjects";
import SxProps from "./sxProps";
import StyledEngineContext from "./StyledEngineProvider/StyledEngineContext";
import mergeStyleProps from "./mergeStyleProps";
import createStyleObject, { StyleObject } from "@suid/css/createStyleObject";
import appendStyleElement from "@suid/css/dom/appendStyleElement";
import findStyleElement from "@suid/css/dom/findStyleElement";
import registerStyleElementUsage from "@suid/css/dom/registerStyleElementUsage";
import unregisterStyleElementUsage from "@suid/css/dom/unregisterStyleElementUsage";
import { createRenderEffect, createSignal, onCleanup } from "solid-js";
import {
createRenderEffect,
createSignal,
onCleanup,
useContext,
} from "solid-js";

export const resolvedPropKey = "__resolved";
const styleObjectCache = new Map<string, StyleObject>();

const cache = new Map<string, StyleObject>();
type StyleProps =
| undefined
| Record<string, any>
| (Record<string, any> | undefined)[];

function createSxClass(value: () => SxProps | undefined) {
function normalizeStyleProps(props: StyleProps) {
if (!props) return [];
return (
(Array.isArray(props) ? props : [props])
// https://github.com/microsoft/TypeScript/issues/44408
.flat(Infinity as 1)
.filter((v) => !!v) as Record<string, any>[]
);
}

function createStyle(value: () => StyleProps | undefined) {
const context = useContext(StyledEngineContext);
const [name, setName] = createSignal("");
let styleElement: HTMLStyleElement | undefined;

createRenderEffect<
{ className?: string; styleElement?: HTMLStyleElement } | undefined
>((prevResult) => {
const v = value();
const propsValue = value();
let styleObject: StyleObject | undefined;
if (v) {
const styles = (Array.isArray(v) ? v : [v])
// https://github.com/microsoft/TypeScript/issues/44408
.flat(Infinity as 1)
.filter((v) => !!v) as Record<string, any>[];

const css = styles.reduce<Record<string, any>>((result, style) => {
if ("name" in style) result[`--${style.name}`] = "0";
mergeSxObjects(result, style);
return result;
}, {});

delete css.name;

if (propsValue) {
styleObject = createStyleObject({
name: "css",
props: css,
cache,
props: mergeStyleProps(normalizeStyleProps(propsValue)),
cache: styleObjectCache,
});

styleElement = findStyleElement(styleObject.id);

if (styleElement) {
registerStyleElementUsage(styleElement);
} else {
styleElement = appendStyleElement(
styleObject.rules,
styleObject.id
).element;
styleElement = appendStyleElement(styleObject.rules, {
id: styleObject.id,
});
}
}

Expand All @@ -66,10 +73,12 @@ function createSxClass(value: () => SxProps | undefined) {
styleElement,
};
}, undefined);

onCleanup(() => {
if (styleElement) unregisterStyleElementUsage(styleElement);
});

return name;
}

export default createSxClass;
export default createStyle;
16 changes: 16 additions & 0 deletions packages/styled-engine/src/mergeStyleProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import deepmerge from "@suid/utils/deepmerge";

function mergeStyleProps(values: Record<string, any>[]) {
const result = values.reduce<Record<string, any>>((result, value) => {
if ("name" in value) result[`--${value.name}`] = "0";
deepmerge(result, value, {
clone: false,
sortKeys: true,
});
return result;
}, {});
delete result.name;
return result;
}

export default mergeStyleProps;
23 changes: 23 additions & 0 deletions packages/styled-engine/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"outDir": "lib",
"rootDir": "src",
"paths": {
"@suid/css": ["./../css/src"],
"@suid/css/*": ["./../css/src/*"],
"@suid/utils": ["./../utils/src"],
"@suid/utils/*": ["./../utils/src/*"]
}
},
"exclude": ["src/**/*.test.*"],
"extends": "./../../tsconfig.json",
"include": ["src/**/*"],
"references": [
{
"path": "./../css"
},
{
"path": "./../utils"
}
]
}
1 change: 1 addition & 0 deletions packages/system/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"dependencies": {
"@suid/css": "workspace:*",
"@suid/styled-engine": "workspace:*",
"@suid/types": "workspace:*",
"@suid/utils": "workspace:*",
"clsx": "^1.1.1",
Expand Down
10 changes: 5 additions & 5 deletions packages/system/src/Box/Box.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BoxSelfProps } from ".";
import Dynamic from "../Dynamic/Dynamic";
import createSxClass, { resolvedPropKey } from "../createSxClass";
import createStyle, { resolvedPropKey } from "../createStyle";
import defineComponent from "../defineComponent";
import resolveSxProps from "../resolveSxProps";
import extendSxProp from "../styleFunctionSx/extendSxProp";
Expand Down Expand Up @@ -35,7 +35,7 @@ export const Box = defineComponent<BoxTypeMap>(function Box(inProps) {
},
});

const sxClass = createSxClass(() => {
const style = createStyle(() => {
const theme = useInTheme();
const haveStyles = !disableSystemProps || !!props.sx;
if (!haveStyles || forwardSx()) return [];
Expand All @@ -48,9 +48,9 @@ export const Box = defineComponent<BoxTypeMap>(function Box(inProps) {

const className = () => {
const className = otherProps.className;
const sxClassValue = sxClass();
if (sxClassValue?.length) {
return className ? `${className} ${sxClassValue}` : sxClassValue;
const styleValue = style();
if (styleValue?.length) {
return className ? `${className} ${styleValue}` : styleValue;
} else {
return className;
}
Expand Down
1 change: 1 addition & 0 deletions packages/system/src/StyledEngineProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "@suid/styled-engine/StyledEngineProvider";
2 changes: 2 additions & 0 deletions packages/system/src/createStyle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from "@suid/styled-engine/createStyle";
export const resolvedPropKey = "__resolved";
2 changes: 1 addition & 1 deletion packages/system/src/resolveStyledProps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { resolvedPropKey } from "./createSxClass";
import { resolvedPropKey } from "./createStyle";
import { StyledProps } from "./styledProps";
import resolve from "@suid/css/resolve";

Expand Down
2 changes: 1 addition & 1 deletion packages/system/src/resolveSxProps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { resolvedPropKey } from "./createSxClass";
import { resolvedPropKey } from "./createStyle";
import { Theme } from "./createTheme";
import { resolveStyledPropsValue } from "./resolveStyledProps";
import { SxPropsObject } from "./sxProps";
Expand Down
Loading

0 comments on commit a0d7365

Please sign in to comment.