Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/**'],
}),
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
}

/**
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' );

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 ) ];
}
}