Skip to content

Commit fc66917

Browse files
committed
Improve documentation with the usual caveats
1 parent a10956d commit fc66917

File tree

1 file changed

+122
-0
lines changed

1 file changed

+122
-0
lines changed

docs/caveats.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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

Comments
 (0)