From 4584898163e4adcbbb37661e18c42154480aa1de Mon Sep 17 00:00:00 2001 From: arturovt Date: Thu, 19 Mar 2026 14:57:42 +0200 Subject: [PATCH] perf: drop lodash, cache publicPath regex, avoid forEach closure and double html scan Replace lodash escapeRegExp with an inline implementation to remove the runtime dependency. Cache compiled publicPath RegExp instances per unique path instead of rebuilding on every getCSSStyle call. Switch forEach to for...of in prepare() to avoid closure allocation per asset. Replace the indexOf + replace double-pass in processStyle with a single replace(), detecting a missing target by comparing the result to the original string. Also convert replaceConfig and styleTagFactory from getters to constructor-assigned readonly fields. --- package.json | 1 - pnpm-lock.yaml | 3 --- src/core/base-plugin.ts | 52 +++++++++++++++++++++++------------------ src/utils.ts | 6 ++--- 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 8a35b93..e3911d9 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,6 @@ "html-webpack-plugin": "^3.0.0 || ^4.0.0 || ^5.0.0" }, "dependencies": { - "lodash": "^4.17.21", "tslib": "^2.6.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 455fc17..70ee05f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: dependencies: - lodash: - specifier: ^4.17.21 - version: 4.17.21 tslib: specifier: ^2.6.0 version: 2.6.0 diff --git a/src/core/base-plugin.ts b/src/core/base-plugin.ts index f22dec4..cfaa9c3 100644 --- a/src/core/base-plugin.ts +++ b/src/core/base-plugin.ts @@ -1,40 +1,40 @@ -import type { Compilation } from 'webpack'; +import type { Compilation } from 'webpack' import { - Config, - StyleTagFactory, + type Config, + type StyleTagFactory, DEFAULT_REPLACE_CONFIG, - FileCache, + type FileCache, + type ReplaceConfig, } from '../types' import { isCSS, escapeRegExp } from '../utils' export class BasePlugin { - protected cssStyleCache: FileCache = {} + private publicPathRegexMap = new Map() + protected readonly replaceConfig: ReplaceConfig + protected readonly styleTagFactory: StyleTagFactory - protected get replaceConfig() { - return this.config.replace || DEFAULT_REPLACE_CONFIG - } + protected cssStyleCache: FileCache = {} - protected get styleTagFactory(): StyleTagFactory { - return ( - this.config.styleTagFactory || + constructor(protected readonly config: Config = {}) { + this.replaceConfig = config.replace || DEFAULT_REPLACE_CONFIG + this.styleTagFactory = + config.styleTagFactory || (({ style }) => ``) - ) } - constructor(protected readonly config: Config = {}) {} - protected prepare({ assets }: Compilation) { - Object.keys(assets).forEach((fileName) => { + for (const fileName of Object.keys(assets)) { if (isCSS(fileName) && this.isCurrentFileNeedsToBeInlined(fileName)) { const source = assets[fileName].source() - this.cssStyleCache[fileName] = typeof source === 'string' ? source : source.toString() + this.cssStyleCache[fileName] = + typeof source === 'string' ? source : source.toString() if (!this.config.leaveCSSFile) { delete assets[fileName] } } - }) + } } protected getCSSStyle({ @@ -44,10 +44,13 @@ export class BasePlugin { cssLink: string publicPath: string }): string | undefined { + let publicPathRegex = this.publicPathRegexMap.get(publicPath) + if (publicPathRegex === undefined) { + publicPathRegex = new RegExp(`^${escapeRegExp(publicPath)}`) + this.publicPathRegexMap.set(publicPath, publicPathRegex) + } // Link pattern: publicPath + fileName + '?' + hash - const fileName = cssLink - .replace(new RegExp(`^${escapeRegExp(publicPath)}`), '') - .replace(/\?.+$/g, '') + const fileName = cssLink.replace(publicPathRegex, '').replace(/\?.+$/g, '') if (this.isCurrentFileNeedsToBeInlined(fileName)) { const style = this.cssStyleCache[fileName] @@ -90,13 +93,16 @@ export class BasePlugin { replaceValues.reverse() } - if (html.indexOf(this.replaceConfig.target) === -1) { + const replaced = html.replace( + this.replaceConfig.target, + replaceValues.join(''), + ) + if (replaced === html) { throw new Error( `Can not inject css style into "${htmlFileName}", as there is not replace target "${this.replaceConfig.target}"`, ) } - - return html.replace(this.replaceConfig.target, replaceValues.join('')) + return replaced } protected cleanUp(html: string) { diff --git a/src/utils.ts b/src/utils.ts index ab3215c..27b1393 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,6 @@ -import { escapeRegExp } from 'lodash' - -export { escapeRegExp } +export function escapeRegExp(str: string): string { + return str.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') +} export function is(filenameExtension: string) { const reg = new RegExp(`\.${filenameExtension}$`)