我如何防止@require缓存外部JS脚本



我目前正在尝试弄清楚如何包括javaScript,在我的scriptish/greasemonkey脚本中,我在网络服务器上都有,并每次调用Userscript时都会重新加载脚本。

我正在编辑网络服务器上的脚本,我真的不想每次更改随附的脚本时重新安装userscript。

有什么办法解决这个问题?我一直在寻找答案,但到目前为止没有运气。

可以清楚地显然,我的用户本看起来像这样:

// ==UserScript==
// @id             HET
// @name           SettingsHandler
// @version        1.0
// @namespace      HET
// @require        http://urltoscript/scripts/he/lib.js
// @run-at         document-end
// ==/UserScript==

和我的外部脚本看起来像这样:

alert('got it');

对于测试目的而言,这仍然非常容易。此设置有效,但仅是第一次,并且当我更改Lib.js脚本时,那么Userscript仍然读取旧的脚本。有没有办法防止用户订阅缓存外部脚本?还是还有其他可以帮助我的metatag?

预先感谢dave

这是唯一可行的答案https://github.com/tampermonkey/tampermonkey/issues/475

建议是选项4

有几种减轻疼痛的方法。:)

  1. 您可以在保存脚本和全部之前增加版本号 外部资源将被重新加载。
  2. 将"配置模式"设置为 "高级"您可以配置外部更新间隔。笔记: "始终"仍然是指在使用资源之后。所以你可能需要 执行/加载页面两次。
  3. 如果您使用tampermonkey beta(chrome或 Firefox)您可以编辑到适当的外部资源(因为那里 现在是删除按钮以外的编辑按钮。
  4. 复制资源 并在本地存储它们。启用了"本地文件访问"之后 Chrome的扩展管理页面或Tampermonkey的设置页面(如果 您正在使用Firefox)您可以通过本地文件@require @require://uri。

不确定如何使用GM/Userscript指令来完成此操作,但是您可以轻松地自己添加脚本并将时间戳附加到URL上,以防止浏览器缓存:

var remoteScript = document.createElement('script');
remoteScript.src = 'http://domain.com/path/to/script.js?ts='+(+new Date());
remoteScript.onload = init;
document.body.appendChild(remoteScript);
function init() {
  ... do stuff
}

Rob M.的答案对我不起作用,因为tampermonkey脚本位置目标站点被注入它可能会有所不同。例如,就我而言,我也有一个本地运行的Web服务器可以在IDE中使用TampermonKey进行Firefox开发,而无需从浏览器中访问TamperMonkey的任何文件系统。

该脚本应注入第三方网站,例如example.com,在该网站上应用修改。因此,浏览器将阻止此脚本,因为它来自与example.com不同的域。

在开发过程中,我想在没有任何缓存的情况下获取我的脚本,以立即应用更改。通过使用`gm.xmlhttprequest获取脚本内容来解决此问题。此外,具有当前时间戳的获取参数充当缓存buster:

let url = `http://localhost/myscript.js?ts=${(+new Date())}`
GM.xmlHttpRequest({
    method: "GET",
    url: url,
    onload: function(response) {
        let remoteScript = document.createElement('script')
        remoteScript.id = 'tm-dev-script'
        remoteScript.innerHTML = response.responseText
        document.body.appendChild(remoteScript)
    }
})

请注意,由于GM.xmlHttpRequest可以绕过相同的原始策略,因此访问Musst在您的脚本的头部明确宏伟:

// @grant        GM.xmlHttpRequest

对我有用的是通过GM_xmlhttpRequest以文本格式检索我的文件,将其包装在eval()中,然后执行函数,就好像使用@require一样。

值得注意的是,使用eval非常危险,几乎不应使用。话虽如此,这对我来说是一个例外,因为风险很低。

就我而言,我有几个不同的脚本需要在我的主应用程序脚本之前运行(例如实用程序函数)。因此,类似于您将<script>标签放在订单中的方式,具体取决于您希望它们运行的时间,我命令它们首先运行至Array

另外,我将所有TM功能都包装在initialize()函数中,这就是我在末尾所说的启动脚本的所谓。

(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}
        }
    }
})();

eval'd脚本后,您现在可以在您的用户标题的上下文中运行它们。更重要的是,如果您从使用eval的函数中初始化任何变量,则它们也可用于您(例如特定于机器的代码)。或者,如果您在此之前具有eval'D脚本,那么所有这些功能也将可用 - 本质上导入它们。

您可以将以下内容添加到.htaccess文件:

<FilesMatch "filename.js">
Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "0"
</FilesMatch>

并完成开发后将其删除。

最新更新