@svelte-put/preprocess-inline-svg
Version:
minimal svg inliner from local resources at build time
298 lines (292 loc) • 10.3 kB
JavaScript
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
// src/vite/vite.internals.ts
import fs2 from "fs";
import path2 from "path";
// src/preprocessor/preprocessor.internals.ts
import fs from "fs";
import path from "path";
// ../preprocess-helpers/src/index.js
function getAttribute(source, node, attributeName) {
const attr = node.attributes.find(
(attr2) => attr2.name === attributeName && attr2.type === "Attribute"
);
if (attr) {
let raw = source.slice(attr.start + attributeName.length + 1, attr.end);
if (raw.startsWith('"') && raw.endsWith('"')) {
raw = raw.slice(1, -1);
}
return raw;
}
}
// src/preprocessor/preprocessor.internals.ts
import { toHtml } from "hast-util-to-html";
import MagicString from "magic-string";
import { walk } from "svelte/compiler";
import { parse } from "svelte-parse-markup";
import { parse as parseSvg } from "svg-parser";
var DEFAULT_SOURCES_CONFIG = {
directories: [],
attributes: {}
};
function resolveSourceOptions(options) {
return {
directories: (options == null ? void 0 : options.directories) ? Array.isArray(options.directories) ? options.directories : [options.directories] : DEFAULT_SOURCES_CONFIG.directories,
attributes: (options == null ? void 0 : options.attributes) ? __spreadValues(__spreadValues({}, DEFAULT_SOURCES_CONFIG.attributes), options.attributes) : DEFAULT_SOURCES_CONFIG.attributes
};
}
function resolveSources(sources) {
if (!sources)
return { local: DEFAULT_SOURCES_CONFIG, dirs: [] };
if (!Array.isArray(sources)) {
if (!sources.directories) {
return {
local: resolveSourceOptions(sources),
dirs: []
};
}
const dir = resolveSourceOptions(sources);
const local = __spreadProps(__spreadValues({}, dir), {
directories: []
});
return { local, dirs: [dir] };
}
const inputsWithoutDirectories = sources.filter((i) => !i.directories);
if (inputsWithoutDirectories.length > 1) {
throw new Error(
"\n@svelte-put/preprocess-inline-svg: only one default input (one without `directories` option) is allowed"
);
}
return {
local: resolveSourceOptions(inputsWithoutDirectories[0]),
dirs: sources.filter((i) => !!i.directories).map(resolveSourceOptions)
};
}
var DEFAULT_INLINE_SVG_CONFIG = {
inlineSrcAttributeName: "data-inline-src",
keepInlineSrcAttribute: false
};
function resolveInlineSvgConfig(config = {}) {
var _a, _b;
return {
inlineSrcAttributeName: (_a = config.inlineSrcAttributeName) != null ? _a : DEFAULT_INLINE_SVG_CONFIG.inlineSrcAttributeName,
keepInlineSrcAttribute: (_b = config.keepInlineSrcAttribute) != null ? _b : DEFAULT_INLINE_SVG_CONFIG.keepInlineSrcAttribute
};
}
function findSvgSrc(filename, directories, inlineSrc) {
let resolvedSrc = void 0;
if (inlineSrc) {
if (!inlineSrc.endsWith(".svg"))
inlineSrc += ".svg";
if (directories.length === 0) {
resolvedSrc = path.join(path.dirname(filename), inlineSrc);
if (!fs.existsSync(resolvedSrc))
resolvedSrc = void 0;
} else {
for (const dir of directories) {
resolvedSrc = path.join(dir, inlineSrc);
if (!path.isAbsolute(resolvedSrc)) {
resolvedSrc = path.join(process.cwd(), resolvedSrc);
}
if (fs.existsSync(resolvedSrc))
break;
else
resolvedSrc = void 0;
}
}
}
return resolvedSrc;
}
function transform(code, filename, sources, config) {
const { local, dirs } = sources;
const { inlineSrcAttributeName, keepInlineSrcAttribute } = config;
const s = new MagicString(code);
const ast = parse(code, { filename });
walk(ast.html, {
enter(_node) {
const node = _node;
if (node.type !== "Element" || node.name !== "svg")
return;
let options = local;
let inlineSrc = getAttribute(code, node, inlineSrcAttributeName);
let svgSource = findSvgSrc(filename, options.directories, inlineSrc);
if (!svgSource) {
for (let i = 0; i < dirs.length; i++) {
options = dirs[i];
inlineSrc = getAttribute(code, node, inlineSrcAttributeName);
svgSource = findSvgSrc(filename, options.directories, inlineSrc);
if (svgSource)
break;
}
}
if (!inlineSrc)
return;
if (!svgSource) {
throw new Error(
`
@svelte-put/preprocess-inline-svg: cannot find svg source for ${inlineSrc} at ${filename}`
);
}
const svgStr = fs.readFileSync(svgSource, "utf8").replace(/&/g, "&");
const hast = parseSvg(svgStr);
const svg = hast.children[0];
const attributes = __spreadValues(__spreadValues({}, svg.properties), options.attributes);
node.attributes.map((attr) => {
if (attr.type === "Attribute") {
if (attr.name === inlineSrcAttributeName && !keepInlineSrcAttribute) {
s.remove(attr.start, attr.end);
}
if (attributes[attr.name] && attr.value.length === 1) {
delete attributes[attr.name];
}
}
});
for (const [name, value] of Object.entries(attributes)) {
s.appendRight(node.start + "<svg".length, ` ${name}="${value}" `);
}
let insertIndex = node.end - "/>".length;
if (s.slice(insertIndex, node.end) !== "/>") {
insertIndex = node.end - "</svg>".length;
}
const content = toHtml(svg.children, {
allowDangerousCharacters: true
});
s.update(insertIndex, node.end, `>${content}</svg>`);
}
});
return {
code: s.toString(),
map: s.generateMap()
};
}
// src/vite/vite.internals.ts
var DEFAULT_VITE_PLUGIN_CONFIG = __spreadProps(__spreadValues({}, DEFAULT_INLINE_SVG_CONFIG), {
extension: [".svelte"],
svgExtension: [".svg"],
sourceTypingGeneration: true
});
function resolveExtension(extension, fallback = []) {
return (extension ? Array.isArray(extension) ? extension : [extension] : fallback).map(
(ext) => ext.startsWith(".") ? ext : `.${ext}`
);
}
function resolveViteInlineSvgConfig(config = {}) {
var _a;
return __spreadProps(__spreadValues({}, resolveInlineSvgConfig(config)), {
extension: resolveExtension(config.extension, DEFAULT_VITE_PLUGIN_CONFIG.extension),
svgExtension: resolveExtension(config.svgExtension, DEFAULT_VITE_PLUGIN_CONFIG.svgExtension),
sourceTypingGeneration: (_a = config.sourceTypingGeneration) != null ? _a : DEFAULT_VITE_PLUGIN_CONFIG.sourceTypingGeneration
});
}
function findSvgRecursively(dir) {
const files = fs2.readdirSync(dir).map((f) => path2.join(dir, f)).filter((f) => {
const stat = fs2.statSync(f);
if (stat.isDirectory())
return true;
if (stat.isFile())
return f.endsWith(".svg");
return false;
});
const directories = files.filter((f) => fs2.statSync(f).isDirectory());
const subFiles = directories.flatMap(findSvgRecursively);
return [...files, ...subFiles];
}
function generateSourceTyping(sources) {
try {
const { local, dirs } = sources;
const sourcePath = path2.join(
process.cwd(),
"node_modules/@svelte-put/preprocess-inline-svg/dist/sources.generated.d.ts"
);
const directories = [...local.directories, ...dirs.flatMap((d) => d.directories)];
const svgs = /* @__PURE__ */ new Set();
for (const dir of directories) {
const files = findSvgRecursively(dir);
for (const file of files) {
const svg = path2.relative(dir, file).replace(".svg", "");
svgs.add(`'${svg}'`);
}
}
const nonTyped = ["`./${string}`", "`../${string}`"].join("\n | ");
const typing = Array.from(svgs).join("\n | ");
const source = `export type Source =
${nonTyped}
| ${typing};`;
fs2.writeFileSync(sourcePath, source);
} catch (error) {
console.error("[svelte-preprocess-inline-svg]", error);
}
}
// src/vite/vite.ts
import debounce from "lodash.debounce";
function hotReload(server) {
server.ws.send({ type: "full-reload" });
}
function matchFileExt(file, extensions) {
return extensions.some((ext) => file.endsWith(ext));
}
function inlineSvg(sources = [], config = {}) {
const rSources = resolveSources(sources);
const rConfig = resolveViteInlineSvgConfig(config);
return {
name: "svelte-preprocess-inline-svg",
enforce: "pre",
transform(code, id) {
if (matchFileExt(id, rConfig.extension)) {
return transform(code, id, rSources, rConfig);
}
},
configureServer(server) {
if (rConfig.sourceTypingGeneration) {
generateSourceTyping(rSources);
}
const updateSourceTyping = debounce(() => {
if (rConfig.sourceTypingGeneration) {
generateSourceTyping(rSources);
}
hotReload(server);
});
server.watcher.add(rConfig.svgExtension.map((ext) => `**/*${ext}`));
server.watcher.on("add", (file) => {
if (matchFileExt(file, rConfig.svgExtension)) {
updateSourceTyping();
}
});
server.watcher.on("unlink", (file) => {
if (matchFileExt(file, rConfig.svgExtension)) {
updateSourceTyping();
}
});
server.watcher.on("change", (file) => {
if (matchFileExt(file, rConfig.svgExtension)) {
hotReload(server);
}
});
}
};
}
export {
DEFAULT_VITE_PLUGIN_CONFIG,
generateSourceTyping,
inlineSvg,
resolveExtension,
resolveViteInlineSvgConfig
};
//# sourceMappingURL=index.js.map