diff --git a/common/changes/@rushstack/webpack5-localization-plugin/localization-plugin-perf_2024-08-06-01-24.json b/common/changes/@rushstack/webpack5-localization-plugin/localization-plugin-perf_2024-08-06-01-24.json new file mode 100644 index 00000000000..9add64f30f7 --- /dev/null +++ b/common/changes/@rushstack/webpack5-localization-plugin/localization-plugin-perf_2024-08-06-01-24.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/webpack5-localization-plugin", + "comment": "Improve performance of localized asset reconstruction.", + "type": "patch" + } + ], + "packageName": "@rushstack/webpack5-localization-plugin" +} \ No newline at end of file diff --git a/webpack/webpack5-localization-plugin/src/AssetProcessor.ts b/webpack/webpack5-localization-plugin/src/AssetProcessor.ts index 95efe8b8511..efc93e56fb0 100644 --- a/webpack/webpack5-localization-plugin/src/AssetProcessor.ts +++ b/webpack/webpack5-localization-plugin/src/AssetProcessor.ts @@ -228,19 +228,25 @@ function _reconstructLocalized( const escapedBackslash: string = element.escapedBackslash || '\\'; - // Replace backslashes with the properly escaped backslash - BACKSLASH_REGEX.lastIndex = -1; - newValue = newValue.replace(BACKSLASH_REGEX, escapedBackslash); - - // @todo: look into using JSON.parse(...) to get the escaping characters - const escapingCharacterSequence: string = escapedBackslash.slice(escapedBackslash.length / 2); + if (newValue.includes('\\')) { + // The vast majority of localized strings do not contain `\\`, so this check avoids an allocation. + // Replace backslashes with the properly escaped backslash + BACKSLASH_REGEX.lastIndex = -1; + newValue = newValue.replace(BACKSLASH_REGEX, escapedBackslash); + } // Ensure the the quotemark, apostrophe, tab, and newline characters are properly escaped ESCAPE_REGEX.lastIndex = -1; - newValue = newValue.replace( - ESCAPE_REGEX, - (match) => `${escapingCharacterSequence}${ESCAPE_MAP.get(match)}` - ); + if (ESCAPE_REGEX.test(newValue)) { + // The majority of localized strings do not contain the characters that need to be escaped, + // so this check avoids an allocation. + // @todo: look into using JSON.parse(...) to get the escaping characters + const escapingCharacterSequence: string = escapedBackslash.slice(escapedBackslash.length / 2); + newValue = newValue.replace( + ESCAPE_REGEX, + (match) => `${escapingCharacterSequence}${ESCAPE_MAP.get(match)}` + ); + } result.replace(element.start, element.end - 1, newValue); break; @@ -305,9 +311,7 @@ function _parseStringToReconstructionSequence( const jsonStringifyFormatLocaleForFilenameFn: FormatLocaleForFilenameFn = (locale: string) => JSON.stringify(formatLocaleForFilenameFn(locale)); - let regexResult: RegExpExecArray | null; - PLACEHOLDER_REGEX.lastIndex = -1; - while ((regexResult = PLACEHOLDER_REGEX.exec(source))) { + for (const regexResult of source.matchAll(PLACEHOLDER_REGEX)) { const [placeholder, escapedBackslash, elementLabel, placeholderSerialNumber] = regexResult; const start: number = regexResult.index; const end: number = start + placeholder.length;