Автоматическая смена версии стилей при очистке кэша в MODX

Решил поделиться своим решением версионирования файлов стилей. Зачем нужно версионирование, я, наверное не буду объяснять. Вообще смена версий не такой частый процесс и зачастую он нужен не только для того, чтобы браузеры подхватывали измерения в стилях, но и для отслеживания версий сайта. Вариантов как это реализовать масса. Я состряпал наиболее подходящее для меня и решил поделиться им.

К слову, пока писал этот пост, в телеграм канале как раз подняли эту тему. :-)


Шаг 1


Для начала создадим контекстную настройку, запускаем скрипт в консоли:

$contextKey = 'style_version';
if (!$contextSetting = $modx->getObject('modContextSetting', array('key' => $contextKey))) {
  $contextSetting = $modx->newObject('modContextSetting');
}
$contextSetting->set('context_key', 'web');
$contextSetting->set('key', 'style_version');
$contextSetting->set('value', '');
$contextSetting->set('xtype', 'textfield');
$contextSetting->set('namespace', 'core');
$contextSetting->set('area', 'Site');
$contextSetting->save();

// Clear the cache:
$modx->cacheManager->refresh(array(
   'context_settings' => array('contexts' => array('web'))
));

Шаг 2


Далее создаем плагин и вешаем его на событие OnBeforeCacheUpdate. Я использую версионирование с текущей датой, мне так проще.

if ($modx->event->name == 'OnBeforeCacheUpdate') {

    // Version as current date
    $today = date("dmY");

    // Check if we have context with key style_version
    if (!$contextSetting = $modx->getObject('modContextSetting', array('key' => 'style_version', 'context_key' => 'web'))) {
        return;
    }

    $contextSetting->set('value', $today);
    $contextSetting->save();

    // clear context cache
//    $modx->cacheManager->refresh(array(
//        'context_settings' => array('contexts' => array('web'))
//    ));

}

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

<?php
if ($modx->event->name == 'OnBeforeCacheUpdate') {

    // which files we must check
    $files = [
        'public/template/css/styles.css',
        'public/template/js/scripts.min.js'
    ];

    $lastChangeTime = 0;

    // check every file
    foreach($files as $file){
        $filepath = MODX_BASE_PATH.$file;
        if (file_exists($filepath) && filemtime($filepath) > $lastChangeTime) {
            $lastChangeTime = filemtime($filepath);
        }
    }
    
    // Show result as date/month/year/hour/minutes/seconds
    $lastChangeTime = date('dmYHis', $lastChangeTime);

    // Check if we have context with key style_version
    if (!$contextSetting = $modx->getObject('modContextSetting', array('key' => 'style_version', 'context_key' => 'web'))) {
        return;
    }

    $contextSetting->set('value', $lastChangeTime);
    $contextSetting->save();

    // clear context cache
//    $modx->cacheManager->refresh(array(
//        'context_settings' => array('contexts' => array('web'))
//    ));

}

У меня так же есть версия плагина с рандомной строкой, если нужно могу скинуть.

Шаг 3


Ну и к стилям дописываем вывод значения настройки:

<link rel="stylesheet" href="public/template/css/styles.css?v={$_modx->config.style_version}" type="text/css"/>
<script src="public/template/js/scripts.min.js?v={$_modx->config.style_version}" type="text/javascript"></script>

Конечно кто-то скажет что вариант слишком громоздкий. Но меня он вполне устраивает. Если кто-то поделится своим решением, более цивилизованным — то будет круто. Каждый подберет для себя более оптимальное решение.
iWatchYouFromAfar
25 марта 2019, 10:33
modx.pro
10
2 736
+7
Поблагодарить автора Отправить деньги

Комментарии: 17

Василий Наумкин
25 марта 2019, 10:53
0
То есть, даже если ничего не поменялось, а просто сбросили кэш — то юзер перекачивает файлы заново? Как-то это не очень логично.
Лучше уж тогда своим PHP скриптом вставлять в настройку не текущую дату, а дату изменения файлов, или считать их хэш. Чтобы все файлы качались только когда хоть один из них изменился.

Ну а совсем уж идеальный вариант — собирать скрипты и стили через npm и уже там добавлять хэш каждому файлу, автоматически. Чтобы юзер качал только изменённые, а не все.
    iWatchYouFromAfar
    25 марта 2019, 11:01
    0
    Клиенты кэш редко сбрасывают, если вообще сбрасывают. Такое происходит только когда я вношу правки и соответственно я знаю что при очистке кэша, плагин подхватит текущую дату. Плюс, в моем варианте дата указывается в формате день/месяц/год. Так что, в день пользователь максимум 1 раз скачивает файлы стилей. На некоторых сайтах я вообще использую формат месяц/год.

    Ну а вообще да, надо бы сделать версию с функцией

    filemtime
      Василий Наумкин
      25 марта 2019, 11:05
      +1
      Клиенты кэш редко сбрасывают, если вообще сбрасывают.
      Ты же вроде для всех пишешь, а не только для себя.
      Решение должно быть универсальным.

      Плюс, в моем варианте дата указывается в формате день/месяц/год.
      Это, как раз-таки, минус. Потому что посетитель сможет увидеть только одну правку файлов в день.
        iWatchYouFromAfar
        25 марта 2019, 11:11
        0
        Тоже верно, значит есть смысл написать вариант плагина с filemtime. Займусь этим и чуть позже дополню статью еще одним вариантом.

        Спасибо за фидбэк!
    Василий Наумкин
    25 марта 2019, 11:35
    +3
    Только сейчас увидел
    $today = date("d-m-Y");
    $today = trim(preg_replace('/[^0-9]+/', ' ', $today));
    $today = str_replace(' ', '', $today);

    Замени на
    $today = date("dmY");

    Это ж как нужно не читать документацию, чтобы такие велосипеды городить?!
      iWatchYouFromAfar
      25 марта 2019, 11:58
      +1
      Да уж, спасибо, вот так и рождается говнокод… :-))

      Обновил пост, добавил версию плагина который проверяет когда последний раз изменялся файл.
        Илья Уткин
        25 марта 2019, 12:21
        2
        +4
        Ну теперь можно вообще сделать один маленький сниппет
        <?php
        $filepath = MODX_BASE_PATH . $input;
        if (file_exists($filepath)) {
            return $input . '?v=' . date('dmYHis', filemtime($filepath));
        }

        И использовать его как-то так:
        <link rel="stylesheet" href="{'public/template/css/styles.css' | version}" type="text/css"/>
        <script src="{'public/template/js/scripts.min.js' | version}" type="text/javascript"></script>
          Василий Наумкин
          25 марта 2019, 12:25
          +4
          Так можно, конечно, но сниппет будет дёргаться и проверять файл на каждой странице — нафига это надо? Вариант с системной настройкой как-бы кэширует эту работу.

          В принципе, можно вызывать и сниппет кэшированным
          <link rel="stylesheet" href="[[version?input=`public/template/css/styles.css`]]" type="text/css"/>
          <script src="[[version?input=`public/template/js/scripts.min.js`]]" type="text/javascript"></script>
          Систаксис MODX пропишет результат работы прямо в кэш страницы, и удалит при общем сбросе кэша.

          Всё работает, все довольны.
            Алексей Соин
            25 марта 2019, 22:08
            0
            мне кажется, лучше всего возвращать не дату, а md5 хеш файла, по типу как работает gulp-version-number, таким образом хеш изменится только тогда, когда в файле будут внесены изменения, но тут уж кому что нравится))
              Илья Уткин
              25 марта 2019, 22:11
              +2
              А вот тут интересно — если файл большой, то какая операция быстрее — вычисление хеша файла или чтение даты его изменения? Надо бы потестировать…
                Василий Наумкин
                26 марта 2019, 06:19
                +2
                Насколько я понимаю, посмотреть время изменения всегда будет быстрее, так как оно пишется в inode файловой системы, для этого даже не нужно читать сам файл.

                Но и у файла можно читать не всё содержимое, а первые 100 байт, например. В любом случае, речь идёт о микросекундах.
        Здоров Александр
        26 марта 2019, 09:42
        0
        Оставлю данный метод- протестированы на 5 сайтах, полет отличный!
          Александр Мельник
          26 марта 2019, 12:20
          +1
          Мне, как немолодому человеку и ввиду возраста начинающему брюзжать по каждому поводу, кажется что современные подходы в программировании направлены на избыточное усложнение.
          В 95 процентов случаев веб разработчики делают довольно простые проекты, но стремятся наворотить туда как можно больше технологий — композеры, пайпы, варганты, докеры, препроцессоры, галпы, боуэры, энпээмы, рэст фулы, вотчеры, системы контроля версий…

          На мой взгляд и вопрос с кешированием файлов стилей и скриптов также чрезмерно раздут. Прочел и ужаснулся, люди целые программы разрабатывают для этого. Разве не правильнее просто завести в каком-то своем пространстве имен свою системную настройку в которой будет лежать число. Это число и добавляеть как версию файла к стилям и скриптам. Во время разработки сайта это кеширование вообще не имеет смысла, достаточно нажать в браузере ctrl+f5 и весь кеш браузера слетел. На продакшине даже если и пришлось вносить какие то доработку в верстку или стили, зашел и в админке сменил цифру 3 на 4 в системных настройках. И все, все пользователи скачают новые файлы.
            Василий Наумкин
            26 марта 2019, 17:38
            0
            Конечно, старичкам трудно осваивать что-то новое, им бы внуков понянчить.

            На мой взгляд и вопрос с кешированием файлов стилей и скриптов также чрезмерно раздут. Прочел и ужаснулся, люди целые программы разрабатывают для этого.
            Внезапно, программисты любят писать программы и автоматизировать свой труд!

            Во время разработки сайта это кеширование вообще не имеет смысла, достаточно нажать в браузере ctrl+f5 и весь кеш браузера слетел.
            А еще лучше использовать webpack-dev-server, которому ничего и нажимать не надо, он в реальном времени отображает все изменения из IDE.

            На продакшине даже если и пришлось вносить какие то доработку в верстку или стили
            То просто выгрузил все изменения из IDE и запустил npm run build. Чтобы не ошибиться и не забыть что-то нажать в админке.

            Еще недавно я так же вручную менял версию, но всё время выходило вот так:

            И теперь это уже автоматизировано.

            В 95 процентов случаев веб разработчики делают довольно простые проекты
            Но бывает и наоборот, когда разработчик продолжает развиваться и делает всё более сложные проекты.
              Miša Bulic
              31 марта 2019, 13:56
              +1
              Ну на самом деле этот скрипт нужен например когда клиент вносит правки и ты с ним налету делаешь изменения чтобы он посмотрел. Большая часть клиентов не знает что можно скинуть кеш страницы, или он может быть с телефона, или планшета, телевизора, холодильника… После того как закончили с правками, то можно и выключить этот плагин.
              Miša Bulic
              26 марта 2019, 15:41
              1
              0
              Там в обсуждении в телеге предложили такой вариант:
              @Евгений Generalov
              <?php
              switch($scriptProperties['options']['type']) {
                  case 'script':
                      echo '<script src="'.$input.'?v='.filemtime($_SERVER['DOCUMENT_ROOT'] .'/'. $input).'"></script>';
                      break;
              
                  case 'stylesheet':
                      echo '<link rel="stylesheet" type="text/css" href="'.$input.'?v='.filemtime($_SERVER['DOCUMENT_ROOT'] .'/'. $input).'">';
                      break;
              }
              Использование:
              {'assets/css/main.min.css'| includeFile : ['type' => 'stylesheet']}
                Андрей
                27 марта 2019, 20:26
                1
                +1
                Я таким способом пользуюсь, просто дата файла

                if ($modx->event->name == 'pdoToolsOnFenomInit') {
                
                	// return file version
                	$fenom->addModifier('version', function ($input) use ($modx) {
                		$output = $input;
                
                		$file = MODX_BASE_PATH.$input;
                
                		if (file_exists($file)) {
                			$output = $input.'?v='.filemtime($file);
                		}
                
                		return $output;
                	});
                	
                }
                  Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                  17