diff --git a/examples/next-example/src/pages/_app.tsx b/examples/next-example/src/pages/_app.tsx
index 9bd986e..1f398a9 100644
--- a/examples/next-example/src/pages/_app.tsx
+++ b/examples/next-example/src/pages/_app.tsx
@@ -3,12 +3,15 @@
// Licensed under the MIT license. See LICENSE file in the project root for details.
//
+import { StyleRegistryProvider } from "@yoshiki/react/src/registry";
import type { AppProps } from "next/app";
-import { useLayoutEffect, useState } from "react";
-export default function App({ Component, pageProps }: AppProps) {
- const [show, showApp] = useState(false);
+const App = ({ Component, pageProps }: AppProps) => {
+ return (
+
+
+
+ );
+};
- useLayoutEffect(() => showApp(true));
- return show ? : null;
-}
+export default App;
diff --git a/examples/next-example/src/pages/_document.tsx b/examples/next-example/src/pages/_document.tsx
new file mode 100644
index 0000000..d8f41fb
--- /dev/null
+++ b/examples/next-example/src/pages/_document.tsx
@@ -0,0 +1,40 @@
+//
+// Copyright (c) Zoe Roux and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for details.
+//
+
+import {
+ StyleRegistryProvider,
+ createStyleRegistry,
+ useStyleRegistry,
+} from "@yoshiki/react/src/registry";
+import Document, { DocumentContext } from "next/document";
+
+Document.getInitialProps = async (ctx: DocumentContext) => {
+ const renderPage = ctx.renderPage;
+ const registry = createStyleRegistry();
+
+ ctx.renderPage = () =>
+ renderPage({
+ enhanceApp: (App) => (props) => {
+ return (
+
+
+
+ );
+ },
+ });
+
+ const props = await ctx.defaultGetInitialProps(ctx);
+ return {
+ ...props,
+ styles: (
+ <>
+ {props.styles}
+ {registry.flushToComponent()}
+ >
+ ),
+ };
+};
+
+export default Document;
diff --git a/package.json b/package.json
index 3d947a1..00129d1 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,8 @@
"**/react-dom",
"**/react-dom/**",
"**/react-native",
- "**/react-native/**"
+ "**/react-native/**",
+ "**/typescript/**"
]
},
"prettier": {
diff --git a/packages/core/package.json b/packages/core/package.json
index 2b0af27..dd25faa 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -8,6 +8,7 @@
},
"devDependencies": {
"@types/react": "^18.0.24",
+ "react": "^18.2.0",
"typescript": "^4.8.4"
}
}
diff --git a/packages/react/package.json b/packages/react/package.json
index f2ff3a7..5c27c24 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -8,6 +8,8 @@
},
"devDependencies": {
"@types/react": "^18.0.24",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
"typescript": "^4.8.4"
},
"peerDependencies": {
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index a65ec1d..4f2a0a9 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -6,6 +6,7 @@
import { Properties } from "csstype";
import { Theme, YoshikiStyle, useTheme, breakpoints, isBreakpoints } from "@yoshiki/core";
import { CSSProperties, useInsertionEffect } from "react";
+import { useStyleRegistry } from "./registry";
// TODO: shorthands
export type CssObject = {
@@ -50,10 +51,11 @@ const dedupProperties = (...classes: (string | undefined)[]) => {
export const useYoshiki = () => {
const theme = useTheme();
- let classes: string[] = [];
+ const registry = useStyleRegistry();
+
useInsertionEffect(() => {
- document.head.insertAdjacentHTML("beforeend", ``);
- }, [classes]);
+ registry.flushToBrowser();
+ }, [registry]);
return {
css: (
@@ -72,7 +74,7 @@ export const useYoshiki = () => {
},
[[], []],
);
- classes = classes.concat(localStyle);
+ registry.addRules(localClassNames, localStyle);
return {
className: dedupProperties(...localClassNames, className),
style: style,
diff --git a/packages/react/src/registry.tsx b/packages/react/src/registry.tsx
new file mode 100644
index 0000000..1375091
--- /dev/null
+++ b/packages/react/src/registry.tsx
@@ -0,0 +1,96 @@
+//
+// Copyright (c) Zoe Roux and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for details.
+//
+
+import React, { createContext, ReactNode, useContext } from "react";
+
+class StyleRegistry {
+ private completed: string[] = [];
+ private rules: [string, string][] = [];
+ private styleElement: HTMLStyleElement | null = null;
+
+ constructor(isDefault?: true) {
+ if (isDefault) {
+ console.warn(
+ "Warning: Yoshiki was used without a top level StyleRegistry. SSR won't be supported.",
+ );
+ }
+ }
+
+ addRule(key: string, rule: string) {
+ this.rules.push([key, rule]);
+ }
+
+ addRules(keys: string[], rules: string[]) {
+ // I'm sad that sequence is not a thing...
+ for (let i = 0; i < keys.length; i++) {
+ this.rules.push([keys[i], rules[i]]);
+ }
+ }
+
+ flush(): string[] {
+ const ret = this.rules.filter(([key]) => !this.completed.includes(key));
+ console.log(ret);
+ this.rules = [];
+ this.completed.push(...ret.map(([key]) => key));
+ return ret.map(([, value]) => value);
+ }
+
+ flushToBrowser() {
+ if (!this.styleElement) {
+ const styles = document.querySelectorAll("style[data-yoshiki]");
+ for (const style of styles) {
+ this.completed.push(...(style.dataset.yoshiki ?? " ").split(" "));
+ if (!this.styleElement) {
+ this.styleElement = style;
+ style.dataset.yoshiki = "";
+ } else {
+ this.styleElement.textContent = [this.styleElement.textContent, style.textContent].join(
+ "\n",
+ );
+ style.remove();
+ }
+ }
+ }
+
+ const toFlush = this.flush();
+ if (!toFlush.length) return;
+
+ if (!this.styleElement) {
+ document.head.insertAdjacentHTML(
+ "beforeend",
+ ``,
+ );
+ } else {
+ this.styleElement.textContent = [this.styleElement.textContent, ...toFlush].join("\n");
+ }
+ }
+
+ flushToComponent() {
+ const toFlush = this.flush();
+ if (!toFlush.length) return null;
+ return ;
+ }
+}
+
+const RegistryContext = createContext(null);
+
+export const StyleRegistryProvider = ({
+ registry,
+ children,
+}: {
+ registry?: StyleRegistry;
+ children: ReactNode;
+}) => {
+ if (!registry && typeof window === "undefined") return children;
+ return (
+
+ {children}
+
+ );
+};
+
+export const useStyleRegistry = () => useContext(RegistryContext) || new StyleRegistry(true);
+
+export const createStyleRegistry = () => new StyleRegistry();
diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json
index e8f3a4b..aedf3ae 100755
--- a/packages/react/tsconfig.json
+++ b/packages/react/tsconfig.json
@@ -1,6 +1,6 @@
{
"compilerOptions": {
- "target": "es5",
+ "target": "es6",
"lib": ["dom", "dom.iterable", "esnext"],
"declaration": true,
"sourceMap": true,
diff --git a/yarn.lock b/yarn.lock
index 7296f80..57f8ff1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9509,7 +9509,7 @@ react-dom@18.0.0:
loose-envify "^1.1.0"
scheduler "^0.21.0"
-react-dom@18.2.0:
+react-dom@18.2.0, react-dom@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
@@ -9632,7 +9632,7 @@ react@18.0.0:
dependencies:
loose-envify "^1.1.0"
-react@18.2.0:
+react@18.2.0, react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==