diff --git a/package.json b/package.json index 9ebb0d41..9b93ac54 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,6 @@ "husky": "9.1.7", "jest": "29.7.0", "metro-react-native-babel-preset": "0.77.0", - "next": "15.1.4", "nitro-codegen": "0.21.0", "react": "18.3.1", "react-native": "0.76.6", diff --git a/src/index.ts b/src/index.ts index 41fbadd2..13110408 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,3 +4,4 @@ export type { UnistylesThemes, UnistylesBreakpoints } from './global' export { withUnistyles, useUnistyles } from './core' export type { UnistylesVariants } from './types' export { Display, Hide, ScopedTheme } from './components' +export { useServerUnistyles, hydrateServerUnistyles, getServerUnistyles, resetServerUnistyles } from './server' diff --git a/src/server/getServerUnistyles.tsx b/src/server/getServerUnistyles.tsx new file mode 100644 index 00000000..a467d716 --- /dev/null +++ b/src/server/getServerUnistyles.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import { StyleSheet } from 'react-native' +import { error, isServer } from '../web/utils' +import { UnistylesWeb } from '../web' +import { DefaultServerUnistylesSettings, type ServerUnistylesSettings } from './types' + +export const getServerUnistyles = ({ includeRNWStyles = true }: ServerUnistylesSettings = DefaultServerUnistylesSettings) => { + if (!isServer()) { + throw error('Server styles should only be read on the server') + } + // @ts-ignore + const rnwStyle: string | null = includeRNWStyles ? (StyleSheet?.getSheet().textContent ?? '') : null + const css = UnistylesWeb.registry.css.getStyles() + const state = UnistylesWeb.registry.css.getState() + return <> + {rnwStyle && } + + + +} \ No newline at end of file diff --git a/src/server/hydrateServerUnistyles.ts b/src/server/hydrateServerUnistyles.ts new file mode 100644 index 00000000..dd1b9e9f --- /dev/null +++ b/src/server/hydrateServerUnistyles.ts @@ -0,0 +1,17 @@ +import { error, isServer } from '../web/utils' +import { UnistylesWeb } from '../web' + +declare global { + interface Window { + // @ts-ignore + __UNISTYLES_STATE__: ReturnType + } +} + +export const hydrateServerUnistyles = () => { + if (isServer()) { + throw error('Server styles should only be hydrated on the client') + } + UnistylesWeb.registry.css.hydrate(window.__UNISTYLES_STATE__) + document.querySelector('#unistyles-script')?.remove() +} diff --git a/src/server/index.ts b/src/server/index.ts index 3e5e5ec7..8646875b 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1 +1,5 @@ export { useServerUnistyles } from './useServerUnistyles' +export { getServerUnistyles } from './getServerUnistyles' +export { resetServerUnistyles } from './resetServerUnistyles' +export { hydrateServerUnistyles } from './hydrateServerUnistyles' + diff --git a/src/server/resetServerUnistyles.ts b/src/server/resetServerUnistyles.ts new file mode 100644 index 00000000..8d80bccc --- /dev/null +++ b/src/server/resetServerUnistyles.ts @@ -0,0 +1,9 @@ +import { error, isServer } from '../web/utils' +import { UnistylesWeb } from '../web' + +export const resetServerUnistyles = () => { + if (!isServer()) { + throw error('Server styles should only be reset on the server') + } + UnistylesWeb.registry.reset() +} \ No newline at end of file diff --git a/src/server/types.ts b/src/server/types.ts new file mode 100644 index 00000000..50bfba37 --- /dev/null +++ b/src/server/types.ts @@ -0,0 +1,4 @@ +export type ServerUnistylesSettings = { + includeRNWStyles?: boolean +} +export const DefaultServerUnistylesSettings = {} diff --git a/src/server/useServerUnistyles.tsx b/src/server/useServerUnistyles.tsx index 407b5d56..f1876f49 100644 --- a/src/server/useServerUnistyles.tsx +++ b/src/server/useServerUnistyles.tsx @@ -1,42 +1,22 @@ import React, { useRef } from 'react' -import { StyleSheet } from 'react-native' -import { useServerInsertedHTML } from 'next/navigation' -import { UnistylesWeb } from '../web' +import { isServer } from '../web/utils' +import { DefaultServerUnistylesSettings, type ServerUnistylesSettings } from './types' +import { getServerUnistyles } from './getServerUnistyles' +import { resetServerUnistyles } from './resetServerUnistyles' +import { hydrateServerUnistyles } from './hydrateServerUnistyles' -declare global { - interface Window { - // @ts-ignore - __UNISTYLES_STATE__: ReturnType - } -} - -export const useServerUnistyles = () => { +export const useServerUnistyles = (settings: ServerUnistylesSettings = DefaultServerUnistylesSettings): React.ReactNode | null => { const isServerInserted = useRef(false) - useServerInsertedHTML(() => { - if (!isServerInserted.current) { - isServerInserted.current = true - - // @ts-ignore - const rnwStyle = StyleSheet?.getSheet().textContent ?? '' - const css = UnistylesWeb.registry.css.getStyles() - const state = UnistylesWeb.registry.css.getState() - UnistylesWeb.registry.reset() - - return ( - <> - - - - - ) - } - - return null - }) + if (isServer() && !isServerInserted.current) { + isServerInserted.current = true + const components = getServerUnistyles(settings) + resetServerUnistyles() + return components + } - if (typeof window !== 'undefined') { - UnistylesWeb.registry.css.hydrate(window.__UNISTYLES_STATE__) - document.querySelector('#unistyles-script')?.remove() + if (!isServer()) { + hydrateServerUnistyles() } + return null } diff --git a/src/web/state.ts b/src/web/state.ts index 12f0f044..b2ae46f7 100644 --- a/src/web/state.ts +++ b/src/web/state.ts @@ -49,8 +49,10 @@ export class UnistylesState { return } - if (!this.hasAdaptiveThemes && this.CSSVars) { - document.querySelector(':root')?.classList.add(this.themeName ?? '') + // Ensure we have a themeName before calling this + // classList.add throws a "SyntaxError" DOMException if one of the arguments is an empty string. + if (!this.hasAdaptiveThemes && this.CSSVars && this.themeName) { + document.querySelector(':root')?.classList.add(this.themeName) } this.services.listener.initListeners() diff --git a/src/web/utils/common.ts b/src/web/utils/common.ts index ad3f557d..b55c5ae3 100644 --- a/src/web/utils/common.ts +++ b/src/web/utils/common.ts @@ -5,7 +5,7 @@ export const reduceObject = , TReducer>( export const keyInObject = >(obj: T, key: PropertyKey): key is keyof T => key in obj -export const isServer = () => typeof window === 'undefined' +export const isServer = () => typeof window === 'undefined' || typeof document === 'undefined' export const error = (message: string) => new Error(`Unistyles: ${message}`) diff --git a/src/web/utils/unistyle.ts b/src/web/utils/unistyle.ts index 7f7c630d..f5fcfb32 100644 --- a/src/web/utils/unistyle.ts +++ b/src/web/utils/unistyle.ts @@ -68,18 +68,19 @@ export const isInDocument = (element: HTMLElement) => document.body.contains(ele export const getMediaQuery = (query: string, allBreakpoints: Array) => { if (Object.values(Orientation).includes(query as Orientation)) { - return `(orientation: ${query})` + return `@media (orientation: ${query})` } if (isUnistylesMq(query)) { const { minWidth, maxWidth, minHeight, maxHeight } = parseMq(query) - return [ + const queries = [ minWidth ? `(min-width: ${minWidth}px)` : undefined, maxWidth ? `(max-width: ${maxWidth}px)` : undefined, minHeight ? `(min-height: ${minHeight}px)` : undefined, maxHeight ? `(max-height: ${maxHeight}px)` : undefined ].filter(Boolean).join(' and ') + return `@media ${queries}` } const breakpointValue = UnistylesWeb.runtime.breakpoints[query as keyof UnistylesBreakpoints] ?? 0 diff --git a/yarn.lock b/yarn.lock index 2078dc7e..6a4a9bb6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3464,69 +3464,6 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:15.1.4": - version: 15.1.4 - resolution: "@next/env@npm:15.1.4" - checksum: bb26fa2184a81d0d5265962d4f162f32d5a5a19281af475ab32e13bd700b4cf32164ca4add9fd1f849f4c970bad7a7cca25ed3414e94396472a94e2d567c4600 - languageName: node - linkType: hard - -"@next/swc-darwin-arm64@npm:15.1.4": - version: 15.1.4 - resolution: "@next/swc-darwin-arm64@npm:15.1.4" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@next/swc-darwin-x64@npm:15.1.4": - version: 15.1.4 - resolution: "@next/swc-darwin-x64@npm:15.1.4" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@next/swc-linux-arm64-gnu@npm:15.1.4": - version: 15.1.4 - resolution: "@next/swc-linux-arm64-gnu@npm:15.1.4" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@next/swc-linux-arm64-musl@npm:15.1.4": - version: 15.1.4 - resolution: "@next/swc-linux-arm64-musl@npm:15.1.4" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"@next/swc-linux-x64-gnu@npm:15.1.4": - version: 15.1.4 - resolution: "@next/swc-linux-x64-gnu@npm:15.1.4" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"@next/swc-linux-x64-musl@npm:15.1.4": - version: 15.1.4 - resolution: "@next/swc-linux-x64-musl@npm:15.1.4" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"@next/swc-win32-arm64-msvc@npm:15.1.4": - version: 15.1.4 - resolution: "@next/swc-win32-arm64-msvc@npm:15.1.4" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@next/swc-win32-x64-msvc@npm:15.1.4": - version: 15.1.4 - resolution: "@next/swc-win32-x64-msvc@npm:15.1.4" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -4720,22 +4657,6 @@ __metadata: languageName: node linkType: hard -"@swc/counter@npm:0.1.3": - version: 0.1.3 - resolution: "@swc/counter@npm:0.1.3" - checksum: df8f9cfba9904d3d60f511664c70d23bb323b3a0803ec9890f60133954173047ba9bdeabce28cd70ba89ccd3fd6c71c7b0bd58be85f611e1ffbe5d5c18616598 - languageName: node - linkType: hard - -"@swc/helpers@npm:0.5.15": - version: 0.5.15 - resolution: "@swc/helpers@npm:0.5.15" - dependencies: - tslib: ^2.8.0 - checksum: 1a9e0dbb792b2d1e0c914d69c201dbc96af3a0e6e6e8cf5a7f7d6a5d7b0e8b762915cd4447acb6b040e2ecc1ed49822875a7239f99a2d63c96c3c3407fb6fccf - languageName: node - linkType: hard - "@szmarczak/http-timer@npm:^5.0.1": version: 5.0.1 resolution: "@szmarczak/http-timer@npm:5.0.1" @@ -6190,15 +6111,6 @@ __metadata: languageName: node linkType: hard -"busboy@npm:1.6.0": - version: 1.6.0 - resolution: "busboy@npm:1.6.0" - dependencies: - streamsearch: ^1.1.0 - checksum: 32801e2c0164e12106bf236291a00795c3c4e4b709ae02132883fe8478ba2ae23743b11c5735a0aae8afe65ac4b6ca4568b91f0d9fed1fdbc32ede824a73746e - languageName: node - linkType: hard - "bytes@npm:3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" @@ -6360,7 +6272,7 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001579, caniuse-lite@npm:^1.0.30001646, caniuse-lite@npm:^1.0.30001688": +"caniuse-lite@npm:^1.0.30001646, caniuse-lite@npm:^1.0.30001688": version: 1.0.30001692 resolution: "caniuse-lite@npm:1.0.30001692" checksum: 484113e3fabbe223fff0380c25c861da265a34c3f75bb5af1f254423b43e713a3c7f0c313167df52fb203f42ea68bd0df8a9e73642becfe1e9fa5734b5fc55a5 @@ -6627,7 +6539,7 @@ __metadata: languageName: node linkType: hard -"client-only@npm:0.0.1, client-only@npm:^0.0.1": +"client-only@npm:^0.0.1": version: 0.0.1 resolution: "client-only@npm:0.0.1" checksum: 0c16bf660dadb90610553c1d8946a7fdfb81d624adea073b8440b7d795d5b5b08beb3c950c6a2cf16279365a3265158a236876d92bce16423c485c322d7dfaf8 @@ -13543,7 +13455,7 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:3.3.8, nanoid@npm:^3.3.6, nanoid@npm:^3.3.7, nanoid@npm:^3.3.8": +"nanoid@npm:3.3.8, nanoid@npm:^3.3.7, nanoid@npm:^3.3.8": version: 3.3.8 resolution: "nanoid@npm:3.3.8" bin: @@ -13617,67 +13529,6 @@ __metadata: languageName: node linkType: hard -"next@npm:15.1.4": - version: 15.1.4 - resolution: "next@npm:15.1.4" - dependencies: - "@next/env": 15.1.4 - "@next/swc-darwin-arm64": 15.1.4 - "@next/swc-darwin-x64": 15.1.4 - "@next/swc-linux-arm64-gnu": 15.1.4 - "@next/swc-linux-arm64-musl": 15.1.4 - "@next/swc-linux-x64-gnu": 15.1.4 - "@next/swc-linux-x64-musl": 15.1.4 - "@next/swc-win32-arm64-msvc": 15.1.4 - "@next/swc-win32-x64-msvc": 15.1.4 - "@swc/counter": 0.1.3 - "@swc/helpers": 0.5.15 - busboy: 1.6.0 - caniuse-lite: ^1.0.30001579 - postcss: 8.4.31 - sharp: ^0.33.5 - styled-jsx: 5.1.6 - peerDependencies: - "@opentelemetry/api": ^1.1.0 - "@playwright/test": ^1.41.2 - babel-plugin-react-compiler: "*" - react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 - react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 - sass: ^1.3.0 - dependenciesMeta: - "@next/swc-darwin-arm64": - optional: true - "@next/swc-darwin-x64": - optional: true - "@next/swc-linux-arm64-gnu": - optional: true - "@next/swc-linux-arm64-musl": - optional: true - "@next/swc-linux-x64-gnu": - optional: true - "@next/swc-linux-x64-musl": - optional: true - "@next/swc-win32-arm64-msvc": - optional: true - "@next/swc-win32-x64-msvc": - optional: true - sharp: - optional: true - peerDependenciesMeta: - "@opentelemetry/api": - optional: true - "@playwright/test": - optional: true - babel-plugin-react-compiler: - optional: true - sass: - optional: true - bin: - next: dist/bin/next - checksum: e9de936fe41bdddd50be283b6ce1f35ef434f0777026400a9119d91d0f99d0f7d7101599700de7502b0340c247691f98abea551c1738f158c89d4981d11fe1cf - languageName: node - linkType: hard - "nice-try@npm:^1.0.4": version: 1.0.5 resolution: "nice-try@npm:1.0.5" @@ -14719,17 +14570,6 @@ __metadata: languageName: node linkType: hard -"postcss@npm:8.4.31": - version: 8.4.31 - resolution: "postcss@npm:8.4.31" - dependencies: - nanoid: ^3.3.6 - picocolors: ^1.0.0 - source-map-js: ^1.0.2 - checksum: 1d8611341b073143ad90486fcdfeab49edd243377b1f51834dc4f6d028e82ce5190e4f11bb2633276864503654fb7cab28e67abdc0fbf9d1f88cad4a0ff0beea - languageName: node - linkType: hard - "postcss@npm:^7.0.2": version: 7.0.39 resolution: "postcss@npm:7.0.39" @@ -15303,7 +15143,6 @@ __metadata: husky: 9.1.7 jest: 29.7.0 metro-react-native-babel-preset: 0.77.0 - next: 15.1.4 nitro-codegen: 0.21.0 react: 18.3.1 react-native: 0.76.6 @@ -16561,7 +16400,7 @@ __metadata: languageName: node linkType: hard -"sharp@npm:0.33.5, sharp@npm:^0.33.3, sharp@npm:^0.33.5": +"sharp@npm:0.33.5, sharp@npm:^0.33.3": version: 0.33.5 resolution: "sharp@npm:0.33.5" dependencies: @@ -16813,7 +16652,7 @@ __metadata: languageName: node linkType: hard -"source-map-js@npm:^1.0.2, source-map-js@npm:^1.2.0, source-map-js@npm:^1.2.1": +"source-map-js@npm:^1.2.0, source-map-js@npm:^1.2.1": version: 1.2.1 resolution: "source-map-js@npm:1.2.1" checksum: 4eb0cd997cdf228bc253bcaff9340afeb706176e64868ecd20efbe6efea931465f43955612346d6b7318789e5265bdc419bc7669c1cebe3db0eb255f57efa76b @@ -17024,13 +16863,6 @@ __metadata: languageName: node linkType: hard -"streamsearch@npm:^1.1.0": - version: 1.1.0 - resolution: "streamsearch@npm:1.1.0" - checksum: 1cce16cea8405d7a233d32ca5e00a00169cc0e19fbc02aa839959985f267335d435c07f96e5e0edd0eadc6d39c98d5435fb5bbbdefc62c41834eadc5622ad942 - languageName: node - linkType: hard - "strict-uri-encode@npm:^2.0.0": version: 2.0.0 resolution: "strict-uri-encode@npm:2.0.0" @@ -17224,22 +17056,6 @@ __metadata: languageName: node linkType: hard -"styled-jsx@npm:5.1.6": - version: 5.1.6 - resolution: "styled-jsx@npm:5.1.6" - dependencies: - client-only: 0.0.1 - peerDependencies: - react: ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" - peerDependenciesMeta: - "@babel/core": - optional: true - babel-plugin-macros: - optional: true - checksum: 879ad68e3e81adcf4373038aaafe55f968294955593660e173fbf679204aff158c59966716a60b29af72dc88795cfb2c479b6d2c3c87b2b2d282f3e27cc66461 - languageName: node - linkType: hard - "styleq@npm:^0.1.3": version: 0.1.3 resolution: "styleq@npm:0.1.3" @@ -17571,7 +17387,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.8.0": +"tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: e4aba30e632b8c8902b47587fd13345e2827fa639e7c3121074d5ee0880723282411a8838f830b55100cbe4517672f84a2472667d355b81e8af165a55dc6203a