|
1 | | -# postcss-nested-once |
| 1 | +# PostCSS Nested for rollup-plugin-styles |
| 2 | + |
| 3 | +### Summary |
| 4 | + |
| 5 | +This plugin allows using Sass-like nested rules in combination with [css-modules](https://github.com/css-modules/css-modules) by [rollup-plugin-styles](https://github.com/Anidetrix/rollup-plugin-styles). |
| 6 | + |
| 7 | +Specifically, it solves the problem of the ampersand-combined selectors, i.e.: |
| 8 | + |
| 9 | +```postcss |
| 10 | +// styles.css |
| 11 | +.list { |
| 12 | + color: red; |
| 13 | +
|
| 14 | + &_item { |
| 15 | + color: green; |
| 16 | + } |
| 17 | +} |
| 18 | +``` |
| 19 | + |
| 20 | +Results in: |
| 21 | + |
| 22 | +```javascript |
| 23 | +// some-module.js |
| 24 | +import styles from "./styles.css"; |
| 25 | + |
| 26 | +// with any setup: |
| 27 | +console.log(styles.list); // => "styles_list__HASH" |
| 28 | + |
| 29 | +// with postcss-nested plugin: |
| 30 | +console.log(styles.list_item); // => undefined , |
| 31 | + |
| 32 | +// with postcss-nested-once plugin: |
| 33 | +console.log(styles.list_item); // => "styles_list_item__HASH" |
| 34 | +``` |
| 35 | + |
| 36 | +### Usage |
| 37 | + |
| 38 | +Install: |
| 39 | + |
| 40 | +```shell |
| 41 | +yarn add postcss-nested-once -D |
| 42 | +``` |
| 43 | + |
| 44 | +It's intended to replace [postcss-nested](https://github.com/postcss/postcss-nested) for the following [rollup](https://github.com/rollup/rollup) configuration: |
| 45 | + |
| 46 | +```javascript |
| 47 | +// rollup.config.js |
| 48 | + |
| 49 | +// ... |
| 50 | +const stylesRollupPlugin = require("rollup-plugin-styles"); |
| 51 | +const postcssNestedOncePlugin = require("postcss-nested-once"); |
| 52 | + |
| 53 | +module.exports = { |
| 54 | + // ... |
| 55 | + plugins: [ |
| 56 | + // ... |
| 57 | + stylesRollupPlugin({ |
| 58 | + // ... |
| 59 | + mode: "inject", |
| 60 | + modules: true, |
| 61 | + plugins: [ |
| 62 | + // ... |
| 63 | + postcssNestedOnce(), |
| 64 | + ], |
| 65 | + }), |
| 66 | + ], |
| 67 | +}; |
| 68 | +``` |
| 69 | + |
| 70 | +Assuming the following source: |
| 71 | + |
| 72 | +```postcss |
| 73 | +// styles.css |
| 74 | +.parent { |
| 75 | + color: red; |
| 76 | +
|
| 77 | + & .child { |
| 78 | + color: green; |
| 79 | + } |
| 80 | +} |
| 81 | +
|
| 82 | +.list { |
| 83 | + color: red; |
| 84 | +
|
| 85 | + &_item { |
| 86 | + color: green; |
| 87 | + } |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +This will produce: |
| 92 | + |
| 93 | +```javascript |
| 94 | +// styles.js |
| 95 | +// ... |
| 96 | +var css = |
| 97 | + ".styles_parent__HASH {" + |
| 98 | + " color: red" + |
| 99 | + "}" + |
| 100 | + "" + |
| 101 | + " .styles_parent__HASH .styles_child__HASH {" + |
| 102 | + " color: green;" + |
| 103 | + " }" + |
| 104 | + "" + |
| 105 | + ".styles_list__HASH {" + |
| 106 | + " color: red" + |
| 107 | + "}" + |
| 108 | + "" + |
| 109 | + ".styles_list_item__HASH {" + |
| 110 | + " color: green;" + |
| 111 | + " }" + |
| 112 | + ""; |
| 113 | +var modules = { |
| 114 | + parent: "styles_parent__HASH", |
| 115 | + child: "styles_child__HASH", |
| 116 | + list: "styles_list__HASH", |
| 117 | + list_item: "styles_list_item__HASH", |
| 118 | +}; |
| 119 | +injectCss["default"](css, {}); |
| 120 | + |
| 121 | +exports.css = css; |
| 122 | +exports.default = modules; |
| 123 | +``` |
| 124 | + |
| 125 | +Which in turn allows to use all the four classes in js: |
| 126 | + |
| 127 | +```javascript |
| 128 | +// some-module.js |
| 129 | +import styles from "./styles.css"; |
| 130 | + |
| 131 | +console.log(styles.parent); // => "styles_parent__HASH" |
| 132 | +console.log(styles.child); // => "styles_child__HASH" |
| 133 | +console.log(styles.list); // => "styles_list__HASH" |
| 134 | +console.log(styles.list_item); // => "styles_list_item__HASH" |
| 135 | +``` |
| 136 | + |
| 137 | +### Problem Details |
| 138 | + |
| 139 | +The [rollup-plugin-styles](https://github.com/Anidetrix/rollup-plugin-styles) provides an ability to use css modules by simply specifying `modules: true | ModulesOptions` during configuration. |
| 140 | + |
| 141 | +Under the hood it does not rely on the [postcss-modules](https://github.com/madyankin/postcss-modules) package directly, but introduces its own plugins pipeline instead: |
| 142 | + |
| 143 | +``` |
| 144 | +// built-in plugins |
| 145 | +styles-import - internal plugin, uses 'Once' hook, used only if the 'import' option is enabled; |
| 146 | +styles-url - internal plugin, uses 'Once' hook, used only if the 'url' option is enabled; |
| 147 | +
|
| 148 | +// bunch of plugins from options.plugins |
| 149 | +postcss-nested - could be listed here, if specified |
| 150 | +plugin-from-options #1 |
| 151 | +plugin-from-options #2 |
| 152 | +... |
| 153 | +
|
| 154 | +// bunch of plugins from postcss.config.js |
| 155 | +postcss-nested - or here, if specified |
| 156 | +plugin-from-postcss-config #1 |
| 157 | +plugin-from-postcss-config #2 |
| 158 | +... |
| 159 | +
|
| 160 | +// css-modules-related plugins |
| 161 | +postcss-modules-values - dependency plugin, uses 'Once' hook |
| 162 | +postcss-modules-local-by-default - dependency plugin, uses 'Once' hook |
| 163 | +postcss-modules-extract-imports - dependency plugin, uses 'Once' hook |
| 164 | +postcss-modules-scope - dependency plugin, uses 'Once' hook |
| 165 | +styles-icss - internal plugin involved in resulting exports generation, uses 'OnceExit' hook |
| 166 | +``` |
| 167 | + |
| 168 | +By that far it seems like everything should work as expected due to proper plugin's order. |
| 169 | + |
| 170 | +So to make the next guess it's good to know the responsibility of every plugin. To cut the long story short: |
| 171 | + |
| 172 | +- `postcss-modules-values` extracts `@value XX` and `@value YY from` into corresponding internal `:import {}` / `:export {}` selectors and gives local names; |
| 173 | +- `postcss-modules-local-by-default` wraps every suitable css selector in internal `:local` directive; |
| 174 | +- `postcss-modules-extract-imports` is responsible for the `compose` feature; |
| 175 | +- `postcss-modules-scope` among other actions generates `:export {}` directives for every `:local` selector; |
| 176 | +- `styles-icss` fills special object from the contents of every `:export {}` directive. |
| 177 | + |
| 178 | +The object formed by `styles-icss` is used further down the pipeline to write exports from the generated `styles.js` file (which are consumed by `import styles from './styles.css''`). |
| 179 | + |
| 180 | +As a result, for the above input we'll get the following output: |
| 181 | + |
| 182 | +```javascript |
| 183 | +// styles.js (generated) |
| 184 | +var css = |
| 185 | + ".styles_parent__HASH {" + |
| 186 | + " color: red" + |
| 187 | + "}" + |
| 188 | + "" + |
| 189 | + " .styles_parent__HASH .styles_child__HASH {" + |
| 190 | + " color: green;" + |
| 191 | + " }" + |
| 192 | + "" + |
| 193 | + ".styles_list__HASH {" + |
| 194 | + " color: red" + |
| 195 | + "}" + |
| 196 | + "" + |
| 197 | + ".styles_list__HASH_item {" + |
| 198 | + " color: green;" + |
| 199 | + " }" + |
| 200 | + ""; |
| 201 | +var modules = { |
| 202 | + parent: "styles_parent__HASH", |
| 203 | + child: "styles_child__HASH", |
| 204 | + list: "styles_list__HASH", |
| 205 | +}; |
| 206 | +injectCss["default"](css, {}); |
| 207 | + |
| 208 | +exports.css = css; |
| 209 | +exports.default = modules; |
| 210 | +``` |
| 211 | + |
| 212 | +So we have an actual rule `.styles_list__HASH_item` (which will be injected during the import), but do not have the corresponding export (making `styles.list_item === undefined` at runtime). |
| 213 | + |
| 214 | +The key hint is that `_item` suffix is added after the `__HASH` part, which means that `postcss-nested` transformation runs after the `postcss-modules-scope` transformation. This happens because `postcss-nested` plugin uses `Rule` hook while other ones (mostly) use `Once` + `walk()` combination which comes first. |
| 215 | + |
| 216 | +So the most simple solution is to move `postcss-nested`'s logic to the same `Once` hook, which resulted in `postcss-nested-once` plugin. |
| 217 | + |
| 218 | +### Implementation |
| 219 | + |
| 220 | +For the sake of simple maintenance this plugin lists `postcss-nested` as dependency and reuses it by calling `root.walkRules((rule) => { postcssNestedInstance.Rule(rule, postcssAPI); });` in `Once` hook. |
| 221 | + |
| 222 | +It accepts (and passes down) the same options as `postcss-nested`. |
| 223 | + |
| 224 | +Type definitions are copy-pasted from the original plugin. |
0 commit comments