ssr-emotion-react 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 uniho
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,266 @@
1
+ # SSR [Emotion](https://emotion.sh/docs/@emotion/css) React
2
+
3
+ A natural and powerful Zero-Runtime CSS-in-JS solution et ses React 🍅
4
+
5
+ ## LIVE DEMO with Astro
6
+
7
+ 準備中...
8
+
9
+ ## Installation
10
+
11
+ ### 🚀 Astro
12
+
13
+ Create your new app.
14
+
15
+ ``` bash
16
+ npm create astro@latest my-app
17
+ cd my-app
18
+ ```
19
+
20
+ Add `ssr-emotion-react` as a dependency.
21
+
22
+ ``` bash
23
+ npm install ssr-emotion-react
24
+
25
+ ```
26
+
27
+ Add the integration in `astro.config.mjs`.
28
+
29
+ ```js
30
+ import { defineConfig } from 'astro/config';
31
+ import ssrEmotion from 'ssr-emotion-react/astro';
32
+
33
+ export default defineConfig({
34
+ integrations: [ssrEmotion()],
35
+ });
36
+ ```
37
+
38
+ Now, you can use not only Astro components (`.astro`) but also React JSX components (`.jsx` or `.tsx`) with SSR Emotion.
39
+
40
+ > **Note:** React JSX components in Astro Islands
41
+ >
42
+ > * **No directive (Server Only)**: Rendered as just static HTML tags. It results in zero client-side JavaScript. (I used to think there wasn't much point in writing static content in JSX components instead of just using Astro components. It seemed like standard Astro components was more than enough. **However, I've realized one major advantage:** SSR Emotion — the ultimate SSR Zero-Runtime CSS-in-JS solution, seamlessly integrated with Astro. By using React JSX components, your styles are automatically extracted into static CSS during the build process. This means you can enjoy the full power of CSS-in-JS while still shipping zero bytes of JS to the browser. In this regard, it's a significant upgrade over standard Astro components.)
43
+ > * `client:only="react"` **(Client Only)**: This is the mode where the relationship between React and Astro is the clearest. It skips server-side rendering and runs entirely in the browser.
44
+ >
45
+ > * `client:load`(and others like `client:visible` or `client:idle`) **(SSR Hydration)**: Despite its cool and flashy name, "SSR Hydration" is not that complicated: it just creates a static HTML skeleton first, and once the JS is ready, the engine takes over the DOM as if it had been there from the start. If you are particular about the visual transition—like ensuring there is no layout shift by pre-setting an image's height—you might want to take control to make the swap feel completely natural.
46
+
47
+
48
+ ## 📜 The Origin: Born from [Potate](https://github.com/uniho/potate)
49
+
50
+ This plugin wasn't originally built for React. It was first conceived as a core pillar of the [Potate](https://github.com/uniho/potate) engine—a custom JSX runtime designed for ultimate simplicity and performance.
51
+
52
+ While developing [Potate](https://github.com/uniho/potate), I discovered a way to handle SSR [Emotion](https://emotion.sh/docs/@emotion/css) and Hydration that felt more "correct" for the Astro era: **The Full DOM Replacement strategy.** It worked so flawlessly in Potate that I decided to bring this lineage back to the "ancestor," React. By applying Potate's philosophy to React, we've eliminated the historical complexities of Emotion SSR and the fragility of standard React hydration.
53
+
54
+
55
+ ## 💎 The Result
56
+
57
+ * **Zero Runtime by default:** No `Emotion` library is shipped to the browser. It delivers a pure Zero-JS experience.
58
+ * **Familiar DX:** Use the full expressive power of the [Emotion `css()` function](https://emotion.sh/docs/@emotion/css) that you already know.
59
+ * **No Hydration Mismatch:** By adopting the Potate-born "Full DOM Replacement" strategy, it physically eliminates the dreaded React hydration errors.
60
+ * **Static by Default:** Styles are automatically extracted into static CSS during the Astro build process.
61
+ * **Performance:** No hydration overhead for styles and no Flash of Unstyled Content (FOUC).
62
+
63
+
64
+ ## 🛠 How it looks
65
+
66
+ In SSR Emotion, you don't need to learn any special properties or complex setups. It just works with the [Emotion `css()` function](https://emotion.sh/docs/@emotion/css). It feels completely natural, even in Astro's **"No directive" (Server Only)** mode.
67
+
68
+ While you can use [`css()`](https://emotion.sh/docs/@emotion/css) directly, you can also create reusable functions like `flexCol()` (which we call **"The Patterns"**).
69
+
70
+ ```jsx
71
+ import { css } from '@emotion/css'
72
+
73
+ export const MyComponent = () => (
74
+ <div class={flexCol({
75
+ color: 'hotpink',
76
+ '&:hover': { color: 'deeppink' }
77
+ })}>
78
+ Hello, SSR EMOTION!
79
+ </div>
80
+ )
81
+
82
+ const flexCol = (...args) => css({
83
+ display: 'flex',
84
+ flexDirection: 'column',
85
+ }, ...args)
86
+
87
+ ```
88
+
89
+
90
+ ## 🌗 Hybrid Styling (SSR + CSR)
91
+
92
+ In Astro, Island components (`client:*`) get the best of both worlds.
93
+
94
+ ### How it works
95
+
96
+ 1. At Build Time (SSR): SSR Emotion executes your `css()` calls and extracts the initial styles into a static CSS file. This ensures your component looks perfect even before JavaScript loads.
97
+
98
+ 2. At Runtime (Hydration): Once the Island hydrates in the browser, the Emotion runtime takes over.
99
+
100
+ ### Why this is powerful
101
+
102
+ Because the Emotion runtime remains active inside Islands, you can use standard React/Preact patterns to handle dynamic styles without any special APIs.
103
+
104
+ ### Example: Hydration-aware Styling
105
+
106
+ You can easily change styles when the component "wakes up" in the browser:
107
+
108
+ ```jsx
109
+ // src/components/InteractiveBox.jsx
110
+
111
+ export const InteractiveBox = () => {
112
+ const [isLoaded, setIsLoaded] = useState(false);
113
+
114
+ useEffect(() => {
115
+ setIsLoaded(true); // Triggers once JS is active
116
+ }, []);
117
+
118
+ return (
119
+ <div class={css({
120
+ // Red on server (SEO/LCP friendly), Blue once interactive!
121
+ background: isLoaded ? 'blue' : 'red',
122
+ transition: 'background 0.5s',
123
+ padding: '20px'
124
+ })}>
125
+ {isLoaded ? 'I am Interactive!' : 'I am Static HTML'}
126
+ </div>
127
+ );
128
+ };
129
+
130
+ ```
131
+
132
+ ```astro
133
+ ---
134
+ // src/pages/index.astro
135
+
136
+ import Layout from '../layouts/Layout.astro';
137
+ import StaticBox from '../components/StaticBox';
138
+ import InteractiveBox from '../components/InteractiveBox';
139
+ ---
140
+
141
+ <Layout>
142
+ <StaticBox />
143
+ <InteractiveBox client:load />
144
+ </Layout>
145
+
146
+ ```
147
+
148
+ ### Key Benefits
149
+
150
+ * **No FOUC (Flash of Unstyled Content):** Since the "Server Side" style is already in the static CSS, there's no flickering.
151
+ * **Unlimited Flexibility:** Need to change colors based on user input or mouse position? Just pass the props/state to css() like you always do.
152
+ * **Zero Learning Curve:** If you know how to use useEffect and Emotion, you already know how to build dynamic Islands with React.
153
+
154
+
155
+ ## 🛠 The Patterns
156
+
157
+ We refer to reusable CSS logic as "The Patterns".
158
+
159
+ Honestly? They’re just standard JavaScript functions that return styles. No complex registration, no hidden magic. You just write a function, and that's it. Simple, right? 🤤
160
+
161
+ ### LinkOverlay
162
+
163
+ You can easily implement the LinkOverlay pattern. This expands a link's clickable area to its nearest parent with `position: relative`.
164
+
165
+ ```jsx
166
+ import { css } from '@emotion/css'
167
+
168
+ const linkOverlay = (...args) => css({
169
+ '&::before': {
170
+ content: '""',
171
+ position: 'absolute',
172
+ top: 0,
173
+ left: 0,
174
+ width: '100%',
175
+ height: '100%',
176
+ zIndex: 0,
177
+ },
178
+ }, ...args)
179
+
180
+ export default props => (
181
+ // The parent must have position: relative
182
+ <div class={css({ position: 'relative', border: '1px solid #ccc', padding: '1rem' })}>
183
+ <img src="https://via.placeholder.com/150" alt="placeholder" />
184
+ <h3>Card Title</h3>
185
+ <a href="/details" class={linkOverlay()}>
186
+ View more
187
+ </a>
188
+ </div>
189
+ )
190
+
191
+ ```
192
+
193
+ ### Media Query
194
+
195
+ ```jsx
196
+ import { css } from '@emotion/css';
197
+
198
+ const BP = {
199
+ sm: '640px', md: '768px', lg: '1024px', xl: '1280px', '2xl': '1536px',
200
+ }
201
+
202
+ const isBP = value => value in BP
203
+ const _gt = bp => `(min-width: ${isBP(bp) ? BP[bp] : bp})`
204
+ const _lt = bp => `(max-width: ${isBP(bp) ? BP[bp] : bp})`
205
+
206
+ const gt = (bp, ...args) => css({[`@media ${_gt(bp)}`]: args})
207
+ const lt = (bp, ...args) => css({[`@media ${_lt(bp)}`]: args})
208
+ const bw = (min, max, ...args) => css({[`@media ${_gt(min)} and ${_lt(max)}`]: args})
209
+
210
+ export default props => (
211
+ <div class={css(
212
+ { color: 'black' }, // default css
213
+ bw('sm', '75rem', { color: 'blue' }), // between
214
+ gt('75rem', { color: 'red' }), // greater than
215
+ )}>
216
+ Responsive Design!
217
+ </div>
218
+ );
219
+
220
+ ```
221
+
222
+
223
+ ## 🛠 Advanced
224
+
225
+ ### Styled Components (MUI-like)
226
+
227
+ If you prefer the Styled Components pattern (popularized by libraries like MUI or styled-components), `Emotion` makes it incredibly easy to implement.
228
+
229
+ Even with this minimal custom (but powerful) function, the result remains the same: Zero-Runtime CSS. All styles are pre-calculated during SSR and extracted into static CSS files.
230
+
231
+ ```jsx
232
+ import {css, keyframes, injectGlobal, cx} from '@emotion/css'
233
+
234
+ export const styled = (Tag, options) => (style, ...values) => props => {
235
+ const makeClassName = (style, ...values) =>
236
+ typeof style == 'function' ? makeClassName(style(props)) : css(style, ...values);
237
+
238
+ const {sx, 'class': _class, children, ...wosx} = props;
239
+
240
+ Object.keys(wosx).forEach(key => {
241
+ if (options && options.shouldForwardProp && !options.shouldForwardProp(key)) {
242
+ delete wosx[key];
243
+ }
244
+ });
245
+
246
+ const newProps = {
247
+ ...wosx,
248
+ 'class': cx(makeClassName(style, ...values), makeClassName(sx), _class),
249
+ };
250
+
251
+ return (<Tag {...newProps}>{children}</Tag>);
252
+ };
253
+
254
+ ```
255
+
256
+ > **Note:** What is the sx prop? For those unfamiliar with libraries like [MUI, the sx prop](https://mui.com/system/getting-started/the-sx-prop/) is a popular pattern that allows you to apply "one-off" styles directly to a component.
257
+ >
258
+ > In this implementation, you can pass raw style objects to the sx prop without wrapping them in `css()` or "The Patterns" functions (whether that's actually convenient or not is another story 🤤).
259
+
260
+
261
+ ## 🚫 Won't Do
262
+
263
+ The following features are not planned for the core roadmap (though contributors are welcome to explore them):
264
+
265
+ * React Server Components (RSC)
266
+ * Async SSR
@@ -0,0 +1 @@
1
+ import l from"react";import{createRoot as m}from"react-dom/client";import{flushSync as n}from"react-dom";var f=r=>(t,o,a)=>{let e=document.createElement("div"),c=m(e);n(()=>{c.render(l.createElement(t,o))}),queueMicrotask(()=>{e.childNodes.length>0&&r.replaceChildren(...Array.from(e.childNodes))})};export{f as default};
@@ -0,0 +1,2 @@
1
+ import { createRequire } from 'module';const require = createRequire(import.meta.url);
2
+ import s from"@vitejs/plugin-react";function i(){return{name:"ssr-emotion-react",hooks:{"astro:config:setup":({addRenderer:t,updateConfig:r,config:o})=>{t({name:"ssr-emotion-react",serverEntrypoint:"ssr-emotion-react/astro/render",clientEntrypoint:"ssr-emotion-react/astro/client"});let n=o.vite?.plugins?.some(e=>e&&(e.name==="vite:react-babel"||e.name==="vite:react-jsx"));r({vite:{plugins:n?[]:[s()],ssr:{external:["@emotion/css","@emotion/server"]}}})}}}}export{i as default};
@@ -0,0 +1,2 @@
1
+ import { createRequire } from 'module';const require = createRequire(import.meta.url);
2
+ import n from"react";import{renderToString as s}from"react-dom/server";import{extractCritical as i}from"@emotion/server";var u={name:"ssr-emotion",check(t){return typeof t=="function"||t&&t.$$typeof},async renderToStaticMarkup(t,a,e){let c=e?.default?n.createElement("div",{style:{display:"contents"},dangerouslySetInnerHTML:{__html:e.default}}):null,r=s(n.createElement(t,a,c)),{ids:l,css:o}=i(r);return{html:`${`<style data-emotion="css ${l.join(" ")}">${o}</style>`}${r}`}}};export{u as default};
package/dist/type.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type SSREmotion from 'ssr-emotion-react';
2
+ declare function ssrEmotion(): SSREmotion;
3
+ export default ssrEmotion;
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "ssr-emotion-react",
3
+ "version": "1.0.0-beta.0",
4
+ "description": "A natural and powerful Zero-Runtime CSS-in-JS solution for React",
5
+ "author": "uniho",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "keywords": [
9
+ "ssr",
10
+ "emotion",
11
+ "astro",
12
+ "css-in-js",
13
+ "react"
14
+ ],
15
+ "files": [
16
+ "dist/",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "exports": {
21
+ "./astro": {
22
+ "types": "./dist/type.d.ts",
23
+ "import": "./dist/astro-integration.js"
24
+ },
25
+ "./astro/render": {
26
+ "types": "./dist/type.d.ts",
27
+ "import": "./dist/astro-render.js"
28
+ },
29
+ "./astro/client": {
30
+ "types": "./dist/type.d.ts",
31
+ "import": "./dist/astro-client.js"
32
+ }
33
+ },
34
+ "scripts": {
35
+ "build": "node ./scripts/cleanup.mjs && node ./scripts/build.mjs",
36
+ "prepublishOnly": "npm run build"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/uniho/ssr-emotion-react.git"
41
+ },
42
+ "bugs": {
43
+ "url": "https://github.com/uniho/ssr-emotion-react/issues"
44
+ },
45
+ "dependencies": {
46
+ "@emotion/css": "^11.13.5",
47
+ "@emotion/server": "^11.11.0",
48
+ "@vitejs/plugin-react": "^5.1.2"
49
+ },
50
+ "devDependencies": {
51
+ "esbuild": "^0.27.2",
52
+ "react": "^19.2.3",
53
+ "react-dom": "^19.2.3"
54
+ },
55
+ "peerDependencies": {
56
+ "astro": ">=5.0.0",
57
+ "react": ">=18.0.0",
58
+ "react-dom": ">=18.0.0"
59
+ }
60
+ }