Skip to content

Commit d779eb1

Browse files
feat: escape getLocalIdent by default (#1196)
BREAKING CHANGE: returned value from the `getLocalIdent` escapes by default, the `exportName` value is always unescaped
1 parent dd52931 commit d779eb1

File tree

5 files changed

+157
-29
lines changed

5 files changed

+157
-29
lines changed

src/utils.js

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ const filenameReservedRegex = /[<>:"/\\|?*]/g;
4949
// eslint-disable-next-line no-control-regex
5050
const reControlChars = /[\u0000-\u001f\u0080-\u009f]/g;
5151

52+
function escapeLocalident(localident) {
53+
return cssesc(
54+
localident
55+
// For `[hash]` placeholder
56+
.replace(/^((-?[0-9])|--)/, '_$1')
57+
.replace(filenameReservedRegex, '-')
58+
.replace(reControlChars, '-')
59+
.replace(/\./g, '-'),
60+
{ isIdentifier: true }
61+
);
62+
}
63+
5264
function defaultGetLocalIdent(
5365
loaderContext,
5466
localIdentName,
@@ -60,19 +72,9 @@ function defaultGetLocalIdent(
6072
const request = normalizePath(path.relative(context, resourcePath));
6173

6274
// eslint-disable-next-line no-param-reassign
63-
options.content = `${hashPrefix + request}\x00${unescape(localName)}`;
75+
options.content = `${hashPrefix + request}\x00${localName}`;
6476

65-
// Using `[path]` placeholder outputs `/` we need escape their
66-
// Also directories can contains invalid characters for css we need escape their too
67-
return cssesc(
68-
interpolateName(loaderContext, localIdentName, options)
69-
// For `[hash]` placeholder
70-
.replace(/^((-?[0-9])|--)/, '_$1')
71-
.replace(filenameReservedRegex, '-')
72-
.replace(reControlChars, '-')
73-
.replace(/\./g, '-'),
74-
{ isIdentifier: true }
75-
).replace(/\\\[local\\]/gi, localName);
77+
return interpolateName(loaderContext, localIdentName, options);
7678
}
7779

7880
function normalizeUrl(url, isStringValue) {
@@ -143,7 +145,8 @@ function getModulesOptions(rawOptions, loaderContext) {
143145
localIdentHashPrefix: '',
144146
// eslint-disable-next-line no-undefined
145147
localIdentRegExp: undefined,
146-
getLocalIdent: defaultGetLocalIdent,
148+
// eslint-disable-next-line no-undefined
149+
getLocalIdent: undefined,
147150
namedExport: false,
148151
exportLocalsConvention: 'asIs',
149152
exportOnlyLocals: false,
@@ -281,32 +284,42 @@ function getModulesPlugins(options, loaderContext) {
281284
extractImports(),
282285
modulesScope({
283286
generateScopedName(exportName) {
284-
let localIdent = getLocalIdent(
285-
loaderContext,
286-
localIdentName,
287-
exportName,
288-
{
289-
context: localIdentContext,
290-
hashPrefix: localIdentHashPrefix,
291-
regExp: localIdentRegExp,
292-
}
293-
);
287+
let localIdent;
288+
289+
if (typeof getLocalIdent !== 'undefined') {
290+
localIdent = getLocalIdent(
291+
loaderContext,
292+
localIdentName,
293+
unescape(exportName),
294+
{
295+
context: localIdentContext,
296+
hashPrefix: localIdentHashPrefix,
297+
regExp: localIdentRegExp,
298+
}
299+
);
300+
}
294301

295302
// A null/undefined value signals that we should invoke the default
296303
// getLocalIdent method.
297-
if (localIdent == null) {
304+
if (typeof localIdent === 'undefined' || localIdent === null) {
298305
localIdent = defaultGetLocalIdent(
299306
loaderContext,
300307
localIdentName,
301-
exportName,
308+
unescape(exportName),
302309
{
303310
context: localIdentContext,
304311
hashPrefix: localIdentHashPrefix,
305312
regExp: localIdentRegExp,
306313
}
307314
);
315+
316+
return escapeLocalident(localIdent).replace(
317+
/\\\[local\\]/gi,
318+
exportName
319+
);
308320
}
309-
return localIdent;
321+
322+
return escapeLocalident(localIdent);
310323
},
311324
exportGlobals: options.modules.exportGlobals,
312325
}),

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

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,17 +137,69 @@ Array [
137137

138138
exports[`"modules" option issue #861: warnings 1`] = `Array []`;
139139

140+
exports[`"modules" option issue #966 - values in selectors aren't escaped properly: errors 1`] = `Array []`;
141+
142+
exports[`"modules" option issue #966 - values in selectors aren't escaped properly: module 1`] = `
143+
"// Imports
144+
import ___CSS_LOADER_API_IMPORT___ from \\"../../../../src/runtime/api.js\\";
145+
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false);
146+
// Module
147+
___CSS_LOADER_EXPORT___.push([module.id, \\"._7-foo-class {\\\\n color: red;\\\\n}\\\\n\\\\n.\\\\\\\\--bar-class {\\\\n color: red;\\\\n}\\\\n\\\\n.\\\\\\\\--baz-class {\\\\n color: red;\\\\n}\\\\n\\\\n.fooBaz-class-continuation {\\\\n color: red;\\\\n}\\\\n\\\\n.some.class {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]);
148+
// Exports
149+
___CSS_LOADER_EXPORT___.locals = {
150+
\\"foo-class\\": \\"_7-foo-class\\",
151+
\\"bar-class\\": \\"--bar-class\\",
152+
\\"baz-class\\": \\"--baz-class\\",
153+
\\"fooBaz-class\\": \\"fooBaz-class-continuation\\",
154+
\\"some\\": \\"some\\",
155+
\\"class\\": \\"class\\"
156+
};
157+
export default ___CSS_LOADER_EXPORT___;
158+
"
159+
`;
160+
161+
exports[`"modules" option issue #966 - values in selectors aren't escaped properly: result 1`] = `
162+
Array [
163+
Array [
164+
"./modules/issue-966/issue-966.css",
165+
"._7-foo-class {
166+
color: red;
167+
}
168+
169+
.\\\\--bar-class {
170+
color: red;
171+
}
172+
173+
.\\\\--baz-class {
174+
color: red;
175+
}
176+
177+
.fooBaz-class-continuation {
178+
color: red;
179+
}
180+
181+
.some.class {
182+
color: red;
183+
}
184+
",
185+
"",
186+
],
187+
]
188+
`;
189+
190+
exports[`"modules" option issue #966 - values in selectors aren't escaped properly: warnings 1`] = `Array []`;
191+
140192
exports[`"modules" option issue #966: errors 1`] = `Array []`;
141193

142194
exports[`"modules" option issue #966: module 1`] = `
143195
"// Imports
144196
import ___CSS_LOADER_API_IMPORT___ from \\"../../../../src/runtime/api.js\\";
145197
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false);
146198
// Module
147-
___CSS_LOADER_EXPORT___.push([module.id, \\".button.hey {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]);
199+
___CSS_LOADER_EXPORT___.push([module.id, \\".button-hey {\\\\n color: red;\\\\n}\\\\n\\", \\"\\"]);
148200
// Exports
149201
___CSS_LOADER_EXPORT___.locals = {
150-
\\"button\\": \\"button.hey\\"
202+
\\"button\\": \\"button-hey\\"
151203
};
152204
export default ___CSS_LOADER_EXPORT___;
153205
"
@@ -157,7 +209,7 @@ exports[`"modules" option issue #966: result 1`] = `
157209
Array [
158210
Array [
159211
"./modules/issue-966/button.css",
160-
".button.hey {
212+
".button-hey {
161213
color: red;
162214
}
163215
",
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.foo-class {
2+
color: red;
3+
}
4+
5+
.bar-class {
6+
color: red;
7+
}
8+
9+
.baz-class {
10+
color: red;
11+
}
12+
13+
.fooBaz-class {
14+
color: red;
15+
}
16+
17+
.some.class {
18+
color: red;
19+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import css from './issue-966.css';
2+
3+
__export__ = css;
4+
5+
export default css;

test/modules-option.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,45 @@ describe('"modules" option', () => {
491491
expect(getErrors(stats)).toMatchSnapshot('errors');
492492
});
493493

494+
it("issue #966 - values in selectors aren't escaped properly", async () => {
495+
const compiler = getCompiler('./modules/issue-966/issue-966.js', {
496+
modules: {
497+
getLocalIdent: (loaderContext, localIdentName, localName) => {
498+
if (localName === 'foo-class') {
499+
return `7-${localName}`;
500+
}
501+
502+
if (localName === 'bar-class') {
503+
return `>-${localName}`;
504+
}
505+
506+
if (localName === 'baz-class') {
507+
return `\u0000-${localName}`;
508+
}
509+
510+
if (localName === 'fooBaz-class') {
511+
return `${localName}.continuation`;
512+
}
513+
514+
return null;
515+
},
516+
localIdentName: '[local]',
517+
},
518+
});
519+
const stats = await compile(compiler);
520+
521+
expect(
522+
getModuleSource('./modules/issue-966/issue-966.css', stats)
523+
).toMatchSnapshot('module');
524+
525+
expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot(
526+
'result'
527+
);
528+
529+
expect(getWarnings(stats)).toMatchSnapshot('warnings');
530+
expect(getErrors(stats)).toMatchSnapshot('errors');
531+
});
532+
494533
it('issue #967', async () => {
495534
const compiler = getCompiler('./modules/issue-967/path-placeholder.js', {
496535
modules: {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy