Skip to content

Commit

Permalink
Refactor <Style> component for react >=19
Browse files Browse the repository at this point in the history
uses the new <style> element special rendering behavior and props introduced in react v19 to handle style de-duplication + SSR + render behavior (e.g. suspense while loading):
• https://react.dev/reference/react-dom/components/style#propshttps://react.dev/reference/react-dom/components/style#special-rendering-behavior
  • Loading branch information
acusti committed Sep 1, 2024
1 parent 2c779b3 commit 966d865
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 300 deletions.
4 changes: 2 additions & 2 deletions packages/styling/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"vitest": "^1.1.0"
},
"peerDependencies": {
"react": "^16.8 || ^17 || ^18 || ^19.0.0-0",
"react-dom": "^16.8 || ^17 || ^18 || ^19.0.0-0"
"react": "^19.0.0-0",
"react-dom": "^19.0.0-0"
}
}
68 changes: 14 additions & 54 deletions packages/styling/src/Style.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,25 @@
import * as React from 'react';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React from 'react';

import {
getRegisteredStyles,
registerStyles,
unregisterStyles,
updateStyles,
} from './style-registry.js';

const { useCallback, useEffect, useMemo, useRef, useState } = React;
import { useStyles } from './useStyles.js';

type Props = {
children: string;
precedence?: string;
};

const Style = ({ children }: Props) => {
const Style = ({ children, precedence = 'medium' }: Props) => {
// Minify CSS styles by replacing consecutive whitespace (including \n) with ' '
const styles = useMemo(() => children.replace(/\s+/gm, ' '), [children]);
const [ownerDocument, setOwnerDocument] = useState<Document | null>(null);
const isMountedRef = useRef<boolean>(false);

useEffect(() => {
isMountedRef.current = true;
unregisterStyles({ ownerDocument: 'global', styles });
}, []); // eslint-disable-line react-hooks/exhaustive-deps

useEffect(
() => () => {
if (!ownerDocument) return;
unregisterStyles({ ownerDocument, styles });
},
[ownerDocument], // eslint-disable-line react-hooks/exhaustive-deps
const styles = useStyles(children);
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/canary.d.ts
// https://react.dev/reference/react-dom/components/style#props
return (
// @ts-expect-error @types/react is missing new <style> props
// eslint-disable-next-line react/no-unknown-property
<style href={styles} precedence={precedence}>
{styles}
</style>
);

const previousStylesRef = useRef<string>('');

useEffect(() => {
if (!ownerDocument) return;

updateStyles({
ownerDocument,
previousStyles: previousStylesRef.current,
styles,
});

previousStylesRef.current = styles;
}, [ownerDocument, styles]);

const handleRef = useCallback((element: HTMLElement | null) => {
if (!element) return;
setOwnerDocument(element.ownerDocument);
}, []);

if (ownerDocument) return null;

// Avoid duplicate style rendering during SSR via style registry
if (!isMountedRef.current) {
if (getRegisteredStyles({ ownerDocument: 'global', styles })) return null;
registerStyles({ ownerDocument: 'global', styles });
}

return <style dangerouslySetInnerHTML={{ __html: styles }} ref={handleRef} />;
};

export default Style;
1 change: 1 addition & 0 deletions packages/styling/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// <reference types="react/canary" />
export { default as Style } from './Style.js';

export const SYSTEM_UI_FONT =
Expand Down
15 changes: 15 additions & 0 deletions packages/styling/src/minifyStyles.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { describe, expect, it } from 'vitest';

import { minifyStyles } from './minifyStyles.js';

describe('@acusti/styling', () => {
describe('minifyStyles.ts', () => {
it("replaces consecutive whitespace (including \\n) with ' '", () => {
expect(
minifyStyles(`.foo {
color: red;
}`),
).toBe('.foo { color: red; }');
});
});
});
8 changes: 8 additions & 0 deletions packages/styling/src/minifyStyles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// TODO use techniques from https://github.com/jbleuzen/node-cssmin/blob/master/cssmin.js
// (check https://github.com/jbleuzen/node-cssmin/pull/19/files also)
export function minifyStyles(styles: string) {
// Minify CSS styles by replacing consecutive whitespace (including \n) with ' '
return styles.replace(/\s+/gm, ' ');
}

export default minifyStyles;
129 changes: 0 additions & 129 deletions packages/styling/src/style-registry.test.ts

This file was deleted.

115 changes: 0 additions & 115 deletions packages/styling/src/style-registry.ts

This file was deleted.

Loading

0 comments on commit 966d865

Please sign in to comment.