nuxt-svgo
Version:
Nuxt module to load optimized SVG files as Vue components
179 lines (175 loc) • 5.3 kB
JavaScript
import { defineNuxtModule, createResolver, addComponent, addVitePlugin, addComponentsDir, extendWebpackConfig } from '@nuxt/kit';
import { readFile } from 'node:fs/promises';
import { basename, extname } from 'node:path';
import { compileTemplate } from 'vue/compiler-sfc';
import { optimize } from 'svgo';
import urlEncodeSvg from 'mini-svg-data-uri';
function svgLoader(options) {
const { svgoConfig, svgo, defaultImport, explicitImportsOnly, autoImportPath, customComponent } = options || {};
const normalizedCustomComponent = customComponent.includes("-") ? customComponent.split("-").map((c) => c[0].toUpperCase() + c.substring(1).toLowerCase()).join("") : customComponent;
const autoImportPathNormalized = autoImportPath && autoImportPath.replaceAll(/^\.*(?=[/\\])/g, "");
const svgRegex = /\.svg(\?(url_encode|raw|raw_optimized|component|skipsvgo|componentext))?$/;
const explicitImportRegex = /\.svg(\?(url_encode|raw|raw_optimized|component|skipsvgo|componentext))+$/;
return {
name: "svg-loader",
enforce: "pre",
async load(id) {
if (!id.match(svgRegex) || id.startsWith("virtual:public")) {
return;
}
const [path, query] = id.split("?", 2);
if (explicitImportsOnly) {
const isExplicitlyQueried = id.match(explicitImportRegex);
if (!isExplicitlyQueried) {
if (autoImportPathNormalized) {
if (!path.includes(autoImportPathNormalized)) {
return;
}
} else {
return;
}
}
}
const importType = query || defaultImport;
if (importType === "url") {
return;
}
let svg;
try {
svg = await readFile(path, "utf-8");
} catch (ex) {
console.warn(
"\n",
`${id} couldn't be loaded by vite-svg-loader, fallback to default loader`
);
return;
}
if (importType === "raw") {
return `export default ${JSON.stringify(svg)}`;
}
if (svgo !== false && query !== "skipsvgo") {
svg = optimize(svg, {
...svgoConfig,
path
}).data;
}
if (importType === "url_encode") {
return `export default "${urlEncodeSvg(svg)}"`;
}
if (importType === "raw_optimized") {
return `export default ${JSON.stringify(svg)}`;
}
svg = svg.replace(/<style/g, '<component is="style"').replace(/<\/style/g, "</component");
const svgName = basename(path, extname(path));
let { code } = compileTemplate({
id: JSON.stringify(id),
source: svg,
filename: path,
transformAssetUrls: false
});
if (importType === "componentext") {
code = `import {${normalizedCustomComponent}} from "#components";
import {h} from "vue";
` + code;
code += `
export default { render() { return h(${normalizedCustomComponent}, {icon: {render}, name: "${svgName}"}) } }`;
return code;
} else {
return `${code}
export default { render: render }`;
}
}
};
}
function hashCode(str) {
let hash = 0;
for (let i = 0, len = str.length; i < len; i++) {
const chr = str.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0;
}
return hash;
}
const defaultSvgoConfig = {
plugins: [
{
name: "preset-default",
params: {
overrides: {
removeViewBox: false
}
}
},
"removeDimensions",
{
name: "prefixIds",
params: {
prefix(_, info) {
return "i" + hashCode(info.path);
}
}
}
]
};
const nuxtSvgo = defineNuxtModule({
meta: {
name: "nuxt-svgo",
configKey: "svgo",
compatibility: {
// Add -rc.0 due to issue described in https://github.com/nuxt/framework/issues/6699
nuxt: ">=3.0.0-rc.0"
}
},
defaults: {
svgo: true,
defaultImport: "componentext",
autoImportPath: "./assets/icons/",
svgoConfig: void 0,
global: true,
customComponent: "NuxtIcon",
componentPrefix: "svgo"
},
async setup(options) {
const { resolvePath, resolve } = createResolver(import.meta.url);
addComponent({
name: "nuxt-icon",
filePath: resolve("./runtime/components/nuxt-icon.vue")
});
addVitePlugin(
svgLoader({
...options,
svgoConfig: options.svgoConfig || defaultSvgoConfig
})
);
if (options.autoImportPath) {
addComponentsDir({
path: await resolvePath(options.autoImportPath),
global: options.global,
extensions: ["svg"],
prefix: options.componentPrefix || "svgo",
watch: true
});
}
extendWebpackConfig((config) => {
const svgRule = config.module.rules.find((rule) => rule.test.test(".svg"));
svgRule.test = /\.(png|jpe?g|gif|webp)$/;
config.module.rules.push({
test: /\.svg$/,
use: [
"vue-loader",
{
loader: "vue-svg-loader",
options: {
svgo: false
}
},
options.svgo && {
loader: "svgo-loader",
options: options.svgoConfig || defaultSvgoConfig
}
].filter(Boolean)
});
});
}
});
export { nuxtSvgo as default, defaultSvgoConfig };