It looks like sharp does not handle external images well.
You can try to "flatten" SVG before passing it to sharp.
Quick and dirty implementation
This is very error prone. Ideally you would use something like Cheerio for parsing and modifying SVG input.
It also preserves only x, y, width, height and opacity attributes. Others attributes would require further changes.
function flattenSVG (svg) {
const images = svg.match(/<image [^>]+>/g);
if (!images || images.length < 1) {
return svg;
}
var result = svg;
images.forEach(image => {
const [,data] = image.match(/ xlink:href="data:image\/svg\+xml;base64,([^"]+)"/) || [];
if (!data) {
return;
}
const innerSVG = Buffer.from(data, 'base64').toString();
const [,width] = image.match(/ width="([^"]+)"/) || [];
const [,height] = image.match(/ height="([^"]+)"/) || [];
const [,opacity] = image.match(/ opacity="([^"]+)"/) || [];
const [,x] = image.match(/ x="([^"]+)"/) || [];
const [,y] = image.match(/ y="([^"]+)"/) || [];
const [header] = innerSVG && innerSVG.match(/<svg[^>]+>/) || [];
const fixedHeader = header
.replace(/ (x|y|width|height)="([^"]+)"/g, '')
.replace('<svg', `<svg x="${x}" y="${y}" width="${width}" height="${height}" opacity="${opacity || 1.0}"`);
const replacement = innerSVG && innerSVG.replace(header, fixedHeader);
result = result.replace(image, replacement);
});
return result;
}
So, in your code, you would use something like:
sharp(Buffer.from(flattenSVG(testSVG)))
.toFormat('png')
.toFile('output.png')
You can check working code (a bit different for test purposes, as it uses buffer instead of file output) at Glitch.