Skip to content

Commit a0efcda

Browse files
alreadyExistedevilebottnawi
authored andcommitted
fix: properly export locals with escaped characters (#917)
1 parent edb3ffa commit a0efcda

File tree

4 files changed

+213
-3
lines changed

4 files changed

+213
-3
lines changed

src/utils.js

+47-3
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,7 @@ function getLocalIdent(loaderContext, localIdentName, localName, options) {
8888
options
8989
);
9090

91-
return hash
92-
.replace(new RegExp('[^a-zA-Z0-9\\-_\u00A0-\uFFFF]', 'g'), '-')
93-
.replace(/^((-?[0-9])|--)/, '_$1');
91+
return normalizeIdentifier(hash);
9492
}
9593

9694
function getFilter(filter, resourcePath, defaultFilter = null) {
@@ -107,6 +105,52 @@ function getFilter(filter, resourcePath, defaultFilter = null) {
107105
};
108106
}
109107

108+
function normalizeIdentifier(value) {
109+
const escapedSymbols = [
110+
'~',
111+
'!',
112+
'@',
113+
'#',
114+
'$',
115+
'%',
116+
'&',
117+
'^',
118+
'*',
119+
'(',
120+
')',
121+
'{',
122+
'}',
123+
'[',
124+
']',
125+
'`',
126+
'/',
127+
'=',
128+
'?',
129+
'+',
130+
'\\',
131+
'|',
132+
'-',
133+
'_',
134+
':',
135+
';',
136+
"'",
137+
'"',
138+
',',
139+
'<',
140+
'.',
141+
'>',
142+
];
143+
144+
const identifiersRegExp = new RegExp(
145+
`[^a-zA-Z0-9${escapedSymbols.join('\\')}\\-_\u00A0-\uFFFF]`,
146+
'g'
147+
);
148+
149+
return value
150+
.replace(identifiersRegExp, '-')
151+
.replace(/^((-?[0-9])|--)/, '_$1');
152+
}
153+
110154
export {
111155
getImportPrefix,
112156
getLocalIdent,

test/__snapshots__/localIdentName-option.test.js.snap

+134
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ Array [
3535
:local(.-a0-34a___f) {
3636
color: red;
3737
}
38+
39+
:local(.m_x_\\\\@) {
40+
margin-left: auto !important;
41+
margin-right: auto !important;
42+
}
43+
44+
:local(.B\\\\&W\\\\?) {
45+
margin-left: auto !important;
46+
margin-right: auto !important;
47+
}
3848
",
3949
"",
4050
],
@@ -78,6 +88,16 @@ Array [
7888
:local(.-a0-34a___f) {
7989
color: red;
8090
}
91+
92+
:local(.m_x_\\\\@) {
93+
margin-left: auto !important;
94+
margin-right: auto !important;
95+
}
96+
97+
:local(.B\\\\&W\\\\?) {
98+
margin-left: auto !important;
99+
margin-right: auto !important;
100+
}
81101
",
82102
"",
83103
],
@@ -121,6 +141,16 @@ Array [
121141
:local(.-a0-34a___f) {
122142
color: red;
123143
}
144+
145+
:local(.m_x_\\\\@) {
146+
margin-left: auto !important;
147+
margin-right: auto !important;
148+
}
149+
150+
:local(.B\\\\&W\\\\?) {
151+
margin-left: auto !important;
152+
margin-right: auto !important;
153+
}
124154
",
125155
"",
126156
],
@@ -164,6 +194,16 @@ Array [
164194
:local(.-a0-34a___f) {
165195
color: red;
166196
}
197+
198+
:local(.m_x_\\\\@) {
199+
margin-left: auto !important;
200+
margin-right: auto !important;
201+
}
202+
203+
:local(.B\\\\&W\\\\?) {
204+
margin-left: auto !important;
205+
margin-right: auto !important;
206+
}
167207
",
168208
"",
169209
],
@@ -207,6 +247,16 @@ Array [
207247
:local(.-a0-34a___f) {
208248
color: red;
209249
}
250+
251+
:local(.m_x_\\\\@) {
252+
margin-left: auto !important;
253+
margin-right: auto !important;
254+
}
255+
256+
:local(.B\\\\&W\\\\?) {
257+
margin-left: auto !important;
258+
margin-right: auto !important;
259+
}
210260
",
211261
"",
212262
],
@@ -250,6 +300,16 @@ Array [
250300
:local(.-a0-34a___f) {
251301
color: red;
252302
}
303+
304+
:local(.m_x_\\\\@) {
305+
margin-left: auto !important;
306+
margin-right: auto !important;
307+
}
308+
309+
:local(.B\\\\&W\\\\?) {
310+
margin-left: auto !important;
311+
margin-right: auto !important;
312+
}
253313
",
254314
"",
255315
],
@@ -293,10 +353,84 @@ Array [
293353
:local(.-a0-34a___f) {
294354
color: red;
295355
}
356+
357+
:local(.m_x_\\\\@) {
358+
margin-left: auto !important;
359+
margin-right: auto !important;
360+
}
361+
362+
:local(.B\\\\&W\\\\?) {
363+
margin-left: auto !important;
364+
margin-right: auto !important;
365+
}
296366
",
297367
"",
298368
],
299369
]
300370
`;
301371

302372
exports[`localIdentName option should use hash prefix: warnings 1`] = `Array []`;
373+
374+
exports[`localIdentName option should сorrectly replace escaped symbols in selector: errors 1`] = `Array []`;
375+
376+
exports[`localIdentName option should сorrectly replace escaped symbols in selector: locals 1`] = `
377+
Object {
378+
"-a0-34a___f": "-a0-34a___f--2nJ5",
379+
"B&W?": "B&W?--1s8i",
380+
"_test": "_test--23te",
381+
"className": "className--1E8H",
382+
"m_x_@": "m_x_@--2G3b",
383+
"someId": "someId--3w7J",
384+
"subClass": "subClass--3lo0",
385+
"test": "test--NW9Y",
386+
}
387+
`;
388+
389+
exports[`localIdentName option should сorrectly replace escaped symbols in selector: module (evaluated) 1`] = `
390+
Array [
391+
Array [
392+
1,
393+
".test--NW9Y {
394+
background: red;
395+
}
396+
397+
._test--23te {
398+
background: blue;
399+
}
400+
401+
.className--1E8H {
402+
background: red;
403+
}
404+
405+
#someId--3w7J {
406+
background: green;
407+
}
408+
409+
.className--1E8H .subClass--3lo0 {
410+
color: green;
411+
}
412+
413+
#someId--3w7J .subClass--3lo0 {
414+
color: blue;
415+
}
416+
417+
.-a0-34a___f--2nJ5 {
418+
color: red;
419+
}
420+
421+
.m_x_\\\\@--2G3b {
422+
margin-left: auto !important;
423+
margin-right: auto !important;
424+
}
425+
426+
.B\\\\&W\\\\?--1s8i {
427+
margin-left: auto !important;
428+
margin-right: auto !important;
429+
}
430+
",
431+
"",
432+
],
433+
]
434+
`;
435+
436+
exports[`localIdentName option should сorrectly replace escaped symbols in selector: warnings 1`] = `Array []`;

test/fixtures/modules/localIdentName.css

+10
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,13 @@
2525
:local(.-a0-34a___f) {
2626
color: red;
2727
}
28+
29+
:local(.m_x_\@) {
30+
margin-left: auto !important;
31+
margin-right: auto !important;
32+
}
33+
34+
:local(.B\&W\?) {
35+
margin-left: auto !important;
36+
margin-right: auto !important;
37+
}

test/localIdentName-option.test.js

+22
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,26 @@ describe('localIdentName option', () => {
117117
expect(stats.compilation.warnings).toMatchSnapshot('warnings');
118118
expect(stats.compilation.errors).toMatchSnapshot('errors');
119119
});
120+
121+
it('should сorrectly replace escaped symbols in selector', async () => {
122+
const config = {
123+
loader: {
124+
options: {
125+
importLoaders: 2,
126+
localIdentName: '[local]--[hash:base64:4]',
127+
modules: true,
128+
},
129+
},
130+
};
131+
const testId = './modules/localIdentName.css';
132+
const stats = await webpack(testId, config);
133+
const { modules } = stats.toJson();
134+
const module = modules.find((m) => m.id === testId);
135+
const evaluatedModule = evaluated(module.source, modules);
136+
137+
expect(evaluatedModule).toMatchSnapshot('module (evaluated)');
138+
expect(evaluatedModule.locals).toMatchSnapshot('locals');
139+
expect(stats.compilation.warnings).toMatchSnapshot('warnings');
140+
expect(stats.compilation.errors).toMatchSnapshot('errors');
141+
});
120142
});

0 commit comments

Comments
 (0)