Skip to content

Commit 244c159

Browse files
reaktivoclaude
andcommitted
fix: replace jsdom/canvas imports with minimal duck-type interfaces
Importing from `jsdom` and `canvas` at the top level of src/types/index.ts, src/core/QRCodeStyling.ts, and src/core/QRSVG.ts caused browser bundlers to pull in — or fail to resolve — those Node.js-only packages. Replace all three with minimal structural interfaces (BrowserWindow, NodeCanvasFactory, NodeCanvasElement, NodeCanvasImage, JSDOMConstructor) that describe only the surface the library actually uses, so the build is usable in a browser with no extraneous Node.js dependencies. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 43d4171 commit 244c159

3 files changed

Lines changed: 46 additions & 14 deletions

File tree

src/core/QRCodeStyling.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@ import drawTypes from "../constants/drawTypes";
66

77
import defaultOptions, { RequiredOptions } from "./QROptions";
88
import sanitizeOptions from "../tools/sanitizeOptions";
9-
import { FileExtension, QRCode, Options, DownloadOptions, ExtensionFunction, Window } from "../types";
9+
import { FileExtension, QRCode, Options, DownloadOptions, ExtensionFunction, Window, NodeCanvasElement } from "../types";
1010
import qrcode from "qrcode-generator";
1111
import getMimeType from "../tools/getMimeType";
12-
import { Canvas as NodeCanvas, Image } from "canvas";
1312

1413
declare const window: Window;
1514

@@ -18,7 +17,7 @@ export default class QRCodeStyling {
1817
_window: Window;
1918
_container?: HTMLElement;
2019
_domCanvas?: HTMLCanvasElement;
21-
_nodeCanvas?: NodeCanvas;
20+
_nodeCanvas?: NodeCanvasElement;
2221
_svg?: SVGElement;
2322
_qr?: QRCode;
2423
_extension?: ExtensionFunction;
@@ -79,7 +78,7 @@ export default class QRCodeStyling {
7978
const image64 = `data:${getMimeType('svg')};base64,${svg64}`;
8079

8180
if (this._options.nodeCanvas?.loadImage) {
82-
return this._options.nodeCanvas.loadImage(image64).then((image: Image) => {
81+
return this._options.nodeCanvas.loadImage(image64).then((image) => {
8382
// fix blurry svg
8483
image.width = this._options.width;
8584
image.height = this._options.height;

src/core/QRSVG.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import QRCornerDot, { availableCornerDotTypes } from "../figures/cornerDot/QRCor
77
import { RequiredOptions } from "./QROptions";
88
import gradientTypes from "../constants/gradientTypes";
99
import shapeTypes from "../constants/shapeTypes";
10-
import { DotType, QRCode, FilterFunction, Gradient, Window } from "../types";
11-
import { Image } from "canvas";
10+
import { DotType, QRCode, FilterFunction, Gradient, Window, NodeCanvasImage } from "../types";
1211

1312
const squareMask = [
1413
[1, 1, 1, 1, 1, 1, 1],
@@ -40,7 +39,7 @@ export default class QRSVG {
4039
_cornersDotClipPath?: SVGElement;
4140
_options: RequiredOptions;
4241
_qr?: QRCode;
43-
_image?: HTMLImageElement | Image;
42+
_image?: HTMLImageElement | NodeCanvasImage;
4443
_imageUri?: string;
4544
_instanceId: number;
4645

@@ -457,7 +456,7 @@ export default class QRSVG {
457456
if (options.nodeCanvas?.loadImage) {
458457
options.nodeCanvas
459458
.loadImage(options.image)
460-
.then((image: Image) => {
459+
.then((image) => {
461460
this._image = image;
462461
if (this._options.imageOptions.saveAsBlob) {
463462
const canvas = options.nodeCanvas?.createCanvas( this._image.width, this._image.height);

src/types/index.ts

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
import { DOMWindow, JSDOM } from "jsdom";
2-
import nodeCanvas from "canvas";
3-
41
export interface UnknownObject {
52
// eslint-disable-next-line @typescript-eslint/no-explicit-any
63
[key: string]: any;
@@ -14,7 +11,44 @@ export type GradientType = "radial" | "linear";
1411
export type DrawType = "canvas" | "svg";
1512
export type ShapeType = "square" | "circle";
1613

17-
export type Window = DOMWindow;
14+
// Minimal window-like interface — compatible with both browser Window and jsdom DOMWindow
15+
export interface BrowserWindow {
16+
document: Document;
17+
XMLSerializer: typeof XMLSerializer;
18+
Image: { new(): HTMLImageElement };
19+
XMLHttpRequest: typeof XMLHttpRequest;
20+
FileReader: typeof FileReader;
21+
}
22+
23+
export type Window = BrowserWindow;
24+
25+
// Minimal node-canvas interfaces — avoids importing the "canvas" package in browser bundles
26+
export interface NodeCanvasImage {
27+
width: number;
28+
height: number;
29+
}
30+
31+
export interface NodeCanvasRenderingContext2D {
32+
drawImage(image: NodeCanvasImage, dx: number, dy: number): void;
33+
}
34+
35+
export interface NodeCanvasElement {
36+
width: number;
37+
height: number;
38+
getContext(contextId: "2d"): NodeCanvasRenderingContext2D | null;
39+
toBuffer(mimeType: string): Buffer;
40+
toDataURL(type?: string): string;
41+
}
42+
43+
export interface NodeCanvasFactory {
44+
createCanvas(width: number, height: number): NodeCanvasElement;
45+
loadImage(src: string): Promise<NodeCanvasImage>;
46+
}
47+
48+
// Minimal jsdom constructor interface — avoids importing "jsdom" in browser bundles
49+
export interface JSDOMConstructor {
50+
new(html: string, options?: { resources?: string }): { window: BrowserWindow };
51+
}
1852

1953
export type Gradient = {
2054
type: GradientType;
@@ -116,8 +150,8 @@ export type Options = {
116150
margin?: number;
117151
data?: string;
118152
image?: string;
119-
nodeCanvas?: typeof nodeCanvas;
120-
jsdom?: typeof JSDOM;
153+
nodeCanvas?: NodeCanvasFactory;
154+
jsdom?: JSDOMConstructor;
121155
qrOptions?: {
122156
typeNumber?: TypeNumber;
123157
mode?: Mode;

0 commit comments

Comments
 (0)