Ive been writing a tampermonkey userscript to convert an ebook to a pdf in google play books. However, whenever i try to use the jsPDF object, it throws an error. This is my the part of the code that is throwing an error:
// ==UserScript==
// @name tampermonkey userscript
// @namespace http://tampermonkey.net/
// @version 2025-05-26
// @description try to take over the world!
// @author You
// @match https://play.google.com/books/*
// @match *://*.googleusercontent.com/*
// @match *://books.google.com/ebooks/reader*
// @icon https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net
// @require https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
// @grant none
// @run-at document-idle
// ==/UserScript==
/* global jsPDF */
(function() {
'use strict';
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
doc.save("example.pdf");
})();
Frankly, I don't even know if i am supposed to do it like this as i just asked chatgpt for this part. I am getting this error message in my console: Console Error Message
And right after that this: Console Error Message
So, from the console messages, as well from the tampermonkey extension ui, I can see that my script is also being executed twice for some reason? Is jsPDF causing this?
As per chatGPT's instructions, ive tried injecting the script tag directly into the HTML as well, tried to use just jspdf.min.js instead of jspdf.umd.min.js in my code and ignored the const { jsPDF } = window.jspdf
part, tried making a global alias by window.jsPDF = window.jspdf.jsPDF
. But all of them say that either jsPDF is an undefined object or that cannot destructure property jsPDF as window.jspdf is undefined.
Please note that I am a complete beginner in javascript.
Can someone point out the mistake im making?
As correctly pointed out by the other answer posted, using @require
for a UMD script, it already makes the object available globally (and not on window
), therefore, you will always get undefined
.
To have it available on the window (not that it matters much in this case), here is how you could do it:
// ==UserScript==
// @name tampermonkey userscript
// @namespace http://tampermonkey.net/
// @version 2025-05-26
// @description try to take over the world!
// @author You
// @match https://play.google.com/books/*
// @match *://*.googleusercontent.com/*
// @match *://books.google.com/ebooks/reader*
// @icon https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net
// @require https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
// @grant none
// @run-at document-idle
// ==/UserScript==
/* global jsPDF */
(function() {
'use strict';
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/jspdf@3.0.1/dist/jspdf.umd.min.js';
script.onload = () => {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
doc.save("example.pdf");
};
document.head.appendChild(script);
})();
Update: Regarding jspdf version, it is recommended to use the latest version, which as of today is 3.0.1
Regarding why the script runs twice, I assume you have the script enabled twice in your tampermonkey extension dashboard. If you delete one or make sure only one is enabled, it should solve your problem. Otherwise, I do not see any reason why the below script would execute twice.
Regarding script injection vs @require
— Both are correct approaches. If you use @require
it will simply not embed the object to browser window but make it available as a global object and allow you to access it without relying on window. With script injection, you are explicitly adding the library object to window for using it later.