Skip to content

Commit ec71ef7

Browse files
committed
v0.4.0: Going full speed [publish]
1 parent 072875e commit ec71ef7

File tree

10 files changed

+320
-247
lines changed

10 files changed

+320
-247
lines changed

.github/workflows/publish.yml

+5-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ jobs:
88
runs-on: ubuntu-latest
99
if: ${{ contains(github.event.head_commit.message, '[publish]') }}
1010
steps:
11-
- uses: actions/checkout@v2
12-
- run: yarn install --frozen-lockfile
13-
- run: yarn prettier-ci
14-
- run: yarn build
11+
- uses: actions/checkout@v3
12+
- uses: xhyrom/[email protected]
13+
- run: bun i
14+
- run: bun ci
1515
- uses: ArnaudBarre/npm-publish@v1
1616
with:
17+
working-directory: dist
1718
npm-token: ${{ secrets.NPM_TOKEN }}

CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## 0.4.0
4+
5+
Turn SVG into React components, even faster.
6+
7+
This new version uses only regex and dangerouslySetInnerHTML to directly create a JS output.
8+
9+
Breaking changes:
10+
11+
- No more options available
12+
- Expose the new `svgToJS` function instead of the previous `svgToJSX`
13+
14+
Compatible with Vite 4.
15+
316
## 0.3.1
417

518
Forward ref to svg element

README.md

+2-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Turn SVG into React components, without Babel.
66

77
## Why
88

9-
While [svgr](https://github.com/gregberge/svgr) is great, it uses AST transformation from Babel, which is too slow (~300ms per SVG). This plugin uses regex manipulations for SVG -> JSX and esbuild for JSX -> JS (~10ms in average). It's working well for SVG optimized by [svgo](https://github.com/svg/svgo).
9+
While [svgr](https://github.com/gregberge/svgr) is great, it uses AST transformation from Babel, which is too slow (~300ms per SVG). This plugin uses regex manipulations and `dangerouslySetInnerHTML`, which is almost instantaneous. It's working well for SVG optimized by [svgo](https://github.com/svg/svgo).
1010

1111
## Installation
1212

@@ -21,7 +21,7 @@ import { defineConfig } from "vite";
2121
import { svgPlugin } from "vite-plugin-fast-react-svg";
2222

2323
export default defineConfig({
24-
plugins: [svgPlugin({ useInnerHTML: true })],
24+
plugins: [svgPlugin()],
2525
});
2626
```
2727

@@ -48,7 +48,3 @@ const Example = () => (
4848
</>
4949
);
5050
```
51-
52-
## Options
53-
54-
**useInnerHTML**: Set to true to use `dangerouslySetInnerHTML` for SVG contents, which improve bundle size. Added in 0.3.0.

bun.lockb

15.5 KB
Binary file not shown.

bunfig.toml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[install.lockfile]
2+
print = "yarn"

package.json

+14-23
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,26 @@
11
{
22
"name": "vite-plugin-fast-react-svg",
3-
"description": "Turn SVG into React components, without Babel",
4-
"version": "0.3.1",
3+
"version": "0.4.0",
54
"license": "MIT",
6-
"author": "Arnaud Barré (https://github.com/ArnaudBarre)",
7-
"main": "dist/index.js",
8-
"files": [
9-
"dist",
10-
"types.d.ts"
11-
],
12-
"repository": "github:ArnaudBarre/vite-plugin-fast-react-svg",
13-
"keywords": [
14-
"vite",
15-
"vite-plugin",
16-
"react",
17-
"svg"
18-
],
195
"scripts": {
20-
"build": "tsc",
6+
"build": "scripts/bundle.ts",
217
"prettier": "yarn prettier-ci --write",
22-
"prettier-ci": "prettier --check '**/*.{ts,json,md,yml}'"
8+
"prettier-ci": "prettier --ignore-path=.gitignore --check '**/*.{ts,json,md,yml}'",
9+
"ci": "tsc && bun prettier-ci && bun run build"
10+
},
11+
"prettier": {
12+
"trailingComma": "all"
2313
},
2414
"peerDependencies": {
2515
"react": ">=16",
26-
"vite": "^2 || ^3"
16+
"vite": "^2 || ^3 || ^4"
2717
},
2818
"devDependencies": {
29-
"@types/node": "^18.0.6",
30-
"@types/react": "^18.0.15",
31-
"prettier": "^2.7.1",
32-
"typescript": "^4.7.4",
33-
"vite": "^3.0.2"
19+
"@nabla/tnode": "^0.8.0",
20+
"@types/node": "^18.11.11",
21+
"@types/react": "^18.0.26",
22+
"prettier": "^2.8.1",
23+
"typescript": "^4.9.3",
24+
"vite": "^4.0.0-beta.4"
3425
}
3526
}

scripts/bundle.ts

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env tnode
2+
import { rmSync, writeFileSync } from "fs";
3+
import { execSync } from "child_process";
4+
import { build } from "esbuild";
5+
6+
import * as packageJSON from "../package.json";
7+
8+
rmSync("dist", { force: true, recursive: true });
9+
10+
build({
11+
bundle: true,
12+
entryPoints: ["src/index.ts"],
13+
outdir: "dist",
14+
platform: "node",
15+
target: "node14",
16+
legalComments: "inline",
17+
external: Object.keys(packageJSON.peerDependencies),
18+
}).then(() => {
19+
execSync("cp LICENSE README.md types.d.ts dist/");
20+
21+
writeFileSync(
22+
"dist/index.d.ts",
23+
`import { Plugin } from "vite";
24+
export declare function svgPlugin(): Plugin;
25+
export declare const svgToJS: (svg: string, production: boolean) => string;
26+
`,
27+
);
28+
29+
writeFileSync(
30+
"dist/package.json",
31+
JSON.stringify(
32+
{
33+
name: packageJSON.name,
34+
description: "Turn SVG into React components, without Babel",
35+
version: packageJSON.version,
36+
author: "Arnaud Barré (https://github.com/ArnaudBarre)",
37+
license: packageJSON.license,
38+
repository: "github:ArnaudBarre/vite-plugin-fast-react-svg",
39+
main: "index.js",
40+
keywords: ["vite", "vite-plugin", "react", "svg"],
41+
peerDependencies: packageJSON.peerDependencies,
42+
},
43+
null,
44+
2,
45+
),
46+
);
47+
});

src/index.ts

+34-32
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,58 @@
11
import { readFileSync } from "fs";
2-
import { transform } from "esbuild";
32
import { Plugin } from "vite";
43

5-
export function svgPlugin(opts?: { useInnerHTML?: boolean }): Plugin {
4+
export function svgPlugin(): Plugin {
5+
let production = false;
66
return {
77
name: "svg",
88
enforce: "pre",
9+
config(_, env) {
10+
production = env.command === "build";
11+
},
912
async load(id) {
1013
if (id.endsWith(".svg")) {
11-
const { code, warnings } = await transform(
12-
svgToJSX(readFileSync(id, "utf-8"), opts?.useInnerHTML),
13-
{ loader: "jsx" }
14-
);
15-
for (const warning of warnings) {
16-
console.log(warning.location, warning.text);
17-
}
18-
return code;
14+
return svgToJS(readFileSync(id, "utf-8"), production);
1915
}
2016
if (id.endsWith(".svg?inline")) {
2117
const base64 = Buffer.from(
22-
readFileSync(id.replace("?inline", ""), "utf-8")
18+
readFileSync(id.replace("?inline", ""), "utf-8"),
2319
).toString("base64");
2420
return `export default "data:image/svg+xml;base64,${base64}"`;
2521
}
2622
},
2723
};
2824
}
2925

30-
export const svgToJSX = (svg: string, useInnerHTML?: boolean) => {
31-
let jsx: string;
32-
if (useInnerHTML) {
33-
const index = svg.indexOf(">");
34-
const content = svg
35-
.slice(index + 1, svg.indexOf("</svg>"))
36-
.trim()
37-
.replace(/\s+/g, " ");
38-
jsx = `${updatePropsCase(
39-
svg.slice(0, index)
40-
)} ref={ref} {...props} dangerouslySetInnerHTML={{ __html: '${content}' }} />`;
41-
} else {
42-
jsx = updatePropsCase(svg).replace(">", " ref={ref} {...props}>");
26+
const attributesRE = /\s([a-zA-Z0-9-:]+)=("[^"]*")/gu;
27+
28+
export const svgToJS = (svg: string, production: boolean) => {
29+
const index = svg.indexOf(">");
30+
const content = svg
31+
.slice(index + 1, svg.indexOf("</svg>"))
32+
.trim()
33+
.replace(/\s+/g, " ");
34+
let attributes = "";
35+
for (const match of svg.slice(0, index).matchAll(attributesRE)) {
36+
attributes += ` ${transformKey(match[1])}: ${match[2]},\n`;
4337
}
44-
return `import React from "react";const ReactComponent = React.forwardRef((props, ref) => (${jsx}));export default ReactComponent;`;
38+
const jsxImport = production
39+
? 'import { jsx } from "react/jsx-runtime";'
40+
: 'import { jsxDEV as jsx } from "react/jsx-dev-runtime";';
41+
return `${jsxImport}
42+
import { forwardRef } from "react";
43+
export default forwardRef((props, ref) => jsx("svg", {
44+
${attributes} ref,
45+
...props,
46+
dangerouslySetInnerHTML: { __html: '${content}' }
47+
})
48+
);`;
4549
};
4650

47-
const updatePropsCase = (svg: string) =>
48-
svg.replace(/\s([a-z-:]*)="[^"]*"/gu, (string, key: string) => {
49-
if (key.startsWith("data-")) return string;
50-
const keyWithoutDashes = camelCaseOn(key, "-");
51-
const keyWithoutDots = camelCaseOn(keyWithoutDashes, ":");
52-
return string.replace(key, keyWithoutDots);
53-
});
51+
const transformKey = (key: string) => {
52+
if (key.startsWith("data-")) return `"${key}"`;
53+
const keyWithoutDashes = camelCaseOn(key, "-");
54+
return camelCaseOn(keyWithoutDashes, ":");
55+
};
5456

5557
const camelCaseOn = (string: string, delimiter: string) =>
5658
string

tsconfig.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
"module": "CommonJS",
66
"lib": ["ES2020"],
77
"target": "ES2020",
8-
"declaration": true,
9-
"outDir": "dist",
108
"skipLibCheck": true,
119

10+
/* Transpile with esbuild */
11+
"noEmit": true,
12+
"isolatedModules": true,
13+
1214
/* Imports */
1315
"moduleResolution": "node", // Allow `index` imports
1416
"resolveJsonModule": true, // Allow json import

0 commit comments

Comments
 (0)