|
1 |
| -# CSS Blocks JSX Analyzer |
| 1 | +# CSS Blocks JSX Analyzer / Rewriter |
2 | 2 |
|
3 |
| -// TODO: Write README |
| 3 | +CSS Blocks' JSX integrations is inspired by CSS Modules to provide an API that is in-line with the expectations of the React and JSX communities. |
4 | 4 |
|
5 |
| -NOTE: To run locally, this module currently requires the latest version of `linkedin/css-blocks` and `epicmiller/babylon` checked out locally, built, and `npm link`ed with this project. |
| 5 | +## Syntax |
6 | 6 |
|
7 |
| -## API Straw Man |
| 7 | +Blocks may be imported to any JSX file like any other asset. |
8 | 8 |
|
9 |
| -### Version 3 |
10 |
| -```css |
11 |
| -{! bar.block.css } |
| 9 | +```jsx |
| 10 | +import styles from "my-block.css"; |
| 11 | +``` |
| 12 | + |
| 13 | +### Scopes, Classes, and States |
| 14 | +Block files have a single default export that is the Block itself. Classes are exposed as properties on this object, and states are exposed as methods. The default import itself represents the `:scope` selector and may be applied like any other class. |
12 | 15 |
|
13 |
| -{! .bar } |
14 |
| -:scope { |
15 |
| - display: none; |
16 |
| - background-color: red; |
17 |
| -} |
| 16 | +```jsx |
| 17 | +import styles from "my-block.css"; |
18 | 18 |
|
19 |
| -{! .bar--open } |
20 |
| -[state|open] { |
21 |
| - display: block; |
22 |
| -} |
| 19 | +// References the `:scope` selector. |
| 20 | +<div className={styles} />; |
23 | 21 |
|
24 |
| -{! .bar__pretty } |
25 |
| -.pretty { |
26 |
| - border: 1px solid red; |
27 |
| -} |
| 22 | +// References the class `.myClass` from the imported block. |
| 23 | +<div className={styles.myClass} />; |
28 | 24 |
|
29 |
| -{! .bar__pretty.bar__pretty--color-pink } |
30 |
| -.pretty[state|color=pink] { |
31 |
| - background-color: pink; |
32 |
| -} |
| 25 | +// References the state `.myClass[state|rootState]` from the imported block. |
| 26 | +<div className={styles.rootState()} />; |
33 | 27 |
|
34 |
| -{! .bar__pretty.bar__pretty--color-blue } |
35 |
| -.pretty[state|color=blue] { |
36 |
| - background-color: blue; |
37 |
| -} |
| 28 | +// References the state `.myClass[state|classState]` from the imported block. |
| 29 | +<div className={styles.myClass.classState()} />; |
38 | 30 |
|
39 | 31 | ```
|
40 | 32 |
|
41 |
| -```javascript |
| 33 | +### Sub-States |
| 34 | +To reference sub-states on a state, pass the sub-state value as the first (and only) argument. If a variable is seen to be passed to a state, the rewriter will add an import for the CSS Blocks runtime and be sure to preserve all possible runtime behaviors. |
42 | 35 |
|
43 |
| -import bar from "bar.block.css"; |
44 | 36 |
|
45 |
| -function render() { |
| 37 | +```jsx |
| 38 | +import styles from "my-block.css"; |
46 | 39 |
|
47 |
| - let style = objstr({ |
48 |
| - [bar]: expr, |
49 |
| - 'my-active-class': state.isActive |
50 |
| - }); |
| 40 | +// References the sub-state `.myClass[state|rootState="foo"]` from the imported block. |
| 41 | +<div className={styles.rootState("foo")} />; |
51 | 42 |
|
52 |
| - return ( |
53 |
| - <div class={style} bar:open={state.open}> |
54 |
| - <div class={bar.pretty} bar.pretty:color={state.color}></div> |
55 |
| - </div> |
56 |
| - ); |
57 |
| -} |
| 43 | +// References the sub-state `.myClass[state|classState="bar"]` from the imported block. |
| 44 | +let tmp = "bar" |
| 45 | +<div className={styles.myClass.classState(bar)} />; |
58 | 46 |
|
59 | 47 | ```
|
60 |
| -Transforms to: |
61 |
| - |
62 |
| -```javascript |
63 |
| - |
64 |
| -function render() { |
65 |
| - |
66 |
| - let s1 = state.open; |
67 |
| - let c1 = objstr({ |
68 |
| - 'my-active-class': state.isActive, |
69 |
| - [objstr({ |
70 |
| - 'bar': true, |
71 |
| - 'bar--open': state.open, |
72 |
| - })]: expr |
73 |
| - }); |
74 |
| - let o1 = { |
75 |
| - bar: { |
76 |
| - open: true |
77 |
| - } |
78 |
| - }; |
79 |
| - |
80 |
| - let s2 = state.color; |
81 |
| - let c2 = objstr({ |
82 |
| - [objstr({ |
83 |
| - 'bar__pretty': true, |
84 |
| - 'bar__pretty--color-blue': s2 === 'blue', |
85 |
| - 'bar__pretty--color-pink': s2 === 'pink' |
86 |
| - })]: true |
87 |
| - }); |
88 |
| - let o2 = { |
89 |
| - bar: { |
90 |
| - pretty: { |
91 |
| - color: s2 |
92 |
| - } |
93 |
| - } |
94 |
| - }; |
95 |
| - |
96 |
| - return ( |
97 |
| - <div class={c1} block={o1}> |
98 |
| - <div class={c2} block={o2}></div> |
99 |
| - </div> |
100 |
| - ); |
101 |
| -} |
102 | 48 |
|
103 |
| -``` |
| 49 | +### Composition |
| 50 | + |
| 51 | +Multiple blocks may be imported into a single JSX file and be applied to a single element. To combine styles, use the [`obj-str`](https://www.npmjs.com/package/obj-str) package. Logic passed to obj-str is preserved in the rewrite. |
104 | 52 |
|
105 |
| -### Version 2 |
106 |
| - |
107 |
| -```javascript |
108 |
| -import * as style from 'css-blocks-api'; |
109 |
| -import grid, { |
110 |
| - states as gridStates, |
111 |
| - classes as gridClasses |
112 |
| -} from 'styles/grid.block.css'; |
113 |
| -import * as nav from 'ui/navigation/navigation.block.css'; |
114 |
| - |
115 |
| -// ... |
116 |
| - |
117 |
| -const activeTabClass = objstr({ |
118 |
| - [nav]: true, |
119 |
| - [gridClasses.foo]: true, |
120 |
| - [gridClasses.bar]: true, |
121 |
| - [gridStates.color.blue]: this.color === 'blue', |
122 |
| - [gridStates.color.red]: this.color === 'red', |
123 |
| - |
124 |
| - nav: { |
125 |
| - 'navigation-item': true, |
126 |
| - 'state:active': true, |
127 |
| - 'state:mode': style.select({ |
128 |
| - open: isOpen(), |
129 |
| - minimized: isMinimized(), |
130 |
| - closed: true, |
131 |
| - }), |
132 |
| - }, |
| 53 | +```jsx |
| 54 | +import objstr from "obj-str"; |
| 55 | +import styles from "my-block.css"; |
| 56 | +import typography from "typography.css"; |
| 57 | + |
| 58 | +// Apply `my-block:scope` and `typography.small` |
| 59 | +let styleOne = objstr({ |
| 60 | + [styles]: true, |
| 61 | + [typography.small]: true |
133 | 62 | });
|
| 63 | +<div className={styleOne} />; |
134 | 64 |
|
135 |
| -return ( |
136 |
| - <nav class="nav:scope"> |
137 |
| - <a href="/feed" class="nav.logo"> |
138 |
| - <icons.Logo /> |
139 |
| - </a> |
140 |
| - <ul role="navigation" class="nav.list"> |
141 |
| - <li class="nav.list-item"> |
142 |
| - <a href="/feed" class={activeTabClass}> |
143 |
| - <icons.Feed style={navIcon} /> |
144 |
| - <span>Home</span> |
145 |
| - </a> |
146 |
| - </li> |
147 |
| - <li class="nav.list-item"> |
148 |
| - <BuddyLink to="/mynetwork/" class={style("nav.navigation-item")}> |
149 |
| - <icons.MyNetwork style="navIcon" /> |
150 |
| - <span>My Network</span> |
151 |
| - </BuddyLink> |
152 |
| - </li> |
153 |
| - </nav> |
154 |
| -); |
155 |
| - |
156 |
| -// ... |
| 65 | +// Apply `my-block:scope` and `my-blocks[state|enabled]` |
| 66 | +let styleOne = objstr({ |
| 67 | + [styles]: true, |
| 68 | + [styles.enabled()]: isEnabled |
| 69 | +}); |
| 70 | +<div className={styleOne} />; |
157 | 71 |
|
158 | 72 | ```
|
159 | 73 |
|
160 |
| -### Version 1 |
| 74 | +### Restrictions |
161 | 75 |
|
162 |
| -```javascript |
163 |
| -import * as style from 'css-blocks-api'; |
164 |
| -import grid 'styles/grid.block.css'; |
165 |
| -import nav from 'ui/navigation/navigation.block.css'; |
| 76 | + 1. Block references may not be used outside of the `className` property (or `class` for Preact), or an `obj-str` call. |
| 77 | + 2. If a dynamic value is passed to a state "method", then we can not determine through static analysis which sub-states are used by the program, so all possible sub-states will be included in the final CSS output. When possible, pass state "methods" a string literal. |
166 | 78 |
|
167 |
| -// ... |
| 79 | +## Integration |
168 | 80 |
|
169 |
| -const style = objstr({ |
170 |
| - 'nav.my-class': true, |
171 |
| - 'state:nav.my-class.a-state': true, |
172 |
| - 'grid.span-6': true |
173 |
| -}); |
| 81 | +### Analyzer |
174 | 82 |
|
175 |
| -return ( |
176 |
| - <nav class="nav:scope"> |
177 |
| - <div class={style}></div> |
178 |
| - <div class="grid.span-6 state:nav.some-state"></div> |
179 |
| - </nav> |
180 |
| -); |
| 83 | +The JSX Analyzer extends the main CSS Blocks core Analyzer. Its constructor accepts a unique name, to help with debugging, and an options hash: |
181 | 84 |
|
182 |
| -// ... |
| 85 | +```js |
| 86 | +import { Analyzer } from "@css-blocks/jsx"; |
| 87 | +let analyzer = new Analyzer("unique-name", options); |
| 88 | +``` |
| 89 | + |
| 90 | +Possible options are: |
| 91 | +| Option | Default | Description | |
| 92 | +|:--|:--|:--| |
| 93 | +| **baseDir** | `process.cwd()` | The root directory from which all sources are relative. | |
| 94 | +| **parserOptions** | [Defaults](https://github.com/linkedin/css-blocks/blob/b5ad979/packages/@css-blocks/jsx/src/options.ts#L7) | Options for the Babel parser used to generate the JSX AST. | |
| 95 | +| **aliases** | `{}` | Resolution aliases used for Block files. If no file is found at the exact path specified, the Block importer will attempt to resolve using these path aliases. | |
| 96 | +| **compilationOptions** | {} | Provide custom compilation options to [@css-blocks/core](../core#options). | |
| 97 | + |
| 98 | +The Analyzer may be passed to a build integration. For JSX, this will typically be [Webpack](../webpack); |
183 | 99 |
|
| 100 | +### Rewriter |
| 101 | + |
| 102 | +The JSX Rewriter is a Babel plugin that rewrites all JSX files that consume CSS Blocks according to data collected by the Analyzer. This Babel plugin will be passed directly to Babel through its `plugins` option, and will typically look something like this. |
| 103 | + |
| 104 | +```js |
| 105 | +plugins: [ |
| 106 | + require("@css-blocks/jsx/dist/src/transformer/babel").makePlugin({ rewriter }), |
| 107 | +], |
184 | 108 | ```
|
| 109 | + |
| 110 | +The `makePlugin` method creates a new instance of the babel plugin with the shared-memory object `rewriter` in scope. The build integration will have some method of populating this `rewriter` shared-memory object with Analyzer data. Please refer to your selected build integration for details. |
0 commit comments