From a0d73652bea194187c6d9fea315ed5fc4d26a8b1 Mon Sep 17 00:00:00 2001 From: Juanra GM Date: Sun, 29 May 2022 11:58:54 +0200 Subject: [PATCH] refactor: extract the style functions into `styled-engine` package --- .changeset/nasty-rabbits-own.md | 8 +++ README.md | 2 + packages/css/src/dom/appendStyleElement.ts | 14 ++--- packages/css/src/dom/createStyleElement.ts | 20 +++++- .../src/GlobalStyles/GlobalStyles.tsx | 4 +- packages/material/src/styles/index.tsx | 1 + packages/styled-engine/LICENSE | 21 +++++++ packages/styled-engine/README.md | 17 +++++ packages/styled-engine/package.json | 20 ++++++ .../StyledEngineContext.tsx | 7 +++ .../StyledEngineProvider.tsx | 15 +++++ .../src/StyledEngineProvider/index.tsx | 1 + .../src/createStyle.ts} | 63 +++++++++++-------- packages/styled-engine/src/mergeStyleProps.ts | 16 +++++ packages/styled-engine/tsconfig.json | 23 +++++++ packages/system/package.json | 1 + packages/system/src/Box/Box.tsx | 10 +-- packages/system/src/StyledEngineProvider.ts | 1 + packages/system/src/createStyle.ts | 2 + packages/system/src/resolveStyledProps.ts | 2 +- packages/system/src/resolveSxProps.ts | 2 +- packages/system/tsconfig.json | 5 ++ pnpm-lock.yaml | 10 +++ tsconfig.build.json | 3 + tsconfig.jest.json | 2 + 25 files changed, 224 insertions(+), 46 deletions(-) create mode 100644 .changeset/nasty-rabbits-own.md create mode 100644 packages/styled-engine/LICENSE create mode 100644 packages/styled-engine/README.md create mode 100644 packages/styled-engine/package.json create mode 100644 packages/styled-engine/src/StyledEngineProvider/StyledEngineContext.tsx create mode 100644 packages/styled-engine/src/StyledEngineProvider/StyledEngineProvider.tsx create mode 100644 packages/styled-engine/src/StyledEngineProvider/index.tsx rename packages/{system/src/createSxClass.ts => styled-engine/src/createStyle.ts} (55%) create mode 100644 packages/styled-engine/src/mergeStyleProps.ts create mode 100644 packages/styled-engine/tsconfig.json create mode 100644 packages/system/src/StyledEngineProvider.ts create mode 100644 packages/system/src/createStyle.ts diff --git a/.changeset/nasty-rabbits-own.md b/.changeset/nasty-rabbits-own.md new file mode 100644 index 000000000..2466acaf8 --- /dev/null +++ b/.changeset/nasty-rabbits-own.md @@ -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 diff --git a/README.md b/README.md index 3dd938baa..df9820049 100644 --- a/README.md +++ b/README.md @@ -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. | @@ -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 diff --git a/packages/css/src/dom/appendStyleElement.ts b/packages/css/src/dom/appendStyleElement.ts index 38b8fc52e..04835770c 100644 --- a/packages/css/src/dom/appendStyleElement.ts +++ b/packages/css/src/dom/appendStyleElement.ts @@ -4,24 +4,22 @@ import setStyleElementText from "./setStyleElementText"; function appendStyleElement( css: string | string[], - id?: string | false -): { - element: HTMLStyleElement; - id: string | false | undefined; -} { + attributes?: Record +) { 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; } } diff --git a/packages/css/src/dom/createStyleElement.ts b/packages/css/src/dom/createStyleElement.ts index 40955188e..ebbd1ce94 100644 --- a/packages/css/src/dom/createStyleElement.ts +++ b/packages/css/src/dom/createStyleElement.ts @@ -1,9 +1,25 @@ import setStyleElementText from "./setStyleElementText"; -function createStyleElement(css: string, id?: string | false) { +function setAttributes( + element: HTMLStyleElement, + attributes: Record +) { + 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) { 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; } diff --git a/packages/material/src/GlobalStyles/GlobalStyles.tsx b/packages/material/src/GlobalStyles/GlobalStyles.tsx index 2b9778ae9..111e9a2fa 100644 --- a/packages/material/src/GlobalStyles/GlobalStyles.tsx +++ b/packages/material/src/GlobalStyles/GlobalStyles.tsx @@ -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()({ name: "MuiGlobalStyles", @@ -18,7 +18,7 @@ const $ = createComponentFactory()({ * - [GlobalStyles API](https://mui.com/api/global-styles/) */ const GlobalStyles = $.component(function GlobalStyles({ props }) { - createSxClass(() => ({ + createStyle(() => ({ "@global": props.styles || {}, })); return <>; diff --git a/packages/material/src/styles/index.tsx b/packages/material/src/styles/index.tsx index f129a52d7..0b9266d12 100644 --- a/packages/material/src/styles/index.tsx +++ b/packages/material/src/styles/index.tsx @@ -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"; diff --git a/packages/styled-engine/LICENSE b/packages/styled-engine/LICENSE new file mode 100644 index 000000000..ad5ae97ce --- /dev/null +++ b/packages/styled-engine/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 Juanra GM + +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. diff --git a/packages/styled-engine/README.md b/packages/styled-engine/README.md new file mode 100644 index 000000000..7d41a0478 --- /dev/null +++ b/packages/styled-engine/README.md @@ -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. diff --git a/packages/styled-engine/package.json b/packages/styled-engine/package.json new file mode 100644 index 000000000..3fc384148 --- /dev/null +++ b/packages/styled-engine/package.json @@ -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" + } +} diff --git a/packages/styled-engine/src/StyledEngineProvider/StyledEngineContext.tsx b/packages/styled-engine/src/StyledEngineProvider/StyledEngineContext.tsx new file mode 100644 index 000000000..e9aa3c330 --- /dev/null +++ b/packages/styled-engine/src/StyledEngineProvider/StyledEngineContext.tsx @@ -0,0 +1,7 @@ +import { createContext } from "solid-js"; + +export type StyledEngineContextValue = {}; + +const StyledEngineContext = createContext({}); + +export default StyledEngineContext; diff --git a/packages/styled-engine/src/StyledEngineProvider/StyledEngineProvider.tsx b/packages/styled-engine/src/StyledEngineProvider/StyledEngineProvider.tsx new file mode 100644 index 000000000..d732b85fa --- /dev/null +++ b/packages/styled-engine/src/StyledEngineProvider/StyledEngineProvider.tsx @@ -0,0 +1,15 @@ +import StyledEngineContext, { + StyledEngineContextValue, +} from "./StyledEngineContext"; +import { JSXElement } from "solid-js"; + +export default function StyledEngineProvider(inProps: { + children: JSXElement; + value?: StyledEngineContextValue; +}) { + return ( + + {inProps.children} + + ); +} diff --git a/packages/styled-engine/src/StyledEngineProvider/index.tsx b/packages/styled-engine/src/StyledEngineProvider/index.tsx new file mode 100644 index 000000000..5a8eac2af --- /dev/null +++ b/packages/styled-engine/src/StyledEngineProvider/index.tsx @@ -0,0 +1 @@ +export { default } from "./StyledEngineProvider"; diff --git a/packages/system/src/createSxClass.ts b/packages/styled-engine/src/createStyle.ts similarity index 55% rename from packages/system/src/createSxClass.ts rename to packages/styled-engine/src/createStyle.ts index 6d2d64d06..021c8b475 100644 --- a/packages/system/src/createSxClass.ts +++ b/packages/styled-engine/src/createStyle.ts @@ -1,42 +1,50 @@ -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(); -const cache = new Map(); +type StyleProps = + | undefined + | Record + | (Record | 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[] + ); +} + +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[]; - - const css = styles.reduce>((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); @@ -44,10 +52,9 @@ function createSxClass(value: () => SxProps | undefined) { if (styleElement) { registerStyleElementUsage(styleElement); } else { - styleElement = appendStyleElement( - styleObject.rules, - styleObject.id - ).element; + styleElement = appendStyleElement(styleObject.rules, { + id: styleObject.id, + }); } } @@ -66,10 +73,12 @@ function createSxClass(value: () => SxProps | undefined) { styleElement, }; }, undefined); + onCleanup(() => { if (styleElement) unregisterStyleElementUsage(styleElement); }); + return name; } -export default createSxClass; +export default createStyle; diff --git a/packages/styled-engine/src/mergeStyleProps.ts b/packages/styled-engine/src/mergeStyleProps.ts new file mode 100644 index 000000000..62e8fb994 --- /dev/null +++ b/packages/styled-engine/src/mergeStyleProps.ts @@ -0,0 +1,16 @@ +import deepmerge from "@suid/utils/deepmerge"; + +function mergeStyleProps(values: Record[]) { + const result = values.reduce>((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; diff --git a/packages/styled-engine/tsconfig.json b/packages/styled-engine/tsconfig.json new file mode 100644 index 000000000..c571b60d2 --- /dev/null +++ b/packages/styled-engine/tsconfig.json @@ -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" + } + ] +} diff --git a/packages/system/package.json b/packages/system/package.json index 37accb261..ddc703190 100644 --- a/packages/system/package.json +++ b/packages/system/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@suid/css": "workspace:*", + "@suid/styled-engine": "workspace:*", "@suid/types": "workspace:*", "@suid/utils": "workspace:*", "clsx": "^1.1.1", diff --git a/packages/system/src/Box/Box.tsx b/packages/system/src/Box/Box.tsx index 36b146cbd..9734b1837 100644 --- a/packages/system/src/Box/Box.tsx +++ b/packages/system/src/Box/Box.tsx @@ -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"; @@ -35,7 +35,7 @@ export const Box = defineComponent(function Box(inProps) { }, }); - const sxClass = createSxClass(() => { + const style = createStyle(() => { const theme = useInTheme(); const haveStyles = !disableSystemProps || !!props.sx; if (!haveStyles || forwardSx()) return []; @@ -48,9 +48,9 @@ export const Box = defineComponent(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; } diff --git a/packages/system/src/StyledEngineProvider.ts b/packages/system/src/StyledEngineProvider.ts new file mode 100644 index 000000000..dd285fcf6 --- /dev/null +++ b/packages/system/src/StyledEngineProvider.ts @@ -0,0 +1 @@ +export { default } from "@suid/styled-engine/StyledEngineProvider"; diff --git a/packages/system/src/createStyle.ts b/packages/system/src/createStyle.ts new file mode 100644 index 000000000..a4265fdff --- /dev/null +++ b/packages/system/src/createStyle.ts @@ -0,0 +1,2 @@ +export { default } from "@suid/styled-engine/createStyle"; +export const resolvedPropKey = "__resolved"; diff --git a/packages/system/src/resolveStyledProps.ts b/packages/system/src/resolveStyledProps.ts index e524aaf2c..081ba06e8 100644 --- a/packages/system/src/resolveStyledProps.ts +++ b/packages/system/src/resolveStyledProps.ts @@ -1,4 +1,4 @@ -import { resolvedPropKey } from "./createSxClass"; +import { resolvedPropKey } from "./createStyle"; import { StyledProps } from "./styledProps"; import resolve from "@suid/css/resolve"; diff --git a/packages/system/src/resolveSxProps.ts b/packages/system/src/resolveSxProps.ts index 631c4657c..0dc04387b 100644 --- a/packages/system/src/resolveSxProps.ts +++ b/packages/system/src/resolveSxProps.ts @@ -1,4 +1,4 @@ -import { resolvedPropKey } from "./createSxClass"; +import { resolvedPropKey } from "./createStyle"; import { Theme } from "./createTheme"; import { resolveStyledPropsValue } from "./resolveStyledProps"; import { SxPropsObject } from "./sxProps"; diff --git a/packages/system/tsconfig.json b/packages/system/tsconfig.json index 05fbcbdfa..956bad226 100644 --- a/packages/system/tsconfig.json +++ b/packages/system/tsconfig.json @@ -5,6 +5,8 @@ "paths": { "@suid/css": ["./../css/src"], "@suid/css/*": ["./../css/src/*"], + "@suid/styled-engine": ["./../styled-engine/src"], + "@suid/styled-engine/*": ["./../styled-engine/src/*"], "@suid/types": ["./../types/src"], "@suid/types/*": ["./../types/src/*"], "@suid/utils": ["./../utils/src"], @@ -18,6 +20,9 @@ { "path": "./../css" }, + { + "path": "./../styled-engine" + }, { "path": "./../types" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5b89c7da3..e0f6b5c9e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -177,15 +177,25 @@ importers: vite: 2.9.9_sass@1.51.0 vite-plugin-solid: 2.2.6_sass@1.51.0 + packages/styled-engine: + specifiers: + '@suid/css': workspace:* + '@suid/utils': workspace:* + dependencies: + '@suid/css': link:../css + '@suid/utils': link:../utils + packages/system: specifiers: '@suid/css': workspace:* + '@suid/styled-engine': workspace:* '@suid/types': workspace:* '@suid/utils': workspace:* clsx: ^1.1.1 csstype: ^3.0.11 dependencies: '@suid/css': link:../css + '@suid/styled-engine': link:../styled-engine '@suid/types': link:../types '@suid/utils': link:../utils clsx: 1.1.1 diff --git a/tsconfig.build.json b/tsconfig.build.json index 52fd156d2..4ce7c676e 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -16,6 +16,9 @@ { "path": "packages/material" }, + { + "path": "packages/styled-engine" + }, { "path": "packages/system" }, diff --git a/tsconfig.jest.json b/tsconfig.jest.json index 6a2e5dc79..3506f6729 100644 --- a/tsconfig.jest.json +++ b/tsconfig.jest.json @@ -11,6 +11,8 @@ "@suid/icons-material/*": ["./packages/icons-material/lib/*"], "@suid/material": ["./packages/material/src"], "@suid/material/*": ["./packages/material/src/*"], + "@suid/styled-engine": ["./packages/styled-engine/src"], + "@suid/styled-engine/*": ["./packages/styled-engine/src/*"], "@suid/system": ["./packages/system/src"], "@suid/system/*": ["./packages/system/src/*"], "@suid/types": ["./packages/types/src"],