Skip to content

Commit 09c5d81

Browse files
committed
Initial commit [publish]
0 parents  commit 09c5d81

10 files changed

+443
-0
lines changed

.github/workflows/publish.yml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Publish to npm
2+
on:
3+
push:
4+
branches:
5+
- main
6+
jobs:
7+
publish:
8+
runs-on: ubuntu-latest
9+
if: ${{ contains(github.event.head_commit.message, '[publish]') }}
10+
steps:
11+
- uses: actions/checkout@v2
12+
- run: yarn install --frozen-lockfile
13+
- run: yarn prettier-ci
14+
- run: yarn build
15+
- uses: ArnaudBarre/npm-publish@v1
16+
with:
17+
npm-token: ${{ secrets.NPM_TOKEN }}

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.idea/
2+
node_modules/
3+
dist/

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Changelog
2+
3+
## 0.1.0
4+
5+
Initial release

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) Arnaud Barré (https://github.com/ArnaudBarre)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# vite-plugin-fast-react-svg [![npm](https://img.shields.io/npm/v/vite-plugin-fast-react-svg)](https://www.npmjs.com/package/vite-plugin-fast-react-svg)
2+
3+
Turn SVG into React components, without Babel.
4+
5+
## Why
6+
7+
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). This plugin will **not** run svgo, so you should run it beforehand and commit the cleaned version.
8+
9+
## Installation
10+
11+
```sh
12+
npm i -D vite-plugin-fast-react-svg
13+
```
14+
15+
In your vite config:
16+
17+
```ts
18+
import { defineConfig } from "vite";
19+
import svgPlugin from "vite-plugin-fast-react-svg";
20+
21+
export default defineConfig({
22+
plugins: [svgPlugin()],
23+
});
24+
```
25+
26+
In `tsconfig.json`:
27+
28+
```json5
29+
{
30+
compilerOptions: {
31+
types: ["vite-plugin-fast-react-svg/types", "vite/client"],
32+
},
33+
}
34+
```
35+
36+
## Usage
37+
38+
```jsx
39+
import Logo from "./logo.svg";
40+
import base64Data from "./logo.svg?inline";
41+
42+
const Example = () => (
43+
<>
44+
<Logo />
45+
<img src={base64Data} alt="Logo" />
46+
</>
47+
);
48+
```

package.json

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "vite-plugin-fast-react-svg",
3+
"description": "Turn SVG into React components, without Babel",
4+
"version": "0.1.0",
5+
"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+
],
19+
"scripts": {
20+
"build": "tsc",
21+
"prettier": "yarn prettier-ci --write",
22+
"prettier-ci": "prettier --check '**/*.{ts,json,md,yml}'"
23+
},
24+
"peerDependencies": {
25+
"react": ">=16",
26+
"vite": "^2"
27+
},
28+
"devDependencies": {
29+
"@types/node": "^17.0.13",
30+
"@types/react": "^17.0.38",
31+
"prettier": "^2.5.1",
32+
"typescript": "^4.5.5",
33+
"vite": "^2.7.13"
34+
}
35+
}

src/index.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { readFileSync } from "fs";
2+
import { transform } from "esbuild";
3+
import { Plugin } from "vite";
4+
5+
export default function svgPlugin(): Plugin {
6+
return {
7+
name: "svg",
8+
enforce: "pre",
9+
load(id) {
10+
if (id.endsWith(".svg")) {
11+
return readFileSync(id, "utf-8");
12+
}
13+
if (id.endsWith(".svg?inline")) {
14+
return readFileSync(id.replace("?inline", ""), "utf-8");
15+
}
16+
},
17+
transform(code, id) {
18+
if (id.endsWith(".svg")) {
19+
return transform(svgToJSX(code), { loader: "jsx" });
20+
}
21+
if (id.endsWith(".svg?inline")) {
22+
const base64 = Buffer.from(code).toString("base64");
23+
return `export default "data:image/svg+xml;base64,${base64}"`;
24+
}
25+
},
26+
};
27+
}
28+
29+
const svgToJSX = (svg: string) =>
30+
`import React from "react";const ReactComponent = (props) => (${svg
31+
.replace(/\s([a-z-:]*)="[^"]*"/gu, (string, key: string) => {
32+
if (key.startsWith("data-")) return string;
33+
const keyWithoutDashes = camelCaseOn(key, "-");
34+
const keyWithoutDots = camelCaseOn(keyWithoutDashes, ":");
35+
return string.replace(key, keyWithoutDots);
36+
})
37+
.replace(">", " {...props}>")});export default ReactComponent`;
38+
39+
const camelCaseOn = (string: string, delimiter: string) =>
40+
string
41+
.split(delimiter)
42+
.map((v, i) => (i === 0 ? v : v[0].toUpperCase() + v.slice(1)))
43+
.join("");

tsconfig.json

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"include": ["src"],
3+
"compilerOptions": {
4+
/* Target node12 */
5+
"module": "CommonJS",
6+
"lib": ["ES2019"],
7+
"target": "ES2019",
8+
"declaration": true,
9+
"outDir": "dist",
10+
11+
/* Imports */
12+
"moduleResolution": "node", // Allow `index` imports
13+
"resolveJsonModule": true, // Allow json import
14+
"forceConsistentCasingInFileNames": true, // Avoid difference in case between file name and import
15+
16+
/* Linting */
17+
"strict": true,
18+
"noUnusedLocals": true,
19+
"noUnusedParameters": true,
20+
"noFallthroughCasesInSwitch": true,
21+
"useUnknownInCatchVariables": true
22+
}
23+
}

types.d.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
declare module "*.svg" {
2+
import { FunctionComponent, SVGProps } from "react";
3+
const ReactComponent: FunctionComponent<
4+
SVGProps<SVGSVGElement> & { title?: string }
5+
>;
6+
export default ReactComponent;
7+
}
8+
9+
declare module "*.svg?inline" {
10+
const data: string;
11+
export default data;
12+
}

0 commit comments

Comments
 (0)