使用node-canvas与jsdom添加图片时出现资源重复调用如何解决?
jsdom与node-canvas图片资源重复请求解决方案
重复请求根因
jsdom的资源加载逻辑与node-canvas的图片解码逻辑相互独立,没有共享缓存机制:
- 第一次请求:jsdom配置
resources: "usable"时,会自动解析DOM中的<img>元素src属性,主动拉取图片资源 - 第二次请求:node-canvas侧渲染图片时,会独立发起资源请求读取图片二进制数据,不会复用jsdom已加载的资源
解决方法
方案1:复用jsdom缓存(推荐,适配需要jsdom提前处理图片的场景)
自定义jsdom资源加载器并开启全局内存缓存,同时重写canvas的Image构造函数优先从缓存中取资源,修改后的完整代码如下:
const jsdom = require('jsdom'); const { JSDOM, ResourceLoader } = jsdom; const { createCanvas, Image } = require('canvas'); // 自定义带缓存的资源加载器 class CachedResourceLoader extends ResourceLoader { constructor() { super(); this.cache = new Map(); } async fetch(url, options) { if (this.cache.has(url)) { // 命中缓存直接返回 return Buffer.from(this.cache.get(url)); } const res = await super.fetch(url, options); // 加载成功后存入缓存 if (res) { const buffer = await res.arrayBuffer(); this.cache.set(url, buffer); } return res; } } const cachedLoader = new CachedResourceLoader(); const options = { runScripts: "dangerously", url: `https://localhost:8800`, resources: "usable", // 注入自定义缓存加载器 resourcesLoader: cachedLoader, // 重写全局Image构造函数,复用缓存 beforeParse(window) { const OriginalImage = window.Image; window.Image = function() { const img = new OriginalImage(); // 监听src赋值,优先从缓存拿数据 Object.defineProperty(img, 'src', { set(val) { if (cachedLoader.cache.has(val)) { // 直接给canvas Image喂缓存数据 const canvasImg = new Image(); canvasImg.src = Buffer.from(cachedLoader.cache.get(val)); canvasImg.onload = () => { img.naturalWidth = canvasImg.naturalWidth; img.naturalHeight = canvasImg.naturalHeight; img.dispatchEvent(new window.Event('load')); }; canvasImg.onerror = () => img.dispatchEvent(new window.Event('error')); } else { img.setAttribute('src', val); } }, get() { return img.getAttribute('src'); } }); return img; } } }; new JSDOM(` <html> <head><script src="https://code.jquery.com/jquery-3.5.1.min.js"></script></head> <body> <div id="my_img"></div> <script> var src_img = document.getElementById("my_img"); var testimg = document.createElement("img"); testimg.src = "https://revistahsm.com/wp-content/uploads/2018/08/Fiestas.png"; // example img testimg.width = 0; testimg.height = 0; src_img.appendChild(testimg); </script> </body> </html> `, options);
方案2:关闭jsdom自动资源加载(轻量方案,适配仅canvas侧需要图片的场景)
如果你的业务逻辑不需要jsdom提前处理图片宽高、onload事件等属性,可直接关闭jsdom的自动资源加载,仅由canvas侧发起一次请求:
修改jsdom配置中的resources参数即可:
const options = { runScripts: "dangerously", url: `https://localhost:8800`, // 关闭jsdom自动加载外部资源,避免重复请求 resources: "none" };
注意事项
- 跨域资源需要配置正确的CORS响应头,否则会触发缓存失效
- 若处理大量大体积图片,可给缓存增加过期淘汰逻辑,避免内存占用过高
内容的提问来源于stack exchange,提问作者soni
相关产品推荐
相关产品推荐

