vite-plugin-svg-sprite 0.5.2 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -46
- package/cjs/index.d.ts +3 -1
- package/cjs/index.js +47 -34
- package/cjs/package.json +1 -1
- package/cjs/runtime/adapters/react.d.ts +2 -0
- package/cjs/runtime/adapters/react.js +15 -0
- package/cjs/runtime/adapters/vanilla.d.ts +2 -0
- package/cjs/runtime/adapters/vanilla.js +5 -0
- package/cjs/runtime/adapters/vue.d.ts +2 -0
- package/cjs/runtime/adapters/vue.js +18 -0
- package/cjs/runtime/inject.d.ts +3 -0
- package/cjs/{runtime.js → runtime/inject.js} +16 -5
- package/cjs/runtime/types.d.ts +1 -0
- package/cjs/runtime/types.js +2 -0
- package/cjs/runtime/utils.d.ts +1 -0
- package/cjs/runtime/utils.js +6 -0
- package/cjs/svg-to-symbol.d.ts +8 -0
- package/cjs/svg-to-symbol.js +54 -0
- package/esm/index.d.ts +3 -1
- package/esm/index.js +46 -33
- package/esm/runtime/adapters/react.d.ts +2 -0
- package/esm/runtime/adapters/react.js +11 -0
- package/esm/runtime/adapters/vanilla.d.ts +2 -0
- package/esm/runtime/adapters/vanilla.js +1 -0
- package/esm/runtime/adapters/vue.d.ts +2 -0
- package/esm/runtime/adapters/vue.js +14 -0
- package/esm/runtime/inject.d.ts +3 -0
- package/esm/{runtime.js → runtime/inject.js} +16 -5
- package/esm/runtime/types.d.ts +1 -0
- package/esm/runtime/types.js +1 -0
- package/esm/runtime/utils.d.ts +1 -0
- package/esm/runtime/utils.js +3 -0
- package/esm/svg-to-symbol.d.ts +8 -0
- package/esm/svg-to-symbol.js +48 -0
- package/package.json +26 -30
- package/typings/internal.d.ts +1 -0
- package/typings/react.d.ts +11 -0
- package/typings/vallina.d.ts +9 -0
- package/typings/vue.d.ts +11 -0
- package/cjs/runtime.d.ts +0 -2
- package/client.d.ts +0 -9
- package/esm/runtime.d.ts +0 -2
- package/src/index.ts +0 -85
- package/src/runtime.ts +0 -33
package/README.md
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
# vite-plugin-svg-sprite
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> A Vite plugin for importing SVG files as SVG sprite symbols or components.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
You can install the plugin using npm, pnpm, or yarn:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install vite-plugin-svg-sprite --save-dev
|
|
11
|
+
# or
|
|
12
|
+
pnpm add vite-plugin-svg-sprite --save-dev
|
|
13
|
+
# or
|
|
14
|
+
yarn add vite-plugin-svg-sprite --dev
|
|
8
15
|
```
|
|
9
16
|
|
|
10
|
-
##
|
|
17
|
+
## How to Use
|
|
11
18
|
|
|
12
|
-
|
|
19
|
+
To use the plugin, import and configure it in your Vite configuration file (`vite.config.js|ts`):
|
|
13
20
|
|
|
14
21
|
```javascript
|
|
15
22
|
import createSvgSpritePlugin from 'vite-plugin-svg-sprite';
|
|
@@ -17,66 +24,70 @@ import createSvgSpritePlugin from 'vite-plugin-svg-sprite';
|
|
|
17
24
|
const config = {
|
|
18
25
|
plugins: [
|
|
19
26
|
createSvgSpritePlugin({
|
|
20
|
-
|
|
27
|
+
exportType: 'vanilla', // or 'react' or 'vue'
|
|
28
|
+
include: '**/icons/*.svg'
|
|
21
29
|
}),
|
|
22
30
|
],
|
|
23
31
|
}
|
|
24
32
|
```
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
### React
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
import appIconId from './path/to/icons/app.svg';
|
|
36
|
+
For React projects, set the `exportType` to `'react'` to import SVGs as components:
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<use
|
|
36
|
-
xlinkHref={`#${appIconId}`}
|
|
37
|
-
/>
|
|
38
|
-
</svg>
|
|
39
|
-
);
|
|
40
|
-
}
|
|
38
|
+
```javascript
|
|
39
|
+
import IconFoo from './icons/foo.svg';
|
|
40
|
+
|
|
41
|
+
<IconFoo />
|
|
41
42
|
```
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
This may seem similar to `svgr` but internally they are different.
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
import appIconId, { size } from './path/to/icons/app.svg';
|
|
46
|
+
`vite-plugin-svg-sprite` usually has a better render performance.
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
export default function App() {
|
|
50
|
-
return (
|
|
51
|
-
<svg {...size}>
|
|
52
|
-
<use
|
|
53
|
-
xlinkHref={`#${appIconId}`}
|
|
54
|
-
/>
|
|
55
|
-
</svg>
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
```
|
|
48
|
+
### Vue
|
|
59
49
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
50
|
+
For Vue projects, set the `exportType` to `'vue'` to import SVGs as components:
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
import IconFoo from './icons/foo.svg';
|
|
54
|
+
|
|
55
|
+
<IconFoo />
|
|
64
56
|
```
|
|
65
57
|
|
|
66
|
-
|
|
58
|
+
### Non-React / Non-Vue
|
|
59
|
+
|
|
60
|
+
For users not using React or Vue, set the `exportType` to `'vanilla'`. The imported value will be the `symbolId`, which can be used with SVG `<use>`:
|
|
67
61
|
|
|
68
62
|
```javascript
|
|
69
|
-
|
|
63
|
+
import IconFoo from './icons/foo.svg';
|
|
64
|
+
const html = `
|
|
65
|
+
<svg>
|
|
66
|
+
<use xlink:href="#${IconFoo}" />
|
|
67
|
+
</svg>
|
|
68
|
+
`;
|
|
70
69
|
```
|
|
71
70
|
|
|
72
|
-
###
|
|
71
|
+
### TypeScript Users
|
|
72
|
+
|
|
73
|
+
To get proper type hints in TypeScript, include the appropriate type definitions in your `tsconfig.json`:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
"types": [
|
|
77
|
+
// or "vite-plugin-svg-sprite/typings/react" | "vite-plugin-svg-sprite/typings/vue"
|
|
78
|
+
"vite-plugin-svg-sprite/typings/vanilla"
|
|
79
|
+
],
|
|
80
|
+
```
|
|
73
81
|
|
|
74
|
-
|
|
82
|
+
## API Configuration Options
|
|
75
83
|
|
|
76
|
-
|
|
84
|
+
- **symbolId**: (`string`, optional) Controls the generated symbol ID. Default is `'icon-[name]'`.
|
|
77
85
|
|
|
78
|
-
|
|
86
|
+
- **exportType**: (`'vanilla' | 'react' | 'vue'`, optional) Determines the type of the exported value. Default is `'vanilla'`.
|
|
87
|
+
- If set to `'vanilla'`, the value will be the `symbolId`.
|
|
88
|
+
- If set to `'react'`, the value will be a React component.
|
|
89
|
+
- If set to `'vue'`, the value will be a Vue component.
|
|
79
90
|
|
|
80
|
-
|
|
91
|
+
- **svgo**: (object, optional) Configuration for SVGO, refer to the [SVGO documentation](https://github.com/svg/svgo) for details.
|
|
81
92
|
|
|
82
|
-
|
|
93
|
+
- **include**: (string | string[], optional) Paths to match SVG files that should be processed. Default is `'**/icons/*.svg'`, following [micromatch](https://github.com/micromatch/micromatch) rules.
|
package/cjs/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Config as SvgoOptimizeOptions } from 'svgo';
|
|
2
2
|
import { Plugin } from 'vite';
|
|
3
|
+
declare const exportTypes: readonly ["vanilla", "react", "vue"];
|
|
3
4
|
export type { SvgoOptimizeOptions };
|
|
4
5
|
export interface SvgSpriteOptions {
|
|
5
6
|
include?: string[] | string;
|
|
6
7
|
symbolId?: string;
|
|
7
|
-
svgo?:
|
|
8
|
+
svgo?: SvgoOptimizeOptions;
|
|
9
|
+
exportType?: (typeof exportTypes)[number];
|
|
8
10
|
moduleSideEffects?: boolean;
|
|
9
11
|
}
|
|
10
12
|
declare const _default: (options?: SvgSpriteOptions) => Plugin<any>;
|
package/cjs/index.js
CHANGED
|
@@ -3,17 +3,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
6
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
9
9
|
const micromatch_1 = __importDefault(require("micromatch"));
|
|
10
|
-
const svg_baker_1 = __importDefault(require("svg-baker"));
|
|
11
|
-
const svg_parser_1 = require("svg-parser");
|
|
12
10
|
const svgo_1 = require("svgo");
|
|
11
|
+
const svg_to_symbol_js_1 = require("./svg-to-symbol.js");
|
|
13
12
|
const { stringify } = JSON;
|
|
13
|
+
const exportTypes = ['vanilla', 'react', 'vue'];
|
|
14
|
+
function getHash(content) {
|
|
15
|
+
const h = node_crypto_1.default.createHash('sha256');
|
|
16
|
+
h.update(content);
|
|
17
|
+
return h.digest('hex');
|
|
18
|
+
}
|
|
14
19
|
exports.default = (options) => {
|
|
15
20
|
var _a;
|
|
16
|
-
const svgCompiler = new svg_baker_1.default();
|
|
17
21
|
const match = (_a = options === null || options === void 0 ? void 0 : options.include) !== null && _a !== void 0 ? _a : '**.svg';
|
|
18
22
|
const svgoOptions = options === null || options === void 0 ? void 0 : options.svgo;
|
|
19
23
|
const plugin = {
|
|
@@ -25,37 +29,46 @@ exports.default = (options) => {
|
|
|
25
29
|
})) {
|
|
26
30
|
return undefined;
|
|
27
31
|
}
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
const code = await node_fs_1.default.promises.readFile(filepath, 'utf-8');
|
|
33
|
+
const hash = getHash(code).slice(0, 8);
|
|
34
|
+
const { name } = node_path_1.default.parse(filepath);
|
|
35
|
+
const optimizedSvg = (0, svgo_1.optimize)(code, {
|
|
36
|
+
...svgoOptions,
|
|
37
|
+
plugins: [
|
|
38
|
+
{
|
|
39
|
+
name: 'prefixIds',
|
|
40
|
+
params: {
|
|
41
|
+
prefix: hash,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
...(_a = svgoOptions === null || svgoOptions === void 0 ? void 0 : svgoOptions.plugins) !== null && _a !== void 0 ? _a : [],
|
|
45
|
+
],
|
|
46
|
+
}).data;
|
|
47
|
+
const symbolId = ((_b = options === null || options === void 0 ? void 0 : options.symbolId) !== null && _b !== void 0 ? _b : 'icon-[name]').replace(/\[hash\]/g, hash).replace(/\[name\]/g, name);
|
|
48
|
+
const symbolResults = (0, svg_to_symbol_js_1.svgToSymbol)(optimizedSvg, symbolId);
|
|
49
|
+
if (!symbolResults) {
|
|
50
|
+
throw new Error(`invalid svg file: ${filepath}`);
|
|
33
51
|
}
|
|
34
|
-
const {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
52
|
+
const { symbolXml, attributes } = symbolResults;
|
|
53
|
+
let exportType = options === null || options === void 0 ? void 0 : options.exportType;
|
|
54
|
+
if (!exportType || !exportTypes.includes(exportType)) {
|
|
55
|
+
exportType = 'vanilla';
|
|
38
56
|
}
|
|
39
|
-
let id = name;
|
|
40
|
-
if (options === null || options === void 0 ? void 0 : options.symbolId) {
|
|
41
|
-
id = options.symbolId;
|
|
42
|
-
if (id.includes('[hash]')) {
|
|
43
|
-
const hash = crypto_1.default.createHash('sha256');
|
|
44
|
-
hash.update(code);
|
|
45
|
-
id = id.replace(/\[hash\]/g, hash.digest('hex').slice(0, 6));
|
|
46
|
-
}
|
|
47
|
-
id = id.replace(/\[name\]/g, name);
|
|
48
|
-
}
|
|
49
|
-
const symbol = await svgCompiler.addSymbol({
|
|
50
|
-
id,
|
|
51
|
-
content: code,
|
|
52
|
-
path: filepath,
|
|
53
|
-
});
|
|
54
57
|
const codeToReturn = `
|
|
55
|
-
import addSymbol from 'vite-plugin-svg-sprite/runtime';
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
import addSymbol from 'vite-plugin-svg-sprite/runtime/inject.js';
|
|
59
|
+
import { adapter } from 'vite-plugin-svg-sprite/runtime/adapters/${exportType}.js';
|
|
60
|
+
const id = ${stringify(symbolId)};
|
|
61
|
+
const name = ${stringify(name)};
|
|
62
|
+
const symbolXml = ${stringify(symbolXml)};
|
|
63
|
+
const { dispose } = addSymbol(symbolXml, id);
|
|
64
|
+
|
|
65
|
+
export default adapter(id, name);
|
|
66
|
+
export const attributes = ${stringify(attributes)}
|
|
67
|
+
|
|
68
|
+
if (import.meta.hot) {
|
|
69
|
+
import.meta.hot.dispose(dispose);
|
|
70
|
+
import.meta.hot.accept();
|
|
71
|
+
}
|
|
59
72
|
`;
|
|
60
73
|
return {
|
|
61
74
|
code: codeToReturn,
|
package/cjs/package.json
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.adapter = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const utils_js_1 = require("../utils.js");
|
|
6
|
+
const adapter = (id, name) => {
|
|
7
|
+
const Icon = (0, react_1.memo)((props) => ((0, react_1.createElement)('svg', {
|
|
8
|
+
width: '1em',
|
|
9
|
+
height: '1em',
|
|
10
|
+
...props,
|
|
11
|
+
}, (0, react_1.createElement)('use', { xlinkHref: `#${id}` }))));
|
|
12
|
+
Icon.displayName = `Icon${(0, utils_js_1.capitalizeFirstLetter)(name)}`;
|
|
13
|
+
return Icon;
|
|
14
|
+
};
|
|
15
|
+
exports.adapter = adapter;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.adapter = void 0;
|
|
4
|
+
const vue_1 = require("vue");
|
|
5
|
+
const utils_js_1 = require("../utils.js");
|
|
6
|
+
const adapter = (id, name) => (0, vue_1.defineComponent)({
|
|
7
|
+
name: `Icon${(0, utils_js_1.capitalizeFirstLetter)(name)}`,
|
|
8
|
+
setup(props, { attrs }) {
|
|
9
|
+
return () => (0, vue_1.h)('svg', {
|
|
10
|
+
width: '1em',
|
|
11
|
+
height: '1em',
|
|
12
|
+
...attrs,
|
|
13
|
+
}, [
|
|
14
|
+
(0, vue_1.h)('use', { 'xlink:href': `#${id}` }),
|
|
15
|
+
]);
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
exports.adapter = adapter;
|
|
@@ -2,13 +2,19 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
function createAddSymbol() {
|
|
4
4
|
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
5
|
-
return () => { };
|
|
5
|
+
return () => () => { };
|
|
6
6
|
}
|
|
7
|
-
const idSet =
|
|
7
|
+
const idSet = (
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
window._SVG_SPRITE_IDS_ = window._SVG_SPRITE_IDS_ || new Set());
|
|
8
10
|
const root = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
9
11
|
root.style.position = 'absolute';
|
|
10
12
|
root.style.width = '0';
|
|
11
13
|
root.style.height = '0';
|
|
14
|
+
root.style.overflow = 'hidden';
|
|
15
|
+
root.ariaHidden = 'true';
|
|
16
|
+
// DO NOT SET THIS
|
|
17
|
+
// root.style.visibility = 'hidden';
|
|
12
18
|
function insertRoot() {
|
|
13
19
|
document.body.insertBefore(root, document.body.firstChild);
|
|
14
20
|
}
|
|
@@ -19,11 +25,16 @@ function createAddSymbol() {
|
|
|
19
25
|
insertRoot();
|
|
20
26
|
}
|
|
21
27
|
return function addSymbol(symbol, id) {
|
|
22
|
-
if (idSet.
|
|
23
|
-
console.warn(`Icon #${id} was
|
|
28
|
+
if (idSet.has(id) || document.getElementById(id)) {
|
|
29
|
+
console.warn(`Icon #${id} was repeatedly registered. It must be globally unique.`);
|
|
24
30
|
}
|
|
25
|
-
idSet.
|
|
31
|
+
idSet.add(id);
|
|
26
32
|
root.insertAdjacentHTML('beforeend', symbol);
|
|
33
|
+
const el = root.lastChild;
|
|
34
|
+
return function dispose() {
|
|
35
|
+
idSet.delete(id);
|
|
36
|
+
el === null || el === void 0 ? void 0 : el.remove();
|
|
37
|
+
};
|
|
27
38
|
};
|
|
28
39
|
}
|
|
29
40
|
exports.default = createAddSymbol();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type Adapter = (id: string, name: string) => unknown;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function capitalizeFirstLetter(text: string): string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.svgToSymbol = svgToSymbol;
|
|
7
|
+
const xmldom_1 = require("@xmldom/xmldom");
|
|
8
|
+
const micromatch_1 = __importDefault(require("micromatch"));
|
|
9
|
+
const preserveAttrs = [
|
|
10
|
+
'viewBox',
|
|
11
|
+
'preserveAspectRatio',
|
|
12
|
+
'class',
|
|
13
|
+
'overflow',
|
|
14
|
+
'stroke?(-*)',
|
|
15
|
+
'fill?(-*)',
|
|
16
|
+
'xmlns?(:*)',
|
|
17
|
+
'role',
|
|
18
|
+
'aria-*',
|
|
19
|
+
];
|
|
20
|
+
function findSvgNode(doc) {
|
|
21
|
+
return Array.from(doc.childNodes).find((node) => node.nodeType === doc.ELEMENT_NODE && node.tagName === 'svg');
|
|
22
|
+
}
|
|
23
|
+
function svgToSymbol(xml, id) {
|
|
24
|
+
const domParser = new xmldom_1.DOMParser();
|
|
25
|
+
const doc = domParser.parseFromString(xml.trim(), 'text/xml');
|
|
26
|
+
const svg = findSvgNode(doc);
|
|
27
|
+
if (!svg) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const symbol = doc.createElement('symbol');
|
|
31
|
+
symbol.setAttribute('id', id);
|
|
32
|
+
Array.from(svg.attributes).forEach((attr) => {
|
|
33
|
+
if (micromatch_1.default.isMatch(attr.name, preserveAttrs)) {
|
|
34
|
+
symbol.setAttribute(attr.name, attr.value);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
Array.from(svg.childNodes).forEach((node) => {
|
|
38
|
+
symbol.appendChild(node);
|
|
39
|
+
});
|
|
40
|
+
const width = svg.getAttribute('width');
|
|
41
|
+
const height = svg.getAttribute('height');
|
|
42
|
+
if (!symbol.hasAttribute('viewBox') && width && height) {
|
|
43
|
+
symbol.setAttribute('viewBox', `0 0 ${width} ${height}`);
|
|
44
|
+
}
|
|
45
|
+
const serializer = new xmldom_1.XMLSerializer();
|
|
46
|
+
return {
|
|
47
|
+
symbolXml: serializer.serializeToString(symbol),
|
|
48
|
+
attributes: {
|
|
49
|
+
width,
|
|
50
|
+
height,
|
|
51
|
+
viewBox: svg.getAttribute('viewBox'),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
package/esm/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Config as SvgoOptimizeOptions } from 'svgo';
|
|
2
2
|
import { Plugin } from 'vite';
|
|
3
|
+
declare const exportTypes: readonly ["vanilla", "react", "vue"];
|
|
3
4
|
export type { SvgoOptimizeOptions };
|
|
4
5
|
export interface SvgSpriteOptions {
|
|
5
6
|
include?: string[] | string;
|
|
6
7
|
symbolId?: string;
|
|
7
|
-
svgo?:
|
|
8
|
+
svgo?: SvgoOptimizeOptions;
|
|
9
|
+
exportType?: (typeof exportTypes)[number];
|
|
8
10
|
moduleSideEffects?: boolean;
|
|
9
11
|
}
|
|
10
12
|
declare const _default: (options?: SvgSpriteOptions) => Plugin<any>;
|
package/esm/index.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import p from 'path';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import crypto from 'crypto';
|
|
1
|
+
import p from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
4
|
import micromatch from 'micromatch';
|
|
5
|
-
import SVGCompiler from 'svg-baker';
|
|
6
|
-
import { parse } from 'svg-parser';
|
|
7
5
|
import { optimize } from 'svgo';
|
|
6
|
+
import { svgToSymbol } from './svg-to-symbol.js';
|
|
8
7
|
const { stringify } = JSON;
|
|
8
|
+
const exportTypes = ['vanilla', 'react', 'vue'];
|
|
9
|
+
function getHash(content) {
|
|
10
|
+
const h = crypto.createHash('sha256');
|
|
11
|
+
h.update(content);
|
|
12
|
+
return h.digest('hex');
|
|
13
|
+
}
|
|
9
14
|
export default (options) => {
|
|
10
15
|
var _a;
|
|
11
|
-
const svgCompiler = new SVGCompiler();
|
|
12
16
|
const match = (_a = options === null || options === void 0 ? void 0 : options.include) !== null && _a !== void 0 ? _a : '**.svg';
|
|
13
17
|
const svgoOptions = options === null || options === void 0 ? void 0 : options.svgo;
|
|
14
18
|
const plugin = {
|
|
@@ -20,37 +24,46 @@ export default (options) => {
|
|
|
20
24
|
})) {
|
|
21
25
|
return undefined;
|
|
22
26
|
}
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
let topLevelAttributes = {};
|
|
26
|
-
if (((_a = root.children[0]) === null || _a === void 0 ? void 0 : _a.type) === 'element') {
|
|
27
|
-
topLevelAttributes = (_b = root.children[0].properties) !== null && _b !== void 0 ? _b : {};
|
|
28
|
-
}
|
|
27
|
+
const code = await fs.promises.readFile(filepath, 'utf-8');
|
|
28
|
+
const hash = getHash(code).slice(0, 8);
|
|
29
29
|
const { name } = p.parse(filepath);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
const optimizedSvg = optimize(code, {
|
|
31
|
+
...svgoOptions,
|
|
32
|
+
plugins: [
|
|
33
|
+
{
|
|
34
|
+
name: 'prefixIds',
|
|
35
|
+
params: {
|
|
36
|
+
prefix: hash,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
...(_a = svgoOptions === null || svgoOptions === void 0 ? void 0 : svgoOptions.plugins) !== null && _a !== void 0 ? _a : [],
|
|
40
|
+
],
|
|
41
|
+
}).data;
|
|
42
|
+
const symbolId = ((_b = options === null || options === void 0 ? void 0 : options.symbolId) !== null && _b !== void 0 ? _b : 'icon-[name]').replace(/\[hash\]/g, hash).replace(/\[name\]/g, name);
|
|
43
|
+
const symbolResults = svgToSymbol(optimizedSvg, symbolId);
|
|
44
|
+
if (!symbolResults) {
|
|
45
|
+
throw new Error(`invalid svg file: ${filepath}`);
|
|
33
46
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const hash = crypto.createHash('sha256');
|
|
39
|
-
hash.update(code);
|
|
40
|
-
id = id.replace(/\[hash\]/g, hash.digest('hex').slice(0, 6));
|
|
41
|
-
}
|
|
42
|
-
id = id.replace(/\[name\]/g, name);
|
|
47
|
+
const { symbolXml, attributes } = symbolResults;
|
|
48
|
+
let exportType = options === null || options === void 0 ? void 0 : options.exportType;
|
|
49
|
+
if (!exportType || !exportTypes.includes(exportType)) {
|
|
50
|
+
exportType = 'vanilla';
|
|
43
51
|
}
|
|
44
|
-
const symbol = await svgCompiler.addSymbol({
|
|
45
|
-
id,
|
|
46
|
-
content: code,
|
|
47
|
-
path: filepath,
|
|
48
|
-
});
|
|
49
52
|
const codeToReturn = `
|
|
50
|
-
import addSymbol from 'vite-plugin-svg-sprite/runtime';
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
import addSymbol from 'vite-plugin-svg-sprite/runtime/inject.js';
|
|
54
|
+
import { adapter } from 'vite-plugin-svg-sprite/runtime/adapters/${exportType}.js';
|
|
55
|
+
const id = ${stringify(symbolId)};
|
|
56
|
+
const name = ${stringify(name)};
|
|
57
|
+
const symbolXml = ${stringify(symbolXml)};
|
|
58
|
+
const { dispose } = addSymbol(symbolXml, id);
|
|
59
|
+
|
|
60
|
+
export default adapter(id, name);
|
|
61
|
+
export const attributes = ${stringify(attributes)}
|
|
62
|
+
|
|
63
|
+
if (import.meta.hot) {
|
|
64
|
+
import.meta.hot.dispose(dispose);
|
|
65
|
+
import.meta.hot.accept();
|
|
66
|
+
}
|
|
54
67
|
`;
|
|
55
68
|
return {
|
|
56
69
|
code: codeToReturn,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { memo, createElement } from 'react';
|
|
2
|
+
import { capitalizeFirstLetter } from '../utils.js';
|
|
3
|
+
export const adapter = (id, name) => {
|
|
4
|
+
const Icon = memo((props) => (createElement('svg', {
|
|
5
|
+
width: '1em',
|
|
6
|
+
height: '1em',
|
|
7
|
+
...props,
|
|
8
|
+
}, createElement('use', { xlinkHref: `#${id}` }))));
|
|
9
|
+
Icon.displayName = `Icon${capitalizeFirstLetter(name)}`;
|
|
10
|
+
return Icon;
|
|
11
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const adapter = (id) => id;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineComponent, h } from 'vue';
|
|
2
|
+
import { capitalizeFirstLetter } from '../utils.js';
|
|
3
|
+
export const adapter = (id, name) => defineComponent({
|
|
4
|
+
name: `Icon${capitalizeFirstLetter(name)}`,
|
|
5
|
+
setup(props, { attrs }) {
|
|
6
|
+
return () => h('svg', {
|
|
7
|
+
width: '1em',
|
|
8
|
+
height: '1em',
|
|
9
|
+
...attrs,
|
|
10
|
+
}, [
|
|
11
|
+
h('use', { 'xlink:href': `#${id}` }),
|
|
12
|
+
]);
|
|
13
|
+
},
|
|
14
|
+
});
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
function createAddSymbol() {
|
|
2
2
|
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
3
|
-
return () => { };
|
|
3
|
+
return () => () => { };
|
|
4
4
|
}
|
|
5
|
-
const idSet =
|
|
5
|
+
const idSet = (
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
window._SVG_SPRITE_IDS_ = window._SVG_SPRITE_IDS_ || new Set());
|
|
6
8
|
const root = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
7
9
|
root.style.position = 'absolute';
|
|
8
10
|
root.style.width = '0';
|
|
9
11
|
root.style.height = '0';
|
|
12
|
+
root.style.overflow = 'hidden';
|
|
13
|
+
root.ariaHidden = 'true';
|
|
14
|
+
// DO NOT SET THIS
|
|
15
|
+
// root.style.visibility = 'hidden';
|
|
10
16
|
function insertRoot() {
|
|
11
17
|
document.body.insertBefore(root, document.body.firstChild);
|
|
12
18
|
}
|
|
@@ -17,11 +23,16 @@ function createAddSymbol() {
|
|
|
17
23
|
insertRoot();
|
|
18
24
|
}
|
|
19
25
|
return function addSymbol(symbol, id) {
|
|
20
|
-
if (idSet.
|
|
21
|
-
console.warn(`Icon #${id} was
|
|
26
|
+
if (idSet.has(id) || document.getElementById(id)) {
|
|
27
|
+
console.warn(`Icon #${id} was repeatedly registered. It must be globally unique.`);
|
|
22
28
|
}
|
|
23
|
-
idSet.
|
|
29
|
+
idSet.add(id);
|
|
24
30
|
root.insertAdjacentHTML('beforeend', symbol);
|
|
31
|
+
const el = root.lastChild;
|
|
32
|
+
return function dispose() {
|
|
33
|
+
idSet.delete(id);
|
|
34
|
+
el === null || el === void 0 ? void 0 : el.remove();
|
|
35
|
+
};
|
|
25
36
|
};
|
|
26
37
|
}
|
|
27
38
|
export default createAddSymbol();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type Adapter = (id: string, name: string) => unknown;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function capitalizeFirstLetter(text: string): string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { DOMParser, XMLSerializer, } from '@xmldom/xmldom';
|
|
2
|
+
import micromatch from 'micromatch';
|
|
3
|
+
const preserveAttrs = [
|
|
4
|
+
'viewBox',
|
|
5
|
+
'preserveAspectRatio',
|
|
6
|
+
'class',
|
|
7
|
+
'overflow',
|
|
8
|
+
'stroke?(-*)',
|
|
9
|
+
'fill?(-*)',
|
|
10
|
+
'xmlns?(:*)',
|
|
11
|
+
'role',
|
|
12
|
+
'aria-*',
|
|
13
|
+
];
|
|
14
|
+
function findSvgNode(doc) {
|
|
15
|
+
return Array.from(doc.childNodes).find((node) => node.nodeType === doc.ELEMENT_NODE && node.tagName === 'svg');
|
|
16
|
+
}
|
|
17
|
+
export function svgToSymbol(xml, id) {
|
|
18
|
+
const domParser = new DOMParser();
|
|
19
|
+
const doc = domParser.parseFromString(xml.trim(), 'text/xml');
|
|
20
|
+
const svg = findSvgNode(doc);
|
|
21
|
+
if (!svg) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const symbol = doc.createElement('symbol');
|
|
25
|
+
symbol.setAttribute('id', id);
|
|
26
|
+
Array.from(svg.attributes).forEach((attr) => {
|
|
27
|
+
if (micromatch.isMatch(attr.name, preserveAttrs)) {
|
|
28
|
+
symbol.setAttribute(attr.name, attr.value);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
Array.from(svg.childNodes).forEach((node) => {
|
|
32
|
+
symbol.appendChild(node);
|
|
33
|
+
});
|
|
34
|
+
const width = svg.getAttribute('width');
|
|
35
|
+
const height = svg.getAttribute('height');
|
|
36
|
+
if (!symbol.hasAttribute('viewBox') && width && height) {
|
|
37
|
+
symbol.setAttribute('viewBox', `0 0 ${width} ${height}`);
|
|
38
|
+
}
|
|
39
|
+
const serializer = new XMLSerializer();
|
|
40
|
+
return {
|
|
41
|
+
symbolXml: serializer.serializeToString(symbol),
|
|
42
|
+
attributes: {
|
|
43
|
+
width,
|
|
44
|
+
height,
|
|
45
|
+
viewBox: svg.getAttribute('viewBox'),
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-svg-sprite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "SVG sprite plugin for Vite",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -14,18 +14,9 @@
|
|
|
14
14
|
"default": "./cjs/index.js"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
|
-
"./runtime":
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"default": "./esm/runtime.js"
|
|
21
|
-
},
|
|
22
|
-
"require": {
|
|
23
|
-
"types": "./cjs/runtime.d.ts",
|
|
24
|
-
"default": "./cjs/runtime.js"
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
"./client": {
|
|
28
|
-
"types": "./client.d.ts"
|
|
17
|
+
"./runtime/*.js": "./esm/runtime/*.js",
|
|
18
|
+
"./react": {
|
|
19
|
+
"types": "./react.d.ts"
|
|
29
20
|
}
|
|
30
21
|
},
|
|
31
22
|
"main": "cjs/index.js",
|
|
@@ -33,14 +24,12 @@
|
|
|
33
24
|
"files": [
|
|
34
25
|
"cjs",
|
|
35
26
|
"esm",
|
|
36
|
-
"
|
|
37
|
-
"client.d.ts",
|
|
38
|
-
"index.mjs",
|
|
27
|
+
"typings",
|
|
39
28
|
"LICENSE"
|
|
40
29
|
],
|
|
41
30
|
"scripts": {
|
|
42
31
|
"lint": "eslint src",
|
|
43
|
-
"build": "rm -rf cjs esm && tsc &&
|
|
32
|
+
"build": "shx rm -rf cjs esm && tsc && tsc -m commonjs --outDir cjs && shx cp ./package-cjs.json cjs/package.json",
|
|
44
33
|
"prepublishOnly": "npm run build"
|
|
45
34
|
},
|
|
46
35
|
"keywords": [
|
|
@@ -56,24 +45,31 @@
|
|
|
56
45
|
"devDependencies": {
|
|
57
46
|
"@types/micromatch": "^4.0.1",
|
|
58
47
|
"@types/node": "^20.4.2",
|
|
59
|
-
"@types/
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
"@typescript-eslint/eslint-plugin": "^7.7.1",
|
|
63
|
-
"eslint": "^8.45.0",
|
|
64
|
-
"eslint-config-airbnb-base": "^15.0.0",
|
|
65
|
-
"eslint-config-airbnb-typescript": "^18.0.0",
|
|
66
|
-
"eslint-plugin-import": "^2.22.1",
|
|
48
|
+
"@types/react": "^18.3.12",
|
|
49
|
+
"eslint": "^9.17.0",
|
|
50
|
+
"shx": "^0.3.4",
|
|
67
51
|
"typescript": "^5.1.6",
|
|
68
|
-
"
|
|
52
|
+
"typescript-eslint": "^8.18.0",
|
|
53
|
+
"vite": "^6.0.3",
|
|
54
|
+
"vue": "^3.5.13"
|
|
69
55
|
},
|
|
70
56
|
"dependencies": {
|
|
57
|
+
"@xmldom/xmldom": "^0.9.4",
|
|
71
58
|
"micromatch": "^4.0.2",
|
|
72
|
-
"svg-baker": "~1.7.0",
|
|
73
|
-
"svg-parser": "^2.0.4",
|
|
74
59
|
"svgo": "^3.0.2"
|
|
75
60
|
},
|
|
76
61
|
"peerDependencies": {
|
|
77
|
-
"
|
|
78
|
-
|
|
62
|
+
"react": "17 || 18 || 19",
|
|
63
|
+
"vite": "2 || 3 || 4 || 5 || 6",
|
|
64
|
+
"vue": "3"
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"react": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"vue": {
|
|
71
|
+
"optional": true
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"packageManager": "pnpm@9.12.3+sha512.cce0f9de9c5a7c95bef944169cc5dfe8741abfb145078c0d508b868056848a87c81e626246cb60967cbd7fd29a6c062ef73ff840d96b3c86c40ac92cf4a813ee"
|
|
79
75
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module 'domready';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare module '*.svg' {
|
|
2
|
+
import { SVGAttributes, ComponentClass } from 'react';
|
|
3
|
+
|
|
4
|
+
const Component: ComponentClass<SVGAttributes<SVGElement>>;
|
|
5
|
+
export default Component;
|
|
6
|
+
export const attributes: {
|
|
7
|
+
width?: string;
|
|
8
|
+
height?: string;
|
|
9
|
+
viewBox?: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
package/typings/vue.d.ts
ADDED
package/cjs/runtime.d.ts
DELETED
package/client.d.ts
DELETED
package/esm/runtime.d.ts
DELETED
package/src/index.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import p from 'path';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import crypto from 'crypto';
|
|
4
|
-
import micromatch from 'micromatch';
|
|
5
|
-
import SVGCompiler from 'svg-baker';
|
|
6
|
-
import { parse } from 'svg-parser';
|
|
7
|
-
import { optimize, Config as SvgoOptimizeOptions } from 'svgo';
|
|
8
|
-
import { Plugin } from 'vite';
|
|
9
|
-
|
|
10
|
-
const { stringify } = JSON;
|
|
11
|
-
|
|
12
|
-
export type { SvgoOptimizeOptions };
|
|
13
|
-
export interface SvgSpriteOptions {
|
|
14
|
-
include?: string[] | string;
|
|
15
|
-
symbolId?: string;
|
|
16
|
-
svgo?: boolean | SvgoOptimizeOptions;
|
|
17
|
-
moduleSideEffects?: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export default (options?: SvgSpriteOptions) => {
|
|
21
|
-
const svgCompiler = new SVGCompiler();
|
|
22
|
-
const match = options?.include ?? '**.svg';
|
|
23
|
-
const svgoOptions = options?.svgo;
|
|
24
|
-
|
|
25
|
-
const plugin: Plugin = {
|
|
26
|
-
name: 'svg-sprite',
|
|
27
|
-
|
|
28
|
-
async transform(src, filepath) {
|
|
29
|
-
if (!micromatch.isMatch(filepath, match, {
|
|
30
|
-
dot: true,
|
|
31
|
-
})) {
|
|
32
|
-
return undefined;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
let code = await fs.promises.readFile(filepath, 'utf-8');
|
|
36
|
-
|
|
37
|
-
const root = parse(code);
|
|
38
|
-
let topLevelAttributes: Record<string, string | number> = {};
|
|
39
|
-
if (root.children[0]?.type === 'element') {
|
|
40
|
-
topLevelAttributes = root.children[0].properties ?? {};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const { name } = p.parse(filepath);
|
|
44
|
-
if (svgoOptions !== false) {
|
|
45
|
-
const result = (optimize(code, svgoOptions === true ? undefined : svgoOptions));
|
|
46
|
-
code = result.data;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
let id = name;
|
|
50
|
-
|
|
51
|
-
if (options?.symbolId) {
|
|
52
|
-
id = options.symbolId;
|
|
53
|
-
|
|
54
|
-
if (id.includes('[hash]')) {
|
|
55
|
-
const hash = crypto.createHash('sha256');
|
|
56
|
-
hash.update(code);
|
|
57
|
-
id = id.replace(/\[hash\]/g, hash.digest('hex').slice(0, 6));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
id = id.replace(/\[name\]/g, name);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const symbol = await svgCompiler.addSymbol({
|
|
64
|
-
id,
|
|
65
|
-
content: code,
|
|
66
|
-
path: filepath,
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
const codeToReturn = `
|
|
70
|
-
import addSymbol from 'vite-plugin-svg-sprite/runtime';
|
|
71
|
-
addSymbol(${stringify(symbol.render())}, ${stringify(id)});
|
|
72
|
-
export default ${stringify(id)};
|
|
73
|
-
export const size = { width: ${stringify(topLevelAttributes.width)}, height: ${stringify(topLevelAttributes.height)} };
|
|
74
|
-
`;
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
code: codeToReturn,
|
|
78
|
-
moduleSideEffects: options?.moduleSideEffects ?? true,
|
|
79
|
-
map: { mappings: '' },
|
|
80
|
-
};
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
return plugin;
|
|
85
|
-
};
|
package/src/runtime.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
function createAddSymbol() {
|
|
2
|
-
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
3
|
-
return () => {};
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
const idSet = (window as any)._SVG_SPRITE_IDS_ = (window as any)._SVG_SPRITE_IDS_ || [];
|
|
7
|
-
|
|
8
|
-
const root = document.createElementNS('http://www.w3.org/2000/svg', 'svg') as SVGSVGElement;
|
|
9
|
-
root.style.position = 'absolute';
|
|
10
|
-
root.style.width = '0';
|
|
11
|
-
root.style.height = '0';
|
|
12
|
-
|
|
13
|
-
function insertRoot() {
|
|
14
|
-
document.body.insertBefore(root, document.body.firstChild);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (document.readyState === 'loading') {
|
|
18
|
-
document.addEventListener('DOMContentLoaded', insertRoot);
|
|
19
|
-
} else {
|
|
20
|
-
insertRoot();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return function addSymbol(symbol: string, id: string) {
|
|
24
|
-
if (idSet.indexOf(id) > -1 || document.getElementById(id)) {
|
|
25
|
-
console.warn(`Icon #${id} was duplicately registered. It must be globally unique.`);
|
|
26
|
-
}
|
|
27
|
-
idSet.push(id);
|
|
28
|
-
|
|
29
|
-
root.insertAdjacentHTML('beforeend', symbol);
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export default createAddSymbol();
|