UNPKG

nuxt-svgo

Version:

Nuxt module to load optimized SVG files as Vue components

179 lines (175 loc) 5.3 kB
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 };