Как минимизировать JavaScript, как Google Analytics?

Большинство из вас, вероятно, знакомы с этим небольшим кодом отслеживания, предлагаемым Google Analytics.

<script>
(
    function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    }
)(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-00000000-0', 'auto');
ga('send', 'pageview');
</script>

Интересная часть состоит в том, что эта небольшая выдержка содержит аргументы, которые образуют слово изограмма. Этот script также использует аргументы для объявления переменных, чтобы сбрить некоторые биты из окончательного размера файла. Возможно, вы не использовали бы этот шаблон при написании кода (?), Поэтому мой вопрос: как Google минимизирует свой код и эти методы доступны для простых смертных?

Я нашел онлайн этот пример Стивена Морли, который включает в себя код, который выглядит как что-то, что вы пишете, прежде чем его использовать. Я взял этот код и запустил его через Google очень собственный Closure Compiler в расширенной оптимизации. Как и ожидалось, полученный код не похож на фактический script, используемый Google Analytics.

(function(){window.b="ga";"ga"in window||(window.a=function(){window.a.q.push(arguments)},window.a.q=[]);window.a.c=(new Date).getTime();var c=document.createElement("script");c.src="//www.google-analytics.com/analytics.js";c.async=!0;var d=document.getElementsByTagName("script")[0];d.parentNode.insertBefore(c,d)})();

На этот раз код меньше DRY и больше, даже без двух дополнительных команд.

Итак, чтобы уточнить, мне любопытно, как инженеры Google пришли к вышеуказанному результату (я не думаю, что их код на самом деле похож на тот, что приведен в примере Стивена), и может ли этот процесс быть реплицирован, даже если вы не являетесь частью Google? Заранее благодарю вас!

Ответ 1

У меня такое чувство, что слово "изограмма" - это немного хитроумный намек от сотрудника Google, который уменьшил этот код.

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

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

Так как слово isogram само является изограммой, человек, создавший логику минимизации, может установить его для проверки списка параметров или аргументов для случая, в котором есть 7 аргументов /params, и в этом случае просто заменить каждый с соответствующей буквой в слове "изограмма". Это добавит некоторые накладные расходы, но такие случаи встречаются редко, и у Google есть много серверов и сетевых инженеров для оптимизации своих сценариев.

Ответ 2

Google хорош, потому что они дают нам полную документацию о многих вещах на https://developers.google.com p >

Так много ваших ответов можно найти на:

Вот неопределенный Analytics.js

(function(i, s, o, g, r, a, m){
  i['GoogleAnalyticsObject'] = r; // Acts as a pointer to support renaming.

  // Creates an initial ga() function.
  // The queued commands will be executed once analytics.js loads.
  i[r] = i[r] || function() {
    (i[r].q = i[r].q || []).push(arguments)
  },

  // Sets the time (as an integer) this tag was executed.
  // Used for timing hits.
  i[r].l = 1 * new Date();

  // Insert the script tag asynchronously.
  // Inserts above current tag to prevent blocking in addition to using the
  // async attribute.
  a = s.createElement(o),
  m = s.getElementsByTagName(o)[0];
  a.async = 1;
  a.src = g;
  m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

// Creates a default tracker with automatic cookie domain configuration.
ga('create', 'UA-XXXXX-Y', 'auto');

// Sends a pageview hit from the tracker just created.
ga('send', 'pageview');

И вот мини-версия, которую они предоставляют (красивая версия):

(function (i, s, o, g, r, a, m) {
    i['GoogleAnalyticsObject'] = r;
    i[r] = i[r] || function () {
            (i[r].q = i[r].q || []).push(arguments)
        }, i[r].l = 1 * new Date();
    a = s.createElement(o),
        m = s.getElementsByTagName(o)[0];
    a.async = 1;
    a.src = g;
    m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');

ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');

И здесь мини-версия с инструментами компилятора закрытия

(function (a, e, f, g, b, c, d) {
    a.GoogleAnalyticsObject = b;
    a[b] = a[b] || function () {(a[b].q = a[b].q || []).push(arguments)};
    a[b].l = 1 * new Date;
    c = e.createElement(f);
    d = e.getElementsByTagName(f)[0];
    c.async = 1;
    c.src = g;
    d.parentNode.insertBefore(c, d)
})(window, document, "script", "//www.google-analytics.com/analytics.js", "ga");
ga("create", "UA-XXXXX-Y", "auto");
ga("send", "pageview");

Это похоже на то же.
Более подробную информацию о проекте вы можете найти в репозиторий Github.

Ответ 3

На самом деле это довольно простая и забавная задача для написания таких скриптов.

Вот длинный пример того, как превратить регулярную функцию в нечто вроде этого:

Я бы начал с воображаемого script. Я включил scriptLoader, который загружает файл javascript асинхронно:

window.loadScript  = function(src){
    const scriptTag = document.createElement('script');
    scriptTag.async = true;
    scriptTag.src = src;

    const anyOtherScriptTag = document.getElementsByTagName('script')[0];
    anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag);
}

При вызове так: loadScript("/url.js") он вставляет новый тег script (перед первым тегом script) в DOM, и браузер загрузит script.

Пока все хорошо. Скажем, я хочу передать этот аргумент script до его загрузки. Внутри script, который будет загружен, я получаю доступ к уникальному глобальному объекту. Позвольте называть его window.myScriptArgs. Поэтому в идеале, после загрузки script он считывает window.myScriptArgs и выполняет соответственно.

Теперь я мог бы сделать window.myScriptArgs = [] и назвать его днем, но так как мой гипотетический пример будет загружать только файл single script, я добавляю логику к функции loadScript.

window.loadScript  = function(src){
    window.myScriptArgs = window.myScriptArgs || [];
    const scriptTag = document.createElement('script');
    scriptTag.async = true;
    scriptTag.src = src;

    const anyOtherScriptTag = document.getElementsByTagName('script')[0];
    anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag);
}
loadScript("/my-script.js");

Хорошо, поэтому я проверяю, присутствует ли myScriptArgs, и если нет, я устанавливаю его в пустой массив. Теперь я также знаю, что my-script.js предоставляет глобальный метод myScript(). Поэтому я пишу для этого заглушку. Этот заглушка поместит каждый аргумент в массив myScriptArgs:

window.myScript = () => {
     window.myScriptArgs = window.myScriptArgs || [];
     window.myScriptArgs.push(arguments);
}

Теперь я могу вызвать loadScript и сразу вызвать myScript() с заданными аргументами. Не нужно беспокоиться о проблемах с загрузкой или о многом. После загрузки "my- script.js" он читает window.myScriptArgs и действует как исключение. Код выглядит следующим образом:

window.myScript = () => {
    window.myScriptArgs = window.myScriptArgs || [];
    window.myScriptArgs.push(arguments);
}

window.loadScript  = function(src){
    window.myScriptArgs = window.myScriptArgs || [];
    const scriptTag = document.createElement('script');
    scriptTag.async = true;
    scriptTag.src = src;

    const anyOtherScriptTag = document.getElementsByTagName('script')[0];
    anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag);
}
loadScript("/my-script.js");
myScript('command', 'args', 'args1');
myScript('command2', 'args3', 'args4');

Хорошо, это работает так, как ожидалось. Пусть оптимизирует его. Сначала я объединяю заглушку loadScript и myScript с одной функцией, называемой initMyScript():

window.initMyScript = function(src){
    window.myScriptArgs = window.myScriptArgs || [];
    window.myScript = window.myScript || function(){
        window.myScriptArgs.push(arguments);
    }

    const scriptTag = document.createElement('script');
    scriptTag.async = true;
    scriptTag.src = src;

    const anyOtherScriptTag = document.getElementsByTagName('script')[0];
    anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag);
}
initMyScript("/my-script.js");
myScript('command', 'args', 'args1');
myScript('command2', 'args3', 'args4');

Это не слишком причудливо. Теперь я избавлюсь от множества вызовов window., передав window в качестве аргумента initMyScript. Я также сделаю это с помощью document.

script выглядит следующим образом:

window.initMyScript = function(p, a, src){
    p.myScriptArgs = p.myScriptArgs || [];
    p.myScript = p.myScript || function(){
        p.myScriptArgs.push(arguments);
    }

    const scriptTag = a.createElement('script');
    scriptTag.async = true;
    scriptTag.src = src;

    const anyOtherScriptTag = a.getElementsByTagName('script')[0];
    anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag);
}
initMyScript(window, document, "/my-script.js");

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

window.initMyScript = function(p, a, s, c, src){
    p.myScriptArgs = p.myScriptArgs || [];
    p[c] = p[c] || function(){
        p.myScriptArgs.push(arguments);
    }

    const scriptTag = a.createElement(s);
    scriptTag.async = true;
    scriptTag.src = src;

    const anyOtherScriptTag = a.getElementsByTagName(s)[0];
    anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag);
}
initMyScript(window, document, 'script', 'myScript', "/my-script.js");

Следующий шаг в моем путешествии - сделать переменные короткими. И я также включил эту функцию в функцию самоисполнения, чтобы сохранить определение window.initMyScript:

(function(p, a, s, c, src){
    p.myScriptArgs = p.myScriptArgs || [];
    p[c] = p[c] || function(){
        p.myScriptArgs.push(arguments);
    }

    const q = a.createElement(s);
    q.async = true;
    q.src = src;

    const d = a.getElementsByTagName(s)[0];
    d.parentNode.insertBefore(q, d);
})(window, document, 'script', 'myScript', "/my-script.js");

И к моей последней тайне: я редактирую параметры функции, чтобы запутать людей, а также еще более уменьшить код. Вы действительно можете связать функции в javascript с помощью запятых;).

(function(p, a, s, c, A, l, i){
    p["myScriptArgs"]=p["myScriptArgs"]||[],p[c] = p[c]||function(){
        p["myScriptArgs"].push(arguments)},
    l = a.createElement(s);l.async = true;l[A] = A;
    i = a.getElementsByTagName(s)[0];
    i.parentNode.insertBefore(l, i);
})(window, document, 'script', 'myScript', "/my-script.js");
myScript("arg1", "arg2");
myScript("arg2", "arg3");

Заметьте, что я добавляю два дополнительных параметра в функцию, потому что мне нужно сохранить элемент, возвращаемый createElement, и не хочу использовать инструкцию var;).

Вы можете принять это еще дальше, но вы понимаете. Для небольших функций вы можете сделать это самостоятельно без проблем.

Кроме того, вы можете использовать minifier как UglifyJS, а затем переименовать переменные самостоятельно, если вы действительно входите в эту целую изограмму...

Примечание. Я не тестировал этот код. Здесь будут драконы. Мнимый код - моя неудачная попытка де-обфускации примера Google. Фрагмент google-analytics работает почти так же, как и мой пользовательский фрагмент. GA оптимизирует немного больше (например, превращая true в 1), но вы получите точку.

Подробнее о том, что используется в моем примере: Немедленное вызывное выражение функции Атрибуты свойств (особенно обозначение скобок)

И javascript специфические вещи, такие как передача трех аргументов функции, которая принимает 5.

Ответ 4

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

function NewObject(prefix)
{
    var count=0;
    this.SayHello=function(msg)
    {
          count++;
          alert(prefix+msg);
    }
    this.GetCount=function()
    {
          return count;
    }
}
var obj=new NewObject("Message : ");
obj.SayHello("You are welcome.");

Может быть запутан, чтобы выглядеть так:

var _0x3c28=["\x53\x61\x79\x48\x65\x6C\x6C\x6F","\x47\x65\x74\x43\x6F\x75\x6E\x74","\x4D\x65\x73\x73\x61\x67\x65\x20\x3A\x20","\x59\x6F\x75\x20\x61\x72\x65\x20\x77\x65\x6C\x63\x6F\x6D\x65\x2E"];function NewObject(_0x12c4x2){var _0x12c4x3=0;this[_0x3c28[0]]= function(_0x12c4x4){_0x12c4x3++;alert(_0x12c4x2+ _0x12c4x4)};this[_0x3c28[1]]= function(){return _0x12c4x3}}var obj= new NewObject(_0x3c28[2]);obj.SayHello(_0x3c28[3])

Это было сделано с использованием бесплатного обфускационного алгоритма на https://javascriptobfuscator.com/Javascript-Obfuscator.aspx.

Я уверен, что у Google есть свои способы обработки кода:).

Ответ 5

Изображение, чтобы иметь фрагмент кода, например:

(function(){
  window.GoogleAnalyticsObject = 'ga';
  window.ga = window.ga || function(){
    (window.ga.q = window.ga.q || []).push(arguments)
  },
  window.ga.l =1 * new Date();
  var a = document.createElement('script'),
  var m = document.getElementsByTagName('script')[0];
  a.async = 1;
  a.src = '//www.google-analytics.com/analytics.js';
  m.parentNode.insertBefore(a, m)
})();

Затем измените свой код, чтобы передать весь необходимый вам объект в качестве параметров:

(function(i, s, o, g, r, a, m){
  i['GoogleAnalyticsObject'] = r;
  i[r] = i[r] || function(){
    (i[r].q = i[r].q || []).push(arguments)
  },
  i[r].l =1 * new Date();
  a = s.createElement(o),
  m = s.getElementsByTagName(o)[0];
  a.async = 1;
  a.src = g;
  m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

Удалите все пробелы и, наконец, вы получите:

(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');

Надеюсь, я был прост, пока.

Обновлено. Вы спрашиваете, почему они выбирают слово "изограмма"? Это один из "известных" слов изограммы, см. Wikipedia, если вам нужно больше параметров.

Ответ 6

Вы можете использовать npm и бегун задачи как gulp. Gulp имеет плагин с именем uglify, который устраняет лишние пробелы и принимает параметры и переменные и сводит их к одной букве, чтобы еще больше уменьшить общее количество символов в коде.