vite-vanjs-svg
Version:
Vite plugin to transform SVGs into VanJS components
108 lines (93 loc) • 3.33 kB
JavaScript
import fs from "node:fs";
import path from "node:path";
import { createFilter } from "@rollup/pluginutils";
import { htmlToVanCode } from "./htmlToVanCode.mjs";
import process from "node:process";
const cwd = process.cwd();
/** @typedef {import("vanjs-core").PropsWithKnownKeys<SVGSVGElement>} PropsWithKnownKeys */
/** @typedef {import("vite").ResolvedConfig} ResolvedConfig */
/** @typedef {typeof import("./types").VitePluginVanSVG} VitePluginVanSVG */
/** @typedef {import("vite").BuildAppHook} BuildAppHook */
/** @typedef {ThisParameterType<BuildAppHook>} PluginContext */
/**
* Compiles SVGs to VanJS component code
* @param {string} svgCode
* @returns {string} the compiled code
*/
function transformSvgToVanJS(svgCode) {
// Convert the SVG string directly to VanJS code using htmlToVanCode
const vanCode = htmlToVanCode(svgCode);
// Wrap the converted code in a component
const componentCode = `
import van from 'vanjs-core';
export default ({ children, ...rest }) => {
const { ${vanCode.tags.join(", ")} } = van.tags("http://www.w3.org/2000/svg");
const props = Object.fromEntries(Object.entries(rest).filter(([_, val]) => val));
return ${vanCode.code}
}
`.trim();
return componentCode;
}
/** @type {VitePluginVanSVG} */
export default function vitePluginSvgVan(options = {}) {
const {
esbuildOptions,
oxcOptions,
include = ["**/*.svg?van"],
exclude,
} = options;
const filter = createFilter(include, exclude);
const postfixRE = /[?#].*$/s;
/** @type {Partial<ResolvedConfig>} */
let config;
let isOxc = true;
return {
name: "vanjs-svg",
enforce: "pre",
buildStart() {
const { viteVersion } = this.meta;
isOxc = Number(viteVersion[0]) >= 8;
},
// istanbul ignore next - impossible to test outside of vite runtime
configResolved(cfg) {
config = cfg;
},
async load(id) {
if (filter(id)) {
const file = id.replace(postfixRE, "");
// Resolve the file path
/* istanbul ignore next @preserve - we cannot test this outside the vite runtime */
const filePath =
!file.startsWith(cwd) && file.startsWith("/") && config?.publicDir
? path.resolve(config.publicDir, file.slice(1))
: file;
// Read the SVG file
const svgCode = await fs.promises.readFile(filePath, "utf8");
// Transform SVG to VanJS component
const componentCode = transformSvgToVanJS(svgCode);
const vite = await import("vite");
const transformer = isOxc ? "transformWithOxc" : "transformWithEsbuild";
const langProp = isOxc ? "lang" : "loader";
const options = (isOxc ? oxcOptions : esbuildOptions) || {};
// const mapProp = isOxc ? "source_map" : "sourcemap";
// Transform the component code using esbuild/oxc
const result = await vite[transformer](componentCode, id, {
[langProp]: "jsx",
sourcemap: true,
...options,
});
return {
code: result.code,
map: result.map
? (
typeof result.map === "string"
? JSON.parse(result.map)
: result.map
)
: /* istanbul ignore next @preserve */ null,
};
}
return null;
},
};
}