lisonge / vite-plugin-monkey

A vite plugin server and build your.user.js for userscript engine like Tampermonkey, Violentmonkey, Greasemonkey, ScriptCat
MIT License
1.31k stars 70 forks source link

Loading libraries by inserting script elements into the body #100

Closed misaalanshori closed 1 year ago

misaalanshori commented 1 year ago

I am having some problems where epub.js does not work when loaded through @require. But from my testing it works when I load the script by inserting a script element into the HTML.

Here is the alternative loading method that worked with epub.js:

// ==UserScript==
// @name         PLAYGROUND
// @namespace    http://tampermonkey.net/
// @match        https://archiveofourown.org/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=archiveofourown.org
// @connect      download.archiveofourown.org
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// ==/UserScript==

(function() {
    'use strict';
    console.log("userscript start")

    const getFileBlob = (url) => (new Promise((resolve, reject) => GM_xmlhttpRequest({
        method: "GET",
        url: url,
        responseType: "blob",
        onload: response => {
            const blob = response.response;
            resolve(blob);
        },
        onerror: reject
    })));

    // this is just from a random StackOverflow answer
    function addScript(src) {
        return new Promise((resolve, reject) => {
            const s = document.createElement('script');

            s.setAttribute('src', src);
            s.addEventListener('load', resolve);
            s.addEventListener('error', reject);

            document.body.appendChild(s);
        });
    }

    async function loadLibrary() {
        await addScript("https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js");
        await addScript("https://cdn.jsdelivr.net/npm/epubjs@0.3.93/dist/epub.min.js");
    }

    async function loadEpub() {
        await loadLibrary();
        const ePub = unsafeWindow.ePub
        console.log("Start Load Epub")
        const fileUrl = "https://archiveofourown.org/downloads/25830331/Locked%20Out.epub?updated_at=1667832644";
        const epubBlob = await getFileBlob(fileUrl);
        const epubArrayBuffer = await epubBlob.arrayBuffer();
        const epub = ePub(epubArrayBuffer);
        document.body.querySelector("#outer").innerHTML = ""
        unsafeWindow.epubrendition = epub.renderTo("outer", { method: "default", width: "100vw", height: "100vh" });
        unsafeWindow.epubrendition.display();
    }

    loadEpub();
})();

Epub.js did not work when it was loaded using @require.

Is there a way to get this plugin to build the script to use this loading process instead of using @require? Maybe it's something that can be added?

lisonge commented 1 year ago

Epub.js did not work when it was loaded using @require.

what is the UserScript header code that not work ?

misaalanshori commented 1 year ago

When loaded using @require like this:

// @require      https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js
// @require      https://cdn.jsdelivr.net/npm/epubjs@0.3.93/dist/epub.min.js

Everything does seem to get loaded, but epub.js does not seem to work properly (it does not render the epub).

The fact that epub.js does not work properly here is not an issue with this plugin, but it seems to be an issue with how the scripts are loaded by @require, so having this alternative way of loading scripts would bypass this issue with @require

misaalanshori commented 1 year ago

This is an example of my userscript that i patched with this alternative method of loading libraries: Link to code

Here is the original unpatched userscript generated by vite-plugin-monkey

misaalanshori commented 1 year ago

This patched version works as expected. the only changes are adding the function to insert the script elements, and changing the arguments to the main userscript function

lisonge commented 1 year ago

epub.js/src/archive.js#L153 will failed in UserScript scope

lisonge commented 1 year ago
import { GM_xmlhttpRequest } from '$';
import JSZip from 'jszip';

const getFileBlob = (url: string) => {
    return new Promise<Blob>((resolve, reject) =>
        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            responseType: 'blob',
            onload: (response) => {
                const blob = response.response;
                resolve(blob);
            },
            onerror: reject,
        })
    );
};

const fileUrl =
    'https://archiveofourown.org/downloads/25830331/Locked%20Out.epub?updated_at=1667832644';
const epubBlob = await getFileBlob(fileUrl);
const epubArrayBuffer = await epubBlob.arrayBuffer();
const zip = await JSZip().loadAsync(epubArrayBuffer);
const fileTask = zip.file('META-INF/container.xml')?.async?.('string');
console.log(await fileTask);

It is always in pending state in UserScript scope

misaalanshori commented 1 year ago

Okay, so is it possible to add this alternative method of loading the script so that it's loaded in the scope of the website instead of the userscript scope?

misaalanshori commented 1 year ago

I was thinking maybe there could be another option added similar to externalGlobals (maybe called externalWindowGlobals) that works similarly to externalGlobals but instead of loading the script using @require, it would load it by inserting script tags to the body (like using the functions I shared above). This way some libraries can be loaded through @require and some that need it can be loaded through inserted script tags.

Or another way could be adding a boolean option that would switch the externalGlobals option to instead get loaded using the script tag method instead of using @require.

lisonge commented 1 year ago

Okay, so is it possible to add this alternative method of loading the script so that it's loaded in the scope of the website instead of the userscript scope?

# .npmrc , pnpm install
public-hoist-pattern[]=jszip
// vite.config.ts
import { defineConfig } from 'vite';
import monkey from 'vite-plugin-monkey';
import epubjsPkg from './node_modules/epubjs/package.json';
import jszipPkg from './node_modules/jszip/package.json';

const epubjs_code = `
import { unsafeWindow } from '$';
const importScript = (src) => {
    return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = src;
        script.addEventListener('load', () => {
            resolve();
            script.parentElement?.removeChild(script);
        });
        script.addEventListener('error', (e) => {
            reject(e);
            script.parentElement?.removeChild(script);
        });
        document.body.appendChild(script);
    });
};
await importScript(
    'https://cdn.jsdelivr.net/npm/jszip@${jszipPkg.version}/dist/jszip.min.js'
);
await importScript(
    'https://cdn.jsdelivr.net/npm/epubjs@${epubjsPkg.version}/dist/epub.min.js'
);
export default unsafeWindow?.ePub;
`.trim();

export default defineConfig({
    plugins: [
        {
            name: 'script:epubjs',
            enforce: 'pre',
            apply: 'build',
            resolveId(source, importer, options) {
                if (source == 'epubjs') return `\0epubjs`;
            },
            load(id, options) {
                if (id == `\0epubjs`) {
                    return epubjs_code;
                }
            },
        },
        monkey({
            entry: 'src/main.ts',
            userscript: {
                icon: 'https://vitejs.dev/logo.svg',
                namespace: 'npm/vite-plugin-monkey',
                match: ['https://songe.li/'],
                noframes: true,
                connect: [`archiveofourown.org`],
                grant: ['unsafeWindow'],
            },
        }),
    ],
});
lisonge commented 1 year ago

Is this problem solved ? I think it can be closed

misaalanshori commented 1 year ago

Yeah I think we can close this