Как запретить @require кэшировать внешние js-скрипты

В настоящее время я пытаюсь выяснить, как я могу включить JavaScript, установленный на моем веб-сервере, в мой скрипт scriptish / greasemonkey и заставить его перезагружать скрипт при каждом вызове usercript.

Я редактирую скрипт на своем веб-сервере, и я действительно не хочу переустанавливать скрипт пользователя каждый раз, когда я делаю изменения во включенном скрипте.

Есть ли способ обойти эту проблему? Я искал ответ, но пока не повезло.

Итак, чтобы быть ясным, мой пользовательский скрипт выглядит так:

// ==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, тогда пользовательский скрипт все еще читает старый. Есть ли способ предотвратить кеширование внешнего скрипта в скрипте пользователя? Или есть какой-нибудь другой метатег, который может мне помочь?

Заранее спасибо, Дейв

5 ответов

Вот единственный работоспособный ответ https://github.com/Tampermonkey/tampermonkey/issues/475

Рекомендуется вариант 4. Однако они загружаются асинхронно, поэтому порядок загрузки может отличаться

Есть несколько способов облегчить вашу боль.:)

  1. Вы можете увеличить номер версии перед сохранением скрипта, и все внешние ресурсы будут перезагружены.
  2. После установки "Режим конфигурации" в "Дополнительно" вы можете настроить интервал внешнего обновления. Примечание: "Всегда" по-прежнему означает после использования ресурса. Так что вам может понадобиться выполнить / загрузить страницу дважды.
  3. Если вы используете Tampermonkey Beta (Chrome или Firefox), вы можете редактировать внешний ресурс на месте (потому что теперь есть кнопка редактирования, кроме кнопки удаления).
  4. Скопируйте ресурсы и храните их локально. После того, как вы включили "Локальный доступ к файлам" на странице управления расширениями Chrome или на странице настроек Tampermonkey (если вы используете Firefox), вы можете @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
}

Ответ от Роба М. не работает для меня, потому что местоположение сценария Tampermonkey и целевой сайт, куда он был внедрен, могут отличаться. Например, в моем случае у меня также есть локально работающий веб-сервер для разработки в среде IDE с использованием Tampermonkey для Firefox без необходимости доступа к файловой системе для Tampermonkey из браузера.

Этот сценарий должен быть внедрен на сторонний сайт, например example.com, где он вносит изменения. Таким образом, браузер заблокирует этот сценарий, поскольку он из другого домена, чем example.com.

Во время разработки я бы хотел получить свой скрипт без кеширования, чтобы сразу применить изменения. Решили эту проблему, получив содержимое скриптов с помощью GM.xmlHttpRequest. Кроме того, параметр GET с текущей меткой времени действует как средство блокировки кеша:

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 может обойти ту же политику происхождения, доступ должен быть явно разрешен в заголовке вашего скрипта:

// @grant        GM.xmlHttpRequest

Что мне помогает, так это получить файлы в текстовом формате черезGM_xmlhttpRequest, оберните это внутриeval()а затем выполнять функции, как будто@requireбыл использован.

Стоит отметить, что использование чрезвычайно опасно и его практически никогда не следует использовать. Однако для меня это исключение, поскольку риск низкий.

В моем случае у меня есть несколько разных сценариев, которые необходимо запустить перед основным сценарием приложения (например, служебные функции). Таким образом, аналогично тому, как вы разместите<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Если бы скрипты были созданы до этого, все эти функции также будут доступны — по сути, их импортирование.

Вы можете добавить следующее в ваш файл.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>

И удалите его, когда закончите разработку.

Другие вопросы по тегам