UNPKG

@svgx/core

Version:

Transform SVG inputs into many outputs, especially "HAST" "JSX" and "JS".

223 lines (222 loc) 9.88 kB
import { optimize } from "svgo"; import { generate } from "astring"; import { traverse } from "estraverse"; // transform an svg string to HAST (Hypertext Abstract Syntax Tree) export const toHast = async (svg, options = {}) => { const defaultOptions = { optimize: true, svgoConfig: {}, }; const mergedOptions = Object.assign({}, defaultOptions, options); let optimized = svg; if (mergedOptions.optimize) { optimized = optimize(svg, mergedOptions.svgoConfig).data; } const { fromHtml } = await eval(`import('hast-util-from-html')`); return fromHtml(optimized, { space: "svg", fragment: true, }); }; // transform an svg into an estree export const toEstree = async (svg, options) => { const hast = await toHast(svg, options); const { toEstree: getEstree } = await eval(`import('hast-util-to-estree')`); return getEstree(hast, { space: "svg" }); }; // transform an svg into raw jsx syntax (just jsx, not a component) export const toJsx = async (svg, options) => { const esTree = await toEstree(svg, options); const { toJs: getJs, jsx } = await eval(`import('estree-util-to-js')`); const { value } = getJs(esTree, { handlers: jsx }); return value; }; // transform an svg into javascript function calls (using pragmas) export const toJs = async (svg, options) => { const defaultOptions = { optimize: true, svgoConfig: {}, runtime: "automatic", importSource: "react", pragma: (options === null || options === void 0 ? void 0 : options.runtime) === "classic" ? "createElement" : undefined, pragmaFrag: (options === null || options === void 0 ? void 0 : options.runtime) === "classic" ? "Fragment" : undefined, }; const mergedOptions = Object.assign({}, defaultOptions, options); const esTree = await toEstree(svg, mergedOptions); const { buildJsx } = await eval(`import('estree-util-build-jsx')`); const esAst = buildJsx(esTree, mergedOptions); return generate(esAst); }; // transform an svg into a component with raw jsx return value export const toJsxComponent = async (svg, options) => { const esTreeComponent = await toEstreeComponent(svg, options); const { toJs: getJs, jsx } = await eval(`import('estree-util-to-js')`); const { value } = getJs(esTreeComponent, { handlers: jsx }); return value; }; // transform an svg into a component with jsx runtime function calls return value export const toJsComponent = async (svg, options) => { const defaultOptions = { optimize: true, svgoConfig: {}, componentName: "Component", passProps: true, defaultExport: true, importSource: "react", runtime: "automatic", pragma: (options === null || options === void 0 ? void 0 : options.runtime) === "classic" ? "createElement" : undefined, pragmaFrag: (options === null || options === void 0 ? void 0 : options.runtime) === "classic" ? "Fragment" : undefined, }; const mergedOptions = Object.assign({}, defaultOptions, options); const esTreeComponent = await toEstreeComponent(svg, mergedOptions); const { buildJsx } = await eval(`import('estree-util-build-jsx')`); const esAst = buildJsx(esTreeComponent, mergedOptions); return generate(esAst); }; // transform an svg into a component in estree format export const toEstreeComponent = async (svg, options = {}) => { const defaultOptions = { optimize: true, svgoConfig: {}, componentName: "Component", passProps: true, defaultExport: true, runtime: "automatic", pragma: (options === null || options === void 0 ? void 0 : options.runtime) === "classic" ? "createElement" : undefined, pragmaFrag: (options === null || options === void 0 ? void 0 : options.runtime) === "classic" ? "Fragment" : undefined, importSource: "react", }; const mergedOptions = Object.assign({}, defaultOptions, options); const esTree = await toEstree(svg, mergedOptions); traverse(esTree, { enter: function (node) { if (node.type === "Program" && node.body) { const previousNodeBody = node.body[0]; // this is for importing pragma and pragmaFrag when using classic runtime const importDeclaration = (mergedOptions === null || mergedOptions === void 0 ? void 0 : mergedOptions.runtime) === "classic" ? { type: "ImportDeclaration", specifiers: [ { type: "ImportSpecifier", imported: { type: "Identifier", name: mergedOptions.pragma || "createElement", }, local: { type: "Identifier", name: mergedOptions.pragma || "createElement", }, }, { type: "ImportSpecifier", imported: { type: "Identifier", name: mergedOptions.pragmaFrag || "Fragment", }, local: { type: "Identifier", name: mergedOptions.pragmaFrag || "Fragment", }, }, ], source: { type: "Literal", value: mergedOptions.importSource || "react", raw: `"${mergedOptions.importSource}"` || '"react"', }, } : null; const componentNode = { type: "VariableDeclaration", kind: "const", declarations: [ { type: "VariableDeclarator", id: { type: "Identifier", name: mergedOptions.componentName, }, init: { type: "ArrowFunctionExpression", expression: false, generator: false, async: false, params: mergedOptions.passProps ? [ { type: "Identifier", name: "props", }, ] : [], body: { type: "BlockStatement", body: [ { type: "ReturnStatement", argument: previousNodeBody.expression, }, ], }, }, }, ], }; const componentWithNamedExport = [ { type: "ExportNamedDeclaration", declaration: componentNode, specifiers: [], source: null, }, ]; const componentWithDefaultExport = [ componentNode, { type: "ExportDefaultDeclaration", declaration: { type: "Identifier", name: mergedOptions.componentName, }, }, ]; if (importDeclaration) { node.body = mergedOptions.defaultExport ? [importDeclaration, ...componentWithDefaultExport] : [importDeclaration, ...componentWithNamedExport]; } else { node.body = mergedOptions.defaultExport ? componentWithDefaultExport : componentWithNamedExport; } } if (mergedOptions.passProps && node.type === "JSXOpeningElement" && node.name.type === "JSXIdentifier" && node.name.name === "svg") { node.attributes.push({ type: "JSXSpreadAttribute", argument: { type: "Identifier", name: "props" }, }); } }, fallback: function (node) { if (node.type === "JSXFragment") return ["children"]; if (node.type === "JSXElement") return ["openingElement"]; if (node.type === "JSXOpeningElement") return ["attributes", "name", "selfClosing"]; if (node.type === "JSXAttribute") return ["name", "value"]; if (node.type === "JSXIdentifier") return ["name"]; if (node.type === "JSXSpreadAttribute") return ["argument"]; throw new Error("unknown node: " + node.type); }, }); return esTree; };