javascriptjspdftampermonkey

Error on trying to use jsPDF in tampermonkey. Saying jsPDF object is undefined


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?


Solution

  • 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.