99
1010const fs = require ( 'fs' ) ;
1111const path = require ( 'path' ) ;
12- const SvgPrep = require ( 'svg-prep' ) ;
1312const { Compilation, sources } = require ( 'webpack' ) ;
1413const { RawSource } = sources ;
1514
15+ /**
16+ * Builds an SVG sprite from individual SVG files. Each SVG becomes a
17+ * <symbol> element identified by its filename (without extension).
18+ */
19+ function buildSvgSprite ( files ) {
20+ const symbols = files . map ( file => {
21+ const name = path . basename ( file , '.svg' ) ;
22+ const svg = fs . readFileSync ( file , 'utf-8' ) ;
23+
24+ // Extract viewBox from the root <svg> element
25+ const viewBoxMatch = svg . match ( / v i e w B o x = " ( [ ^ " ] * ) " / ) ;
26+ const viewBox = viewBoxMatch ? viewBoxMatch [ 1 ] : '0 0 100 100' ;
27+
28+ // Extract inner content between <svg ...> and </svg>
29+ const innerMatch = svg . match ( / < s v g [ ^ > ] * > ( [ \s \S ] * ) < \/ s v g > / ) ;
30+ const inner = innerMatch ? innerMatch [ 1 ] : '' ;
31+
32+ // Strip elements that are unnecessary or potential XSS vectors
33+ // Remove attributes that interfere with sprite styling or pose security risks
34+ const cleaned = inner
35+ . replace ( / < s t y l e [ \s \S ] * ?< \/ s t y l e > / gi, '' )
36+ . replace ( / < d e f s [ \s \S ] * ?< \/ d e f s > / gi, '' )
37+ . replace ( / < t i t l e [ \s \S ] * ?< \/ t i t l e > / gi, '' )
38+ . replace ( / < d e s c [ \s \S ] * ?< \/ d e s c > / gi, '' )
39+ . replace ( / < s c r i p t [ \s \S ] * ?< \/ s c r i p t > / gi, '' )
40+ . replace ( / < ! - - [ \s \S ] * ?- - > / g, '' )
41+ . replace ( / \s + i d = " [ ^ " ] * " / g, '' )
42+ . replace ( / \s + f i l l = " [ ^ " ] * " / g, '' )
43+ . replace ( / \s + c l a s s = " [ ^ " ] * " / g, '' )
44+ . replace ( / \s + s t y l e = " [ ^ " ] * " / g, '' )
45+ . replace ( / \s + s t r o k e = " [ ^ " ] * " / g, '' )
46+ . replace ( / \s + s t r o k e - [ a - z ] + = " [ ^ " ] * " / g, '' )
47+ . replace ( / \s + o n [ a - z A - Z ] + = " [ ^ " ] * " / g, '' )
48+ . replace ( / \s + o n [ a - z A - Z ] + = ' [ ^ ' ] * ' / g, '' )
49+ . replace ( / \s + h r e f = " [ ^ " ] * " / g, '' )
50+ . replace ( / \s + x l i n k : h r e f = " [ ^ " ] * " / g, '' ) ;
51+
52+ return ` <symbol id="${ name } " viewBox="${ viewBox } ">\n ${ cleaned . trim ( ) } \n </symbol>` ;
53+ } ) ;
54+
55+ return [
56+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' ,
57+ '<svg id="sprites" xmlns="http://www.w3.org/2000/svg" style="display:none">' ,
58+ symbols . join ( '\n' ) ,
59+ '</svg>' ,
60+ ] . join ( '\n' ) ;
61+ }
62+
1663function SvgPrepPlugin ( options ) {
1764 this . options = { } ;
1865 Object . assign (
@@ -33,17 +80,16 @@ SvgPrepPlugin.prototype.apply = function (compiler) {
3380 } ,
3481 async ( ) => {
3582 if ( ! this . options . source ) {
36- return Promise . resolve ( ) ;
83+ return ;
3784 }
3885
39- // TODO: Keep track of file hashes, so we can avoid recompiling when none have changed
4086 const files = fs
4187 . readdirSync ( this . options . source )
4288 . filter ( name => name . endsWith ( '.svg' ) )
89+ . sort ( )
4390 . map ( name => path . join ( this . options . source , name ) ) ;
4491
45- const sprited = await SvgPrep ( files ) . filter ( { removeIds : true , noFill : true } ) . output ( ) ;
46-
92+ const sprited = buildSvgSprite ( files ) ;
4793 compilation . emitAsset ( this . options . output , new RawSource ( sprited ) ) ;
4894 }
4995 ) ;
0 commit comments