vite-awesome-svg-loader
Version:
A universal Vite SVG loader. Imports SVGs as source code, base64 and data URI. Preserves stroke width. Replaces colors with currentColor or custom colors. Creates SVG sprites. Optimizes SVGs.
416 lines (415 loc) • 14.9 kB
TypeScript
import { MaybeArray } from "../common-utils/index";
/**
* `vite-awesome-svg-loader` options
*/
export interface SvgLoaderOptions {
/**
* Temporary directory where SVG files will be stored in dev mode.
*
* @default "temp"
*/
tempDir?: string;
/**
* Files where line width should be preserved by adding `vector-effect="non-scaling-stroke"`.
*
* See {@link FileMatchers} for the available formats.
*
* **Alternative**: Use import query: `import image from "./image.svg?preserve-line-width"`.
*/
preserveLineWidthList?: FileMatchers;
/**
* Files where line width should **NOT** be preserved.
*
* Takes precedence over {@link preserveLineWidthList} and import queries
* (`import image from "./image.svg?preserve-line-width"`).
*
* See {@link FileMatchers} for the available formats.
*/
skipPreserveLineWidthList?: FileMatchers;
/**
* CSS selectors where line width should **NOT** be preserved. Use this to keep specific elements' stroke widths
* unchanged.
*
* See {@link CssSelectors} for the available formats and more configuration options.
*
* **Note:** Unlike {@link skipTransformsSelectors}, doesn't impact build performance.
*
* @example
*
* viteAwesomeSvgLoader({
* skipPreserveLineWidthSelectors: [
* // Global selector:
* '*[data-original-line-width="true"], *[data-original-line-width="true"] *',
*
* // File-scoped selector:
* {
* files: [new RegExp("\\/some\\-file\\.svg"), new RegExp("\\/other\\-file\\.svg")],
* selectors: ['*[data-original-line-width="true"], *[data-original-line-width="true"] *'],
* },
* ],
* })
*/
skipPreserveLineWidthSelectors?: CssSelectors;
/**
* Files where fill, stroke and `<stop>` colors should be replaced with `currentColor` (default) or custom colors.
*
* **What's replaced:**
*
* 1. `fill`, `stroke` and `stop-color` attributes.
* 1. CSS identifiers.
*
* **What's untouched:**
*
* 1. `none`, `transparent`, and `currentColor` values.
*
* **Available formats** in application priority (from highest to lowest):
*
* 1. {@link ColorMapPerFiles} - a color to replacement map scoped to the files (see {@link FileMatchers})
* specified in {@link ColorMapPerFile.files} property.
*
* 1. {@link ColorMap} - a color to replacement map applied to all files.
*
* 1. {@link FileMatcher} - files where all colors should be replaced with `currentColor`.
*
* If multiple entries match the same file, replacements are merged by following rules:
*
* 1. If two entries replace the same color or have {@link ColorMapPerFiles.default} set, the entry with highest
* priority wins.
*
* 1. If, in previous case, both entries have the same priority, the entry with the lowest index in the original array
* wins.
*
* **Caveats:**
*
* Opacity set by initial colors, i.e. `rgba()` is lost when color is replaced. This is so because opacity retention
* would:
*
* 1. Still bring confusion because one may rightfully expect that color replacement will completely replace colors
* and not correct them.
*
* 1. Slow down build process.
*
* 1. Impose high maintenance complexity.
*
* **Notes:**
*
* Setting `currentColor` can be done with an import: `import imageSrc from "./path/to/image.svg?set-current-color"`.
*
* Setting custom color is not possible with import queries.
*
* @example
*
* viteAwesomeSvgLoader({
* replaceColorsList: [
* // FileMatcher: files where all colors should be replaced with `currentColor`
* "some-file.svg",
* new RegExp("\\/some\\/directory\\/"),
* (ctx) => ctx.relativePath.includes("/some/directory/") || ctx.fullPath.endsWith("-image.svg"),
*
* // ColorMap: map of color replacements.
* // Key is an original color, value is its replacement.
* {
* "#003147": "red",
* "rgb(0, 49, 71)": "#003147",
* "myCustomColor": "var(--some-color-var)",
* },
*
* // ColorMapPerFiles: a color to replacement map scoped to the specified files
* {
* // File matchers
* files: ["vars.svg"],
*
* // Replacements
* replacements: {
* red: "var(--primary-color)",
* green: "var(--secondary-color)",
* blue: "var(--tertiary-color)",
* },
*
* // A default color for unspecified colors
* default: "red",
* },
* ],
* })
*/
replaceColorsList?: MaybeArray<FileMatcher | ColorMap | ColorMapPerFiles>;
/**
* Files where colors should **NOT** be replaced.
*
* Takes precedence over {@link replaceColorsList} and import queries
* (`import image from "./image.svg?set-current-color"`).
*
* See {@link FileMatchers} for the available formats.
*/
skipReplaceColorsList?: FileMatchers;
/**
* CSS selectors where colors should **NOT** be replaced. Use it to leave specific elements colors unchanged.
*
* See {@link CssSelectors} for the available formats and more configuration options.
*
* **Warning**: Heavy usage can significantly slow down build performance.
*
* Limit selectors to specific files to improve performance.
*
* Or better, consider alternative approaches such as using color maps. See {@link replaceColorsList},
* {@link ColorMap}, and {@link ColorMapPerFiles}) for the details.
*
* @example
*
* viteAwesomeSvgLoader({
* skipReplaceColorsSelectors: [
* // Global selector:
* '*[data-original-colors="true"], *[data-original-colors="true"] *',
*
* // File-scoped selector:
* {
* files: [new RegExp("\\/some\\-file\\.svg"), new RegExp("\\/other\\-file\\.svg")],
* selectors: ['*[data-original-colors="true"], *[data-original-colors="true"] *'],
* },
* ],
* })
*/
skipReplaceColorsSelectors?: CssSelectors;
/**
* Files to **NOT** transform (except SVGO).
*
* See {@link FileMatchers} for the available formats.
*
* Takes precedence over {@link replaceColorsList}, {@link preserveLineWidthList}, and import queries
* (`import image from "./image.svg?set-current-color&preserve-line-width"`).
*
* **Alternative**: Use import query: `import image from "./image.svg?skip-transforms"`.
*/
skipTransformsList?: FileMatchers;
/**
* CSS selectors which should **NOT** be transformed. Use this to leave specific elements untouched.
*
* See {@link CssSelectors} for the available formats and more configuration options.
*
* **Warning**: Heavy usage can significantly slow down build performance.
*
* Limit selectors to specific files to improve performance.
*
* Or better, consider following approach:
*
* 1. To avoid color replacement, consider alternative approaches such as using color maps.
* See {@link replaceColorsList}, {@link ColorMap}, and {@link ColorMapPerFiles}) for the details.
*
* 1. To avoid line width preservation, just use {@link skipPreserveLineWidthSelectors}.
*
* @example
*
* viteAwesomeSvgLoader({
* skipTransformsSelectors: [
* // Global selector:
* '*[data-no-transforms="true"], *[data-no-transforms="true"] *',
*
* // File-scoped selector:
* {
* files: [new RegExp("\\/some\\-file\\.svg"), new RegExp("\\/other\\-file\\.svg")],
* selectors: ['*[data-no-transforms="true"], *[data-no-transforms="true"] *'],
* },
* ],
* })
*/
skipTransformsSelectors?: CssSelectors;
/**
* Files to bypass. They will be passed to another loader.
*
* See {@link FileMatchers} for the available formats.
*/
skipFilesList?: FileMatchers;
/**
* Default import type when no import query is specified. Available values:
*
* 1. `source` - SVG source code (default).
* 1. `url` - URL pointing to the file.
* 1. `source-data-uri` - SVG source code encoded in data URI.
* 1. `base64` - SVG source code encoded in base-64.
* 1. `base64-data-uri`: SVG source code encoded in base-64 data URI.
*
* @default "source"
*/
defaultImport?: ImportType;
/**
* Behavior for `?url` imports in [library mode](https://vite.dev/guide/build.html#library-mode). Possible values:
*
* 1. `source-data-uri` - source data URI will be exported, no files will be emitted.
*
* 1. `base-64-data-uri` - base64 data URI will be exported, no files will be emitted.
*
* 1. `emit-files` - files will be emitted, following exports will be produced:
*
* ```ts
* "" + new URL("file-name-[hash].svg", import.meta.url).href
* ```
*
* **Warning:** May cause issues when your library is consumed. Requires additional configuration from the users.
* **Warning:** This behavior is not aligned with the default Vite behavior.
*
* @default "source-data-uri"
*/
urlImportsInLibraryMode?: UrlImportsInLibraryMode;
}
/**
* A single instance or an array of file matchers in following formats:
*
* 1. **File name**: `image.svg`.
*
* 1. **Path to a file** from the project's root: `/src/assets/image.svg`.
*
* 1. **RegExp**: tested against file name (`image.svg`) and relative path (`/src/assets/image.svg`).
*
* 1. **Function** ({@link FileMatcherFn}) that accepts information about a file and returns `true`, if line width
* should be preserved.
*
* All paths use forward slash (`/`) as a separator. Relative paths start with `/`.
*
* **Recommendation:** Include path information and use clean project structure to avoid unintentional matches.
*
* @example
*
* const matchers: FileMatchers = [
* // Any file named "image.svg"
* "image.svg",
*
* // Specific file
* "/src/assets/image.svg",
*
* // Any path containing "/some/directory/"
* new RegExp("\\/some\\/directory\\/"),
*
* // Any file ending with "-image.svg"
* new RegExp("\\-image\\.svg$"),
*
* // Any path containing "/some/directory/" or any file ending with "-image.svg"
* (ctx) => ctx.relativePath.includes("/some/directory/") || ctx.fullPath.endsWith("-image.svg"),
* ];
*/
export type FileMatchers = MaybeArray<FileMatcher>;
/**
* Everything a file can be matched against
*/
export type FileMatcher = string | RegExp | FileMatcherFn;
/**
* Custom file matcher
*
* @param ctx Matching context (information about a file)
* @returns `true`, if file matches
*/
export type FileMatcherFn = (ctx: FileMatcherFnContext) => boolean;
/**
* Information about a file to be matched against a custom rule
*/
export interface FileMatcherFnContext {
/**
* Full path to the file. Uses forward slash (`/`) as a separator.
*/
fullPath: string;
/**
* Path to the file from the project's root. Uses forward slash (`/`) as a separator. Starts with a slash.
*/
relativePath: string;
}
/**
* SVG import type, see {@link SvgLoaderOptions.defaultImport}
*/
export type ImportType = "url" | "source" | "source-data-uri" | "base64" | "base64-data-uri";
/**
* See {@link SvgLoaderOptions.urlImportsInLibraryMode}
*/
export type UrlImportsInLibraryMode = "emit-files" | "source-data-uri" | "base64-data-uri";
/**
* A single instance or an array of CSS selectors ({@link CssSelector}).
*
* @example
*
* const selectors: CssSelectors = [
* // Global selector: applies to all files
* ".box > path",
*
* // File-scoped: single selector in single file
* { selectors: ".box > path", files: "/assets/my-file.svg" },
*
* // File-scoped: multiple selectors, multiple file matchers
* {
* files: ["/assets/my-file.svg", new RegExp("\\/some\\/directory\\/")],
* selectors: [".box > path", ".stripe.red"],
* }
* ];
*/
export type CssSelectors = MaybeArray<CssSelector>;
/**
* CSS selectors in following formats:
*
* 1. **String** - a selector that will be applied to all files.
* 1. **Object** ({@link SelectorsPerFiles}) - selectors applied only to the specified files.
*/
export type CssSelector = string | SelectorsPerFiles;
/**
* One or more selectors applied to one or more specified files
*/
export interface SelectorsPerFiles {
/**
* Files to which CSS selectors should be applied. See {@link FileMatchers} for the available formats.
*/
files: FileMatchers;
/**
* A single instance or an array of CSS selectors
*/
selectors: MaybeArray<string>;
}
/**
* Maps original SVG colors (case-insensitive) to their replacements (any values: HEX, name, rgb() or even arbitrary
* strings).
*
* Only specified keys will be replaced. Any other value even with the same color (i.e. `#00f` instead of `blue`)
* won't be affected.
*/
export type ColorMap = Record<string, string>;
/**
* Maps original SVG colors (case-insensitive) to their replacements per specified files.
*
* @example
*
* const map = {
* // Files where colors should be replaced
* files: ["my-file.svg", /icon\-*.\.svg/],
*
* // Color replacements
* replacements: {
* // Will replace all colors with "red" value with "var(--primary-color)".
* // It'll replace only "red" value. HEX ("#ff0000"), RGB ("rgb(255, 0, 0)") and other values won't be affected.
* "red": "var(--primary-color)",
*
* // Same as above: this HEX value (case-insensitive) will be replaced with "var(--secondary-color)".
* // Any other value with the same color (such as "blue" or "hsl(240deg 100% 50%)") won't be affected.
* "#0000ff": "var(--secondary-color)",
*
* // Again, only this value (case-insensitive) will be replaced with "var(--tertiary-color)".
* // Any other value won't be affected.
* "rgb(0, 255, 0)": "var(--tertiary-color)",
* },
*
* // A default color for unspecified colors.
* // If not specified, "currentColor" is used.
* // If an empty string specified, other colors won't be affected.
* default: "red",
* }
*/
export interface ColorMapPerFiles {
/**
* Files where colors should be replaced. See {@link FileMatchers} for the available formats.
*/
files: FileMatchers;
/**
* Color replacements. See {@link ColorMap} for the details.
*/
replacements: ColorMap;
/**
* Replacement for other colors. Set to empty string to leave colors untouched.
*
* @default "currentColor"
*/
default?: string;
}