tree shaking что это

2 минуты с Webpack tree-shaking и re-export

Вступление

Позвольте мне начать. У нас был монолитный фронтэнд с большим наследием. Сервисы жили в одних файлах с компонентами. Всё было вперемешку и с лозунгом на фасаде: “Пусть всё будет под рукой – так легче найти, что надо». И не важно, что длина файла 200+, 300+, 500+ или даже больше строк кода.

Cделать всё читабельнее, меньше и быстрее.

Реализация

Разделить всё, что возможно на файлы и золотая пуля здесь это принцип единственной ответственности. Если у нас есть компонент и чистые функции внутри файла, мы их разделим.

С приходом ES6+, стало возможно использовать import … from синтакс – это отличная фича, ведь мы можем также использовать export … from.

Рефакторинг

Представьте себе файл с такой структурой:

Мы можем разделить этот код на файлы таким образом:

Теперь мы можем импортировать функции по отдельности. Но с дополнительными строками и этими длинных именами в импортах это всё ещё выглядит ужасно.

А вот для этого у нас есть прекрасная фишка, которая появилась с приходом нового синтаксиса JS, который зовется реэкспортом (re-export). В папке мы должны сделать index.js файл, чтобы объединить все наши функции. И теперь мы можем переписать наш код таким образом:

Чуть подшаманим App.js:

Тестирование.

Теперь давайте проверим, как наш Webpack скомпилирует build для продакшна. Давайте создадим небольшое приложение на React, чтобы проверить, как всё работает. Проверим загружаем ли мы только то, что нам нужно, или все что указано в index.js из папки utils.

Продакшн версия приложения:

Как видно выше, мы загрузили только функцию sum из utils.
Давайте проверим еще раз, и на этот раз мы будем использовать multiply.

Продакшн версия приложения:

Здесь мы даже не видим функции внутри кода, потому что Webpack скомпилировал наше значение ещё перед деплоем.

Финальный тест

Итак, давайте проведем наш последний тест и используем все функции сразу, чтобы проверить, все ли работает.

Продакшн версия приложения:

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

Заключение

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

Спасибо за прочтение! Чистого кода и притяного рефакторинга!

Источник

Что такое Tree shaking?

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

Tree shaking (Встряхивание дерева) — это метод оптимизации библиотек путем удаления любого кода из окончательного файла, который фактически не используется.

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

Если воспользуемся tree shaking, тогда только то, что мы импортировали и фактически использовали попадет в окончательную сборку.

Как работает tree shaking?

Хотя концепция tree shaking существует примерно с 1990-х годов, она стала доступно в Javascript только с момента появления модулей в стиле ES6. Это потому, что tree shaking может работать только в том случае, если модули являются статическими («static»).

Динамическая природа модулей CommonJS означает, невозможность применение tree shaking, потому что не получится определить, какие модули будут необходимы, до того как код фактически будет запущен.

Это не будет работать

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

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

От чего избавляет tree shaking?

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

В приведенном выше примере функция multiply() никогда не используется и будет удалена из окончательного пакета.

Так же удаляются свойства импортированных объектов, к которым никогда не осуществляется доступ.

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

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

Как насчет «побочных эффектов»?

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

Tree shaking не может автоматически определить, подобные сценарии, поэтому важно указать их вручную, как мы увидим ниже.

Как использовать tree shake

Для получения дополнительной информации о том, как использовать tree shaking в webpack, ознакомьтесь с их документация.

Источник

Оптимизация фронтенда. Часть 1. Почему я не люблю слово treeshaking или где вас обманывает webpack

tree shaking что это. Смотреть фото tree shaking что это. Смотреть картинку tree shaking что это. Картинка про tree shaking что это. Фото tree shaking что это
Мы относимся к технологиям, которые используем, как к покупкам на Яндекс маркете. Смотрим на спецификацию, читаем отзывы и, если проект получил много звездочек на гитхабе, проходит по спецификации, и к тому же внедрение стоит недорого, мы его покупаем устанавливаем. Такой подход иногда очень сильно бьет по голове ручкой от граблей, и тогда все-таки приходится разбираться, что происходит.

Предыстория

В статье одного из авторов rollup рассмотрены две оптимизации, одна называется dead code elimination, а вторая tree-shaking. Автор показывает, что у tree-shaking намного больше возможностей по сжатию кода. И в доказательство приводит несколько соображений о рецептах пирога и разбившихся яйцах. Ох уж эти метафоры!

Эту идею (про tree-shaking, не про пирог и яйца) подхватила команда разработчиков webpack и с версии 2.0 стала официально поддерживать.

Проблема

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

Некоторые, конечно, догадываются о подвохе и даже пишут статьи на Хабр. Но любителей порассуждать о преимуществах tree-shaking над dead code illumination в webpack вокруг меньше не становится, по крайней мере, среди посетителей конференций и среди моих коллег.

Как это должно было работать?

Когда это не работает?

Допустим, у нас все как в документации. Два файла index.js и module.js

Если мы сейчас запустим webpack в режиме оптимизации и минимизации, то все заработает как и ожидалось. (код)

Но если только в файл с модулями добавится любой, даже самый маленький класс с export при наличии babel-loader, то пиши пропало. Класс попадет в итоговую сборку в виде функции, которую выплюнул babel. (код)

Как же так получилось?

Все дело в том, что UglifyJS боится выкинуть что-то лишнее. Оно и понятно: пусть лучше на пару сотен байт больше, только бы не сломалось.

И вот, представьте себе, что UglifyJS получает на вход следующий код:

MyClass после компиляции babel как-то выбрался за осознания себя как класса. И вообще UglifyJS мало что знает про то, как команда babel видит реализацию классов на ES5. Вот и пасует, оставляя это неведомое никем не используемое безобразие в вашей итоговой сборке.
На это даже есть баг в репозитории webpack, и ребята обещают починить все в 4й версии.
Rollup, кстати, не так давно тоже работал только на примерах с математикой, но в последних версиях ребята починили баг. (сломанный пример, работающий пример).

Мораль

Вот так, купив webpack в том числе и за tree-shaking, я получил околонулевую выгоду в этом направлении. И слово tree-shaking теперь вызывает у меня нервный смех, икоту и неконтролируемый сарказм.

И как теперь быть?

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

Извините, не удержался.

Если серьезно, есть очень простой способ починить ситуацию:

А выглядит это как-то так:

Еще пара итераций и мы изобретем статическую типизацию 😉

Кстати, новая версия babel 7 уже делает это. К сожалению, она пока еще в бете, что как бы намекает на невозможность использования прямо сейчас. Но если вы смелы и решительны, можно попробовать обновиться.

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

Источник

Почему не работает Tree Shaking и как с этим жить

tree shaking что это. Смотреть фото tree shaking что это. Смотреть картинку tree shaking что это. Картинка про tree shaking что это. Фото tree shaking что это

Кто трясет деревья?

Огромные JavaScript бандлы — это не очень хорошо. В мире браузеров они увеличивают время загрузки страницы: сначала такой бандл надо скачать, потом распарсить, потом выполнить. В мире backend и скриптов-в-облаке тоже свои нюансы. Чтобы выполнять тысячи звонков в секунду, контролируемых через JavaScript, наш SpiderMonkey ограничивает память JavaScript сессии 16-ю мегабайтами. Это на все: исходный код, ast, структуры данных. Архитектура нашей платформы подразумевает, что в облаке выполняется код, который должен работать в реальном времени. А все «тяжелые» вещи можно перенести на свой backend и делать к нему HTTP запросы прямо во время звонка. Беда в том, что пара методов из lodash выглядят как замечательная идея для легковесного кода в облаке. Хоп — и плюс полмегабайта к результирующему JavaScript.

Сообщество JavaScript разработчиков знает об этой проблеме давно, и кроме «давайте выкинем все пробелы и переименуем что не страшно в однобуквенные варианты» (uglify до dead code elimination) активно разрабатывает «Tree Shaking». В идеале, «Tree Shaking» должно убирать весь неиспользуемый код: импорты, вызовы методов, глобальные переменные. И для нашего кода мы должны получить несколько функций lodash, наш код – и всё. А вместо этого получаем lodash целиком. WTF?

Webpack, Rollup и Uglify

Поддержка tree shaking считается сильной стороной rollup, заявлена в последних версиях webpack и уже давно присутствует в UglifyJS в виде «dead code elimination». О разнице между «dead code elimination» два года назад очень хорошо написал автор Rollup: если dead code elimination получает на вход скомпилированный бандл и пытается выкинуть из него неиспользуемый код, то tree shaking работает с AST кода во время компиляции и пытается включить только тот код, который используется. Кстати, Webpack рассчитан на комбинированный подход: вначале tree shaking во время сборки бандла, а затем dead code elimination с помощью UglifyJS плагина.

Только в реальном мире Tree Shaking не работает.

По словам самих авторов, определить используемый код в слабо типизированным языке – задача нетривиальная. И, чтобы ничего не сломать, в непонятных ситуациях код всегда включается. К несчастью, примерами таких «непонятных» ситуаций являются самые популярные библиотеки общего назначения: lodash, underscore, – все вот эти ребята.

Что делать?

Можно, конечно, подождать еще пару лет. Вывод типов становится лучше, ведутся работы над поддержкой tree shaking для типизированных диалектов вроде TypeScript. Но писать ES2017 код с библиотеками хочется сейчас. Без многомегабайтных бандлов.

Сообщество и об этой проблеме знает, поэтому сейчас активно используется временное решение: большие монстры вроде lodash разбиваются на огромную кучу мелких модулей, который можно импортить по отдельности. И тут уже tree shaking сбоев не дает:

Конечно, это демонстрация «в лоб» с пустыми конфигами webpack/rollup. Можно докрутить и до более впечатляющих цифр, но основная идея в том, что не стоит огорчаться тысячам зависимостей, которые ставит yarn. Минимально допиленный напильником стек позволяет выкинуть большую часть неиспользуемого кода и получить вполне читаемый бандл для загрузки в Voximplant или любую другую платформу, которые программируется на JavaScript.

Источник

Angular — настройка среды разработки и production сборки с AOT-компиляцией и tree-shaking (Gulp, Rollup, SystemJS)

Одна из особенностей Angular, присущая и первой и новой версии — высокий порог вхождения. Новый Angular, помимо всего прочего, трудно даже запустить. А и запустив, легко получить 1-2 Мб скриптов и порядка нескольких сотен запросов при загрузке hello world страницы. Можно, конечно, использовать всякие стартеры, seed’ы или Angular CLI, но для использования в серъезном проекте нужно самому во всем разбираться.

В этой статье я постараюсь описать, как настроить удобную среду разработки с использованием SystemJS, и production сборку Angular приложения на основе Rollup, с выходом около 100кб скриптов и нескольких запросов при открытии страницы. Использовать будем TypeScript и SCSS.

Попробовать все в деле можно в моем angular-gulp-starter проекте.

tree shaking что это. Смотреть фото tree shaking что это. Смотреть картинку tree shaking что это. Картинка про tree shaking что это. Фото tree shaking что это

Среда разработки

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

Технически, нам нужно решить три задачи:

Первые две задачи удобнее всего решать при помощи функции compile-on-save, которая работает почти в любой IDE. При таком подходе достаточно сохранить свои правки в коде, переключиться на окно браузера и нажать F5 — очень быстро и удобно. Кроме того, результаты компиляции легко проконтролировать, js-файлы лежат рядом c ts, и в случае чего всегда можно их поисследовать.

Из IDE для работы с TypeScript могу порекомендовать Visual Studio (например, Visual Studio 2015 Community Edition), которая имеет встроенную поддержку TypeScript + расширение Web Compiler для SCSS. Я пробовал Atom, Visual Studio Code, но на моем ноутбуке они слишком тормозят. Visual Studio (не Code) хорошо справляется с подсветкой, автодополнением и компиляцией на лету даже на слабой машине. Хотя там есть некоторые проблемы подсветки при использовании es6 import.

Третья задача (загрузить все в браузер) — наиболее проблемная, т.к. скрипты зависят друг от друга, и должны загружаться в правильном порядке. Контролировать все это вручную трудно и не нужно. Лучше всего оставить разбираться с зависимостями библиотеке SystemJS: в коде используем ES6 import/export синтаксис, и основываясь на этом, SystemJS подгружает динамически все необходимые файлы. Не нужно строить никаких бандлов, выполнять какую-то специальную сборку, достаточно просто настроить config.

Конфигурация SystemJS — это js-файл, который может выглядеть примерно так:

Здесь мы делаем следующее:

После этого, нам достаточно включить в index.html ряд служебных скриптов: zone.js, reflect-metadata, core-js (или es6-shim), саму systemjs, ее конфиг и вызвать импорт главного модуля:

Однако, это еще не совсем все. Дело в том, что библиотека rxjs, активно используемая в Angular, состоит из множества маленьких модулей. Поэтому, если оставить все так, то при обновлении страницы все они будут грузится по одному, что несколько медленно (до 100-300 запросов).

tree shaking что это. Смотреть фото tree shaking что это. Смотреть картинку tree shaking что это. Картинка про tree shaking что это. Фото tree shaking что это

Поэтому, в своем стартер-проекте я собираю всю rxjs в один бандл, с помощью Rollup. Перед этим, она дополнительно компилируется в ES6, что используется после, в продакшн сборке.

Сборка этого rxjs бандла получается довольно сложной. Сначала компилируются TypeScript исходники в ES6 (из папки node_modules/rxjs/src ), после чего все это пакуется при помощи Rollup в один файл, и транспилируется в ES5. При этом, чтобы подружить этот бандл с SystemJS, создается временный файл, который служит входной точкой для Rollup, и выглядит примерно так:

Все это можно найти в файлах build.common.js/rxjsToEs и build.dev.js/rxjsBundle. Скомпилированные в ES исходники также используется при продакшн сборке, поэтому компиляция вынесена отдельно.

После того как бандл собран, его нужно загрузить перед тем, как будет загружен код нашего приложения. Делается это так:

В результате получаем примерно на секунду быстрее загрузку страницы:

tree shaking что это. Смотреть фото tree shaking что это. Смотреть картинку tree shaking что это. Картинка про tree shaking что это. Фото tree shaking что это

Для удобства разработки, вам также пригодится простой веб-сервер, с поддержкой HTML5 роутинга (когда на все запросы возвращается index.html). Пример такого сервера на основе express можно также найти в стартере.

Знающий читатель еще может спросить, почему не Webpack? Если коротко — webpack хорош для продакшн, но, имхо, неудобен во время разработки. Подробнее в спойлере ниже.

Webpack

Angular CLI и многие starter и seed проекты используют Webpack. Он теперь умеет делать tree-shaking, и говорят, даже hot module reloading (кто-нибудь пробовал именно в контексте Angular?). Но я не разделяю ажиотажа вокруг этого сборщика, и не понимаю, откуда он берется. Webpack это бандлер, и он может только построить бандл. Это порождает множество проблем:

Впрочем, я могу ошибаться, так как с Webpack особо не работал.

JSPM

JSPM — это первое, что приходит в голову, когда речь заходит о SystemJS. Действительно, с его помощью довольно легко настроить удобную среду разработки для Angular. При этом можно использовать как compile-on-save, так и TypeScript загрузчик. Говорят, там даже работает tree-shaking на основе Rollup. Казалось бы, все идеально.

Но это только на первый взгляд. Порой мне кажется, что JSPM живет в каком-то своем параллельном мире, далеком от всего происходящего вокруг. Зачем-то им понадобилось хранить все пакеты, в том числе npm-пакеты, в своей отдельной папке особым образом. В результате, вместо удобного «из коробки» инструмента, вы получаете кучу головной боли, о том, как заставить все остальные утилиты (которые, как правило, умеют работать с node_modules) подружить с JSPM.

Как минимум, придется устанавливать отдельно typings для зависимостей, чтобы подружить JSPM с TypeScipt (или еще хуже, прописывать пути). Заставить работать AOT-компилятор — тоже отдельная тема. Если нужно сделать что-то нестандартное (как с rxjs), тоже проблемы. Вообщем, у меня просто не получилось все увязать и сделать production сборку на JSPM. Если у кого-то получится, мне было бы очень интересно посмотреть.

Browserify

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

Production сборка

Релизная сборка Angular включает в себя следующие этапы:

AOT-компиляция

AOT-компиляция осуществляется при помощи пакета @angular/compiler-cli (называемый также ngc), который построен на базе компилятора TypeScript. Для выполнения компиляции нужно:

NGC построен на основе TypeScript, но построен, стоит сказать, плохо. Не все возможности TypeScript в нем работают, как надо. Например, наследование конфигураций не работает (поэтому в стартере 3 отдельных tsconfig-файла). В этой статье можно посмотреть, что еще не поддерживает AOT-компилятор. Список далеко не полный (вот, например), поэтому будьте готовы, что с этим будут проблемы. Компилятор может «упасть» где-то в своих недрах или уйти в бесконечный цикл, и выяснить причину не всегда просто. Проверять, что все компилируется нужно часто, чтобы потом не разбираться со всем разом.

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

Это актуально еще и потому, что компилятор обрабатывает также компоненты самого Angular. Поэтому если не указать genDir, то часть результатов появится в папке node_modules. Это как минимум странно.

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

Релизная компиляция TypeScript

Отличие релизной компиляции от обычной заключается, во-первых, в том, что необходимо создать отдельный main.ts файл. Во время разработки его следует исключить из компиляции, а в релизной сборке, наоборот, заменить им dev-версию. Отличие этого файла в том, что используется специальная bootstrap функция, которая задействует результаты AOT-компиляции. В частности, мы запускаем AppModuleNgFactory (результат компиляции AppModule) из genDir AOT-компиляции:

Также здесь мы включаем продакшн режим для Angular (это важно сделать, так как сильно влияет на производительность):

Второе отличие релизной компиляции — использование целевой платформы ES6. Если этого не сделать, Rollup не выдаст ошибки, но и tree-shaking не выполнит. По этой же причине, нам необходима ES6 версия rxjs. Раньше у rxjs был специальный пакет rxjs-es, и все примеры сборки Angular на gulp, которые показывает гугл на первых страницах, используют именно его. К сожалению, данный пакет перестали поддерживать. Поэтому нам необходимо самим компилировать rxjs из TypeScript исходников, как было описано выше.

Tree-shaking при помощи библиотеки Rollup

Сборка при помощи Rollup — это ключевой этап сборки, способный превратить 1 Мб исходников в 100 Кб. Rollup анализирует исходники, и выбрасывает из них те участки кода, которые не используются.

В случае, если нужно сделать что-то специфичное, то легко написать свой плагин. Например, таким образом я указываю Rollup, что rxjs нужно брать из той самой папки, где лежит наша скомпилированная ES6 версия (RollupNG2 в том же rollup-config).

Транспиляция результата в ES5 при помощи TypeScript довольно проста, главное выставить параметр allowJs. Операция занимает пару строчек в файле bundling.js в функции rollupBundle.

Подготовка релизного index.html

После всей проделанной выше работы, нам остается только собрать все вспомогательные библиотеки в один бандл, и добавить результат работы rollup на страницу через script-тэг. Все это стандартные для gulp задачи.

В стартере все это сделано из расчета на максимальную простоту, чтобы не заставлять пользователей лишний раз разбираться. Найти соответствующий код можно в файле build-prod.js. Для тестирования, там также настроен express-сервер, со включенным gzip-сжатием.

В итоге получаем 118 Кб после gzip:

tree shaking что это. Смотреть фото tree shaking что это. Смотреть картинку tree shaking что это. Картинка про tree shaking что это. Фото tree shaking что это

В примере используется Tour of Heroes из официальных руководств Angular, который не совсем «Hello world». Если совсем упростить, то может получиться вплоть до 50-80 Кб.

По ссылкам ниже можно попробовать обе версии сборки вживую:

В заключении, хочу порекомендовать отменную статью по теме Minko Gechev. В ней он приводит пример простейшей сборки из 6 npm-скриптов, которая выполняет все основные шаги (учтите, что там используется rxjs-es, который больше не поддерживается). Правда seed-проект за его авторством мне не понравился, из-за высокой сложности и не очень высокого удобства.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *