Skip to content

Commit 6eb5661

Browse files
feat: use template literal when it possible to prevent Maximum call stack size exceeded (#1525)
1 parent 0a2a596 commit 6eb5661

13 files changed

+1286
-134
lines changed

.cspell.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
"Brotli",
3737
"Contex",
3838
"vspace",
39-
"commitlint"
39+
"commitlint",
40+
"eslintcache"
4041
],
4142

4243
"ignorePaths": [

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ npm-debug.log*
1414
/test/fixtures/modules/composes/composes-absolute.css
1515
/test/fixtures/import/import-file-protocol.css
1616
/test/fixtures/url/url-file-protocol.css
17+
/test/fixtures/url/many-urls.css
1718

1819
.DS_Store
1920
Thumbs.db

src/index.js

+27-2
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,36 @@ export default async function loader(content, map, meta) {
227227
}
228228
}
229229

230+
let isTemplateLiteralSupported = false;
231+
232+
if (
233+
// eslint-disable-next-line no-underscore-dangle
234+
this._compilation &&
235+
// eslint-disable-next-line no-underscore-dangle
236+
this._compilation.options &&
237+
// eslint-disable-next-line no-underscore-dangle
238+
this._compilation.options.output &&
239+
// eslint-disable-next-line no-underscore-dangle
240+
this._compilation.options.output.environment &&
241+
// eslint-disable-next-line no-underscore-dangle
242+
this._compilation.options.output.environment.templateLiteral
243+
) {
244+
isTemplateLiteralSupported = true;
245+
}
246+
230247
const importCode = getImportCode(imports, options);
231248

232249
let moduleCode;
233250

234251
try {
235-
moduleCode = getModuleCode(result, api, replacements, options, this);
252+
moduleCode = getModuleCode(
253+
result,
254+
api,
255+
replacements,
256+
options,
257+
isTemplateLiteralSupported,
258+
this
259+
);
236260
} catch (error) {
237261
callback(error);
238262

@@ -243,7 +267,8 @@ export default async function loader(content, map, meta) {
243267
exports,
244268
replacements,
245269
needToUseIcssPlugin,
246-
options
270+
options,
271+
isTemplateLiteralSupported
247272
);
248273

249274
callback(null, `${importCode}${moduleCode}${exportCode}`);

src/utils.js

+89-25
Original file line numberDiff line numberDiff line change
@@ -1018,7 +1018,14 @@ function printParams(media, dedupe, supports, layer) {
10181018
return result;
10191019
}
10201020

1021-
function getModuleCode(result, api, replacements, options, loaderContext) {
1021+
function getModuleCode(
1022+
result,
1023+
api,
1024+
replacements,
1025+
options,
1026+
isTemplateLiteralSupported,
1027+
loaderContext
1028+
) {
10221029
if (options.modules.exportOnlyLocals === true) {
10231030
return "";
10241031
}
@@ -1034,7 +1041,9 @@ function getModuleCode(result, api, replacements, options, loaderContext) {
10341041
)}`;
10351042
}
10361043

1037-
let code = JSON.stringify(result.css);
1044+
let code = isTemplateLiteralSupported
1045+
? convertToTemplateLiteral(result.css)
1046+
: JSON.stringify(result.css);
10381047

10391048
let beforeCode = `var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(${
10401049
options.sourceMap
@@ -1067,12 +1076,21 @@ function getModuleCode(result, api, replacements, options, loaderContext) {
10671076
if (localName) {
10681077
code = code.replace(new RegExp(replacementName, "g"), () =>
10691078
options.modules.namedExport
1070-
? `" + ${importName}_NAMED___[${JSON.stringify(
1071-
getValidLocalName(
1072-
localName,
1073-
options.modules.exportLocalsConvention
1074-
)
1075-
)}] + "`
1079+
? isTemplateLiteralSupported
1080+
? `\${ ${importName}_NAMED___[${JSON.stringify(
1081+
getValidLocalName(
1082+
localName,
1083+
options.modules.exportLocalsConvention
1084+
)
1085+
)}] }`
1086+
: `" + ${importName}_NAMED___[${JSON.stringify(
1087+
getValidLocalName(
1088+
localName,
1089+
options.modules.exportLocalsConvention
1090+
)
1091+
)}] + "`
1092+
: isTemplateLiteralSupported
1093+
? `\${${importName}.locals[${JSON.stringify(localName)}]}`
10761094
: `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
10771095
);
10781096
} else {
@@ -1084,9 +1102,10 @@ function getModuleCode(result, api, replacements, options, loaderContext) {
10841102
getUrlOptions.length > 0 ? `, { ${getUrlOptions.join(", ")} }` : "";
10851103

10861104
beforeCode += `var ${replacementName} = ___CSS_LOADER_GET_URL_IMPORT___(${importName}${preparedOptions});\n`;
1087-
code = code.replace(
1088-
new RegExp(replacementName, "g"),
1089-
() => `" + ${replacementName} + "`
1105+
code = code.replace(new RegExp(replacementName, "g"), () =>
1106+
isTemplateLiteralSupported
1107+
? `\${${replacementName}}`
1108+
: `" + ${replacementName} + "`
10901109
);
10911110
}
10921111
}
@@ -1101,13 +1120,38 @@ function getModuleCode(result, api, replacements, options, loaderContext) {
11011120
return `${beforeCode}// Module\n___CSS_LOADER_EXPORT___.push([module.id, ${code}, ""${sourceMapValue}]);\n`;
11021121
}
11031122

1123+
const SLASH = "\\".charCodeAt(0);
1124+
const BACKTICK = "`".charCodeAt(0);
1125+
const DOLLAR = "$".charCodeAt(0);
1126+
1127+
function convertToTemplateLiteral(str) {
1128+
let escapedString = "";
1129+
1130+
for (let i = 0; i < str.length; i++) {
1131+
const code = str.charCodeAt(i);
1132+
1133+
escapedString +=
1134+
code === SLASH || code === BACKTICK || code === DOLLAR
1135+
? `\\${str[i]}`
1136+
: str[i];
1137+
}
1138+
1139+
return `\`${escapedString}\``;
1140+
}
1141+
11041142
function dashesCamelCase(str) {
11051143
return str.replace(/-+(\w)/g, (match, firstLetter) =>
11061144
firstLetter.toUpperCase()
11071145
);
11081146
}
11091147

1110-
function getExportCode(exports, replacements, icssPluginUsed, options) {
1148+
function getExportCode(
1149+
exports,
1150+
replacements,
1151+
icssPluginUsed,
1152+
options,
1153+
isTemplateLiteralSupported
1154+
) {
11111155
let code = "// Exports\n";
11121156

11131157
if (icssPluginUsed) {
@@ -1120,13 +1164,21 @@ function getExportCode(exports, replacements, icssPluginUsed, options) {
11201164

11211165
for (const name of normalizedNames) {
11221166
if (options.modules.namedExport) {
1123-
localsCode += `export var ${name} = ${JSON.stringify(value)};\n`;
1167+
localsCode += `export var ${name} = ${
1168+
isTemplateLiteralSupported
1169+
? convertToTemplateLiteral(value)
1170+
: JSON.stringify(value)
1171+
};\n`;
11241172
} else {
11251173
if (localsCode) {
11261174
localsCode += `,\n`;
11271175
}
11281176

1129-
localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}`;
1177+
localsCode += `\t${JSON.stringify(name)}: ${
1178+
isTemplateLiteralSupported
1179+
? convertToTemplateLiteral(value)
1180+
: JSON.stringify(value)
1181+
}`;
11301182
}
11311183
}
11321184
};
@@ -1148,23 +1200,35 @@ function getExportCode(exports, replacements, icssPluginUsed, options) {
11481200
new RegExp(replacementName, "g"),
11491201
() => {
11501202
if (options.modules.namedExport) {
1151-
return `" + ${importName}_NAMED___[${JSON.stringify(
1152-
getValidLocalName(
1153-
localName,
1154-
options.modules.exportLocalsConvention
1155-
)
1156-
)}] + "`;
1203+
return isTemplateLiteralSupported
1204+
? `\${${importName}_NAMED___[${JSON.stringify(
1205+
getValidLocalName(
1206+
localName,
1207+
options.modules.exportLocalsConvention
1208+
)
1209+
)}]}`
1210+
: `" + ${importName}_NAMED___[${JSON.stringify(
1211+
getValidLocalName(
1212+
localName,
1213+
options.modules.exportLocalsConvention
1214+
)
1215+
)}] + "`;
11571216
} else if (options.modules.exportOnlyLocals) {
1158-
return `" + ${importName}[${JSON.stringify(localName)}] + "`;
1217+
return isTemplateLiteralSupported
1218+
? `\${${importName}[${JSON.stringify(localName)}]}`
1219+
: `" + ${importName}[${JSON.stringify(localName)}] + "`;
11591220
}
11601221

1161-
return `" + ${importName}.locals[${JSON.stringify(localName)}] + "`;
1222+
return isTemplateLiteralSupported
1223+
? `\${${importName}.locals[${JSON.stringify(localName)}]}`
1224+
: `" + ${importName}.locals[${JSON.stringify(localName)}] + "`;
11621225
}
11631226
);
11641227
} else {
1165-
localsCode = localsCode.replace(
1166-
new RegExp(replacementName, "g"),
1167-
() => `" + ${replacementName} + "`
1228+
localsCode = localsCode.replace(new RegExp(replacementName, "g"), () =>
1229+
isTemplateLiteralSupported
1230+
? `\${${replacementName}}`
1231+
: `" + ${replacementName} + "`
11681232
);
11691233
}
11701234
}

0 commit comments

Comments
 (0)