ssr-emotion-react 1.0.0-beta.5 → 1.0.1

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/README.md CHANGED
@@ -2,10 +2,6 @@
2
2
 
3
3
  A natural and powerful Zero-Runtime CSS-in-JS solution et ses React 🍅
4
4
 
5
- ## LIVE DEMO with Astro
6
-
7
- 準備中...
8
-
9
5
  ## Installation
10
6
 
11
7
  ### 🚀 Astro
@@ -39,8 +35,9 @@ Now, you can use not only Astro components (`.astro`) but also React JSX compone
39
35
 
40
36
  > **Note:** React JSX components in Astro Islands
41
37
  >
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.
38
+ > * **No directive (SSR 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.)
39
+ >
40
+ > * `client:only="react"` **(CSR Only)**: As you know, this is the standard mode where Emotion is used, and this plugin does nothing. It skips server-side rendering and runs entirely in the browser.
44
41
  >
45
42
  > * `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
43
 
@@ -52,24 +49,24 @@ This plugin wasn't originally built for React. It was first conceived as a core
52
49
  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
50
 
54
51
 
55
- ## 💎 The Result
52
+ ## 💎 SSR Only
53
+
54
+ 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" (SSR Only)** mode.
56
55
 
57
56
  * **Zero Runtime by default:** No `Emotion` library is shipped to the browser. It delivers a pure Zero-JS experience.
58
57
  * **Familiar DX:** Use the full expressive power of the [Emotion `css()` function](https://emotion.sh/docs/@emotion/css) that you already know.
59
58
  * **Decoupled Asset Delivery:** Styles are moved to separate `.css` files to allow for flexible cache strategies. By using unique filenames (cache busting), we ensure that updates are immediately reflected even when long-term caching is enabled on the server.
60
- * **Hydration Stability:** No overhead for style re-calculation. Interactive Islands remain stable and fully dynamic without visual flickering during the hydration process.
61
-
62
-
63
- ## 🛠 How it looks
64
59
 
65
- 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.
60
+ ### Example: SSR Only Styling
66
61
 
67
62
  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"**).
68
63
 
69
64
  ```jsx
65
+ // src/components/StaticBox.jsx
66
+
70
67
  import { css } from '@emotion/css'
71
68
 
72
- export const MyComponent = () => (
69
+ export default props => (
73
70
  <div class={flexCol({
74
71
  color: 'hotpink',
75
72
  '&:hover': { color: 'deeppink' }
@@ -85,10 +82,27 @@ const flexCol = (...args) => css({
85
82
 
86
83
  ```
87
84
 
85
+ ```astro
86
+ ---
87
+ // src/pages/index.astro
88
+
89
+ import Layout from '../layouts/Layout.astro'
90
+ import StaticBox from '../components/StaticBox'
91
+ ---
92
+
93
+ <Layout>
94
+ <StaticBox />
95
+ </Layout>
96
+
97
+ ```
98
+
99
+ ## 🌗 SSR Hydration (SSR + CSR)
88
100
 
89
- ## 🌗 Hybrid Styling (SSR + CSR)
101
+ In Astro, Island components (`client:load` and others) get the best of both worlds.
90
102
 
91
- In Astro, Island components (`client:*`) get the best of both worlds.
103
+ * **Hydration Stability:** No overhead for style re-calculation. Interactive Islands remain stable and fully dynamic without visual flickering during the hydration process.
104
+ * **Unlimited Flexibility:** Need to change colors based on user input or mouse position? Just pass the props/state to css() like you always do.
105
+ * **Zero Learning Curve:** If you know how to use useEffect and Emotion, you already know how to build dynamic Islands with React.
92
106
 
93
107
  ### How it works
94
108
 
@@ -107,12 +121,12 @@ You can easily change styles when the component "wakes up" in the browser:
107
121
  ```jsx
108
122
  // src/components/InteractiveBox.jsx
109
123
 
110
- export const InteractiveBox = () => {
111
- const [isLoaded, setIsLoaded] = useState(false);
124
+ export default props => {
125
+ const [isLoaded, setIsLoaded] = useState(false)
112
126
 
113
127
  useEffect(() => {
114
- setIsLoaded(true); // Triggers once JS is active
115
- }, []);
128
+ setIsLoaded(true) // Triggers once JS is active
129
+ }, [])
116
130
 
117
131
  return (
118
132
  <div class={css({
@@ -123,8 +137,8 @@ export const InteractiveBox = () => {
123
137
  })}>
124
138
  {isLoaded ? 'I am Interactive!' : 'I am Static HTML'}
125
139
  </div>
126
- );
127
- };
140
+ )
141
+ }
128
142
 
129
143
  ```
130
144
 
@@ -132,9 +146,9 @@ export const InteractiveBox = () => {
132
146
  ---
133
147
  // src/pages/index.astro
134
148
 
135
- import Layout from '../layouts/Layout.astro';
136
- import StaticBox from '../components/StaticBox';
137
- import InteractiveBox from '../components/InteractiveBox';
149
+ import Layout from '../layouts/Layout.astro'
150
+ import StaticBox from '../components/StaticBox'
151
+ import InteractiveBox from '../components/InteractiveBox'
138
152
  ---
139
153
 
140
154
  <Layout>
@@ -144,13 +158,6 @@ import InteractiveBox from '../components/InteractiveBox';
144
158
 
145
159
  ```
146
160
 
147
- ### Key Benefits
148
-
149
- * **No FOUC (Flash of Unstyled Content):** Since the "Server Side" style is already in the static CSS, there's no flickering.
150
- * **Unlimited Flexibility:** Need to change colors based on user input or mouse position? Just pass the props/state to css() like you always do.
151
- * **Zero Learning Curve:** If you know how to use useEffect and Emotion, you already know how to build dynamic Islands with React.
152
-
153
-
154
161
  ## 🛠 The Patterns
155
162
 
156
163
  We refer to reusable CSS logic as "The Patterns".
@@ -176,7 +183,9 @@ const linkOverlay = (...args) => css({
176
183
  },
177
184
  }, ...args)
178
185
 
179
- export default props => (
186
+ :
187
+
188
+ const MyComponent = props => (
180
189
  // The parent must have position: relative
181
190
  <div class={css({ position: 'relative', border: '1px solid #ccc', padding: '1rem' })}>
182
191
  <img src="https://via.placeholder.com/150" alt="placeholder" />
@@ -202,11 +211,13 @@ const isBP = value => value in BP
202
211
  const _gt = bp => `(min-width: ${isBP(bp) ? BP[bp] : bp})`
203
212
  const _lt = bp => `(max-width: ${isBP(bp) ? BP[bp] : bp})`
204
213
 
205
- const gt = (bp, ...args) => css({[`@media ${_gt(bp)}`]: args})
206
- const lt = (bp, ...args) => css({[`@media ${_lt(bp)}`]: args})
207
- const bw = (min, max, ...args) => css({[`@media ${_gt(min)} and ${_lt(max)}`]: args})
214
+ const gt = (bp, ...args) => css({[`@media ${_gt(bp)}`]: css(...args)})
215
+ const lt = (bp, ...args) => css({[`@media ${_lt(bp)}`]: css(...args)})
216
+ const bw = (min, max, ...args) => css({[`@media ${_gt(min)} and ${_lt(max)}`]: css(...args)})
208
217
 
209
- export default props => (
218
+ :
219
+
220
+ const MyComponent = props => (
210
221
  <div class={css(
211
222
  { color: 'black' }, // default css
212
223
  bw('sm', '75rem', { color: 'blue' }), // between
@@ -234,7 +245,7 @@ export const styled = (Tag) => (style, ...values) => props => {
234
245
  const makeClassName = (style, ...values) =>
235
246
  typeof style == 'function' ? makeClassName(style(props)) : css(style, ...values);
236
247
 
237
- const {sx, className, 'class': _class, children, ...wosx} = props;
248
+ const {as: As, sx, className, 'class': _class, children, ...wosx} = props;
238
249
 
239
250
  // cleanup transient props
240
251
  Object.keys(wosx).forEach(key => {
@@ -246,7 +257,8 @@ export const styled = (Tag) => (style, ...values) => props => {
246
257
  className: cx(makeClassName(style, ...values), makeClassName(sx), _class, className),
247
258
  };
248
259
 
249
- return (<Tag {...newProps}>{children}</Tag>);
260
+ const T = As || Tag;
261
+ return (<T {...newProps}>{children}</T>);
250
262
  };
251
263
 
252
264
  ```
@@ -255,26 +267,23 @@ What is the sx prop? For those unfamiliar with libraries like [MUI, the sx prop]
255
267
 
256
268
  In this implementation, you can pass raw style objects to the sx prop without wrapping them in `css()` or "The Patterns" functions.
257
269
 
258
- However, since the underlying tag of a button is not always `<button>`, and because using something like `createElement()` locally is generally not recommended, I personally prefer the approach shown below. In any case, how you choose to implement this is entirely up to you.
270
+ However, defining a `styled` component inside a render function is **a pitfall** because it creates a new component identity every time, forcing React to re-mount.
271
+ I personally prefer the approach shown below. In any case, how you choose to implement this is entirely up to you.
259
272
 
260
273
  ```js
261
- // the-sx-prop.js
274
+ // the-sx-prop.jsx
262
275
 
263
276
  import {css, cx} from '@emotion/css'
264
277
 
265
- const sx = (props, ...styles) => {
266
- const result = typeof props === 'object' ? {...props} : {};
267
- const _css = [];
268
- for (let arg of styles) {
269
- if (typeof arg === 'function') {
270
- _css.push(arg(result));
271
- } else if (arg) {
272
- _css.push(arg);
273
- }
278
+ export const sx = (props, style, ...values) => {
279
+ let result = (props && typeof props === 'object' ? props : {});
280
+ if (typeof style === 'function') {
281
+ result = {...style(result), ...result};
282
+ result.className = cx(css(result?.$css, ...values), result.className);
283
+ } else {
284
+ result.className = cx(css(style, ...values), result.className);
274
285
  }
275
286
 
276
- result.className = cx(css(_css), result.className);
277
-
278
287
  // cleanup transient props
279
288
  Object.keys(result).forEach(key => {
280
289
  if (key.startsWith('$')) delete result[key];
@@ -283,10 +292,16 @@ const sx = (props, ...styles) => {
283
292
  return result;
284
293
  }
285
294
 
286
- export default sx;
295
+ // Factory for component-scoped sx functions (adds `.css()` automatically)
296
+ sx._factory = (genCSS) => {
297
+ const f = (props, ...styles) => sx(props || {}, genCSS, ...styles);
298
+ f.css = (...styles) => f({}, ...styles); // style only
299
+ // f.curry = (props) => (...values) => f(props || {}, ...values); // currying
300
+ return f;
301
+ }
287
302
 
288
303
  // My button style
289
- sx.button = (props, ...styles) => sx(props, ...styles, props => {
304
+ sx.button = sx._factory(props => {
290
305
  const style = {
291
306
  // default is text button
292
307
  padding: '8px 16px',
@@ -305,7 +320,7 @@ sx.button = (props, ...styles) => sx(props, ...styles, props => {
305
320
  style.boxShadow = 'var(--style-shadows-level1)';
306
321
  }
307
322
 
308
- return [
323
+ return {$css: [
309
324
  css`
310
325
  line-height: 1;
311
326
  display: inline-flex;
@@ -316,26 +331,80 @@ sx.button = (props, ...styles) => sx(props, ...styles, props => {
316
331
  }
317
332
  `,
318
333
  style,
319
- ];
334
+ ]};
320
335
  });
321
336
 
322
337
  ```
323
338
 
324
339
  ```jsx
325
- import sx from './the-sx-prop'
340
+ import {sx} from './the-sx-prop'
326
341
 
327
342
  export default props => {
328
343
  return (<>
329
344
  <button {...sx.button({}, {margin: '1rem'})}>Buuton1</button>
345
+ <button {...sx.button.css({margin: '1rem'})}>Buuton1</button>
330
346
  <button {...sx.button({$elevated: true}, {margin: '1rem'})}>Button2</button>
331
347
  <div {...sx.button({$elevated: true}, {margin: '1rem'})}>Button3</div>
332
- <button disabled {...sx.button({}, {margin: '1rem'})}>Button4</button>
348
+ <button disabled {...sx.button.css({margin: '1rem'})}>Button4</button>
333
349
  <button {...sx.button({disabled: true}, {margin: '1rem'})}>Button5</button>
334
350
  </>);
335
351
  }
336
352
 
337
353
  ```
338
354
 
355
+ Furthermore, by creating a component like the one below, you evolve into a **Super Saiyan** (for those who aren't familiar, it's like a classic Superman).
356
+
357
+ ```js
358
+ // the-sx-prop.jsx
359
+
360
+ :
361
+ :
362
+
363
+ export const As = ({as: Tag = 'div', children, ...props}) => <Tag {...props}>{children}</Tag>;
364
+
365
+ // My list style
366
+ sx.ul = sx._factory(props => ({
367
+ as: 'ul',
368
+ $css: css`
369
+ & > li {
370
+ position: relative;
371
+ padding-left: 1.5rem;
372
+
373
+ &:before {
374
+ content: "\\2022";
375
+ position: absolute;
376
+ width: 1.5rem;
377
+ left: 0; top: 0;
378
+ text-align: center;
379
+ }
380
+ & + li, & > ul {
381
+ margin-top: .25rem;
382
+ }
383
+ }
384
+ & > ul {
385
+ margin-left: 1rem;
386
+ }
387
+ `}));
388
+
389
+ ```
390
+
391
+ ```jsx
392
+ import {sx, As} from './the-sx-prop'
393
+
394
+ const MyComponent = props => {
395
+ return (
396
+ <As {...sx.ul.css({ padding: '20px', border: '1px solid #ccc' })}>
397
+ <li>Is it Flexible?</li>
398
+ <li>Is it Dynamic?</li>
399
+ <li>Is it Polymorphic?</li>
400
+ <li>No, it's <strong>As</strong>!</li>
401
+ </As>
402
+ )
403
+ }
404
+
405
+ ```
406
+
407
+
339
408
  ## 🚫 Won't Do
340
409
 
341
410
  The following features are not planned for the core roadmap (though contributors are welcome to explore them):