|
| 1 | +# Using rename-css-selectors correctly and patterns to avoid |
| 2 | + |
| 3 | +`rename-css-selectors` is using `rcs-core` to perform its CSS selectors optimization. |
| 4 | +The core is performing three steps on your files: |
| 5 | +1. Extracting CSS's class and id selectors, keyframes, variables and attributes selectors |
| 6 | +2. Assigning minified value for each entry it has found |
| 7 | +3. Trying to replace all elements that could match in your files with the minified version |
| 8 | + |
| 9 | +## Caveats for step 1 |
| 10 | +Currently, the first step is done on CSS syntax only (either from CSS files or extracted from HTML's `<style>` tags) |
| 11 | +This means that if you have some HTML code doing this: |
| 12 | +```html |
| 13 | +<div class='something'>[...]</div> |
| 14 | +<div class='something'>[...]</div> |
| 15 | +``` |
| 16 | +without a CSS rule for class's `something`, it'll not be replaced/minified. |
| 17 | +In the future, the core might add a notice for these cases. |
| 18 | + |
| 19 | +HTML replacement should be quite solid for now, since the core can inspect attribute's name and only act upon `class` and `id` (and label's `for`). |
| 20 | + |
| 21 | +## Caveats for step 2 |
| 22 | +It's very important that the assigned *minified* name for each entry does not collide with a valid CSS selector that you could encounter. |
| 23 | +Paradoxically, if you initial selector are very large, you'll less chances to encounter an issue. Using `rename-css-selectors` in your pipeline should encourage more meaningful selectors in your file. |
| 24 | +By default, the core tries to minimize the length of the mapped minified name's selector. So if you have a CSS selector in your code like `.something`, it'll be mapped to (for example), `.a` if available. |
| 25 | +If you already have a small selector (like `.b`), the core is smart enough to map it to a similar length's selector (but not necessarly `.b`, it can be `.t`). When all first letter selectors are used, it's generating two letter selectors and so on. |
| 26 | + |
| 27 | +As you'll see in the next section, because of javascript's openness, some selectors can be understood differently. |
| 28 | +Imagine you have such document: |
| 29 | +```html |
| 30 | +<style> |
| 31 | +.b { color: red; } |
| 32 | +b { color: blue; } |
| 33 | +</style> |
| 34 | +<div class='b'> |
| 35 | + <b>Yeah</b> |
| 36 | +</div> |
| 37 | +<script> |
| 38 | + var a = 'b'; |
| 39 | + var c = document.querySelector(a); |
| 40 | + var d = document.getElementsByClassName(a); |
| 41 | +</script> |
| 42 | +``` |
| 43 | +Here, the *key* `'b'` in the javascript code can refer to the tag name `<b>` (when being called by `document.querySelector`) or to the class `.b` (when being called by `document.getElementsByClassName`. |
| 44 | +The core does not run your code (it only parses it) and, as such, can't figure out when a variable is being used for the former or latter case. |
| 45 | +Please notice that the core can't expect a *class* selection by looking at `getElementsByClassName` since you can be using any library that's abstracting this (like jQuery, React...). |
| 46 | + |
| 47 | +So, to avoid this caveat, you can either: |
| 48 | +* Avoid conflicting class name with potential HTML tag name (don't name your CSS class like `.b` or `.i` or `.span`, ...) |
| 49 | +* Avoid using `getElementById`, `getElementsByClassName` in your javascript code (and only fallback to `querySelector` and `querySelectorsAll`) |
| 50 | +* Reserve some mapping so the core can't use them (check the `config.reserve` array in the documentation about how to do that) |
| 51 | +* Exclude any selector that's conflicting with any HTML tag name (check the `config.exclude` array in the documentation about how to do that) |
| 52 | + |
| 53 | + |
| 54 | +### Warning: |
| 55 | + |
| 56 | +In the example above, `<div class='b'>` can be what the core is generating for your initial class `something`. So the example above could have been generated from this source: |
| 57 | +``` |
| 58 | +<style> |
| 59 | +.something { color: red; } |
| 60 | +b { color: blue; } |
| 61 | +</style> |
| 62 | +<div class='something'> |
| 63 | + <b>Yeah</b> |
| 64 | +</div> |
| 65 | +<script> |
| 66 | + var a = 'something'; |
| 67 | + var c = document.querySelector(a); |
| 68 | + var d = document.getElementsByClassName(a); |
| 69 | +</script> |
| 70 | +``` |
| 71 | +In that case, the initial code is semantically wrong since the `querySelector` should return `undefined`. The minified CSS selector code will thus return a different result in that case. |
| 72 | + |
| 73 | +If you had followed the advice above and excluded (or reserved) all potential HTML tag name (see below), then `something` won't be mapped to `b`, but, for example, `t` and the initial code behavior will be preserved. |
| 74 | + |
| 75 | +Similarly, if your initial code was: |
| 76 | +```js |
| 77 | +[...] |
| 78 | +var a = 'b'; |
| 79 | +[...] |
| 80 | +``` |
| 81 | +then the core will generate a warning telling you that `b` is already used in the renaming map and that you should fix the ambiguity. |
| 82 | + |
| 83 | +If you are in such cases (you are using `getElementById` et al) then the easiest solution is to exclude all potential HTML tag name with a config file containing: |
| 84 | +```json |
| 85 | +{ |
| 86 | + "exclude": [ |
| 87 | +"html", "head", "title", "base", "link", "meta", "style", "body", "article", "section", "nav", "aside", "h1", "h2", "h3", "h4", "h5", "h6", "hgroup", "address", "p", "hr", "pre", "blockquote", "ol", "ul", "menu", "li", "dl", "dt", "dd", "figure", "figcaption", "main", "div", "a", "em", "strong", "small", "s", "cite", "q", "dfn", "abbr", "ruby", "rt", "rp", "data", "time", "code", "var", "samp", "kbd", "sub", "sup", "i", "b", "u", "mark", "bdi", "bdo", "span", "br", "wbr", "ins", "del", "picture", "source", "img", "iframe", "embed", "object", "param", "video", "audio", "track", "map", "area", "table", "caption", "colgroup", "col", "tbody", "thead", "tfoot", "tr", "td", "th", "form", "label", "input", "button", "select", "datalist", "optgroup", "option", "textarea", "output", "progress", "meter", "fieldset", "legend", "details", "summary", "dialog", "script", "noscript", "template", "slot", "canvas"] |
| 88 | +} |
| 89 | +``` |
| 90 | +The incovenient being that such selectors won't be minified (think about `header` for example) |
| 91 | + |
| 92 | +## Caveat for step 3 |
| 93 | + |
| 94 | +Replacement is relatively safe for CSS and HTML (again, because such format are explicit enough to avoiding ambiguity). |
| 95 | + |
| 96 | +Replacements inside Javascript code is much harder because a string for a selector can be generated from many small parts (like in `var a = '.' + objClass;`), can be used in |
| 97 | +functions that have different semantics (like `getElementById` and `querySelector`), etc... |
| 98 | + |
| 99 | +So, here's the way the core is trying to resolve ambiguities (but as seen above, not all ambiguities can be solved): |
| 100 | +1. Ecmascript template, like in ``var a = `some text ${jsVar}`;`` is parsed like HTML, looking for `class="X"` or `id='Y'` or `for="Z"`. |
| 101 | + If you use template code to make your CSS selector from each part, it'll fail. Don't do ``var myClass = 'something'; var sel = `.${myClass}`;``, but instead `var myClass = '.something'`. |
| 102 | +2. Each string literal is extracted from your javascript code and: |
| 103 | + 1. If it contains some CSS specific selectors chars (like `#` or `.` or ` ` etc...), then it's parsed as a CSS selector. **This is safer and usually gives the expected result.** |
| 104 | + 2. If it does not contain some CSS specific selectors (like a single word), then it's tried as an ID selector first and if not found as a class selector (or not replaced if not found in either one). |
| 105 | + **This can lead to the ambiguity shown above.** |
| 106 | + |
| 107 | +So, to avoid such ambiguity, try to store your class or id with their respective prefix (`.` or `#`) in your code, and rely on `querySelector` et al. |
| 108 | + |
| 109 | +### Another remark: |
| 110 | + |
| 111 | +Some are writing CSS selector this way: |
| 112 | +```css |
| 113 | +div[class = something] |
| 114 | +{ |
| 115 | +color: red; |
| 116 | +} |
| 117 | +``` |
| 118 | +This is perfectly safe from javascript code (if you have `var a = 'div[class=something]';`), then this will be replaced correctly by the core (*to* `var a = 'div[class=b]';`). |
| 119 | + |
| 120 | +This however requires much more work from the developper so this is not a solution we recommend. |
| 121 | + |
| 122 | + |
0 commit comments