Skip to content
2 changes: 2 additions & 0 deletions config/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ 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) {
const plugins = [
new WebpackThemeJsonPlugin({
watch: mode !== 'production',
}),
new SpriteHashPlugin(),
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: ['**/*', '!images', '!images/**'],
}),
Expand Down
73 changes: 73 additions & 0 deletions config/sprite-hash-plugin.js
Original file line number Diff line number Diff line change
@@ -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;
39 changes: 34 additions & 5 deletions inc/Services/Svg.php
Original file line number Diff line number Diff line change
Expand Up @@ -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( '<svg class="%s" aria-hidden="true" focusable="false"><use href="%s#%s"></use></svg>', implode( ' ', $classes ), \get_theme_file_uri( sprintf( '/dist/icons/%s.svg', $sprite_name ) ), $icon_slug ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return sprintf( '<svg class="%s" aria-hidden="true" focusable="false"><use href="%s%s#%s"></use></svg>', implode( ' ', $classes ), \get_theme_file_uri( sprintf( '/dist/icons/%s.svg', $sprite_name ) ), $hash_sprite, $icon_slug ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
Comment thread
firestar300 marked this conversation as resolved.
Outdated
}

/**
Expand Down Expand Up @@ -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' );
Comment thread
firestar300 marked this conversation as resolved.
Outdated

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 ) ];
Comment thread
firestar300 marked this conversation as resolved.
Outdated
}
}