@gem-sdk/hash-class-names 1.0.0 → 1.11.0

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.
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ // https://drafts.csswg.org/cssom/#serialize-an-identifier
4
+ // repo: https://github.com/mathiasbynens/CSS.escape
5
+ const cssEscape = function(value) {
6
+ if (arguments.length == 0) {
7
+ throw new TypeError('`CSS.escape` requires an argument.');
8
+ }
9
+ const string = String(value);
10
+ const length = string.length;
11
+ let index = -1;
12
+ let codeUnit;
13
+ let result = '';
14
+ const firstCodeUnit = string.charCodeAt(0);
15
+ if (// If the character is the first character and is a `-` (U+002D), and
16
+ // there is no second character, […]
17
+ length == 1 && firstCodeUnit == 0x002d) {
18
+ return '\\' + string;
19
+ }
20
+ while(++index < length){
21
+ codeUnit = string.charCodeAt(index);
22
+ // Note: there’s no need to special-case astral symbols, surrogate
23
+ // pairs, or lone surrogates.
24
+ // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
25
+ // (U+FFFD).
26
+ if (codeUnit == 0x0000) {
27
+ result += '\uFFFD';
28
+ continue;
29
+ }
30
+ if (// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
31
+ // U+007F, […]
32
+ codeUnit >= 0x0001 && codeUnit <= 0x001f || codeUnit == 0x007f || // If the character is the first character and is in the range [0-9]
33
+ // (U+0030 to U+0039), […]
34
+ index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039 || // If the character is the second character and is in the range [0-9]
35
+ // (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
36
+ index == 1 && codeUnit >= 0x0030 && codeUnit <= 0x0039 && firstCodeUnit == 0x002d) {
37
+ // https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
38
+ result += '\\' + codeUnit.toString(16) + ' ';
39
+ continue;
40
+ }
41
+ // If the character is not handled by one of the above rules and is
42
+ // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
43
+ // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
44
+ // U+005A), or [a-z] (U+0061 to U+007A), […]
45
+ if (codeUnit >= 0x0080 || codeUnit == 0x002d || codeUnit == 0x005f || codeUnit >= 0x0030 && codeUnit <= 0x0039 || codeUnit >= 0x0041 && codeUnit <= 0x005a || codeUnit >= 0x0061 && codeUnit <= 0x007a) {
46
+ // the character itself
47
+ result += string.charAt(index);
48
+ continue;
49
+ }
50
+ // Otherwise, the escaped character.
51
+ // https://drafts.csswg.org/cssom/#escape-a-character
52
+ result += '\\' + string.charAt(index);
53
+ }
54
+ return result;
55
+ };
56
+
57
+ exports.cssEscape = cssEscape;
@@ -0,0 +1,122 @@
1
+ 'use strict';
2
+
3
+ const cssToJson = (str)=>{
4
+ let style = str;
5
+ const jsonCSS = {
6
+ children: {},
7
+ attributes: ''
8
+ };
9
+ const mediaQuery = {};
10
+ // eslint-disable-next-line regexp/prefer-d, no-useless-escape, regexp/no-dupe-characters-character-class, regexp/no-useless-escape, regexp/strict, regexp/no-obscure-range, regexp/no-useless-flag, regexp/no-super-linear-backtracking
11
+ const regex = /(@media[^{]+\)).*?{([\s\S]+?})\s*}/g;
12
+ let m;
13
+ while((m = regex.exec(str)) !== null){
14
+ // This is necessary to avoid infinite loops with zero-width matches
15
+ if (m.index === regex.lastIndex) {
16
+ regex.lastIndex++;
17
+ }
18
+ // The result can be accessed through the `m`-variable.
19
+ const css = {
20
+ media: '',
21
+ style: ''
22
+ };
23
+ m.forEach((match, groupIndex)=>{
24
+ if (groupIndex == 0) {
25
+ style = style.replaceAll(match, '');
26
+ }
27
+ if (groupIndex == 1) {
28
+ css.media = match;
29
+ }
30
+ if (groupIndex == 2) {
31
+ css.style = match;
32
+ }
33
+ });
34
+ mediaQuery[css.media] = mediaQuery[css.media] || [];
35
+ mediaQuery[css.media]?.push(css.style);
36
+ }
37
+ // Parse css root
38
+ const cssRoot = parseCSS(style);
39
+ for(const key in cssRoot){
40
+ if (Object.prototype.hasOwnProperty.call(cssRoot, key)) {
41
+ const value = cssRoot[key];
42
+ jsonCSS.children[key] = {
43
+ children: {},
44
+ attributes: value
45
+ };
46
+ }
47
+ }
48
+ // Append css media
49
+ for(const media in mediaQuery){
50
+ if (Object.prototype.hasOwnProperty.call(mediaQuery, media)) {
51
+ const css = mediaQuery[media];
52
+ if (css?.length) {
53
+ const jsonQueryCss = {
54
+ children: {},
55
+ attributes: ''
56
+ };
57
+ const cssQuery = parseCSS(css?.join(''));
58
+ for(const key in cssQuery){
59
+ if (Object.prototype.hasOwnProperty.call(cssQuery, key)) {
60
+ const value = cssQuery[key];
61
+ jsonQueryCss.children[key] = {
62
+ children: {},
63
+ attributes: value
64
+ };
65
+ }
66
+ }
67
+ jsonCSS.children[media] = jsonQueryCss;
68
+ }
69
+ }
70
+ }
71
+ return jsonCSS;
72
+ };
73
+ const jsonToCss = (jsonCSS, selector)=>{
74
+ // Replace selector
75
+ let value = '';
76
+ for(const selector in jsonCSS.children){
77
+ if (Object.prototype.hasOwnProperty.call(jsonCSS.children, selector)) {
78
+ const style = jsonCSS.children[selector];
79
+ if (style) {
80
+ value += jsonToCss(style, selector);
81
+ }
82
+ }
83
+ }
84
+ if (!value) {
85
+ value = jsonCSS?.attributes || '';
86
+ }
87
+ if (selector) {
88
+ return `${selector}{${value}}`;
89
+ } else {
90
+ return value;
91
+ }
92
+ };
93
+ const parseCSS = (css)=>{
94
+ const cssJSON = {};
95
+ // eslint-disable-next-line regexp/prefer-d, no-useless-escape, regexp/no-dupe-characters-character-class, regexp/no-useless-escape, regexp/strict, regexp/no-obscure-range, regexp/no-useless-flag, regexp/no-super-linear-backtracking, regexp/prefer-character-class, regexp/no-dupe-disjunctions
96
+ const cssRegex = /((.|\n|\r|\t)*?){((.|\n|\r|\t)*?)}/gm;
97
+ let mCSS;
98
+ while((mCSS = cssRegex.exec(css)) !== null){
99
+ // This is necessary to avoid infinite loops with zero-width matches
100
+ if (mCSS.index === cssRegex.lastIndex) {
101
+ cssRegex.lastIndex++;
102
+ }
103
+ // The result can be accessed through the `m`-variable.
104
+ const selector = {
105
+ key: '',
106
+ value: ''
107
+ };
108
+ mCSS.forEach((match, groupIndex)=>{
109
+ if (groupIndex == 1) {
110
+ selector.key = match.trim();
111
+ }
112
+ if (groupIndex == 3) {
113
+ selector.value = match;
114
+ }
115
+ });
116
+ cssJSON[selector.key] = selector.value;
117
+ }
118
+ return cssJSON;
119
+ };
120
+
121
+ exports.cssToJson = cssToJson;
122
+ exports.jsonToCss = jsonToCss;
package/dist/cjs/index.js CHANGED
@@ -1,111 +1,234 @@
1
1
  'use strict';
2
2
 
3
3
  var parse5 = require('parse5');
4
+ var cssEscape = require('./css-escape.js');
5
+ var cssParser = require('./css-parser.js');
4
6
 
5
- const hashClassNames = (html, css, options) => {
6
- const documentFragment = parse5.parseFragment(html);
7
- const jsonHTML = documentFragment.childNodes;
8
- if (jsonHTML?.length) {
9
- const classes = {};
10
- const hashClasses = {};
11
- // Find & Obfuscate class in html
12
- loopNode(jsonHTML, (node) => {
13
- if (node?.attrs?.length) {
14
- const attrClass = node.attrs.find((item) => item.name == 'class');
15
- if (attrClass?.value) {
16
- const nodeClasses = attrClass.value.split(' ');
17
- if (nodeClasses?.length) {
18
- for (let i = 0; i < nodeClasses.length; i++) {
19
- const nodeClass = nodeClasses[i]?.trim() || '';
20
- if (options?.ignoreClasses?.includes(nodeClass)) {
21
- continue;
22
- }
23
- if (nodeClass) {
24
- const data = classes[nodeClass];
25
- if (!data) {
26
- // eslint-disable-next-line no-constant-condition
27
- while (true) {
28
- const newClass = `a${ID()}`;
29
- if (!hashClasses[newClass]) {
30
- hashClasses[newClass] = true; // flag
31
- // Cache
32
- classes[nodeClass] = {
33
- hash: newClass,
34
- };
35
- // replace
36
- attrClass.value = attrClass.value.replace(nodeClass, newClass);
37
- break;
38
- }
39
- }
40
- }
41
- else {
42
- attrClass.value = attrClass.value.replace(nodeClass, data.hash);
43
- }
44
- }
45
- }
46
- }
47
- }
48
- }
49
- });
50
- let newHTML = '';
51
- for (let i = 0; i < jsonHTML.length; i++) {
52
- const node = jsonHTML[i];
53
- if (node) {
54
- newHTML += parse5.serializeOuter(node);
55
- }
56
- }
57
- // Replace class in css
58
- const orderClasses = [];
59
- for (const oldClass in classes) {
60
- if (Object.prototype.hasOwnProperty.call(classes, oldClass)) {
61
- const data = classes[oldClass];
62
- if (data?.hash) {
63
- orderClasses.push({
64
- oldClass: oldClass,
65
- newClass: data.hash,
66
- });
67
- }
68
- }
69
- }
70
- orderClasses.sort((a, b) => {
71
- if (a.oldClass.includes(b.oldClass)) {
72
- return -1;
73
- }
74
- return 1;
75
- });
76
- let newCSS = css;
77
- for (let i = 0; i < orderClasses.length; i++) {
78
- const data = orderClasses[i];
79
- newCSS = newCSS.replaceAll(`.${data?.oldClass}`, `.${data?.newClass}`);
80
- }
81
- return {
82
- html: newHTML,
83
- css: newCSS,
84
- };
85
- }
86
- return {
87
- html,
88
- css,
89
- };
90
- };
91
- const loopNode = (childNodes, callback) => {
92
- if (childNodes?.length) {
93
- for (let i = 0; i < childNodes.length; i++) {
94
- const childNode = childNodes[i];
95
- if (childNode) {
96
- callback(childNode);
97
- if (childNode.childNodes?.length) {
98
- loopNode(childNode.childNodes, callback);
99
- }
100
- }
101
- }
102
- }
103
- };
104
- const ID = function () {
105
- // Math.random should be unique because of its seeding algorithm.
106
- // Convert it to base 36 (numbers + letters), and grab the first 9 characters
107
- // after the decimal.
108
- return Math.random().toString(36).substr(2, 5);
7
+ const hashClassNames = (html, css, options)=>{
8
+ const { html: htmlWithoutLiquid , arrayMatch } = replaceLiquidToBKPlaceholder(html);
9
+ const documentFragment = parse5.parseFragment(htmlWithoutLiquid);
10
+ const jsonHTML = documentFragment.childNodes;
11
+ if (jsonHTML?.length) {
12
+ const classes = {};
13
+ const hashClasses = {};
14
+ // Find & Obfuscate class in html
15
+ const loopNode = (childNodes, callback)=>{
16
+ if (childNodes?.length) {
17
+ for(let i = 0; i < childNodes.length; i++){
18
+ const childNode = childNodes[i];
19
+ if (childNode) {
20
+ const keepClass = childNode.attrs?.find((item)=>item.name == 'data-keep-class')?.value;
21
+ if (!keepClass) {
22
+ callback(childNode);
23
+ if (childNode.childNodes?.length) {
24
+ loopNode(childNode.childNodes, callback);
25
+ }
26
+ }
27
+ }
28
+ }
29
+ }
30
+ };
31
+ loopNode(jsonHTML, (node)=>{
32
+ if (node?.attrs?.length) {
33
+ const attrClass = node.attrs.find((item)=>item.name == 'class');
34
+ if (attrClass?.value) {
35
+ const nodeClasses = attrClass.value.split(' ');
36
+ if (nodeClasses?.length) {
37
+ for(let i = 0; i < nodeClasses.length; i++){
38
+ let nodeClass = nodeClasses[i]?.trim() || '';
39
+ nodeClass = nodeClass.replace(/bk-liquid.*?bk-liquid/g, '');
40
+ if (options?.ignoreClasses?.includes(nodeClass)) {
41
+ continue;
42
+ }
43
+ if (nodeClass) {
44
+ const data = classes[nodeClass];
45
+ if (!data) {
46
+ // eslint-disable-next-line no-constant-condition
47
+ while(true){
48
+ const newClass = `a${ID()}`;
49
+ if (!hashClasses[newClass]) {
50
+ hashClasses[newClass] = true; // flag
51
+ // Cache
52
+ classes[nodeClass] = {
53
+ hash: newClass
54
+ };
55
+ // replace
56
+ replaceClass(node, nodeClass, newClass);
57
+ break;
58
+ }
59
+ }
60
+ } else {
61
+ replaceClass(node, nodeClass, data.hash);
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ });
69
+ let newHTML = '';
70
+ for(let i = 0; i < jsonHTML.length; i++){
71
+ const node = jsonHTML[i];
72
+ if (node) {
73
+ newHTML += parse5.serializeOuter(node);
74
+ }
75
+ }
76
+ // Replace class in css
77
+ const orderClasses = [];
78
+ for(const oldClass in classes){
79
+ if (Object.prototype.hasOwnProperty.call(classes, oldClass)) {
80
+ const data = classes[oldClass];
81
+ if (data?.hash) {
82
+ orderClasses.push({
83
+ oldClass: oldClass,
84
+ newClass: data.hash
85
+ });
86
+ }
87
+ }
88
+ }
89
+ orderClasses.sort((a, b)=>{
90
+ if (a.oldClass.includes(b.oldClass)) {
91
+ return -1;
92
+ }
93
+ return 1;
94
+ });
95
+ let newCSS = css;
96
+ if (newCSS) {
97
+ const jsonCSS = cssParser.cssToJson(newCSS);
98
+ const loopCSSSelector = (style)=>{
99
+ // Replace selector
100
+ for(const selector in style.children){
101
+ if (Object.prototype.hasOwnProperty.call(style.children, selector)) {
102
+ let newSelector = selector;
103
+ // Replace selector new selector
104
+ const styleSelector = style.children[selector];
105
+ const selectors = newSelector.split(',');
106
+ for(let index = 0; index < selectors.length; index++){
107
+ const itemSelector = selectors[index];
108
+ if (itemSelector) {
109
+ const selectorClasses = itemSelector.split('.');
110
+ for(let i = 0; i < selectorClasses.length; i++){
111
+ let selectorClass = selectorClasses[i];
112
+ selectorClass = selectorClass?.trim();
113
+ // eslint-disable-next-line prefer-regex-literals, no-useless-escape, regexp/no-useless-flag
114
+ selectorClass = selectorClass?.replace(new RegExp(`\[.*?\]`, 'gm'), ''); // Replace [data]
115
+ selectorClass = selectorClass?.replace(/:(a(ctive|ny(-link)*)|checked|d(efault|i(r\(\)|sabled))|e(mpty|nabled)|f(irst(-(child|of-type))*|ullscreen|ocus)|hover|in(determinate|valid|-range)|la(ng\(\)|(st-(child|of-type)))|l(eft|ink)|n(ot\(\)|th-((last-)*(child|of-type)\(\)))|o(nly-(child|of-type)|ptional|ut-of-range)|r(e(ad-(only|write)|quired)|ight|oot)|scope|target|v(alid|isited))/g, '');
116
+ if (selectorClass) {
117
+ for(let index = 0; index < orderClasses.length; index++){
118
+ const orderClass = orderClasses[index];
119
+ if (orderClass) {
120
+ let flag = false;
121
+ if (orderClass.oldClass == selectorClass) {
122
+ flag = true;
123
+ } else if (cssEscape.cssEscape(orderClass.oldClass) == selectorClass) {
124
+ flag = true;
125
+ }
126
+ if (flag) {
127
+ selectorClasses[i] = replaceSelector(selectorClasses[i] || '', orderClass.oldClass, orderClass.newClass);
128
+ break;
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
134
+ selectors[index] = selectorClasses.join('.');
135
+ }
136
+ }
137
+ newSelector = selectors.join(',');
138
+ // replace
139
+ if (newSelector != selector && styleSelector) {
140
+ style.children[newSelector] = styleSelector;
141
+ delete style.children[selector];
142
+ }
143
+ }
144
+ }
145
+ // Loop
146
+ if (style.children) {
147
+ for(const selectorChild in style.children){
148
+ if (Object.prototype.hasOwnProperty.call(style.children, selectorChild)) {
149
+ const styleChild = style.children[selectorChild];
150
+ if (styleChild) {
151
+ loopCSSSelector(styleChild);
152
+ }
153
+ }
154
+ }
155
+ }
156
+ };
157
+ loopCSSSelector(jsonCSS);
158
+ newCSS = cssParser.jsonToCss(jsonCSS);
159
+ }
160
+ newHTML = replaceBKPlaceholderToLiquid(newHTML, arrayMatch);
161
+ return {
162
+ html: newHTML,
163
+ css: newCSS
164
+ };
165
+ }
166
+ // Return default
167
+ return {
168
+ html,
169
+ css
170
+ };
171
+ };
172
+ const ID = function() {
173
+ // Math.random should be unique because of its seeding algorithm.
174
+ // Convert it to base 36 (numbers + letters), and grab the first 9 characters
175
+ // after the decimal.
176
+ return Math.random().toString(36).substr(2, 5);
177
+ };
178
+ const replaceClass = (node, oldClass, newClass)=>{
179
+ if (node.attrs?.length) {
180
+ const attrClass = node.attrs.find((item)=>item.name == 'class');
181
+ if (attrClass?.value) {
182
+ const values = attrClass.value.split(' ');
183
+ for(let i = 0; i < values.length; i++){
184
+ const value = values[i];
185
+ if (value) {
186
+ const valueWithoutLiquid = value?.replace(/bk-liquid.*?bk-liquid/g, '');
187
+ if (valueWithoutLiquid == oldClass) {
188
+ const newValue = value?.replace(oldClass, newClass) || value;
189
+ values[i] = newValue;
190
+ }
191
+ }
192
+ }
193
+ attrClass.value = values.join(' ');
194
+ }
195
+ }
196
+ };
197
+ const replaceSelector = (selector, oldClass, newClass)=>{
198
+ let newSelector = selector;
199
+ newSelector = newSelector.replaceAll(oldClass, newClass);
200
+ newSelector = newSelector.replaceAll(cssEscape.cssEscape(oldClass), cssEscape.cssEscape(newClass));
201
+ return newSelector;
202
+ };
203
+ const replaceLiquidToBKPlaceholder = (html)=>{
204
+ // eslint-disable-next-line regexp/no-dupe-disjunctions
205
+ const regex = /\{%(.|[\n\r\t])*?%\}|\{\{(.|[\n\r\t])*?\}\}/g;
206
+ const arrayMatch = html.match(regex);
207
+ if (arrayMatch?.length) {
208
+ for(let i = 0; i < arrayMatch.length; i++){
209
+ const match = arrayMatch[i];
210
+ if (match) {
211
+ html = html.replace(match, 'bk-liquid' + i + 'bk-liquid');
212
+ }
213
+ }
214
+ }
215
+ return {
216
+ html,
217
+ arrayMatch
218
+ };
219
+ };
220
+ const replaceBKPlaceholderToLiquid = (html, arrayMatch)=>{
221
+ if (arrayMatch?.length) {
222
+ for(let i = 0; i < arrayMatch.length; i++){
223
+ const match = arrayMatch[i];
224
+ if (match) {
225
+ html = html.replace('bk-liquid' + i + 'bk-liquid=""', match); // html data auto fix
226
+ html = html.replace('bk-liquid' + i + "bk-liquid=''", match); // html data auto fix
227
+ html = html.replace('bk-liquid' + i + 'bk-liquid', match);
228
+ }
229
+ }
230
+ }
231
+ return html;
109
232
  };
110
233
 
111
234
  exports.hashClassNames = hashClassNames;
@@ -0,0 +1,55 @@
1
+ // https://drafts.csswg.org/cssom/#serialize-an-identifier
2
+ // repo: https://github.com/mathiasbynens/CSS.escape
3
+ const cssEscape = function(value) {
4
+ if (arguments.length == 0) {
5
+ throw new TypeError('`CSS.escape` requires an argument.');
6
+ }
7
+ const string = String(value);
8
+ const length = string.length;
9
+ let index = -1;
10
+ let codeUnit;
11
+ let result = '';
12
+ const firstCodeUnit = string.charCodeAt(0);
13
+ if (// If the character is the first character and is a `-` (U+002D), and
14
+ // there is no second character, […]
15
+ length == 1 && firstCodeUnit == 0x002d) {
16
+ return '\\' + string;
17
+ }
18
+ while(++index < length){
19
+ codeUnit = string.charCodeAt(index);
20
+ // Note: there’s no need to special-case astral symbols, surrogate
21
+ // pairs, or lone surrogates.
22
+ // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
23
+ // (U+FFFD).
24
+ if (codeUnit == 0x0000) {
25
+ result += '\uFFFD';
26
+ continue;
27
+ }
28
+ if (// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
29
+ // U+007F, […]
30
+ codeUnit >= 0x0001 && codeUnit <= 0x001f || codeUnit == 0x007f || // If the character is the first character and is in the range [0-9]
31
+ // (U+0030 to U+0039), […]
32
+ index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039 || // If the character is the second character and is in the range [0-9]
33
+ // (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
34
+ index == 1 && codeUnit >= 0x0030 && codeUnit <= 0x0039 && firstCodeUnit == 0x002d) {
35
+ // https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
36
+ result += '\\' + codeUnit.toString(16) + ' ';
37
+ continue;
38
+ }
39
+ // If the character is not handled by one of the above rules and is
40
+ // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
41
+ // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
42
+ // U+005A), or [a-z] (U+0061 to U+007A), […]
43
+ if (codeUnit >= 0x0080 || codeUnit == 0x002d || codeUnit == 0x005f || codeUnit >= 0x0030 && codeUnit <= 0x0039 || codeUnit >= 0x0041 && codeUnit <= 0x005a || codeUnit >= 0x0061 && codeUnit <= 0x007a) {
44
+ // the character itself
45
+ result += string.charAt(index);
46
+ continue;
47
+ }
48
+ // Otherwise, the escaped character.
49
+ // https://drafts.csswg.org/cssom/#escape-a-character
50
+ result += '\\' + string.charAt(index);
51
+ }
52
+ return result;
53
+ };
54
+
55
+ export { cssEscape };
@@ -0,0 +1,119 @@
1
+ const cssToJson = (str)=>{
2
+ let style = str;
3
+ const jsonCSS = {
4
+ children: {},
5
+ attributes: ''
6
+ };
7
+ const mediaQuery = {};
8
+ // eslint-disable-next-line regexp/prefer-d, no-useless-escape, regexp/no-dupe-characters-character-class, regexp/no-useless-escape, regexp/strict, regexp/no-obscure-range, regexp/no-useless-flag, regexp/no-super-linear-backtracking
9
+ const regex = /(@media[^{]+\)).*?{([\s\S]+?})\s*}/g;
10
+ let m;
11
+ while((m = regex.exec(str)) !== null){
12
+ // This is necessary to avoid infinite loops with zero-width matches
13
+ if (m.index === regex.lastIndex) {
14
+ regex.lastIndex++;
15
+ }
16
+ // The result can be accessed through the `m`-variable.
17
+ const css = {
18
+ media: '',
19
+ style: ''
20
+ };
21
+ m.forEach((match, groupIndex)=>{
22
+ if (groupIndex == 0) {
23
+ style = style.replaceAll(match, '');
24
+ }
25
+ if (groupIndex == 1) {
26
+ css.media = match;
27
+ }
28
+ if (groupIndex == 2) {
29
+ css.style = match;
30
+ }
31
+ });
32
+ mediaQuery[css.media] = mediaQuery[css.media] || [];
33
+ mediaQuery[css.media]?.push(css.style);
34
+ }
35
+ // Parse css root
36
+ const cssRoot = parseCSS(style);
37
+ for(const key in cssRoot){
38
+ if (Object.prototype.hasOwnProperty.call(cssRoot, key)) {
39
+ const value = cssRoot[key];
40
+ jsonCSS.children[key] = {
41
+ children: {},
42
+ attributes: value
43
+ };
44
+ }
45
+ }
46
+ // Append css media
47
+ for(const media in mediaQuery){
48
+ if (Object.prototype.hasOwnProperty.call(mediaQuery, media)) {
49
+ const css = mediaQuery[media];
50
+ if (css?.length) {
51
+ const jsonQueryCss = {
52
+ children: {},
53
+ attributes: ''
54
+ };
55
+ const cssQuery = parseCSS(css?.join(''));
56
+ for(const key in cssQuery){
57
+ if (Object.prototype.hasOwnProperty.call(cssQuery, key)) {
58
+ const value = cssQuery[key];
59
+ jsonQueryCss.children[key] = {
60
+ children: {},
61
+ attributes: value
62
+ };
63
+ }
64
+ }
65
+ jsonCSS.children[media] = jsonQueryCss;
66
+ }
67
+ }
68
+ }
69
+ return jsonCSS;
70
+ };
71
+ const jsonToCss = (jsonCSS, selector)=>{
72
+ // Replace selector
73
+ let value = '';
74
+ for(const selector in jsonCSS.children){
75
+ if (Object.prototype.hasOwnProperty.call(jsonCSS.children, selector)) {
76
+ const style = jsonCSS.children[selector];
77
+ if (style) {
78
+ value += jsonToCss(style, selector);
79
+ }
80
+ }
81
+ }
82
+ if (!value) {
83
+ value = jsonCSS?.attributes || '';
84
+ }
85
+ if (selector) {
86
+ return `${selector}{${value}}`;
87
+ } else {
88
+ return value;
89
+ }
90
+ };
91
+ const parseCSS = (css)=>{
92
+ const cssJSON = {};
93
+ // eslint-disable-next-line regexp/prefer-d, no-useless-escape, regexp/no-dupe-characters-character-class, regexp/no-useless-escape, regexp/strict, regexp/no-obscure-range, regexp/no-useless-flag, regexp/no-super-linear-backtracking, regexp/prefer-character-class, regexp/no-dupe-disjunctions
94
+ const cssRegex = /((.|\n|\r|\t)*?){((.|\n|\r|\t)*?)}/gm;
95
+ let mCSS;
96
+ while((mCSS = cssRegex.exec(css)) !== null){
97
+ // This is necessary to avoid infinite loops with zero-width matches
98
+ if (mCSS.index === cssRegex.lastIndex) {
99
+ cssRegex.lastIndex++;
100
+ }
101
+ // The result can be accessed through the `m`-variable.
102
+ const selector = {
103
+ key: '',
104
+ value: ''
105
+ };
106
+ mCSS.forEach((match, groupIndex)=>{
107
+ if (groupIndex == 1) {
108
+ selector.key = match.trim();
109
+ }
110
+ if (groupIndex == 3) {
111
+ selector.value = match;
112
+ }
113
+ });
114
+ cssJSON[selector.key] = selector.value;
115
+ }
116
+ return cssJSON;
117
+ };
118
+
119
+ export { cssToJson, jsonToCss };
package/dist/esm/index.js CHANGED
@@ -1,109 +1,232 @@
1
1
  import { parseFragment, serializeOuter } from 'parse5';
2
+ import { cssEscape } from './css-escape.js';
3
+ import { cssToJson, jsonToCss } from './css-parser.js';
2
4
 
3
- const hashClassNames = (html, css, options) => {
4
- const documentFragment = parseFragment(html);
5
- const jsonHTML = documentFragment.childNodes;
6
- if (jsonHTML?.length) {
7
- const classes = {};
8
- const hashClasses = {};
9
- // Find & Obfuscate class in html
10
- loopNode(jsonHTML, (node) => {
11
- if (node?.attrs?.length) {
12
- const attrClass = node.attrs.find((item) => item.name == 'class');
13
- if (attrClass?.value) {
14
- const nodeClasses = attrClass.value.split(' ');
15
- if (nodeClasses?.length) {
16
- for (let i = 0; i < nodeClasses.length; i++) {
17
- const nodeClass = nodeClasses[i]?.trim() || '';
18
- if (options?.ignoreClasses?.includes(nodeClass)) {
19
- continue;
20
- }
21
- if (nodeClass) {
22
- const data = classes[nodeClass];
23
- if (!data) {
24
- // eslint-disable-next-line no-constant-condition
25
- while (true) {
26
- const newClass = `a${ID()}`;
27
- if (!hashClasses[newClass]) {
28
- hashClasses[newClass] = true; // flag
29
- // Cache
30
- classes[nodeClass] = {
31
- hash: newClass,
32
- };
33
- // replace
34
- attrClass.value = attrClass.value.replace(nodeClass, newClass);
35
- break;
36
- }
37
- }
38
- }
39
- else {
40
- attrClass.value = attrClass.value.replace(nodeClass, data.hash);
41
- }
42
- }
43
- }
44
- }
45
- }
46
- }
47
- });
48
- let newHTML = '';
49
- for (let i = 0; i < jsonHTML.length; i++) {
50
- const node = jsonHTML[i];
51
- if (node) {
52
- newHTML += serializeOuter(node);
53
- }
54
- }
55
- // Replace class in css
56
- const orderClasses = [];
57
- for (const oldClass in classes) {
58
- if (Object.prototype.hasOwnProperty.call(classes, oldClass)) {
59
- const data = classes[oldClass];
60
- if (data?.hash) {
61
- orderClasses.push({
62
- oldClass: oldClass,
63
- newClass: data.hash,
64
- });
65
- }
66
- }
67
- }
68
- orderClasses.sort((a, b) => {
69
- if (a.oldClass.includes(b.oldClass)) {
70
- return -1;
71
- }
72
- return 1;
73
- });
74
- let newCSS = css;
75
- for (let i = 0; i < orderClasses.length; i++) {
76
- const data = orderClasses[i];
77
- newCSS = newCSS.replaceAll(`.${data?.oldClass}`, `.${data?.newClass}`);
78
- }
79
- return {
80
- html: newHTML,
81
- css: newCSS,
82
- };
83
- }
84
- return {
85
- html,
86
- css,
87
- };
88
- };
89
- const loopNode = (childNodes, callback) => {
90
- if (childNodes?.length) {
91
- for (let i = 0; i < childNodes.length; i++) {
92
- const childNode = childNodes[i];
93
- if (childNode) {
94
- callback(childNode);
95
- if (childNode.childNodes?.length) {
96
- loopNode(childNode.childNodes, callback);
97
- }
98
- }
99
- }
100
- }
101
- };
102
- const ID = function () {
103
- // Math.random should be unique because of its seeding algorithm.
104
- // Convert it to base 36 (numbers + letters), and grab the first 9 characters
105
- // after the decimal.
106
- return Math.random().toString(36).substr(2, 5);
5
+ const hashClassNames = (html, css, options)=>{
6
+ const { html: htmlWithoutLiquid , arrayMatch } = replaceLiquidToBKPlaceholder(html);
7
+ const documentFragment = parseFragment(htmlWithoutLiquid);
8
+ const jsonHTML = documentFragment.childNodes;
9
+ if (jsonHTML?.length) {
10
+ const classes = {};
11
+ const hashClasses = {};
12
+ // Find & Obfuscate class in html
13
+ const loopNode = (childNodes, callback)=>{
14
+ if (childNodes?.length) {
15
+ for(let i = 0; i < childNodes.length; i++){
16
+ const childNode = childNodes[i];
17
+ if (childNode) {
18
+ const keepClass = childNode.attrs?.find((item)=>item.name == 'data-keep-class')?.value;
19
+ if (!keepClass) {
20
+ callback(childNode);
21
+ if (childNode.childNodes?.length) {
22
+ loopNode(childNode.childNodes, callback);
23
+ }
24
+ }
25
+ }
26
+ }
27
+ }
28
+ };
29
+ loopNode(jsonHTML, (node)=>{
30
+ if (node?.attrs?.length) {
31
+ const attrClass = node.attrs.find((item)=>item.name == 'class');
32
+ if (attrClass?.value) {
33
+ const nodeClasses = attrClass.value.split(' ');
34
+ if (nodeClasses?.length) {
35
+ for(let i = 0; i < nodeClasses.length; i++){
36
+ let nodeClass = nodeClasses[i]?.trim() || '';
37
+ nodeClass = nodeClass.replace(/bk-liquid.*?bk-liquid/g, '');
38
+ if (options?.ignoreClasses?.includes(nodeClass)) {
39
+ continue;
40
+ }
41
+ if (nodeClass) {
42
+ const data = classes[nodeClass];
43
+ if (!data) {
44
+ // eslint-disable-next-line no-constant-condition
45
+ while(true){
46
+ const newClass = `a${ID()}`;
47
+ if (!hashClasses[newClass]) {
48
+ hashClasses[newClass] = true; // flag
49
+ // Cache
50
+ classes[nodeClass] = {
51
+ hash: newClass
52
+ };
53
+ // replace
54
+ replaceClass(node, nodeClass, newClass);
55
+ break;
56
+ }
57
+ }
58
+ } else {
59
+ replaceClass(node, nodeClass, data.hash);
60
+ }
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+ });
67
+ let newHTML = '';
68
+ for(let i = 0; i < jsonHTML.length; i++){
69
+ const node = jsonHTML[i];
70
+ if (node) {
71
+ newHTML += serializeOuter(node);
72
+ }
73
+ }
74
+ // Replace class in css
75
+ const orderClasses = [];
76
+ for(const oldClass in classes){
77
+ if (Object.prototype.hasOwnProperty.call(classes, oldClass)) {
78
+ const data = classes[oldClass];
79
+ if (data?.hash) {
80
+ orderClasses.push({
81
+ oldClass: oldClass,
82
+ newClass: data.hash
83
+ });
84
+ }
85
+ }
86
+ }
87
+ orderClasses.sort((a, b)=>{
88
+ if (a.oldClass.includes(b.oldClass)) {
89
+ return -1;
90
+ }
91
+ return 1;
92
+ });
93
+ let newCSS = css;
94
+ if (newCSS) {
95
+ const jsonCSS = cssToJson(newCSS);
96
+ const loopCSSSelector = (style)=>{
97
+ // Replace selector
98
+ for(const selector in style.children){
99
+ if (Object.prototype.hasOwnProperty.call(style.children, selector)) {
100
+ let newSelector = selector;
101
+ // Replace selector new selector
102
+ const styleSelector = style.children[selector];
103
+ const selectors = newSelector.split(',');
104
+ for(let index = 0; index < selectors.length; index++){
105
+ const itemSelector = selectors[index];
106
+ if (itemSelector) {
107
+ const selectorClasses = itemSelector.split('.');
108
+ for(let i = 0; i < selectorClasses.length; i++){
109
+ let selectorClass = selectorClasses[i];
110
+ selectorClass = selectorClass?.trim();
111
+ // eslint-disable-next-line prefer-regex-literals, no-useless-escape, regexp/no-useless-flag
112
+ selectorClass = selectorClass?.replace(new RegExp(`\[.*?\]`, 'gm'), ''); // Replace [data]
113
+ selectorClass = selectorClass?.replace(/:(a(ctive|ny(-link)*)|checked|d(efault|i(r\(\)|sabled))|e(mpty|nabled)|f(irst(-(child|of-type))*|ullscreen|ocus)|hover|in(determinate|valid|-range)|la(ng\(\)|(st-(child|of-type)))|l(eft|ink)|n(ot\(\)|th-((last-)*(child|of-type)\(\)))|o(nly-(child|of-type)|ptional|ut-of-range)|r(e(ad-(only|write)|quired)|ight|oot)|scope|target|v(alid|isited))/g, '');
114
+ if (selectorClass) {
115
+ for(let index = 0; index < orderClasses.length; index++){
116
+ const orderClass = orderClasses[index];
117
+ if (orderClass) {
118
+ let flag = false;
119
+ if (orderClass.oldClass == selectorClass) {
120
+ flag = true;
121
+ } else if (cssEscape(orderClass.oldClass) == selectorClass) {
122
+ flag = true;
123
+ }
124
+ if (flag) {
125
+ selectorClasses[i] = replaceSelector(selectorClasses[i] || '', orderClass.oldClass, orderClass.newClass);
126
+ break;
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }
132
+ selectors[index] = selectorClasses.join('.');
133
+ }
134
+ }
135
+ newSelector = selectors.join(',');
136
+ // replace
137
+ if (newSelector != selector && styleSelector) {
138
+ style.children[newSelector] = styleSelector;
139
+ delete style.children[selector];
140
+ }
141
+ }
142
+ }
143
+ // Loop
144
+ if (style.children) {
145
+ for(const selectorChild in style.children){
146
+ if (Object.prototype.hasOwnProperty.call(style.children, selectorChild)) {
147
+ const styleChild = style.children[selectorChild];
148
+ if (styleChild) {
149
+ loopCSSSelector(styleChild);
150
+ }
151
+ }
152
+ }
153
+ }
154
+ };
155
+ loopCSSSelector(jsonCSS);
156
+ newCSS = jsonToCss(jsonCSS);
157
+ }
158
+ newHTML = replaceBKPlaceholderToLiquid(newHTML, arrayMatch);
159
+ return {
160
+ html: newHTML,
161
+ css: newCSS
162
+ };
163
+ }
164
+ // Return default
165
+ return {
166
+ html,
167
+ css
168
+ };
169
+ };
170
+ const ID = function() {
171
+ // Math.random should be unique because of its seeding algorithm.
172
+ // Convert it to base 36 (numbers + letters), and grab the first 9 characters
173
+ // after the decimal.
174
+ return Math.random().toString(36).substr(2, 5);
175
+ };
176
+ const replaceClass = (node, oldClass, newClass)=>{
177
+ if (node.attrs?.length) {
178
+ const attrClass = node.attrs.find((item)=>item.name == 'class');
179
+ if (attrClass?.value) {
180
+ const values = attrClass.value.split(' ');
181
+ for(let i = 0; i < values.length; i++){
182
+ const value = values[i];
183
+ if (value) {
184
+ const valueWithoutLiquid = value?.replace(/bk-liquid.*?bk-liquid/g, '');
185
+ if (valueWithoutLiquid == oldClass) {
186
+ const newValue = value?.replace(oldClass, newClass) || value;
187
+ values[i] = newValue;
188
+ }
189
+ }
190
+ }
191
+ attrClass.value = values.join(' ');
192
+ }
193
+ }
194
+ };
195
+ const replaceSelector = (selector, oldClass, newClass)=>{
196
+ let newSelector = selector;
197
+ newSelector = newSelector.replaceAll(oldClass, newClass);
198
+ newSelector = newSelector.replaceAll(cssEscape(oldClass), cssEscape(newClass));
199
+ return newSelector;
200
+ };
201
+ const replaceLiquidToBKPlaceholder = (html)=>{
202
+ // eslint-disable-next-line regexp/no-dupe-disjunctions
203
+ const regex = /\{%(.|[\n\r\t])*?%\}|\{\{(.|[\n\r\t])*?\}\}/g;
204
+ const arrayMatch = html.match(regex);
205
+ if (arrayMatch?.length) {
206
+ for(let i = 0; i < arrayMatch.length; i++){
207
+ const match = arrayMatch[i];
208
+ if (match) {
209
+ html = html.replace(match, 'bk-liquid' + i + 'bk-liquid');
210
+ }
211
+ }
212
+ }
213
+ return {
214
+ html,
215
+ arrayMatch
216
+ };
217
+ };
218
+ const replaceBKPlaceholderToLiquid = (html, arrayMatch)=>{
219
+ if (arrayMatch?.length) {
220
+ for(let i = 0; i < arrayMatch.length; i++){
221
+ const match = arrayMatch[i];
222
+ if (match) {
223
+ html = html.replace('bk-liquid' + i + 'bk-liquid=""', match); // html data auto fix
224
+ html = html.replace('bk-liquid' + i + "bk-liquid=''", match); // html data auto fix
225
+ html = html.replace('bk-liquid' + i + 'bk-liquid', match);
226
+ }
227
+ }
228
+ }
229
+ return html;
107
230
  };
108
231
 
109
232
  export { hashClassNames };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gem-sdk/hash-class-names",
3
- "version": "1.0.0",
3
+ "version": "1.11.0",
4
4
  "license": "MIT",
5
5
  "main": "dist/cjs/index.js",
6
6
  "scripts": {
@@ -17,6 +17,10 @@
17
17
  "dependencies": {
18
18
  "parse5": "^7.1.2"
19
19
  },
20
+ "devDependencies": {},
21
+ "files": [
22
+ "dist"
23
+ ],
20
24
  "module": "dist/esm/index.js",
21
25
  "types": "dist/types/index.d.ts",
22
26
  "exports": {
@@ -26,8 +30,5 @@
26
30
  "require": "./dist/cjs/index.js",
27
31
  "types": "./dist/types/index.d.ts"
28
32
  }
29
- },
30
- "files": [
31
- "dist"
32
- ]
33
+ }
33
34
  }