diff --git a/config/plugins.js b/config/plugins.js index 8cd63bb7..349fb033 100644 --- a/config/plugins.js +++ b/config/plugins.js @@ -10,10 +10,12 @@ const DependencyExtractionWebpackPlugin = require('@wordpress/dependency-extract const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin const WebpackImageSizesPlugin = require('./webpack-image-sizes-plugin') +const SpriteHashPlugin = require('./sprite-hash-plugin') module.exports = { get: function (mode) { const plugins = [ + 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 ) ]; + } }