Skip to content

Commit 2d10d9c

Browse files
committed
feat(webpack-svg-sprite-hashes): add returnFormat option to output php file
1 parent 729e0d5 commit 2d10d9c

File tree

4 files changed

+119
-88
lines changed

4 files changed

+119
-88
lines changed

config/plugins.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,17 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPl
1111

1212
const WebpackImageSizesPlugin = require('./webpack-image-sizes-plugin')
1313
const WebpackThemeJsonPlugin = require('./webpack-theme-json-plugin')
14-
const SpriteHashPlugin = require('./sprite-hash-plugin')
14+
const SpriteHashPlugin = require('./webpack-sprite-hash-plugin')
1515

1616
module.exports = {
1717
get: function (mode) {
1818
const plugins = [
1919
new WebpackThemeJsonPlugin({
2020
watch: mode !== 'production',
2121
}),
22-
new SpriteHashPlugin(),
22+
new SpriteHashPlugin({
23+
returnFormat: 'php',
24+
}),
2325
new CleanWebpackPlugin({
2426
cleanOnceBeforeBuildPatterns: ['**/*', '!images', '!images/**'],
2527
}),

config/sprite-hash-plugin.js

Lines changed: 0 additions & 73 deletions
This file was deleted.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
const fs = require('fs')
2+
const path = require('path')
3+
const crypto = require('crypto')
4+
5+
const ALLOWED_RETURN_FORMATS = ['json', 'php']
6+
7+
/**
8+
* Webpack plugin to generate content hashes for SVG sprite files.
9+
* Creates a sprite-hashes.json (or sprite-hashes.asset.php) file in the dist folder.
10+
*
11+
* @param {Object} options Plugin options.
12+
* @param {string} [options.returnFormat='json'] Output format: 'json' or 'php'.
13+
* When 'php', generates a .asset.php file (outputFilename .json → .asset.php).
14+
* @param {string} [options.outputPath='dist'] Output directory.
15+
* @param {string} [options.spritePath='dist/icons'] Sprite SVG directory.
16+
* @param {string} [options.outputFilename='sprite-hashes.json'] Output file name.
17+
* @param {number} [options.hashLength=8] Hash length in characters.
18+
*/
19+
class SpriteHashPlugin {
20+
constructor(options = {}) {
21+
const returnFormat = options.returnFormat || 'json'
22+
if (!ALLOWED_RETURN_FORMATS.includes(returnFormat)) {
23+
throw new Error(`SpriteHashPlugin: returnFormat must be one of ${ALLOWED_RETURN_FORMATS.join(', ')}`)
24+
}
25+
26+
this.options = {
27+
outputPath: options.outputPath || 'dist',
28+
spritePath: options.spritePath || 'dist/icons',
29+
outputFilename: options.outputFilename || 'sprite-hashes.' + returnFormat,
30+
hashLength: options.hashLength || 8,
31+
returnFormat,
32+
}
33+
}
34+
35+
/**
36+
* Formats a plain object as a PHP associative array string.
37+
*
38+
* @param {Record<string, string>} obj Key-value pairs.
39+
* @return {string} PHP array literal.
40+
*/
41+
formatPhpArray(obj) {
42+
const entries = Object.entries(obj).map(([key, value]) => {
43+
const escapedKey = key.replace(/'/g, "\\'")
44+
const escapedValue = String(value).replace(/'/g, "\\'")
45+
return `\t'${escapedKey}' => '${escapedValue}'`
46+
})
47+
return `array(\n${entries.join(',\n')}\n)`
48+
}
49+
50+
apply(compiler) {
51+
compiler.hooks.afterEmit.tapAsync('SpriteHashPlugin', (compilation, callback) => {
52+
const spriteDir = path.resolve(compiler.options.context, this.options.spritePath)
53+
const outputFilename =
54+
this.options.returnFormat === 'php'
55+
? this.options.outputFilename.replace(/\.json$/i, '.asset.php')
56+
: this.options.outputFilename
57+
const outputFile = path.resolve(compiler.options.context, this.options.outputPath, outputFilename)
58+
59+
if (!fs.existsSync(spriteDir)) {
60+
console.warn(`SpriteHashPlugin: Sprite directory not found: ${spriteDir}`)
61+
callback()
62+
return
63+
}
64+
65+
const hashes = {}
66+
const files = fs.readdirSync(spriteDir).filter((file) => file.endsWith('.svg'))
67+
68+
files.forEach((file) => {
69+
const filePath = path.join(spriteDir, file)
70+
const content = fs.readFileSync(filePath)
71+
const hash = crypto.createHash('md5').update(content).digest('hex').substring(0, this.options.hashLength)
72+
73+
// Store with relative path as key
74+
const relativePath = `icons/${file}`
75+
hashes[relativePath] = hash
76+
})
77+
78+
if (this.options.returnFormat === 'php') {
79+
const phpLines = [
80+
'<?php',
81+
'/**',
82+
' * Sprite file hashes. Generated by SpriteHashPlugin.',
83+
' *',
84+
' * @return array<string, string> Path => hash.',
85+
' */',
86+
'return ' + this.formatPhpArray(hashes) + ';',
87+
'',
88+
]
89+
fs.writeFileSync(outputFile, phpLines.join('\n'))
90+
} else {
91+
fs.writeFileSync(outputFile, JSON.stringify(hashes, null, 2))
92+
}
93+
console.log(`SpriteHashPlugin: Generated ${outputFilename} with ${Object.keys(hashes).length} sprites`)
94+
95+
callback()
96+
})
97+
}
98+
}
99+
100+
module.exports = SpriteHashPlugin

inc/Services/Svg.php

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -120,19 +120,21 @@ public function get_sprite_hash( string $sprite_name ): ?string {
120120
static $sprite_hashes = null;
121121

122122
if ( null === $sprite_hashes ) {
123-
$sprite_hash_file = \get_theme_file_path( '/dist/sprite-hashes.json' );
124-
125-
if ( ! is_readable( $sprite_hash_file ) ) {
126-
$sprite_hashes = [];
127-
128-
return null;
129-
}
130-
131-
$sprite_hash = file_get_contents( $sprite_hash_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
132-
133-
try {
134-
$sprite_hash = json_decode( $sprite_hash, true, 512, JSON_THROW_ON_ERROR );
135-
} catch ( \JsonException $e ) {
123+
$php_file = get_theme_file_path( '/dist/sprite-hashes.php' );
124+
$json_file = get_theme_file_path( '/dist/sprite-hashes.json' );
125+
126+
if ( is_readable( $php_file ) ) {
127+
$sprite_hashes = require $php_file;
128+
$sprite_hashes = \is_array( $sprite_hashes ) ? $sprite_hashes : [];
129+
} elseif ( is_readable( $json_file ) ) {
130+
$json_content = file_get_contents( $json_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
131+
try {
132+
$sprite_hashes = json_decode( $json_content, true, 512, JSON_THROW_ON_ERROR );
133+
$sprite_hashes = \is_array( $sprite_hashes ) ? $sprite_hashes : [];
134+
} catch ( \JsonException $e ) {
135+
$sprite_hashes = [];
136+
}
137+
} else {
136138
$sprite_hashes = [];
137139

138140
return null;

0 commit comments

Comments
 (0)