2–3 года назад мы обычно использовали компилятор закрытия Google для обработки процесса сжатия кода javascript. как только вы включите предварительное сжатие, это принесет много перерывов в производственном коде. люди очень осторожны при ведении белого списка для всех ключей, которые вы не хотели бы испортить. например, собственные API, некоторые свойства из результата ответа и т. д.

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

Почему искажение свойств может нарушить ваш кодекс?

Эммм… JavaScript никогда не был статическим языком со дня рождения и до сих пор, может быть, когда-либо. и он слишком динамичен, чтобы компилятору трудно угадать полную структуру во время синтаксического анализа. Определение метода в классе может быть динамическим, добавить свойство в класс - то же самое. Для компилятора это слишком сложно - «Я не могу допинговать все!».

Возьмем пример. очень простой класс Apple с 1 методом получения и 1 методом-прототипом, затем вызовите метод и получатель в замыкании. просто, правда? назовите его demo.js

(function () {
  class Apple {
    get val() { return 1 }
    mycall() { return 'method' }
  }
var instance = new Apple()
  console.log(instance.val)
  console.log(instance.mycall())
})()

Посмотрите, что мы получили после компиляции babel с полным преобразованием в es5 (я проигнорировал вспомогательную часть _createClass). назовите его demo.es5.js

(function() {
  var Apple = (function() {
    function Apple() {
      _classCallCheck(this, Apple);
    }
    _createClass(Apple, [
      {
        key: "mycall",
        value: function mycall() { return "method"; }
      },
      {
        key: "val",
        get: function get() { return 1; }
      }
    ]);
  return Apple;
})();
var instance = new Apple();
  console.log(instance.val);
  console.log(instance.mycall());
})();

Давайте посмотрим, как uglifyJS (здесь я выбираю пакет uglify-es, который может обрабатывать часть синтаксиса es6 +. Я расскажу вам, почему я выбрал его позже)

включите параметр mangle и mangle-props! Посмотрим, что у нас есть сейчас. Назовем его demo.min.js.

(function () {
  class e {
    get t() {
      return 1;
    }
    l() {
      return "method";
    }
  }
  var n = new e();
  n.t;
  n.l();
})();

Успешно и ничего плохого, правильно? Теперь мы знаем, как работают процессы babel и расширенного сжатия, давайте разберемся, как они работают вместе с webpack?

Процесс компиляции и сжатия в Webpack

Обычно мы настраиваем компиляцию babel с помощью babel-loader и сжатие с помощью uglifyjs-webpack-plugin.

Загрузчики применяются к одному модулю, когда файл JS необходимо скомпилировать, он сначала передает загрузчики, а затем объединяется в модули.

Плагины более сложные, у них есть жизненный цикл. (Оформить заказ https://github.com/webpack/docs/wiki/plugins)

Webpack привел пример того, как создать настраиваемый плагин, как показано ниже.

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

// MyPlugin.js

function MyPlugin(options) {
  // Configure your plugin with options...
}

MyPlugin.prototype.apply = function(compiler) {
  compiler.plugin("compile", function(params) {
    console.log("The compiler is starting to compile...");
  });

  compiler.plugin("compilation", function(compilation) {
    compilation.plugin("optimize", function() {});
    compilation.plugin("optimize-chunk-assets", function(chunks, callback) {});
    compiler.plugin("emit", function(compilation, callback) {
    console.log("The compilation is going to emit files...");
    callback();
  });
};

module.exports = MyPlugin;

Это может быть устаревшим, поскольку в webpack 4 добавлен API вкладок для плагинов. Но мы узнаем, что плагины этапов и этапов могут иметь эффект.

Для сжатия UglifyJS требуется контекст вашего кода. Создание плагина не только для экономии времени при установке пакета исправлений, но и для экономии большего количества выходных байтов. Позвольте мне привести пример,

// a.js
exports.HelloMyWorldHeyHowAreYouWorldPlzSayHelloBack = () => console.log('hello')
// main.js
import {HelloMyWorldHeyHowAreYouWorldPlzSayHelloBack} from './a'
HelloMyWorldHeyHowAreYouWorldPlzSayHelloBack()

Если контекст - это просто модуль, например, a.js, как сжатие может сохранить ваши байты? Он должен был убедиться, что экспорт не будет прерван, чтобы сохранить имя. кроме этого, в этом примере больше нечего делать. Невозможно сэкономить слишком много, если мы знаем только фрагмент, модуль или файл.

Если вам нужно изменить имя, вы можете изменить все, кроме выравнивания. Таким образом, плагин UglifyJS использует входные данные из связанного модуля, а не из исходного исходного файла. Тогда порядок задач в webpack - сначала babel, а затем uglify. Теперь приходят проблемы.

Почему код не работает, когда включен Mangle-Props?

Теперь мы убираем demo.es5.js с помощью uglify с включенным mangle-props. Наконец, у нас есть demo.es5.min.js.

(function () {
  var e = function () {
    function e() {
      _classCallCheck(this, e);
    }
    _createClass(e, [{
      key: "mycall",
      value: function e() { return "method"; }
    }, {
      key: "val",
      get: function e() { return 1; }
    }]);
    return e;
  }();
  var n = new e();
  console.log(n.s);
  console.log(n.l());
})();

Запустить его? вы получите ошибку. См. Определение класса babel, метод mycall и получатель val по-прежнему не изменяются, но вызов искажен. Это проблема динамичности, когда при определении прототипов некоторыми функциями синтаксический анализатор не может предсказать всю полную форму класса, когда останавливается. он не знает, что класс получит метод с именем mycall после выполнения _createClass.

Если вы просто используете Object.defineProperties или Object.defineProperty в классе цепочки прототипов, это будет работать. UglifyJS может избежать искажения этих собственных API только при явных вызовах. Или, может быть, мы можем изменить шаблон createClass в babel-runtime?

UglifyJS генерирует AST путем синтаксического анализа кода собственным парсером, который называется parse-js (изменен на основе парсера es3). даже если он поддерживает синтаксический анализ с помощью acorn-js, вы можете обрабатывать его только в исполняемом двоичном коде, а не в API.

Теперь вернемся ко всем инструментам, которые мы используем для синтаксического анализа файла JavaScript.

веб-пакет - acorn-js;

babel - это собственный парсер;

uglify - parse-js, собственный парсер, модифицированный из поддержки es3;

У каждого есть свой парсер с разным уровнем поддержки, поэтому мы столкнемся с проблемами, если об использовании какой-то специальной функции уже предупредил нас. 🙈

Вероятно, это может сломать ваш код!, но все же есть много достоинств!

Как мы это решаем

Решение может быть несложным, просто сначала улейте, а затем убейте, проблема исчезла.

Но до выхода terser-js мы пытались найти способ совместной работы с uglify-es и babel. Потому что uglify-js вообще не может использовать синтаксис es6 +, а в uglify-es есть ошибки.

Пример вычисленных реквизитов. мы получили computed-props.js, как показано ниже.

var a = {
  b: {
    w: 'w',
    x: 'x',
    y: 'y'
  }  
}
var props = a.b
var c = {
  [props.w]: 1,
  [props.x]: 2,
  [props.y]: 3,
}

измените его с помощью mangle props, затем мы получили computed-props.es5.js, это выглядит так.

var a = {
  b: {
    w: "w",
    x: "x",
    y: "y"
  }
};
var props = a.b;
var c = {
  p: 1,
  p: 2,
  p: 3
};

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

Потом? Что мы делаем, так это сначала компилируем его, используя только пару плагинов es6 +, таких как transform-computed-props. мы не трансформируем классы вначале.

Как только мы получили частичный код es6 +, uglify-es мог правильно его исправить без посредников.

После того, как собственная часть uglify не работает, webpack будет передавать ресурсы другому плагину, плагину babel. Этот парень применит на выходе все пресеты и плагины, которые мы указали в babelrc. Были сделаны!

Более простое решение

uglify-js @ 3 поддерживает только es5.

Примерно в августе 2018 года работа в uglify переходит к другому преемнику. Поскольку сопровождающий решил больше не поддерживать uglifyjs, появился terser-js. вы можете рассматривать это как новые уродства.

Использование terser-js упростит этот процесс, сначала кратко, а затем babel.

Будущее Minify

Babel опубликовал свой собственный минификатор под названием babel-minify, но без поддержки изменений свойств. Будет ли он наконец использовать эту функцию? Не знаю.

У манипуляций с реквизитом есть свой ярлык. вы должны сохранить некоторые свойства в строковом формате. а неключевые свойства не могут быть вызваны в вычисляемом формате, например obj [‘prefix’ + someValue].

Сообщество принесло классную вещь, красивее, для автоматического форматирования вашего кода. К сожалению, prettier сотрет кавычки на ключе строкового формата. Я не могу сказать всем, пожалуйста, прокомментируйте / * prettier ignore * / для каждого объекта со свойствами строкового формата, это не круто.

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

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

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

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

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

Пример кода: https://github.com/huozhi/minifyjs-test/