@svgx/core
Version:
Transform SVG inputs into many outputs, especially "HAST" "JSX" and "JS".
223 lines (222 loc) • 9.88 kB
JavaScript
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;
};