ember-scoped-css 1.0.0 → 1.0.2

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.
@@ -1,14 +1,55 @@
1
+ /**
2
+ * Important docs:
3
+ * - https://developer.mozilla.org/en-US/docs/Web/CSS/
4
+ */
1
5
  import postcss from 'postcss';
2
6
  import parser from 'postcss-selector-parser';
3
7
 
4
8
  import { isInsideGlobal } from './utils.js';
5
9
 
10
+ const SEP = '__';
11
+
12
+ function isRule(node) {
13
+ return node.type === 'rule';
14
+ }
15
+
16
+ function isDeclaration(node) {
17
+ return node.type === 'decl';
18
+ }
19
+
20
+ /**
21
+ * NOTE: "keyframes" is a singular definition, in that it's a block containing keyframes
22
+ * using `@keyframes {}` with only one thing on the inside doesn't make sense.
23
+ */
24
+ function rewriteReferencable(node, postfix) {
25
+ let originalName = node.params;
26
+ let postfixedName = node.params + SEP + postfix;
27
+
28
+ node.params = postfixedName;
29
+
30
+ return {
31
+ originalName,
32
+ postfixedName,
33
+ };
34
+ }
35
+
6
36
  function rewriteSelector(sel, postfix) {
7
37
  const transform = (selectors) => {
8
38
  selectors.walk((selector) => {
9
- if (selector.type === 'class' && !isInsideGlobal(selector)) {
39
+ if (isInsideGlobal(selector)) return;
40
+
41
+ // We never want to touch psuedo selectors since we and the user doesn't own them.
42
+ if (selector.type === 'psuedo') return;
43
+
44
+ // :nth-of-type has special syntax where the values passed to nth-of-type()
45
+ // must either be exactly "odd", "even", or a simple formula
46
+ //
47
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-of-type
48
+ if (isNthOfType(selector)) return;
49
+
50
+ if (selector.type === 'class') {
10
51
  selector.value += '_' + postfix;
11
- } else if (selector.type === 'tag' && !isInsideGlobal(selector)) {
52
+ } else if (selector.type === 'tag') {
12
53
  selector.replaceWith(
13
54
  parser.tag({ value: selector.value }),
14
55
  parser.className({ value: postfix }),
@@ -28,6 +69,12 @@ function rewriteSelector(sel, postfix) {
28
69
  return transformed;
29
70
  }
30
71
 
72
+ function isNthOfType(node) {
73
+ if (!node) return false;
74
+
75
+ return node.parent?.value === ':nth-of-type' || isNthOfType(node.parent);
76
+ }
77
+
31
78
  function isInsideKeyframes(node) {
32
79
  const parent = node.parent;
33
80
 
@@ -40,75 +87,111 @@ function isInsideKeyframes(node) {
40
87
  export function rewriteCss(css, postfix, fileName, layerName) {
41
88
  const layerNameWithDefault = layerName ?? 'components';
42
89
  const ast = postcss.parse(css);
90
+ /**
91
+ * kind => originalName => postfixedName
92
+ * @type {{ [kind: string]: { [originalName: string]: string }}}
93
+ */
94
+ const referencables = {
95
+ keyframes: {},
96
+ 'counter-style': {},
97
+ 'position-try': {},
98
+ property: {},
99
+ };
43
100
 
44
- ast.walk((node) => {
45
- if (node.type === 'rule' && !isInsideKeyframes(node)) {
46
- node.selector = rewriteSelector(node.selector, postfix);
101
+ const availableReferencables = new Set(Object.keys(referencables));
102
+
103
+ function isReferencable(node) {
104
+ if (node.type !== 'atrule') return;
105
+
106
+ return availableReferencables.has(node.name);
107
+ }
108
+
109
+ function updateDirectReferences(node) {
110
+ if (!node.value) return;
111
+
112
+ for (let [, map] of Object.entries(referencables)) {
113
+ if (map[node.value]) {
114
+ node.value = map[node.value];
115
+ }
47
116
  }
48
- });
117
+ }
49
118
 
50
- const rewrittenCss = ast.toString();
119
+ function updateShorthandContents(node) {
120
+ if (node.prop === 'animation') {
121
+ let parts = node.value.split(' ');
122
+ let match = parts.filter((x) => referencables.keyframes[x]);
51
123
 
52
- if (!layerNameWithDefault) {
53
- return `/* ${fileName} */\n${rewrittenCss}\n`;
124
+ if (match.length) {
125
+ match.forEach((x) => {
126
+ let replacement = referencables.keyframes[x];
127
+
128
+ if (!replacement) return;
129
+
130
+ node.value = node.value.replace(x, replacement);
131
+ });
132
+ }
133
+ }
134
+
135
+ for (let [lookFor, replaceWith] of Object.entries(referencables.property)) {
136
+ let lookForVar = `var(${lookFor})`;
137
+ let replaceWithVar = `var(${replaceWith})`;
138
+
139
+ node.value = node.value.replace(lookForVar, replaceWithVar);
140
+ }
54
141
  }
55
142
 
56
- return (
57
- `/* ${fileName} */\n@layer ${layerNameWithDefault} {\n\n` +
58
- rewrittenCss +
59
- '\n}\n'
60
- );
61
- }
143
+ /**
144
+ * We have to do two passes:
145
+ * 1. postfix all the referencable syntax
146
+ * 2. postfix as normal, but also checking values of CSS properties
147
+ * that could match postfixed referencables from step 1
148
+ */
62
149
 
63
- if (import.meta.vitest) {
64
- const { it, expect } = import.meta.vitest;
150
+ // Step 1: find referencables
151
+ ast.walk((node) => {
152
+ /**
153
+ * @keyframes, @counter-style, etc
154
+ */
155
+ if (isReferencable(node)) {
156
+ let name = node.name;
157
+ let { originalName, postfixedName } = rewriteReferencable(node, postfix);
65
158
 
66
- it('should rewrite css', function () {
67
- const css = '.foo { color: red; }';
68
- const postfix = 'postfix';
69
- const fileName = 'foo.css';
70
- const rewritten = rewriteCss(css, postfix, fileName, false);
159
+ referencables[name][originalName] = postfixedName;
71
160
 
72
- expect(rewritten).to.equal(`/* foo.css */\n.foo_postfix { color: red; }\n`);
161
+ return;
162
+ }
73
163
  });
74
164
 
75
- it('should use a custom layer', function () {
76
- const css = '.foo { color: red; }';
77
- const postfix = 'postfix';
78
- const fileName = 'foo.css';
79
- const rewritten = rewriteCss(css, postfix, fileName, 'utils');
165
+ // Step 2: postfix and update refenced referencables
166
+ ast.walk((node) => {
167
+ if (isDeclaration(node)) {
168
+ updateDirectReferences(node);
169
+ updateShorthandContents(node);
80
170
 
81
- expect(rewritten).to.equal(
82
- `/* foo.css */\n@layer utils {\n\n.foo_postfix { color: red; }\n}\n`,
83
- );
84
- });
171
+ return;
172
+ }
173
+
174
+ if (isRule(node)) {
175
+ /**
176
+ * The inner-contents of a keyframe are percentages, rather than selectors
177
+ */
178
+ if (isInsideKeyframes(node)) return;
85
179
 
86
- it('shouldnt rewrite global', function () {
87
- const css = '.baz :global(.foo p) .bar { color: red; }';
88
- const postfix = 'postfix';
89
- const fileName = 'foo.css';
90
- const rewritten = rewriteCss(css, postfix, fileName);
180
+ node.selector = rewriteSelector(node.selector, postfix);
91
181
 
92
- expect(rewritten).to.equal(
93
- `/* foo.css */\n@layer components {\n\n.baz_postfix .foo p .bar_postfix { color: red; }\n}\n`,
94
- );
182
+ return;
183
+ }
95
184
  });
96
185
 
97
- it(`shouldn't rewrite keyframes`, function () {
98
- const css = `
99
- @keyframes luna-view-navigation {
100
- 100% {
101
- padding-top: 1rem;
102
- }
103
- }
104
- `;
186
+ const rewrittenCss = ast.toString();
105
187
 
106
- const postfix = 'postfix';
107
- const fileName = 'foo.css';
108
- const rewritten = rewriteCss(css, postfix, fileName);
188
+ if (!layerNameWithDefault) {
189
+ return `/* ${fileName} */\n${rewrittenCss}\n`;
190
+ }
109
191
 
110
- expect(rewritten).to.equal(
111
- `/* foo.css */\n@layer components {\n\n${css}\n}\n`,
112
- );
113
- });
192
+ return (
193
+ `/* ${fileName} */\n@layer ${layerNameWithDefault} {\n\n` +
194
+ rewrittenCss +
195
+ '\n}\n'
196
+ );
114
197
  }