From 4ee092815052f1df67738e7574c3ef02785079f5 Mon Sep 17 00:00:00 2001 From: mricoul Date: Tue, 3 Feb 2026 12:23:38 +0100 Subject: [PATCH 1/8] feat(svg): adds sprite hash manifest for cache busting Implements a webpack plugin that generates a JSON file containing content hashes for SVG sprite files. This allows for cache busting of sprite files by appending the hash as a query parameter to the sprite URL. The `Svg` service is updated to read the hash from the generated JSON and append it to the sprite URL. --- config/plugins.js | 2 + config/sprite-hash-plugin.js | 73 ++++++++++++++++++++++++++++++++++++ inc/Services/Svg.php | 39 ++++++++++++++++--- 3 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 config/sprite-hash-plugin.js diff --git a/config/plugins.js b/config/plugins.js index 8ed7d1c0..537ebd2a 100644 --- a/config/plugins.js +++ b/config/plugins.js @@ -11,6 +11,7 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPl const WebpackImageSizesPlugin = require('./webpack-image-sizes-plugin') const WebpackThemeJsonPlugin = require('./webpack-theme-json-plugin') +const SpriteHashPlugin = require('./sprite-hash-plugin') module.exports = { get: function (mode) { @@ -18,6 +19,7 @@ module.exports = { new WebpackThemeJsonPlugin({ watch: mode !== 'production', }), + new SpriteHashPlugin(), new CleanWebpackPlugin({ cleanOnceBeforeBuildPatterns: ['**/*', '!images', '!images/**'], }), diff --git a/config/sprite-hash-plugin.js b/config/sprite-hash-plugin.js new file mode 100644 index 00000000..eb2224c7 --- /dev/null +++ b/config/sprite-hash-plugin.js @@ -0,0 +1,73 @@ +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +/** + * Webpack plugin to generate content hashes for SVG sprite files. + * Creates a sprite-hashes.json file in the dist folder. + */ +class SpriteHashPlugin { + constructor(options = {}) { + this.options = { + outputPath: options.outputPath || 'dist', + spritePath: options.spritePath || 'dist/icons', + outputFilename: options.outputFilename || 'sprite-hashes.json', + hashLength: options.hashLength || 8, + }; + } + + apply(compiler) { + compiler.hooks.afterEmit.tapAsync( + 'SpriteHashPlugin', + (compilation, callback) => { + const spriteDir = path.resolve( + compiler.options.context, + this.options.spritePath + ); + const outputFile = path.resolve( + compiler.options.context, + this.options.outputPath, + this.options.outputFilename + ); + + if (!fs.existsSync(spriteDir)) { + console.warn( + `SpriteHashPlugin: Sprite directory not found: ${spriteDir}` + ); + callback(); + return; + } + + const hashes = {}; + const files = fs + .readdirSync(spriteDir) + .filter((file) => file.endsWith('.svg')); + + files.forEach((file) => { + const filePath = path.join(spriteDir, file); + const content = fs.readFileSync(filePath); + const hash = crypto + .createHash('md5') + .update(content) + .digest('hex') + .substring(0, this.options.hashLength); + + // Store with relative path as key + const relativePath = `icons/${file}`; + hashes[relativePath] = hash; + }); + + fs.writeFileSync(outputFile, JSON.stringify(hashes, null, 2)); + console.log( + `SpriteHashPlugin: Generated ${ + this.options.outputFilename + } with ${Object.keys(hashes).length} sprites` + ); + + callback(); + } + ); + } +} + +module.exports = SpriteHashPlugin; diff --git a/inc/Services/Svg.php b/inc/Services/Svg.php index b25df908..14ad9c7c 100644 --- a/inc/Services/Svg.php +++ b/inc/Services/Svg.php @@ -55,12 +55,13 @@ public function get_the_icon( string $icon_class, array $additionnal_classes = [ $icon_class = substr( $icon_class, $slash_pos + 1 ); } - $icon_slug = strpos( $icon_class, 'icon-' ) === 0 ? $icon_class : sprintf( 'icon-%s', $icon_class ); - $classes = [ 'icon', $icon_slug ]; - $classes = array_merge( $classes, $additionnal_classes ); - $classes = array_map( 'sanitize_html_class', $classes ); + $icon_slug = strpos( $icon_class, 'icon-' ) === 0 ? $icon_class : sprintf( 'icon-%s', $icon_class ); + $classes = [ 'icon', $icon_slug ]; + $classes = array_merge( $classes, $additionnal_classes ); + $classes = array_map( 'sanitize_html_class', $classes ); + $hash_sprite = $this->get_sprite_hash( $sprite_name ); - return sprintf( '', implode( ' ', $classes ), \get_theme_file_uri( sprintf( '/dist/icons/%s.svg', $sprite_name ) ), $icon_slug ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + return sprintf( '', implode( ' ', $classes ), \get_theme_file_uri( sprintf( '/dist/icons/%s.svg', $sprite_name ) ), $hash_sprite, $icon_slug ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** @@ -104,4 +105,32 @@ public function allow_svg_tag( $tags ) { return $tags; } + + /** + * Get the hash of the sprite + * + * @param string $sprite_name + * + * @return string + */ + public function get_sprite_hash( $sprite_name ) { + $sprite_hash_file = \get_theme_file_path( '/dist/sprite-hashes.json' ); + + if ( ! is_readable( $sprite_hash_file ) ) { + return ''; + } + + $sprite_hash = file_get_contents( $sprite_hash_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + $sprite_hash = json_decode( $sprite_hash, true ); + + if ( empty( $sprite_hash ) ) { + return ''; + } + + if ( ! isset( $sprite_hash[ sprintf( 'icons/%s.svg', $sprite_name ) ] ) ) { + return ''; + } + + return '?' . $sprite_hash[ sprintf( 'icons/%s.svg', $sprite_name ) ]; + } } From f1042f01d2509b85d29d40ab3460deee4968673e Mon Sep 17 00:00:00 2001 From: mricoul Date: Fri, 6 Feb 2026 14:10:38 +0100 Subject: [PATCH 2/8] feat(Svg): add cache system --- inc/Services/Svg.php | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/inc/Services/Svg.php b/inc/Services/Svg.php index 14ad9c7c..00d3f319 100644 --- a/inc/Services/Svg.php +++ b/inc/Services/Svg.php @@ -111,26 +111,33 @@ public function allow_svg_tag( $tags ) { * * @param string $sprite_name * - * @return string + * @return string | null */ - public function get_sprite_hash( $sprite_name ) { - $sprite_hash_file = \get_theme_file_path( '/dist/sprite-hashes.json' ); + public function get_sprite_hash( string $sprite_name ): ?string { + static $sprite_hashes = null; - if ( ! is_readable( $sprite_hash_file ) ) { - return ''; - } + if ( null === $sprite_hashes ) { + $sprite_hash_file = \get_theme_file_path( '/dist/sprite-hashes.json' ); - $sprite_hash = file_get_contents( $sprite_hash_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents - $sprite_hash = json_decode( $sprite_hash, true ); + if ( ! is_readable( $sprite_hash_file ) ) { + $sprite_hashes = []; - if ( empty( $sprite_hash ) ) { - return ''; - } + return null; + } - if ( ! isset( $sprite_hash[ sprintf( 'icons/%s.svg', $sprite_name ) ] ) ) { - return ''; + $sprite_hash = file_get_contents( $sprite_hash_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + + try { + $sprite_hash = json_decode( $sprite_hash, true, 512, JSON_THROW_ON_ERROR ); + } catch ( \JsonException $e ) { + $sprite_hashes = []; + + return null; + } + + $sprite_hashes = $sprite_hash; } - return '?' . $sprite_hash[ sprintf( 'icons/%s.svg', $sprite_name ) ]; + return $sprite_hash[ sprintf( 'icons/%s.svg', $sprite_name ) ] ?? null; } } From d341e9422374556f7f448291807ed563015feced Mon Sep 17 00:00:00 2001 From: mricoul Date: Fri, 6 Feb 2026 14:10:48 +0100 Subject: [PATCH 3/8] feat(Svg): add query args --- inc/Services/Svg.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inc/Services/Svg.php b/inc/Services/Svg.php index 00d3f319..d8e58623 100644 --- a/inc/Services/Svg.php +++ b/inc/Services/Svg.php @@ -59,9 +59,10 @@ public function get_the_icon( string $icon_class, array $additionnal_classes = [ $classes = [ 'icon', $icon_slug ]; $classes = array_merge( $classes, $additionnal_classes ); $classes = array_map( 'sanitize_html_class', $classes ); + $icon_url = \get_theme_file_uri( sprintf( '/dist/icons/%s.svg', $sprite_name ) ); $hash_sprite = $this->get_sprite_hash( $sprite_name ); - return sprintf( '', implode( ' ', $classes ), \get_theme_file_uri( sprintf( '/dist/icons/%s.svg', $sprite_name ) ), $hash_sprite, $icon_slug ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + return sprintf( '', implode( ' ', $classes ), add_query_arg( [ 'v' => $hash_sprite ], $icon_url ), $icon_slug ); } /** From 811aa1ff00ff235e0f11e9a02a674aba2c6e1ac7 Mon Sep 17 00:00:00 2001 From: mricoul Date: Fri, 6 Feb 2026 14:11:43 +0100 Subject: [PATCH 4/8] fix(Svg): phpcs issue --- inc/Services/Svg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Services/Svg.php b/inc/Services/Svg.php index d8e58623..bd0140b9 100644 --- a/inc/Services/Svg.php +++ b/inc/Services/Svg.php @@ -59,7 +59,7 @@ public function get_the_icon( string $icon_class, array $additionnal_classes = [ $classes = [ 'icon', $icon_slug ]; $classes = array_merge( $classes, $additionnal_classes ); $classes = array_map( 'sanitize_html_class', $classes ); - $icon_url = \get_theme_file_uri( sprintf( '/dist/icons/%s.svg', $sprite_name ) ); + $icon_url = \get_theme_file_uri( sprintf( '/dist/icons/%s.svg', $sprite_name ) ); $hash_sprite = $this->get_sprite_hash( $sprite_name ); return sprintf( '', implode( ' ', $classes ), add_query_arg( [ 'v' => $hash_sprite ], $icon_url ), $icon_slug ); From 729e0d5af1b2db9196b1802315113b47b3314d98 Mon Sep 17 00:00:00 2001 From: mricoul Date: Fri, 6 Feb 2026 14:13:07 +0100 Subject: [PATCH 5/8] feat(Svg): allow width and height attributes to Svg elements --- inc/Services/Svg.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inc/Services/Svg.php b/inc/Services/Svg.php index bd0140b9..ec61caef 100644 --- a/inc/Services/Svg.php +++ b/inc/Services/Svg.php @@ -91,6 +91,8 @@ public function allow_svg_tag( $tags ) { 'focusable' => [], 'class' => [], 'style' => [], + 'width' => [], + 'height' => [], ]; $tags['path'] = [ From 2d10d9caa9ef4a229a9aae68160b1a3c2dc8cc06 Mon Sep 17 00:00:00 2001 From: mricoul Date: Fri, 6 Feb 2026 15:30:36 +0100 Subject: [PATCH 6/8] feat(webpack-svg-sprite-hashes): add returnFormat option to output php file --- config/plugins.js | 6 +- config/sprite-hash-plugin.js | 73 ------------------- config/webpack-sprite-hash-plugin.js | 100 +++++++++++++++++++++++++++ inc/Services/Svg.php | 28 ++++---- 4 files changed, 119 insertions(+), 88 deletions(-) delete mode 100644 config/sprite-hash-plugin.js create mode 100644 config/webpack-sprite-hash-plugin.js diff --git a/config/plugins.js b/config/plugins.js index 537ebd2a..eacfd3f8 100644 --- a/config/plugins.js +++ b/config/plugins.js @@ -11,7 +11,7 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPl const WebpackImageSizesPlugin = require('./webpack-image-sizes-plugin') const WebpackThemeJsonPlugin = require('./webpack-theme-json-plugin') -const SpriteHashPlugin = require('./sprite-hash-plugin') +const SpriteHashPlugin = require('./webpack-sprite-hash-plugin') module.exports = { get: function (mode) { @@ -19,7 +19,9 @@ module.exports = { new WebpackThemeJsonPlugin({ watch: mode !== 'production', }), - new SpriteHashPlugin(), + new SpriteHashPlugin({ + returnFormat: 'php', + }), new CleanWebpackPlugin({ cleanOnceBeforeBuildPatterns: ['**/*', '!images', '!images/**'], }), diff --git a/config/sprite-hash-plugin.js b/config/sprite-hash-plugin.js deleted file mode 100644 index eb2224c7..00000000 --- a/config/sprite-hash-plugin.js +++ /dev/null @@ -1,73 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const crypto = require('crypto'); - -/** - * Webpack plugin to generate content hashes for SVG sprite files. - * Creates a sprite-hashes.json file in the dist folder. - */ -class SpriteHashPlugin { - constructor(options = {}) { - this.options = { - outputPath: options.outputPath || 'dist', - spritePath: options.spritePath || 'dist/icons', - outputFilename: options.outputFilename || 'sprite-hashes.json', - hashLength: options.hashLength || 8, - }; - } - - apply(compiler) { - compiler.hooks.afterEmit.tapAsync( - 'SpriteHashPlugin', - (compilation, callback) => { - const spriteDir = path.resolve( - compiler.options.context, - this.options.spritePath - ); - const outputFile = path.resolve( - compiler.options.context, - this.options.outputPath, - this.options.outputFilename - ); - - if (!fs.existsSync(spriteDir)) { - console.warn( - `SpriteHashPlugin: Sprite directory not found: ${spriteDir}` - ); - callback(); - return; - } - - const hashes = {}; - const files = fs - .readdirSync(spriteDir) - .filter((file) => file.endsWith('.svg')); - - files.forEach((file) => { - const filePath = path.join(spriteDir, file); - const content = fs.readFileSync(filePath); - const hash = crypto - .createHash('md5') - .update(content) - .digest('hex') - .substring(0, this.options.hashLength); - - // Store with relative path as key - const relativePath = `icons/${file}`; - hashes[relativePath] = hash; - }); - - fs.writeFileSync(outputFile, JSON.stringify(hashes, null, 2)); - console.log( - `SpriteHashPlugin: Generated ${ - this.options.outputFilename - } with ${Object.keys(hashes).length} sprites` - ); - - callback(); - } - ); - } -} - -module.exports = SpriteHashPlugin; diff --git a/config/webpack-sprite-hash-plugin.js b/config/webpack-sprite-hash-plugin.js new file mode 100644 index 00000000..61bdc446 --- /dev/null +++ b/config/webpack-sprite-hash-plugin.js @@ -0,0 +1,100 @@ +const fs = require('fs') +const path = require('path') +const crypto = require('crypto') + +const ALLOWED_RETURN_FORMATS = ['json', 'php'] + +/** + * Webpack plugin to generate content hashes for SVG sprite files. + * Creates a sprite-hashes.json (or sprite-hashes.asset.php) file in the dist folder. + * + * @param {Object} options Plugin options. + * @param {string} [options.returnFormat='json'] Output format: 'json' or 'php'. + * When 'php', generates a .asset.php file (outputFilename .json → .asset.php). + * @param {string} [options.outputPath='dist'] Output directory. + * @param {string} [options.spritePath='dist/icons'] Sprite SVG directory. + * @param {string} [options.outputFilename='sprite-hashes.json'] Output file name. + * @param {number} [options.hashLength=8] Hash length in characters. + */ +class SpriteHashPlugin { + constructor(options = {}) { + const returnFormat = options.returnFormat || 'json' + if (!ALLOWED_RETURN_FORMATS.includes(returnFormat)) { + throw new Error(`SpriteHashPlugin: returnFormat must be one of ${ALLOWED_RETURN_FORMATS.join(', ')}`) + } + + this.options = { + outputPath: options.outputPath || 'dist', + spritePath: options.spritePath || 'dist/icons', + outputFilename: options.outputFilename || 'sprite-hashes.' + returnFormat, + hashLength: options.hashLength || 8, + returnFormat, + } + } + + /** + * Formats a plain object as a PHP associative array string. + * + * @param {Record} obj Key-value pairs. + * @return {string} PHP array literal. + */ + formatPhpArray(obj) { + const entries = Object.entries(obj).map(([key, value]) => { + const escapedKey = key.replace(/'/g, "\\'") + const escapedValue = String(value).replace(/'/g, "\\'") + return `\t'${escapedKey}' => '${escapedValue}'` + }) + return `array(\n${entries.join(',\n')}\n)` + } + + apply(compiler) { + compiler.hooks.afterEmit.tapAsync('SpriteHashPlugin', (compilation, callback) => { + const spriteDir = path.resolve(compiler.options.context, this.options.spritePath) + const outputFilename = + this.options.returnFormat === 'php' + ? this.options.outputFilename.replace(/\.json$/i, '.asset.php') + : this.options.outputFilename + const outputFile = path.resolve(compiler.options.context, this.options.outputPath, outputFilename) + + if (!fs.existsSync(spriteDir)) { + console.warn(`SpriteHashPlugin: Sprite directory not found: ${spriteDir}`) + callback() + return + } + + const hashes = {} + const files = fs.readdirSync(spriteDir).filter((file) => file.endsWith('.svg')) + + files.forEach((file) => { + const filePath = path.join(spriteDir, file) + const content = fs.readFileSync(filePath) + const hash = crypto.createHash('md5').update(content).digest('hex').substring(0, this.options.hashLength) + + // Store with relative path as key + const relativePath = `icons/${file}` + hashes[relativePath] = hash + }) + + if (this.options.returnFormat === 'php') { + const phpLines = [ + ' Path => hash.', + ' */', + 'return ' + this.formatPhpArray(hashes) + ';', + '', + ] + fs.writeFileSync(outputFile, phpLines.join('\n')) + } else { + fs.writeFileSync(outputFile, JSON.stringify(hashes, null, 2)) + } + console.log(`SpriteHashPlugin: Generated ${outputFilename} with ${Object.keys(hashes).length} sprites`) + + callback() + }) + } +} + +module.exports = SpriteHashPlugin diff --git a/inc/Services/Svg.php b/inc/Services/Svg.php index ec61caef..af391f96 100644 --- a/inc/Services/Svg.php +++ b/inc/Services/Svg.php @@ -120,19 +120,21 @@ public function get_sprite_hash( string $sprite_name ): ?string { static $sprite_hashes = null; if ( null === $sprite_hashes ) { - $sprite_hash_file = \get_theme_file_path( '/dist/sprite-hashes.json' ); - - if ( ! is_readable( $sprite_hash_file ) ) { - $sprite_hashes = []; - - return null; - } - - $sprite_hash = file_get_contents( $sprite_hash_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents - - try { - $sprite_hash = json_decode( $sprite_hash, true, 512, JSON_THROW_ON_ERROR ); - } catch ( \JsonException $e ) { + $php_file = get_theme_file_path( '/dist/sprite-hashes.php' ); + $json_file = get_theme_file_path( '/dist/sprite-hashes.json' ); + + if ( is_readable( $php_file ) ) { + $sprite_hashes = require $php_file; + $sprite_hashes = \is_array( $sprite_hashes ) ? $sprite_hashes : []; + } elseif ( is_readable( $json_file ) ) { + $json_content = file_get_contents( $json_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + try { + $sprite_hashes = json_decode( $json_content, true, 512, JSON_THROW_ON_ERROR ); + $sprite_hashes = \is_array( $sprite_hashes ) ? $sprite_hashes : []; + } catch ( \JsonException $e ) { + $sprite_hashes = []; + } + } else { $sprite_hashes = []; return null; From 97a49f861015404470873fc5283a3d86e91da941 Mon Sep 17 00:00:00 2001 From: mricoul Date: Fri, 6 Feb 2026 15:33:25 +0100 Subject: [PATCH 7/8] fix(Svg): psalm issue --- inc/Services/Svg.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/inc/Services/Svg.php b/inc/Services/Svg.php index af391f96..f07c791a 100644 --- a/inc/Services/Svg.php +++ b/inc/Services/Svg.php @@ -124,15 +124,17 @@ public function get_sprite_hash( string $sprite_name ): ?string { $json_file = get_theme_file_path( '/dist/sprite-hashes.json' ); if ( is_readable( $php_file ) ) { - $sprite_hashes = require $php_file; - $sprite_hashes = \is_array( $sprite_hashes ) ? $sprite_hashes : []; + $sprite_hash = require $php_file; + $sprite_hash = \is_array( $sprite_hashes ) ? $sprite_hashes : []; } elseif ( is_readable( $json_file ) ) { - $json_content = file_get_contents( $json_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + $sprite_hash = file_get_contents( $json_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents try { - $sprite_hashes = json_decode( $json_content, true, 512, JSON_THROW_ON_ERROR ); - $sprite_hashes = \is_array( $sprite_hashes ) ? $sprite_hashes : []; + $sprite_hash = json_decode( $json_content, true, 512, JSON_THROW_ON_ERROR ); + $sprite_hash = \is_array( $sprite_hashes ) ? $sprite_hashes : []; } catch ( \JsonException $e ) { $sprite_hashes = []; + + return null; } } else { $sprite_hashes = []; From 847082ca2ee315b634069e537b4d9cee226f120d Mon Sep 17 00:00:00 2001 From: mricoul Date: Fri, 6 Feb 2026 16:14:54 +0100 Subject: [PATCH 8/8] refactor(svg): remove json return format --- config/plugins.js | 4 +-- config/webpack-sprite-hash-plugin.js | 52 ++++++++++------------------ inc/Services/Svg.php | 30 +++++++--------- 3 files changed, 31 insertions(+), 55 deletions(-) diff --git a/config/plugins.js b/config/plugins.js index eacfd3f8..dd5858ee 100644 --- a/config/plugins.js +++ b/config/plugins.js @@ -19,9 +19,7 @@ module.exports = { new WebpackThemeJsonPlugin({ watch: mode !== 'production', }), - new SpriteHashPlugin({ - returnFormat: 'php', - }), + new SpriteHashPlugin(), new CleanWebpackPlugin({ cleanOnceBeforeBuildPatterns: ['**/*', '!images', '!images/**'], }), diff --git a/config/webpack-sprite-hash-plugin.js b/config/webpack-sprite-hash-plugin.js index 61bdc446..b33940a8 100644 --- a/config/webpack-sprite-hash-plugin.js +++ b/config/webpack-sprite-hash-plugin.js @@ -2,33 +2,23 @@ const fs = require('fs') const path = require('path') const crypto = require('crypto') -const ALLOWED_RETURN_FORMATS = ['json', 'php'] - /** * Webpack plugin to generate content hashes for SVG sprite files. - * Creates a sprite-hashes.json (or sprite-hashes.asset.php) file in the dist folder. + * Creates a sprite-hashes.php file in the dist folder. * * @param {Object} options Plugin options. - * @param {string} [options.returnFormat='json'] Output format: 'json' or 'php'. - * When 'php', generates a .asset.php file (outputFilename .json → .asset.php). * @param {string} [options.outputPath='dist'] Output directory. * @param {string} [options.spritePath='dist/icons'] Sprite SVG directory. - * @param {string} [options.outputFilename='sprite-hashes.json'] Output file name. + * @param {string} [options.outputFilename='sprite-hashes.php'] Output file name. * @param {number} [options.hashLength=8] Hash length in characters. */ class SpriteHashPlugin { constructor(options = {}) { - const returnFormat = options.returnFormat || 'json' - if (!ALLOWED_RETURN_FORMATS.includes(returnFormat)) { - throw new Error(`SpriteHashPlugin: returnFormat must be one of ${ALLOWED_RETURN_FORMATS.join(', ')}`) - } - this.options = { outputPath: options.outputPath || 'dist', spritePath: options.spritePath || 'dist/icons', - outputFilename: options.outputFilename || 'sprite-hashes.' + returnFormat, + outputFilename: options.outputFilename || 'sprite-hashes.asset.php', hashLength: options.hashLength || 8, - returnFormat, } } @@ -50,11 +40,7 @@ class SpriteHashPlugin { apply(compiler) { compiler.hooks.afterEmit.tapAsync('SpriteHashPlugin', (compilation, callback) => { const spriteDir = path.resolve(compiler.options.context, this.options.spritePath) - const outputFilename = - this.options.returnFormat === 'php' - ? this.options.outputFilename.replace(/\.json$/i, '.asset.php') - : this.options.outputFilename - const outputFile = path.resolve(compiler.options.context, this.options.outputPath, outputFilename) + const outputFile = path.resolve(compiler.options.context, this.options.outputPath, this.options.outputFilename) if (!fs.existsSync(spriteDir)) { console.warn(`SpriteHashPlugin: Sprite directory not found: ${spriteDir}`) @@ -75,22 +61,20 @@ class SpriteHashPlugin { hashes[relativePath] = hash }) - if (this.options.returnFormat === 'php') { - const phpLines = [ - ' Path => hash.', - ' */', - 'return ' + this.formatPhpArray(hashes) + ';', - '', - ] - fs.writeFileSync(outputFile, phpLines.join('\n')) - } else { - fs.writeFileSync(outputFile, JSON.stringify(hashes, null, 2)) - } - console.log(`SpriteHashPlugin: Generated ${outputFilename} with ${Object.keys(hashes).length} sprites`) + const phpLines = [ + ' Path => hash.', + ' */', + 'return ' + this.formatPhpArray(hashes) + ';', + '', + ] + fs.writeFileSync(outputFile, phpLines.join('\n')) + console.log( + `SpriteHashPlugin: Generated ${this.options.outputFilename} with ${Object.keys(hashes).length} sprites` + ) callback() }) diff --git a/inc/Services/Svg.php b/inc/Services/Svg.php index f07c791a..a8a3e17e 100644 --- a/inc/Services/Svg.php +++ b/inc/Services/Svg.php @@ -120,23 +120,17 @@ public function get_sprite_hash( string $sprite_name ): ?string { static $sprite_hashes = null; if ( null === $sprite_hashes ) { - $php_file = get_theme_file_path( '/dist/sprite-hashes.php' ); - $json_file = get_theme_file_path( '/dist/sprite-hashes.json' ); - - if ( is_readable( $php_file ) ) { - $sprite_hash = require $php_file; - $sprite_hash = \is_array( $sprite_hashes ) ? $sprite_hashes : []; - } elseif ( is_readable( $json_file ) ) { - $sprite_hash = file_get_contents( $json_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents - try { - $sprite_hash = json_decode( $json_content, true, 512, JSON_THROW_ON_ERROR ); - $sprite_hash = \is_array( $sprite_hashes ) ? $sprite_hashes : []; - } catch ( \JsonException $e ) { - $sprite_hashes = []; - - return null; - } - } else { + $sprite_hash_file = get_theme_file_path( '/dist/sprite-hashes.asset.php' ); + + if ( ! is_readable( $sprite_hash_file ) ) { + $sprite_hashes = []; + + return null; + } + + $sprite_hash = require $sprite_hash_file; + + if ( ! is_array( $sprite_hash ) ) { $sprite_hashes = []; return null; @@ -145,6 +139,6 @@ public function get_sprite_hash( string $sprite_name ): ?string { $sprite_hashes = $sprite_hash; } - return $sprite_hash[ sprintf( 'icons/%s.svg', $sprite_name ) ] ?? null; + return $sprite_hashes[ sprintf( 'icons/%s.svg', $sprite_name ) ] ?? null; } }