UNPKG

@poppanator/sveltekit-svg

Version:

Import SVG files as Svelte components

202 lines (201 loc) 8.32 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; const pluginutils_1 = require("@rollup/pluginutils"); const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); const compiler_1 = require("svelte/compiler"); const svgo_1 = require("svgo"); const { readFile } = fs_1.promises; function isCompileError(err) { return err instanceof Error && 'code' in err && 'frame' in err; } const svgRegex = /<svg(.*?)>(.*)<\/svg>/s; function color(start, end = '\u001b[0m') { return (text) => `${start}${text}${end}`; } // const green = color('\u001b[32m') const yellow = color('\u001b[33m'); const blue = color('\u001b[34m'); function toComponent(svg) { const parts = svgRegex.exec(svg); if (!parts) { throw new Error('Invalid SVG'); } const [, attributes, content] = parts; // JSON.stringify escapes any characters that need to be escaped and // surrounds `content` with double quotes const contentStrLiteral = JSON.stringify(content); const component = `<svg ${attributes} {...$$props}>{@html ${contentStrLiteral}}</svg>`; return { attributes, content, component, }; } function isSvgoOptimizeError(obj) { return typeof obj === 'object' && obj !== null && !('data' in obj); } function hasCdata(code) { return code.includes('<![CDATA['); } function isDevelopmentMode() { return process.env['NODE_ENV'] === 'development'; } function readSvg(options = { type: 'component' }) { const resvg = /\.svg(?:\?(src|url|component|dataurl)(=(base64|(un)?enc))?)?$/; if (options.includePaths) { // Normalize the include paths prefixes ahead of time options.includePaths = options.includePaths.map((pattern) => { const filepath = path_1.default.resolve(path_1.default.normalize(pattern)); return (0, pluginutils_1.normalizePath)(filepath); }); } const isType = (str, type) => { return (!str && options.type === type) || str === type; }; const handlePath = (id) => { if (!resvg.test(id)) { return false; } if (options.includePaths) { return options.includePaths.some((pattern) => { return id.startsWith(pattern); }); } return true; }; const hook = options.preCompileHook; /** * This contains URLs of generated static assets, that is SVGs that are * imported as URLs, so we can optimize them before they are written to disk. */ const optmizeUrls = new Set(); const plugin = { name: 'sveltekit-svg', // NOTE: This will only run in production mode renderChunk(_code, chunk) { var _a; // Check if this chunk is an SVG imported as an URL... const mods = chunk.moduleIds.filter((id) => { var _a; return (_a = this.getModuleInfo(id)) === null || _a === void 0 ? void 0 : _a.meta['isSvgUrl']; }); // ...if so, we should have 1 module and one importAssets... if (mods.length !== 1 || ((_a = chunk.viteMetadata) === null || _a === void 0 ? void 0 : _a.importedAssets.size) !== 1) { return undefined; } // ...and store the output filename in the lookup so we can verify in // `generateBundle()` that the SVG should be optimized before written // to disk const outputId = Array.from(chunk.viteMetadata.importedAssets)[0]; if (outputId) { optmizeUrls.add(outputId); } }, // NOTE: This will only run in production mode async generateBundle(_options, bundle) { // Resolve assets that should be SVGO optimized before written to disk. // Such assets should exist in the `optimizeUrl` lookup data structure. for (const [k, v] of Object.entries(bundle)) { if (!optmizeUrls.has(k) || v.type !== 'asset') { continue; } const optSvg = (0, svgo_1.optimize)(v.source.toString(), options.svgoOptions || undefined); v.source = Buffer.from(optSvg.data); } }, async transform(source, id, transformOptions) { var _a, _b; if (!handlePath(id)) { return undefined; } const match = id.match(resvg); if (!match) { return undefined; } const isBuild = (_a = transformOptions === null || transformOptions === void 0 ? void 0 : transformOptions.ssr) !== null && _a !== void 0 ? _a : false; const type = match[1]; if (isType(type, 'url')) { if (isDevelopmentMode()) { const msg = `"url" imports are not optimized in development mode`; this.info({ id, message: msg, }); } return { code: source, map: null, meta: { isSvgUrl: true } }; } let svgo = options.svgoOptions; let isSvgoDataUri = false; if (svgo && typeof svgo === 'object') { if (svgo.datauri) { isSvgoDataUri = true; } } if (isSvgoDataUri && type === 'component') { console.warn(`%s Type %O can not be imported as a Svelte component ` + `since "datauri" is set in vite.config`, yellow('[WARNING]'), id); } else if (type === 'dataurl') { const t = (_b = match[3]) !== null && _b !== void 0 ? _b : 'base64'; if (!svgo) { svgo = {}; } svgo.datauri = t; isSvgoDataUri = true; } try { const filename = id.replace(/\.svg(\?.*)$/, '.svg'); const data = (await readFile(filename)).toString('utf-8'); const opt = svgo !== false ? (0, svgo_1.optimize)(data, { path: filename, ...svgo }) : { data }; if (isSvgoOptimizeError(opt)) { console.error('Got optimize error from SVGO:', opt); return undefined; } if (isType(type, 'src') || isSvgoDataUri) { return { code: `\nexport default \`${opt.data}\`;`, map: null, }; } const comp = toComponent(opt.data); opt.data = hook ? hook(opt.data, comp) : comp.component; const { js } = (0, compiler_1.compile)(opt.data, { css: 'external', filename: id, namespace: 'svg', generate: isBuild ? 'server' : 'client', }); // Remove query string from sources js.map.sources.forEach((v, idx, a) => { if (v.includes('?')) { const t = v.split('?')[0]; if (t) { a[idx] = t; } } }); return js; } catch (err) { if (isCompileError(err) && hasCdata(err.frame)) { const msg = `\n%s The SVG file %O contains a %s section which is not ` + `supported by Svelte. To make this SVG work with the %s ` + `plugin, you need to remove all %s sections from the SVG.\n`; console.warn(msg, yellow('[WARNING]'), id, blue('<![CDATA[...]]>'), blue('@poppanator/sveltekit-svg'), blue('<![CDATA[...]]>')); } else { console.error('Failed reading SVG "%s": %s', id, err.message, err); } return undefined; } }, }; if (isDevelopmentMode()) { delete plugin.renderChunk; delete plugin.generateBundle; } return plugin; } module.exports = readSvg;