What works for me is to retrieve my files in a text format via GM_xmlhttpRequest
, wrap that inside of an eval()
and then execute functions as though @require
was used.
It's worth noting that using eval
is extremely dangerous and should almost never be used. That being said, this is the exception for me as there is low risk.
In my case, I have a couple of different scripts that need to be run before my main application script (such as utility functions). Therefore, similar to how you would place <script>
tags in an order depending on when you want them to run, I order them to run first to last in an Array
.
Also, I wrap all my TM functions inside of an initialize()
function, and that's what I call at the end which kicks off my script.
(async function() {
try {
const scriptsToExecute = [
{ resource: 'waitForElement', url: 'https://www.example.org/waitForElement.js', },
{ resource: 'utils', url: 'https://www.example.org/utils.js', },
{ resource: 'main', url: 'https://www.example.org/mainApp.js'},
];
const getScripts = await retrieveScripts(scriptsToExecute).catch(e => {debugger;console.error('Error caught @ retrieveScripts',e);});
if (getScripts?.status !== "success" || !Array.isArray(getScripts?.scripts || getScripts?.find(f => f.status !== "success"))) throw {getScripts};
try {
const scripts = getScripts?.scripts;
const mainAppScript = scripts?.find(f => f?.resource === "main");
const scriptsToExecute = scripts?.filter(f => f?.resource !== "main");
for (let i in scriptsToExecute){
if (scriptsToExecute[i]?.status !== "success" || !scripts[i]?.retrieved) throw {"erroredScript": scripts[i]}
const thisScript = scripts[i]?.retrieved;
// eslint-disable-next-line
eval(thisScript?.script);
}
// eslint-disable-next-line
eval(mainAppScript);
try {
// once you've eval'd the script, you can call functions inside that script from within your UserScript environment
// all my main app scripts are wrapped inside of a function called `initialize()`.
// though once you've eval'd the script, you can call whatever you want.
initialize();
} catch (err){debugger; console.error('Error caught @ attempting to initialize', err);}
} catch(err){debugger; console.error('Error caught @ top level', err);}
} catch (err) {debugger}
async function retrieveScripts(scriptsToRetrieve){
try {
const scriptsContent = await Promise.all(scriptsToRetrieve.map(m => retrieveScript(m))).catch(e => {debugger;});
if (!Array.isArray(scriptsContent) || scriptsContent?.length !== scriptsToRetrieve?.length && scriptsContent?.find(f => f.status !== "success")) {debugger;return {status: "error", msg: "unable to retrieve the script(s) requested.", scriptsContent,};}
else return {status: "success", "scripts": scriptsContent};
}
catch (err){debugger;return {status: "error", msg: "(caught) unable to retrieve the script(s) requested.", scriptsToRetrieve, "error": err, "errorStringified": String(err)};}
function retrieveScript(scriptToRetrieve){
if (!scriptToRetrieve?.url) return {status: "error", msg: "no url found", scriptToRetrieve};
try {
return new Promise((resolve,reject) => {
GM_xmlhttpRequest({
method: "GET",
url: scriptToRetrieve.url,
onerror: function (response) {debugger;return reject({status: "error", response, scriptToRetrieve });},
onload: function (response) {
if (response?.status !== 200) {debugger;return reject({status: "error", response, scriptToRetrieve });}
else {
try {
if (response?.response) {
scriptToRetrieve.script = response.response;
return resolve({status: "success", "retrieved": scriptToRetrieve})
}
else throw {status: "error", "response": response.response, scriptToRetrieve }
} catch (err) {return reject(err);}
}
}
});
});
} catch (err){debugger}
}
}
})();
Once you've eval
'd your scripts, you can now run them from within the context of your Userscript. What's more, if you initialize any variables from within the function where you use eval
, they are available to you as well (such as machine-specific code). Or if you have eval
'd scripts before this, all those functions will available as well -- essentially importing them.