|
// plugins/sanity-inline-svgs/gatsby-node.js |
|
const crypto = require(`crypto`) |
|
const fs = require(`fs-extra`) |
|
const { createRemoteFileNode } = require(`gatsby-source-filesystem`) |
|
const { default: PQueue } = require("p-queue") |
|
const SVGO = require("svgo") |
|
|
|
const queue = new PQueue({ concurrency: 5 }) |
|
|
|
const svgo = new SVGO({ |
|
multipass: true, |
|
floatPrecision: 2, |
|
plugins: [ |
|
{ removeDoctype: true }, |
|
{ removeXMLProcInst: true }, |
|
{ removeComments: true }, |
|
{ removeMetadata: true }, |
|
{ removeXMLNS: false }, |
|
{ removeEditorsNSData: true }, |
|
{ cleanupAttrs: true }, |
|
{ inlineStyles: true }, |
|
{ minifyStyles: true }, |
|
{ convertStyleToAttrs: true }, |
|
{ cleanupIDs: true }, |
|
{ prefixIds: true }, |
|
{ removeRasterImages: true }, |
|
{ removeUselessDefs: true }, |
|
{ cleanupNumericValues: true }, |
|
{ cleanupListOfValues: true }, |
|
{ convertColors: true }, |
|
{ removeUnknownsAndDefaults: true }, |
|
{ removeNonInheritableGroupAttrs: true }, |
|
{ removeUselessStrokeAndFill: true }, |
|
{ removeViewBox: false }, |
|
{ cleanupEnableBackground: true }, |
|
{ removeHiddenElems: true }, |
|
{ removeEmptyText: true }, |
|
{ convertShapeToPath: true }, |
|
{ moveElemsAttrsToGroup: true }, |
|
{ moveGroupAttrsToElems: true }, |
|
{ collapseGroups: true }, |
|
{ convertPathData: true }, |
|
{ convertTransform: true }, |
|
{ removeEmptyAttrs: true }, |
|
{ removeEmptyContainers: true }, |
|
{ mergePaths: true }, |
|
{ removeUnusedNS: true }, |
|
{ sortAttrs: true }, |
|
{ removeTitle: true }, |
|
{ removeDesc: true }, |
|
{ removeDimensions: true }, |
|
{ removeAttrs: false }, |
|
{ removeAttributesBySelector: false }, |
|
{ removeElementsByAttr: false }, |
|
{ addClassesToSVGElement: false }, |
|
{ removeStyleElement: false }, |
|
{ removeScriptElement: false }, |
|
{ addAttributesToSVGElement: false }, |
|
{ removeOffCanvasPaths: true }, |
|
{ reusePaths: false }, // note: incompatible with multipass |
|
], |
|
}) |
|
|
|
exports.createSchemaCustomization = ({ actions }) => { |
|
actions.createTypes(` |
|
type InlineSvg implements Node @noInfer { |
|
content: String |
|
optimizedContent: String |
|
dataURI: String |
|
absolutePath: String |
|
relativePath: String |
|
} |
|
`) |
|
} |
|
|
|
async function parseSVG({ |
|
source, |
|
url, |
|
store, |
|
cache, |
|
createNode, |
|
createNodeId, |
|
}) { |
|
const { absolutePath } = await createRemoteFileNode({ |
|
url, |
|
parentNodeId: source.id, |
|
store, |
|
cache, |
|
createNode, |
|
createNodeId, |
|
}) |
|
|
|
const svg = await fs.readFile(absolutePath, "utf8") |
|
|
|
if (!svg) throw new Error("Unable to read " + source.id + ": " + absolutePath) |
|
if (svg.includes("base64")) |
|
console.log( |
|
"SVG contains bitmap data; this data will be removed.", |
|
source.id |
|
) |
|
|
|
const { data: optimizedSVG } = await svgo.optimize(svg) |
|
|
|
return { |
|
content: optimizedSVG, |
|
originalContent: svg, |
|
} |
|
} |
|
|
|
exports.createResolvers = ({ |
|
actions, |
|
cache, |
|
createNodeId, |
|
createResolvers, |
|
store, |
|
reporter, |
|
}) => { |
|
const { createNode } = actions |
|
|
|
createResolvers({ |
|
SanityImageAsset: { |
|
svg: { |
|
type: `InlineSvg`, |
|
resolve: async source => { |
|
const { url, mimeType } = source |
|
if (mimeType !== "image/svg+xml" || !url) return null |
|
|
|
const cacheId = |
|
"sanity-svg-content-" + |
|
crypto |
|
.createHash(`md5`) |
|
.update(url) |
|
.digest(`hex`) |
|
|
|
const result = await queue.add(async () => { |
|
try { |
|
const cachedData = await cache.get(cacheId) |
|
if (cachedData) return cachedData |
|
|
|
const result = await parseSVG({ |
|
source, |
|
url, |
|
store, |
|
cache, |
|
createNode, |
|
createNodeId, |
|
}) |
|
|
|
await cache.set(cacheId, result) |
|
|
|
return result |
|
} catch (err) { |
|
console.error(err, source.id) |
|
return null |
|
} |
|
}) |
|
|
|
return result |
|
}, |
|
}, |
|
}, |
|
}) |
|
} |