Практика оптимизации производительности Tree-Shaking — принципы

Webpack rollup.js
Практика оптимизации производительности Tree-Shaking — принципы

1. Что такое встряска дерева

Давайте посмотрим на первоначальный замысел Tree-shaking.



Изображение выше объясняет первоначальное значение tree-shaking.tree-shaking во внешнем интерфейсе, упомянутом в этой статье, можно понимать как «встряхивание» нашего JS-файла с помощью инструментов и «встряхивание» в нем неиспользуемого кода, что является перформансом. категория оптимизации. В частности, в проекте веб-пакета есть файл входа, который эквивалентен стволу дерева, и файл входа имеет много зависимых модулей, которые эквивалентны ветвям. На практике, хотя это зависит от определенного модуля, на самом деле он использует только некоторые из его функций. Благодаря встряхиванию дерева неиспользуемые модули встряхиваются, чтобы достичь цели удаления бесполезного кода.


Tree-shaking был реализован ранее в накопительном пакете Rich_Harris, а позже webpack2 также добавил функцию tree-shaking. На самом деле, ранее компилятор закрытия Google также делал подобные вещи. Эффекты и способы использования трех инструментов различны.Методы использования можно понять из документации на официальном веб-сайте.Эффекты трех инструментов будут подробно сравниваться позже.


2. Принцип встряхивания дерева

Суть Tree-shaking заключается в том, чтобы исключить бесполезный js-код. Устранение бесполезного кода широко используется в компиляторах традиционных языков программирования. Компилятор может определить, что некоторые коды вообще не влияют на вывод, а затем удалить эти коды.Это называется DCE (устранение мертвого кода).

Tree-shaking — это новая реализация DCE. Javascript отличается от традиционных языков программирования. В большинстве случаев javascript необходимо загрузить по сети, а затем выполнить. Чем меньше размер загружаемого файла, тем короче общее время выполнения, поэтому удаление бесполезного кода для уменьшения размера файла имеет больше смысла для javascript.

Tree-shaking отличается от традиционного DCE: традиционный DCE устраняет невозможный код, а Tree-shaking больше фокусируется на удалении неиспользуемого кода. Ниже приводится подробное введение в DCE и Tree-shaking.


(1) Давайте посмотрим на Дафа ликвидации DCE.


Dead Code обычно имеет следующие характеристики

• Код не будет выполнен, недоступен

• Результат выполнения кода не будет использован

• Код влияет только на мертвые переменные (только запись, не чтение)


Код, отмеченный в красной рамке ниже, является мертвым кодом и соответствует указанным выше характеристикам.

Рисунок 4

В традиционных компилируемых языках компилятор удаляет мертвый код из AST (абстрактного синтаксического дерева).Кто делает DCE в javascript?

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

На самом деле, это не три упомянутых выше инструмента, rollup, webpack, cc, а известный инструмент оптимизации сжатия кода uglify, uglify завершил DCE javascript, давайте проверим это с помощью эксперимента.


Все следующие примеры кодов можно найти на нашем github, добро пожаловать в ❤

GitHub.com/lin-hi/tree…


Используйте rollup и webpack для упаковки кода на рис. 4 соответственно.

Рисунок 5

Середина — результат упаковки rollup, а справа — результат упаковки webpack.

Можно обнаружить, что rollup устраняет бесполезный код функции foo и unused function, но все равно сохраняет код, который не будет выполняться, а webpack полностью сохраняет весь бесполезный код и код, который не будет выполняться.


Упакуйте код на рис. 4 с помощью rollup + uglify и webpack + uglify соответственно.

Изображение 6

Посередине файл конфигурации, справа результат

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


(2) Давайте взглянем на метод устранения встряхивания деревьев.

Как упоминалось ранее, встряска дерева больше связана с устранением бесполезных модулей, удалением тех модулей, на которые есть ссылки, но которые не используются.

Сначала подумайте над вопросом, почему в последние годы древотрясение популярно? Концепция модульности внешнего интерфейса имеет многолетнюю историю, фактически принцип устранения tree-shaking зависит от характеристик модуля ES6.

Особенности модуля ES6:

  • Появляется только как оператор на верхнем уровне модуля
  • Имя модуля импорта может быть только строковой константой.
  • привязка импорта неизменна

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

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

Это важное соображение при проектировании модулей ES6, а также причина, по которой CommonJS не используется напрямую, именно на этой основе возможен tree-shaking.


Давайте подробнее рассмотрим пример

Функции процедурного программирования и объектно-ориентированное программирование являются наиболее часто используемыми шаблонами программирования и методами организации кода в javascript. Экспериментируйте с этими двумя аспектами:

  • Эксперимент по исключению функции
  • эксперимент по ликвидации класса

Первый взгляд на эксперимент по устранению функций

Метод get в utils не используется, мы ожидаем, что в конечном итоге метод get будет исключен.

Обратите внимание, что в настоящее время uglify не выполняет DCE для файлов, поэтому в приведенном выше случае uglify не может быть оптимизирован.

Первый взгляд на результаты упаковки роллапа

Как и ожидалось, в конечном результате нет метода get.

Посмотрите еще раз на результаты webpack

Также, как и ожидалось, в конечном результате нет метода get.

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

В эксперименте по удалению функций и rollup, и webpack прошли, как и ожидалось.


Давайте посмотрим на эксперимент по исключению классов

Добавлена ​​ссылка на menu.js, но на самом деле в коде не используются никакие методы и переменные меню, поэтому мы ожидаем, что содержимое в menu.js в финальном коде будет удалено

main.js
menu.js

накопительные результаты упаковки

Пакет фактически содержит весь код menu.js.

результат упаковки webpack

Пакет даже содержит весь код menu.js

В ходе эксперимента по устранению классов накопительный пакет и веб-пакет были уничтожены и не оправдали ожиданий.
что случилось?

Это полностью отличается от того, что мы себе представляли, не так ли? Зачем? Бесполезные классы не могут быть устранены, можно ли это все еще называть встряхиванием деревьев? В то время я подозревал, что проблема в моей демо-версии, но позже, после различных поисков в Интернете, я понял, что эта демо-версия не была ошибочной.

Вот несколько ответов от основных участников объединения:

Рисунок 7
  • rollup обрабатывает только функции и переменные импорта/экспорта верхнего уровня, он не может исключить методы неиспользуемых классов.
  • Характеристики динамического языка javascript затрудняют статический анализ.
  • Примером побочных эффектов является код в нижней части рисунка 7. Если во время статического анализа удалить бег или прыжок, может появиться сообщение об ошибке при работе программы, затем телега перед лошадью перевернется.Наша цель состоит в том, чтобы оптимизировать, и это не должно влиять на выполнение.


Приведите еще один пример, объясняющий, почему нельзя исключить menu.js, например следующий сценарий.

function Menu() {
}

Menu.prototype.show = function() {
}

Array.prototype.unique = function() {
    // 将 array 中的重复元素去除
}

export default Menu;

Если вы удалите menu.js, расширение Array также будет удалено, что повлияет на функцию. Тогда вы можете спросить, не могут ли rollup и webpack различать определение прототипа меню и определение прототипа массива? Конечно, если код написан в виде выше, то его можно отличить, а если я напишу так?

function Menu() {
}

Menu.prototype.show = function() {
}

var a = 'Arr' + 'ay'
var b
if(a == 'Array') {
    b = Array
} else {
    b = Menu
}

b.prototype.unique = function() {
    // 将 array 中的重复元素去除
}

export default Menu;

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

Подробнее о побочных эффектах см.

图标

Tree shaking class methods · Issue #349 · rollup/rollupgithub.com


tree-shaking лучше работает для функций

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


Три инструмента, о которых мы упоминали в начале, rollup и webpack, работают не очень хорошо, так что насчет замыкающего компилятора?

Результаты, полученные после упаковки кода в примере с CC:

Боже, разве это не то, чего мы хотим? Результат идеального устранения всего бесполезного кода, результат очень сексуальный.

Закройте компилятор, результат tree-shaking идеален!

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

Google определяет набор спецификаций аннотаций Annoting JavaScript for Closure Compiler, Если вы хотите узнать больше, вы можете перейти на официальный сайт.

Навязчивость делает людей очень неудобными.Компилятор Google Closure написан на java, и он не может быть совместим с нашими различными библиотеками сборки на основе узлов (но, похоже, уже есть версия компилятора Closure для nodejs), компилятор Closure также более хлопотно в использовании, поэтому, хотя эффект очень хороший, его трудно применить к проекту, а стоимость миграции высока.


Сказав все это, позвольте мне резюмировать:

Встряхивание дерева трех основных инструментов ограничено и обусловлено удалением бесполезного кода и бесполезных модулей. Компилятор закрытия является лучшим, но он плохо совместим с нашим повседневным процессом разработки на основе узлов.

Tree-shaking имеет большое значение для Интернета, это идеальный мир экстремальной оптимизации и еще один высший идеал эволюции интерфейса.

Идеал прекрасен, но он все еще находится в стадии разработки, и он еще относительно сложен.

Тем не менее, мы по-прежнему должны верить, что новые технологии могут сделать внешний мир лучше.

Оптимизация - это отношение, а не из-за малого, а не из-за трудностей.


Мои знания ограничены, пожалуйста, поправьте меня, если я ошибаюсь, спасибо


В следующей статье мы продолжим знакомствоПрактика оптимизации производительности Tree-Shaking — Практика

图标



Пример кода в этой статье можно найти на нашем гитхабе, добро пожаловать на тык ❤

图标

lin-xi/treeshakinggithub.com