@effing/satori 0.10.5 → 0.11.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 CHANGED
@@ -120,6 +120,23 @@ await pool.destroy();
120
120
 
121
121
  **Peer dependencies:** The pool and serde sub-paths require `react` and `tinypool` to be installed. They are listed as optional peer dependencies so the main `@effing/satori` entry works without them.
122
122
 
123
+ ### Vite Plugin (SSR)
124
+
125
+ **The `@effing/satori/vite` plugin is required when using the worker pool in production SSR builds.** Without it, the worker file path breaks after Vite bundles the pool code, because `import.meta.url` points at the build output directory instead of `node_modules`.
126
+
127
+ The plugin bundles the worker into a self-contained file in the SSR output and rewrites `createSatoriPool()` calls to point at it.
128
+
129
+ ```typescript
130
+ // vite.config.ts
131
+ import { satoriPoolPlugin } from "@effing/satori/vite";
132
+
133
+ export default defineConfig({
134
+ plugins: [satoriPoolPlugin()],
135
+ });
136
+ ```
137
+
138
+ In dev mode the plugin is inert — `import.meta.url` still resolves into `node_modules` correctly, so no rewriting is needed.
139
+
123
140
  ## API Overview
124
141
 
125
142
  ### `pngFromSatori(template, options)`
@@ -185,6 +202,16 @@ function createSatoriPool(options?: SatoriPoolOptions): SatoriPool;
185
202
  - `rasterizeSvgToPng(svg, options?)` — Rasterize SVG to PNG buffer
186
203
  - `destroy()` — Shut down the pool
187
204
 
205
+ ### `@effing/satori/vite`
206
+
207
+ #### `satoriPoolPlugin()`
208
+
209
+ Vite plugin that bundles the satori worker into the SSR output. Required for production SSR builds using the worker pool.
210
+
211
+ ```typescript
212
+ function satoriPoolPlugin(): Plugin;
213
+ ```
214
+
188
215
  ### `@effing/satori/serde`
189
216
 
190
217
  React element serialization for cross-thread communication. Used internally by the pool, but available for custom worker setups.
@@ -6,6 +6,8 @@ type SatoriPoolOptions = {
6
6
  minThreads?: number;
7
7
  /** Maximum number of worker threads (default: os.cpus().length) */
8
8
  maxThreads?: number;
9
+ /** Absolute path to a bundled worker file (set automatically by the Vite plugin) */
10
+ workerFile?: string;
9
11
  };
10
12
  type SatoriPool = {
11
13
  /** Render a serialized React element to PNG via the worker pool */
@@ -14,7 +16,7 @@ type SatoriPool = {
14
16
  height: number;
15
17
  fonts: Array<{
16
18
  name: string;
17
- data: Buffer;
19
+ data: Buffer | ArrayBuffer;
18
20
  weight: number;
19
21
  style: string;
20
22
  }>;
@@ -26,7 +28,7 @@ type SatoriPool = {
26
28
  height: number;
27
29
  fonts: Array<{
28
30
  name: string;
29
- data: Buffer;
31
+ data: Buffer | ArrayBuffer;
30
32
  weight: number;
31
33
  style: string;
32
34
  }>;
@@ -10,7 +10,7 @@ import { fileURLToPath } from "url";
10
10
  import path from "path";
11
11
  import Tinypool from "tinypool";
12
12
  function createSatoriPool(options) {
13
- const workerFile = path.resolve(
13
+ const workerFile = options?.workerFile ?? path.resolve(
14
14
  path.dirname(fileURLToPath(import.meta.url)),
15
15
  "../worker/index.js"
16
16
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pool/index.ts"],"sourcesContent":["import os from \"os\";\nimport { fileURLToPath } from \"url\";\nimport path from \"path\";\n\nimport type { ReactNode } from \"react\";\nimport type satori from \"satori\";\nimport Tinypool from \"tinypool\";\n\nimport type { EmojiStyle } from \"../emoji.ts\";\nimport {\n ensureSingleElement,\n expandElement,\n serializeElement,\n} from \"../serde/index.ts\";\n\nexport type SatoriPoolOptions = {\n /** Minimum number of worker threads (default: 1) */\n minThreads?: number;\n /** Maximum number of worker threads (default: os.cpus().length) */\n maxThreads?: number;\n};\n\nexport type SatoriPool = {\n /** Render a serialized React element to PNG via the worker pool */\n renderToPng(\n element: Parameters<typeof satori>[0],\n options: {\n width: number;\n height: number;\n fonts: Array<{\n name: string;\n data: Buffer;\n weight: number;\n style: string;\n }>;\n emoji?: EmojiStyle;\n },\n ): Promise<Buffer>;\n\n /** Render a serialized React element to SVG via the worker pool */\n renderToSvg(\n element: Parameters<typeof satori>[0],\n options: {\n width: number;\n height: number;\n fonts: Array<{\n name: string;\n data: Buffer;\n weight: number;\n style: string;\n }>;\n emoji?: EmojiStyle;\n },\n ): Promise<string>;\n\n /** Rasterize an SVG string to PNG via the worker pool */\n rasterizeSvgToPng(\n svg: string,\n options?: {\n fitTo?:\n | { mode: \"original\" }\n | { mode: \"width\"; value: number }\n | { mode: \"height\"; value: number }\n | { mode: \"zoom\"; value: number };\n crop?: {\n left: number;\n top: number;\n right?: number;\n bottom?: number;\n };\n },\n ): Promise<Buffer>;\n\n /** Shut down the worker pool */\n destroy(): Promise<void>;\n};\n\n/**\n * Create a worker pool for parallelized satori rendering.\n *\n * @param options Pool configuration\n * @returns A `SatoriPool` with `renderToPng`, `renderToSvg`, `rasterizeSvgToPng`, and `destroy`\n */\nexport function createSatoriPool(options?: SatoriPoolOptions): SatoriPool {\n const workerFile = path.resolve(\n path.dirname(fileURLToPath(import.meta.url)),\n \"../worker/index.js\",\n );\n\n const pool = new Tinypool({\n filename: workerFile,\n minThreads: options?.minThreads ?? 1,\n maxThreads: options?.maxThreads ?? os.cpus().length,\n });\n\n return {\n async renderToPng(element, opts) {\n const serialized = serializeElement(\n ensureSingleElement(expandElement(element as ReactNode)),\n );\n const result = await pool.run(\n {\n element: serialized,\n width: opts.width,\n height: opts.height,\n fonts: opts.fonts,\n emoji: opts.emoji,\n },\n { name: \"renderToPng\" },\n );\n return Buffer.from(result);\n },\n\n async renderToSvg(element, opts) {\n const serialized = serializeElement(\n ensureSingleElement(expandElement(element as ReactNode)),\n );\n return pool.run(\n {\n element: serialized,\n width: opts.width,\n height: opts.height,\n fonts: opts.fonts,\n emoji: opts.emoji,\n },\n { name: \"renderToSvg\" },\n );\n },\n\n async rasterizeSvgToPng(svg, opts) {\n const result = await pool.run(\n { svg, options: opts },\n { name: \"rasterizeSvgToPng\" },\n );\n return Buffer.from(result);\n },\n\n async destroy() {\n await pool.destroy();\n },\n };\n}\n"],"mappings":";;;;;;;AAAA,OAAO,QAAQ;AACf,SAAS,qBAAqB;AAC9B,OAAO,UAAU;AAIjB,OAAO,cAAc;AA6Ed,SAAS,iBAAiB,SAAyC;AACxE,QAAM,aAAa,KAAK;AAAA,IACtB,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAAA,IAC3C;AAAA,EACF;AAEA,QAAM,OAAO,IAAI,SAAS;AAAA,IACxB,UAAU;AAAA,IACV,YAAY,SAAS,cAAc;AAAA,IACnC,YAAY,SAAS,cAAc,GAAG,KAAK,EAAE;AAAA,EAC/C,CAAC;AAED,SAAO;AAAA,IACL,MAAM,YAAY,SAAS,MAAM;AAC/B,YAAM,aAAa;AAAA,QACjB,oBAAoB,cAAc,OAAoB,CAAC;AAAA,MACzD;AACA,YAAM,SAAS,MAAM,KAAK;AAAA,QACxB;AAAA,UACE,SAAS;AAAA,UACT,OAAO,KAAK;AAAA,UACZ,QAAQ,KAAK;AAAA,UACb,OAAO,KAAK;AAAA,UACZ,OAAO,KAAK;AAAA,QACd;AAAA,QACA,EAAE,MAAM,cAAc;AAAA,MACxB;AACA,aAAO,OAAO,KAAK,MAAM;AAAA,IAC3B;AAAA,IAEA,MAAM,YAAY,SAAS,MAAM;AAC/B,YAAM,aAAa;AAAA,QACjB,oBAAoB,cAAc,OAAoB,CAAC;AAAA,MACzD;AACA,aAAO,KAAK;AAAA,QACV;AAAA,UACE,SAAS;AAAA,UACT,OAAO,KAAK;AAAA,UACZ,QAAQ,KAAK;AAAA,UACb,OAAO,KAAK;AAAA,UACZ,OAAO,KAAK;AAAA,QACd;AAAA,QACA,EAAE,MAAM,cAAc;AAAA,MACxB;AAAA,IACF;AAAA,IAEA,MAAM,kBAAkB,KAAK,MAAM;AACjC,YAAM,SAAS,MAAM,KAAK;AAAA,QACxB,EAAE,KAAK,SAAS,KAAK;AAAA,QACrB,EAAE,MAAM,oBAAoB;AAAA,MAC9B;AACA,aAAO,OAAO,KAAK,MAAM;AAAA,IAC3B;AAAA,IAEA,MAAM,UAAU;AACd,YAAM,KAAK,QAAQ;AAAA,IACrB;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/pool/index.ts"],"sourcesContent":["import os from \"os\";\nimport { fileURLToPath } from \"url\";\nimport path from \"path\";\n\nimport type { ReactNode } from \"react\";\nimport type satori from \"satori\";\nimport Tinypool from \"tinypool\";\n\nimport type { EmojiStyle } from \"../emoji.ts\";\nimport {\n ensureSingleElement,\n expandElement,\n serializeElement,\n} from \"../serde/index.ts\";\n\nexport type SatoriPoolOptions = {\n /** Minimum number of worker threads (default: 1) */\n minThreads?: number;\n /** Maximum number of worker threads (default: os.cpus().length) */\n maxThreads?: number;\n /** Absolute path to a bundled worker file (set automatically by the Vite plugin) */\n workerFile?: string;\n};\n\nexport type SatoriPool = {\n /** Render a serialized React element to PNG via the worker pool */\n renderToPng(\n element: Parameters<typeof satori>[0],\n options: {\n width: number;\n height: number;\n fonts: Array<{\n name: string;\n data: Buffer | ArrayBuffer;\n weight: number;\n style: string;\n }>;\n emoji?: EmojiStyle;\n },\n ): Promise<Buffer>;\n\n /** Render a serialized React element to SVG via the worker pool */\n renderToSvg(\n element: Parameters<typeof satori>[0],\n options: {\n width: number;\n height: number;\n fonts: Array<{\n name: string;\n data: Buffer | ArrayBuffer;\n weight: number;\n style: string;\n }>;\n emoji?: EmojiStyle;\n },\n ): Promise<string>;\n\n /** Rasterize an SVG string to PNG via the worker pool */\n rasterizeSvgToPng(\n svg: string,\n options?: {\n fitTo?:\n | { mode: \"original\" }\n | { mode: \"width\"; value: number }\n | { mode: \"height\"; value: number }\n | { mode: \"zoom\"; value: number };\n crop?: {\n left: number;\n top: number;\n right?: number;\n bottom?: number;\n };\n },\n ): Promise<Buffer>;\n\n /** Shut down the worker pool */\n destroy(): Promise<void>;\n};\n\n/**\n * Create a worker pool for parallelized satori rendering.\n *\n * @param options Pool configuration\n * @returns A `SatoriPool` with `renderToPng`, `renderToSvg`, `rasterizeSvgToPng`, and `destroy`\n */\nexport function createSatoriPool(options?: SatoriPoolOptions): SatoriPool {\n const workerFile =\n options?.workerFile ??\n path.resolve(\n path.dirname(fileURLToPath(import.meta.url)),\n \"../worker/index.js\",\n );\n\n const pool = new Tinypool({\n filename: workerFile,\n minThreads: options?.minThreads ?? 1,\n maxThreads: options?.maxThreads ?? os.cpus().length,\n });\n\n return {\n async renderToPng(element, opts) {\n const serialized = serializeElement(\n ensureSingleElement(expandElement(element as ReactNode)),\n );\n const result = await pool.run(\n {\n element: serialized,\n width: opts.width,\n height: opts.height,\n fonts: opts.fonts,\n emoji: opts.emoji,\n },\n { name: \"renderToPng\" },\n );\n return Buffer.from(result);\n },\n\n async renderToSvg(element, opts) {\n const serialized = serializeElement(\n ensureSingleElement(expandElement(element as ReactNode)),\n );\n return pool.run(\n {\n element: serialized,\n width: opts.width,\n height: opts.height,\n fonts: opts.fonts,\n emoji: opts.emoji,\n },\n { name: \"renderToSvg\" },\n );\n },\n\n async rasterizeSvgToPng(svg, opts) {\n const result = await pool.run(\n { svg, options: opts },\n { name: \"rasterizeSvgToPng\" },\n );\n return Buffer.from(result);\n },\n\n async destroy() {\n await pool.destroy();\n },\n };\n}\n"],"mappings":";;;;;;;AAAA,OAAO,QAAQ;AACf,SAAS,qBAAqB;AAC9B,OAAO,UAAU;AAIjB,OAAO,cAAc;AA+Ed,SAAS,iBAAiB,SAAyC;AACxE,QAAM,aACJ,SAAS,cACT,KAAK;AAAA,IACH,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAAA,IAC3C;AAAA,EACF;AAEF,QAAM,OAAO,IAAI,SAAS;AAAA,IACxB,UAAU;AAAA,IACV,YAAY,SAAS,cAAc;AAAA,IACnC,YAAY,SAAS,cAAc,GAAG,KAAK,EAAE;AAAA,EAC/C,CAAC;AAED,SAAO;AAAA,IACL,MAAM,YAAY,SAAS,MAAM;AAC/B,YAAM,aAAa;AAAA,QACjB,oBAAoB,cAAc,OAAoB,CAAC;AAAA,MACzD;AACA,YAAM,SAAS,MAAM,KAAK;AAAA,QACxB;AAAA,UACE,SAAS;AAAA,UACT,OAAO,KAAK;AAAA,UACZ,QAAQ,KAAK;AAAA,UACb,OAAO,KAAK;AAAA,UACZ,OAAO,KAAK;AAAA,QACd;AAAA,QACA,EAAE,MAAM,cAAc;AAAA,MACxB;AACA,aAAO,OAAO,KAAK,MAAM;AAAA,IAC3B;AAAA,IAEA,MAAM,YAAY,SAAS,MAAM;AAC/B,YAAM,aAAa;AAAA,QACjB,oBAAoB,cAAc,OAAoB,CAAC;AAAA,MACzD;AACA,aAAO,KAAK;AAAA,QACV;AAAA,UACE,SAAS;AAAA,UACT,OAAO,KAAK;AAAA,UACZ,QAAQ,KAAK;AAAA,UACb,OAAO,KAAK;AAAA,UACZ,OAAO,KAAK;AAAA,QACd;AAAA,QACA,EAAE,MAAM,cAAc;AAAA,MACxB;AAAA,IACF;AAAA,IAEA,MAAM,kBAAkB,KAAK,MAAM;AACjC,YAAM,SAAS,MAAM,KAAK;AAAA,QACxB,EAAE,KAAK,SAAS,KAAK;AAAA,QACrB,EAAE,MAAM,oBAAoB;AAAA,MAC9B;AACA,aAAO,OAAO,KAAK,MAAM;AAAA,IAC3B;AAAA,IAEA,MAAM,UAAU;AACd,YAAM,KAAK,QAAQ;AAAA,IACrB;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,23 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ /**
4
+ * Vite plugin that bundles the `@effing/satori` worker into the SSR output and
5
+ * rewrites `createSatoriPool()` calls to point at it.
6
+ *
7
+ * **This plugin is required for production SSR builds.** Without it the worker
8
+ * path resolved via `import.meta.url` breaks after Vite bundles the pool code,
9
+ * because the URL points at the build output directory instead of `node_modules`.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // vite.config.ts
14
+ * import { satoriPoolPlugin } from "@effing/satori/vite";
15
+ *
16
+ * export default defineConfig({
17
+ * plugins: [satoriPoolPlugin()],
18
+ * });
19
+ * ```
20
+ */
21
+ declare function satoriPoolPlugin(): Plugin;
22
+
23
+ export { satoriPoolPlugin };
@@ -0,0 +1,55 @@
1
+ // src/vite/index.ts
2
+ import { fileURLToPath } from "url";
3
+ function satoriPoolPlugin() {
4
+ let resolvedConfig;
5
+ return {
6
+ name: "@effing/satori:worker",
7
+ apply: "build",
8
+ configResolved(config) {
9
+ resolvedConfig = config;
10
+ },
11
+ transform(code, _id, options) {
12
+ if (!options?.ssr) return;
13
+ const pattern = /\bcreateSatoriPool\(\s*(\)|\{)/g;
14
+ let matched = false;
15
+ const result = code.replace(pattern, (_match, capture) => {
16
+ matched = true;
17
+ if (capture === ")") {
18
+ return 'createSatoriPool({ workerFile: import.meta.dirname + "/satori-worker.js" })';
19
+ }
20
+ return 'createSatoriPool({ workerFile: import.meta.dirname + "/satori-worker.js", ';
21
+ });
22
+ if (!matched) return;
23
+ return { code: result, map: null };
24
+ },
25
+ async writeBundle(outputOptions) {
26
+ if (!resolvedConfig.build.ssr) return;
27
+ const workerEntry = fileURLToPath(
28
+ new URL("../worker/index.js", import.meta.url)
29
+ );
30
+ const outDir = outputOptions.dir ?? resolvedConfig.build.outDir;
31
+ const { build } = await import("vite");
32
+ await build({
33
+ configFile: false,
34
+ logLevel: "silent",
35
+ build: {
36
+ write: true,
37
+ emptyOutDir: false,
38
+ outDir,
39
+ lib: {
40
+ entry: workerEntry,
41
+ formats: ["es"],
42
+ fileName: () => "satori-worker.js"
43
+ },
44
+ rollupOptions: {
45
+ external: ["@resvg/resvg-js"]
46
+ }
47
+ }
48
+ });
49
+ }
50
+ };
51
+ }
52
+ export {
53
+ satoriPoolPlugin
54
+ };
55
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/vite/index.ts"],"sourcesContent":["import { fileURLToPath } from \"url\";\n\nimport type { Plugin, ResolvedConfig } from \"vite\";\n\n/**\n * Vite plugin that bundles the `@effing/satori` worker into the SSR output and\n * rewrites `createSatoriPool()` calls to point at it.\n *\n * **This plugin is required for production SSR builds.** Without it the worker\n * path resolved via `import.meta.url` breaks after Vite bundles the pool code,\n * because the URL points at the build output directory instead of `node_modules`.\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { satoriPoolPlugin } from \"@effing/satori/vite\";\n *\n * export default defineConfig({\n * plugins: [satoriPoolPlugin()],\n * });\n * ```\n */\nexport function satoriPoolPlugin(): Plugin {\n let resolvedConfig: ResolvedConfig;\n\n return {\n name: \"@effing/satori:worker\",\n apply: \"build\",\n\n configResolved(config) {\n resolvedConfig = config;\n },\n\n transform(code, _id, options) {\n if (!options?.ssr) return;\n\n const pattern = /\\bcreateSatoriPool\\(\\s*(\\)|\\{)/g;\n let matched = false;\n const result = code.replace(pattern, (_match, capture: string) => {\n matched = true;\n if (capture === \")\") {\n return 'createSatoriPool({ workerFile: import.meta.dirname + \"/satori-worker.js\" })';\n }\n // capture === \"{\"\n return 'createSatoriPool({ workerFile: import.meta.dirname + \"/satori-worker.js\", ';\n });\n\n if (!matched) return;\n return { code: result, map: null };\n },\n\n async writeBundle(outputOptions) {\n if (!resolvedConfig.build.ssr) return;\n\n const workerEntry = fileURLToPath(\n new URL(\"../worker/index.js\", import.meta.url),\n );\n const outDir = outputOptions.dir ?? resolvedConfig.build.outDir;\n\n const { build } = await import(\"vite\");\n await build({\n configFile: false,\n logLevel: \"silent\",\n build: {\n write: true,\n emptyOutDir: false,\n outDir,\n lib: {\n entry: workerEntry,\n formats: [\"es\"],\n fileName: () => \"satori-worker.js\",\n },\n rollupOptions: {\n external: [\"@resvg/resvg-js\"],\n },\n },\n });\n },\n };\n}\n"],"mappings":";AAAA,SAAS,qBAAqB;AAsBvB,SAAS,mBAA2B;AACzC,MAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IAEP,eAAe,QAAQ;AACrB,uBAAiB;AAAA,IACnB;AAAA,IAEA,UAAU,MAAM,KAAK,SAAS;AAC5B,UAAI,CAAC,SAAS,IAAK;AAEnB,YAAM,UAAU;AAChB,UAAI,UAAU;AACd,YAAM,SAAS,KAAK,QAAQ,SAAS,CAAC,QAAQ,YAAoB;AAChE,kBAAU;AACV,YAAI,YAAY,KAAK;AACnB,iBAAO;AAAA,QACT;AAEA,eAAO;AAAA,MACT,CAAC;AAED,UAAI,CAAC,QAAS;AACd,aAAO,EAAE,MAAM,QAAQ,KAAK,KAAK;AAAA,IACnC;AAAA,IAEA,MAAM,YAAY,eAAe;AAC/B,UAAI,CAAC,eAAe,MAAM,IAAK;AAE/B,YAAM,cAAc;AAAA,QAClB,IAAI,IAAI,sBAAsB,YAAY,GAAG;AAAA,MAC/C;AACA,YAAM,SAAS,cAAc,OAAO,eAAe,MAAM;AAEzD,YAAM,EAAE,MAAM,IAAI,MAAM,OAAO,MAAM;AACrC,YAAM,MAAM;AAAA,QACV,YAAY;AAAA,QACZ,UAAU;AAAA,QACV,OAAO;AAAA,UACL,OAAO;AAAA,UACP,aAAa;AAAA,UACb;AAAA,UACA,KAAK;AAAA,YACH,OAAO;AAAA,YACP,SAAS,CAAC,IAAI;AAAA,YACd,UAAU,MAAM;AAAA,UAClB;AAAA,UACA,eAAe;AAAA,YACb,UAAU,CAAC,iBAAiB;AAAA,UAC9B;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effing/satori",
3
- "version": "0.10.5",
3
+ "version": "0.11.1",
4
4
  "description": "Render JSX to PNG using Satori with emoji support",
5
5
  "type": "module",
6
6
  "exports": {
@@ -15,24 +15,30 @@
15
15
  "./pool": {
16
16
  "types": "./dist/pool/index.d.ts",
17
17
  "import": "./dist/pool/index.js"
18
+ },
19
+ "./vite": {
20
+ "types": "./dist/vite/index.d.ts",
21
+ "import": "./dist/vite/index.js"
18
22
  }
19
23
  },
20
24
  "files": [
21
25
  "dist"
22
26
  ],
23
27
  "dependencies": {
24
- "@resvg/resvg-js": "^2.6.2",
25
28
  "satori": ">=0.10.0 <1.0.0"
26
29
  },
27
30
  "devDependencies": {
28
31
  "@types/react": "^19.0.0",
29
32
  "tsup": "^8.0.0",
30
33
  "typescript": "^5.9.3",
34
+ "vite": "^6.0.0",
31
35
  "vitest": "^3.2.4"
32
36
  },
33
37
  "peerDependencies": {
38
+ "@resvg/resvg-js": "^2.6.2",
34
39
  "react": "^18.0.0 || ^19.0.0",
35
- "tinypool": "^1.0.0"
40
+ "tinypool": "^1.0.0",
41
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
36
42
  },
37
43
  "peerDependenciesMeta": {
38
44
  "react": {
@@ -40,6 +46,9 @@
40
46
  },
41
47
  "tinypool": {
42
48
  "optional": true
49
+ },
50
+ "vite": {
51
+ "optional": true
43
52
  }
44
53
  },
45
54
  "keywords": [