Вот и вышел angular 8, он включает в себя превью Ivy, поддержку service workers, дифференциальную загрузку и несколько других завершающих штрихов. Manfred Steyer объясняет наиболее важные изменения в новейшем релизе.
Как и планировалось, сюрпризов не было: обновление фреймворка и CLI можно сделать с помощью ng update, а новые функции являются приятным дополнением в соответствии с девизом «эволюция вместо революции».
В этой статье автор рассказывает о наиболее важных новых функциях Angular 8 и Angular CLI 8. Примеры, используемые в статье, можно найти на GitHub.
Следующая большая новость, которую ждет сообщество Angular — это Ivy, новый компилятор, а также новый движок рендеринга. Ivy может генерировать значительно меньшие бандлы, инкрементную компиляцию, а также является основой для будущих инноваций в Angular.
Поскольку многие базовые части Angular были изменены, команда Angular обратила особое внимание на совместимость с предыдущими версиями: после перехода на Ivy существующие приложения должны работать так же, как и раньше. В лучшем случае вы получите бандлы значительно меньшего размера. Это не бескорыстно, так как более 600 приложений в Google официально основаны на Angular – реальное число, по слухам, намного выше.
С Angular 8, предварительная версия Ivy доступна для тестирования. Цель этой версии — получить быструю обратную связь. Поэтому команда angular рекомендует не использовать Ivy в проде прямо сейчас, но продолжить использовать классический view engine (Рис. 1)
Спасибо дифференциальной загрузке (как видно ниже), размеры бандла могут быть оптимизированы прямо сейчас.
Как отмечает Брэд Грин, технический директор Angular team в Google, на ngconf 2019, Ivy позволит заметно улучшить размеры пакетов в режиме совместимости в сочетании с дифференциальной загрузкой. Таким образом, сорвиголовы уже могут опробовать будущий API Ivy. Этот режим, в частности, имеет большой потенциал для оптимизации. API по-прежнему помечен как приватный. Глядя на его классы и функции, вы можете сказать: они начинаются со специального символа ?.
Если вы уже хотите попробовать Ivy, вы можете создать новый проект с ключем enable-ivy:
Данный ключ говрит CLI сохранить следующую запись в конфигурации tsconfig.app.json:
Эта запись также может быть добавлена вручную после обновления до 8 версии, для тестирования существующего приложения с использованием Ivy.
Для запуска приложения в режиме отладки рекомендуется использовать AOT:
Кроме того, стоит обратить внимание на размер приложения, созданного с помощью ng build. С angular 9, Ivy должна быть активирована по умолчанию. До тех пор команда Angular планирует продолжать работу по обеспечению совместимости со старыми версиями.
JavaScript является однопоточным по определению. Из-за этого трудоемкие задачи, такие как запрос данных, обычно выполняются асинхронно. Излишне говорить, что это не помогает в сложных расчетах. Они особенно становятся все более распространенными с обширными решениями JavaScript, поэтому мы поддерживаем web worker'ы почти всех веб-браузеров. Это скрипты, которые запускаются браузером в отдельном потоке. Связь с потоком на вкладке браузера осуществляется посредством сообщений.
Хотя web worker'ы не имеют отношения к Angular как таковому, они должны быть приняты во внимание при сборке. Целью является предоставление одного пакета для каждого web worker. Эта задача выполнена новым CLI.
Для демонстрации новой фичи, я покажу JavaScript реализацию так называемой ”проблемы N королев (n queens problem)". Идея состоит в том, чтобы разместить по одной королеве в ряду на шахматной доске, не имея возможности угрожать друг другу. Это значит, что в той же строке, столбце или диагонали не должно быть другой королевы.
Алгоритм вычисления всех возможных решений на шахматной доске считается вычислительно сложным. Хотя вычисления для обычной шахматной доски с восемью рядами и восемью столбцами довольно быстры, обычные компьютеры достигнут предела, при рамере доски 12 х 12. Решение для доски размером 27 х 27 является текущим рекордом. Для этой задачи использовались российские суперкомпьютеры.
Чтобы перевести такой расчет в фон, мы должны сначала создать web worker'а с помощью CLI:
Эта инструкция создает файл не только для работника, но и для файлов конфигурации, необходимых для процесса сборки и записей в существующих файлах. Если та же папка содержит компонент с тем же именем с общим расширением файла .component.ts, CLI также добавит код, для взаимодействия с web worker.
Сам worker состоит из прослушивателя для события:
Событие выполняется, когда основной поток отправляет сообщение worker'у. Параметр содержит информацию, отправленную из основного потока. В рассматриваемом случае, он ограничен свойством count, которое задает размер шахматной доски. После вычисления функции nQueens, которая здесь опущена, eventListener отправляет результат обратно в основной поток через postMessage. Таким образом, браузер запускает message event.
Класс Worker применяется в использующем компоненте для взаимодействия с worker скриптом:
Компонент отправляет сообщение с желаемым размером шахматной доски worker'у через postMessage и тем самым запускает вычисление там. Результат он получает через событие message.
В дальнейшем CLI заботится о правильной сборки worker скриптов. Компилятор TypeScript распознает их по окончанию .worker.ts, который зарегистрирован в tsconfig.worker.json созданном командой ng generate worker. Чтобы убедиться, что CLI не станет затрагивать эти файлы снова при сборке основного приложения, ng generate worker помещает тот же шаблон файла в секции exclude tsconfig.app.json.
Полная реализация есть в проекте с примерами автора. Для сравнения пример задачи N queens может быть решен как в основном потоке, так и в web worker. Когда вы попытаетесь решить задачу для шахматной доски 12 x 12, например, вы увидите, что UI зависает в первом случае, в то время как фоновый расчет с помощью web worker, не уменьшит производительность.
До сих пор было принято компилировать приложения в старый добрый ES 5, так как эта версия “JavaScript наших отцов” работает почти везде. Это означает, что и IE11 и веб-сканер поисковой системы Google могут выполнять этот код.
Однако новый ES 2015 и его последующие версии более эффективны: они позволяют создавать более компактные пакеты и браузер может также интерпретировать их более эффективно. Поскольку ранее было принято откатываться к ES 5 как наименьшему общему знаменателю, современные браузеры, к сожалению, не могли использовать преимущества новой версии языка.
Теперь с этим покончено: начиная с 8 версии, CLI имеет функцию, которая называется дифференциальной загрузкой. Идея заключается в том, чтобы предоставить две группы пакетов: один основан на ECMAScript 5 и предназначен для старых браузеров, другой основан на новой версии ECMAScript, например ECMAScript 2015, и предоставляет современным браузерам ранее упомянутые преимущества.
Вам не потребуется много работы, чтобы включить дифференциальную загрузку: все, что нужно, это установить верхнюю и нижнюю границу поддерживаемых версий ECMAScript. Верхняя граница указывается в tsconfig.json следующим образом:
Нижняя граница определяется в файле browserslist. Этот файл включает браузеры, которые будут поддерживаться, в соответствии с определенными критериями, такими как доля рынка, например. Они могут быть сохранены, например, в файле browserslist, который CLI создает в корне проекта при создании нового проекта:
В данном случае список браузеров включает браузеры ES 5 с записью IE 9-11. Таким образом, CLI определяет нижний порог как эту версию. Когда CLI получит команду ng build, процесс сборки будет выполняться для обеих версий:
Недостаток этого процесса заключается в следующем: время, необходимое для сборки удваивается.
Теперь браузеры могут решить, какую версию пакетов загружать. Для этого они получают ссылки на скрипты в дополнении index.html: те, кто указывает на пакеты ECMAScript 5, получают добавление nomodule. Таким образом браузеры с поддержкой модулей ECMAScript и, следовательно, поддержкой ECMAScript 2015+ не проигнорируют данный скрипт. С другой стороны, пакеты ECMAScript 2015+ реализуются CLI с type=”module”. Таким образом, старые браузеры будут игнорировать эти скрипты:
В отличие ng build, остальные команды CLI используют только (!) верхнюю границу поддержки ES. В нашем случае это ECMAScript 2015. Это происходит, в том числе, из соображений эффективности: во время отладки и тестирования разработчики обычно хотят увидеть результат как можно скорее, не дожидаясь второй сборки.
С первых дней Angular router поддерживает ленивую загрузку. До сих пор это достигалось магическим определением загружаемого модуля:
Значение перед # описывает путь, ведущий к файлу с реализацией модуля; значение после означает содержащийся в нем класс. Этот стиль описания работает в Angular 8, но был признан устаревшим относительно динамического импорта ECMAScript:
Новый вариант записи по-прежнему содержит имя файла как магическое значение. Однако, поскольку импорт поддерживается многими IDE, недопустимые значения немедленно вернут ошибку.
Есть критические изменения в использовании ViewChild и ContentChild, которые, к сожалению, в прошлом не всегда работали предсказуемо. Если в более ранних версиях они использовались компонентом для запроса элементов не находящихся внутри структурной директивы, такой как ngIf или ngFor, результат запроса уже был доступен в ngOnInit. Иначе мы могли получить доступ к ним не ранее ngAfterViewInit (или ngAfterContentInit для ContentChild). Для элементов, которые были загружены в DOM позже из-за привязки данных, программный код должен был иметь ngAfterViewChecked или, соответственно, ngAfterContentChecked.
Поскольку это поведение было запутанным, компонент теперь должен указывать, когда должно произойти разрешение:
Если флаг static имеет значение true, Angular попытается найти элементы при инициализации компонента. Это работает, только если они не находятся в структурной директиве. При использовании static: false разрешение выполняется после инициализации или обновления представления.
ng update попытается автоматически ввести правильное значение, если это окажется невозможно, он добавит комментарий с TODO.
Это изменение не повлияет на запросы с декораторами ViewChildren и ContentChildren. Они всегда имели динамическое поведение, в новых терминах — в смысле static: false.
До сих пор одна из проблем смешанной работы AngularJS 1.X и Angular с ngUpgrade заключалась в том, что маршрутизаторы обоих фреймворков конкурировали из-за URL. Это приводило к трудно объяснимым побочным эффектам. Чтобы избежать этого, была добавлена возможность использования единой службы локации доступа к URL-адресу в обеих версиях.
Для этого команда Angular расширила возможности служб локации Angular и тем самым обеспечила замену $location в AngularJS.
По этой причине в службу локации был добавлен новый метод onUrlChange для отслеживания изменений URL-адресов:
Сервис PlatformLocation предлагает дополнительный доступ к отдельным частям URL-адреса. Подробное описание того, как замена $location, основанная на ней, используется для лучшей интеграции фреймворков, можно найти здесь. Кроме того, теперь вы можете найти решение для ленивой загрузки AngularJS, которая основана на вышеупомянутом динамическом импорте ECMAScript.
Снова команда Angular сдержала свое слово: переход на новую версию Angular прост и не включает больших изменений. Наоборот некоторые углы были сглажены, сделав работу с SPA-фреймворком от Google еще более комфортной. Дифференциальная загрузка дает возможности для дальнейшей оптимизации размеров пакетов, если старые браузеры либо не поддерживаются, либо поддерживаются отдельными пакетами. Поддержка Web worker показывает, что вычислительно емкие задачи находят путь на обработку в браузере. Энтузиасты теперь могут сделать свои первые шаги с Ivy.
PS: Это мой первый перевод, поэтому замечания, пожелания и ошибки прошу отметить в комментариях.
Как и планировалось, сюрпризов не было: обновление фреймворка и CLI можно сделать с помощью ng update, а новые функции являются приятным дополнением в соответствии с девизом «эволюция вместо революции».
В этой статье автор рассказывает о наиболее важных новых функциях Angular 8 и Angular CLI 8. Примеры, используемые в статье, можно найти на GitHub.
Под катом:
- Первый взгляд на Ivy
- Web workers
- Дифференциальная загрузка
- Ленивая загрузка модулей
- Критические изменения в ViewChild и ContentChild
- Новые фичи ngUpgrade
Первый взгляд на Ivy
Следующая большая новость, которую ждет сообщество Angular — это Ivy, новый компилятор, а также новый движок рендеринга. Ivy может генерировать значительно меньшие бандлы, инкрементную компиляцию, а также является основой для будущих инноваций в Angular.
Поскольку многие базовые части Angular были изменены, команда Angular обратила особое внимание на совместимость с предыдущими версиями: после перехода на Ivy существующие приложения должны работать так же, как и раньше. В лучшем случае вы получите бандлы значительно меньшего размера. Это не бескорыстно, так как более 600 приложений в Google официально основаны на Angular – реальное число, по слухам, намного выше.
С Angular 8, предварительная версия Ivy доступна для тестирования. Цель этой версии — получить быструю обратную связь. Поэтому команда angular рекомендует не использовать Ivy в проде прямо сейчас, но продолжить использовать классический view engine (Рис. 1)
Спасибо дифференциальной загрузке (как видно ниже), размеры бандла могут быть оптимизированы прямо сейчас.
Как отмечает Брэд Грин, технический директор Angular team в Google, на ngconf 2019, Ivy позволит заметно улучшить размеры пакетов в режиме совместимости в сочетании с дифференциальной загрузкой. Таким образом, сорвиголовы уже могут опробовать будущий API Ivy. Этот режим, в частности, имеет большой потенциал для оптимизации. API по-прежнему помечен как приватный. Глядя на его классы и функции, вы можете сказать: они начинаются со специального символа ?.
Если вы уже хотите попробовать Ivy, вы можете создать новый проект с ключем enable-ivy:
ng new ivy-project --enable-ivy
Данный ключ говрит CLI сохранить следующую запись в конфигурации tsconfig.app.json:
"angularCompilerOptions": {
"enableIvy": true
}
Эта запись также может быть добавлена вручную после обновления до 8 версии, для тестирования существующего приложения с использованием Ivy.
Для запуска приложения в режиме отладки рекомендуется использовать AOT:
ng serve --aot
Кроме того, стоит обратить внимание на размер приложения, созданного с помощью ng build. С angular 9, Ivy должна быть активирована по умолчанию. До тех пор команда Angular планирует продолжать работу по обеспечению совместимости со старыми версиями.
Web workers
JavaScript является однопоточным по определению. Из-за этого трудоемкие задачи, такие как запрос данных, обычно выполняются асинхронно. Излишне говорить, что это не помогает в сложных расчетах. Они особенно становятся все более распространенными с обширными решениями JavaScript, поэтому мы поддерживаем web worker'ы почти всех веб-браузеров. Это скрипты, которые запускаются браузером в отдельном потоке. Связь с потоком на вкладке браузера осуществляется посредством сообщений.
Хотя web worker'ы не имеют отношения к Angular как таковому, они должны быть приняты во внимание при сборке. Целью является предоставление одного пакета для каждого web worker. Эта задача выполнена новым CLI.
Для демонстрации новой фичи, я покажу JavaScript реализацию так называемой ”проблемы N королев (n queens problem)". Идея состоит в том, чтобы разместить по одной королеве в ряду на шахматной доске, не имея возможности угрожать друг другу. Это значит, что в той же строке, столбце или диагонали не должно быть другой королевы.
Алгоритм вычисления всех возможных решений на шахматной доске считается вычислительно сложным. Хотя вычисления для обычной шахматной доски с восемью рядами и восемью столбцами довольно быстры, обычные компьютеры достигнут предела, при рамере доски 12 х 12. Решение для доски размером 27 х 27 является текущим рекордом. Для этой задачи использовались российские суперкомпьютеры.
Чтобы перевести такой расчет в фон, мы должны сначала создать web worker'а с помощью CLI:
ng generate worker n-queens
Эта инструкция создает файл не только для работника, но и для файлов конфигурации, необходимых для процесса сборки и записей в существующих файлах. Если та же папка содержит компонент с тем же именем с общим расширением файла .component.ts, CLI также добавит код, для взаимодействия с web worker.
Сам worker состоит из прослушивателя для события:
import nQueens from './n-queens';
addEventListener('message', ({ data }) => {
const result = nQueens(data.count);
postMessage(result, undefined);
});
Событие выполняется, когда основной поток отправляет сообщение worker'у. Параметр содержит информацию, отправленную из основного потока. В рассматриваемом случае, он ограничен свойством count, которое задает размер шахматной доски. После вычисления функции nQueens, которая здесь опущена, eventListener отправляет результат обратно в основной поток через postMessage. Таким образом, браузер запускает message event.
Класс Worker применяется в использующем компоненте для взаимодействия с worker скриптом:
const count = parseInt(this.count, 10);
const worker = new Worker('../logic/n-queens.worker', {
type: 'module' // Worker uses EcmaScript modules
});
worker.postMessage({count});
worker.addEventListener('message', (event) => {
// tslint:disable-next-line: no-console
console.debug('worker result', event.data);
// Update chessboard
this.processResult(event.data);
});
Компонент отправляет сообщение с желаемым размером шахматной доски worker'у через postMessage и тем самым запускает вычисление там. Результат он получает через событие message.
В дальнейшем CLI заботится о правильной сборки worker скриптов. Компилятор TypeScript распознает их по окончанию .worker.ts, который зарегистрирован в tsconfig.worker.json созданном командой ng generate worker. Чтобы убедиться, что CLI не станет затрагивать эти файлы снова при сборке основного приложения, ng generate worker помещает тот же шаблон файла в секции exclude tsconfig.app.json.
Полная реализация есть в проекте с примерами автора. Для сравнения пример задачи N queens может быть решен как в основном потоке, так и в web worker. Когда вы попытаетесь решить задачу для шахматной доски 12 x 12, например, вы увидите, что UI зависает в первом случае, в то время как фоновый расчет с помощью web worker, не уменьшит производительность.
Дифференциальная загрузка
До сих пор было принято компилировать приложения в старый добрый ES 5, так как эта версия “JavaScript наших отцов” работает почти везде. Это означает, что и IE11 и веб-сканер поисковой системы Google могут выполнять этот код.
Однако новый ES 2015 и его последующие версии более эффективны: они позволяют создавать более компактные пакеты и браузер может также интерпретировать их более эффективно. Поскольку ранее было принято откатываться к ES 5 как наименьшему общему знаменателю, современные браузеры, к сожалению, не могли использовать преимущества новой версии языка.
Теперь с этим покончено: начиная с 8 версии, CLI имеет функцию, которая называется дифференциальной загрузкой. Идея заключается в том, чтобы предоставить две группы пакетов: один основан на ECMAScript 5 и предназначен для старых браузеров, другой основан на новой версии ECMAScript, например ECMAScript 2015, и предоставляет современным браузерам ранее упомянутые преимущества.
Вам не потребуется много работы, чтобы включить дифференциальную загрузку: все, что нужно, это установить верхнюю и нижнюю границу поддерживаемых версий ECMAScript. Верхняя граница указывается в tsconfig.json следующим образом:
"target": "es2015"
Нижняя граница определяется в файле browserslist. Этот файл включает браузеры, которые будут поддерживаться, в соответствии с определенными критериями, такими как доля рынка, например. Они могут быть сохранены, например, в файле browserslist, который CLI создает в корне проекта при создании нового проекта:
> 0.5%
last 2 versions
Firefox ESR
not dead
IE 9-11
В данном случае список браузеров включает браузеры ES 5 с записью IE 9-11. Таким образом, CLI определяет нижний порог как эту версию. Когда CLI получит команду ng build, процесс сборки будет выполняться для обеих версий:
Недостаток этого процесса заключается в следующем: время, необходимое для сборки удваивается.
Теперь браузеры могут решить, какую версию пакетов загружать. Для этого они получают ссылки на скрипты в дополнении index.html: те, кто указывает на пакеты ECMAScript 5, получают добавление nomodule. Таким образом браузеры с поддержкой модулей ECMAScript и, следовательно, поддержкой ECMAScript 2015+ не проигнорируют данный скрипт. С другой стороны, пакеты ECMAScript 2015+ реализуются CLI с type=”module”. Таким образом, старые браузеры будут игнорировать эти скрипты:
<script src="main-es2015.js" type="module"></script>
<script src="main-es5.js" nomodule></script>
В отличие ng build, остальные команды CLI используют только (!) верхнюю границу поддержки ES. В нашем случае это ECMAScript 2015. Это происходит, в том числе, из соображений эффективности: во время отладки и тестирования разработчики обычно хотят увидеть результат как можно скорее, не дожидаясь второй сборки.
Ленивая загрузка модулей
С первых дней Angular router поддерживает ленивую загрузку. До сих пор это достигалось магическим определением загружаемого модуля:
{
path: 'lazy',
loadChildren: () => './lazy/lazy.module#LayzModule'
}
Значение перед # описывает путь, ведущий к файлу с реализацией модуля; значение после означает содержащийся в нем класс. Этот стиль описания работает в Angular 8, но был признан устаревшим относительно динамического импорта ECMAScript:
{
path: 'lazy',
loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)
}
Новый вариант записи по-прежнему содержит имя файла как магическое значение. Однако, поскольку импорт поддерживается многими IDE, недопустимые значения немедленно вернут ошибку.
Критические изменения в ViewChild и ContentChild
Есть критические изменения в использовании ViewChild и ContentChild, которые, к сожалению, в прошлом не всегда работали предсказуемо. Если в более ранних версиях они использовались компонентом для запроса элементов не находящихся внутри структурной директивы, такой как ngIf или ngFor, результат запроса уже был доступен в ngOnInit. Иначе мы могли получить доступ к ним не ранее ngAfterViewInit (или ngAfterContentInit для ContentChild). Для элементов, которые были загружены в DOM позже из-за привязки данных, программный код должен был иметь ngAfterViewChecked или, соответственно, ngAfterContentChecked.
Поскольку это поведение было запутанным, компонент теперь должен указывать, когда должно произойти разрешение:
@ViewChild('info', { static: false })
paragraph: ElementRef;
Если флаг static имеет значение true, Angular попытается найти элементы при инициализации компонента. Это работает, только если они не находятся в структурной директиве. При использовании static: false разрешение выполняется после инициализации или обновления представления.
ng update попытается автоматически ввести правильное значение, если это окажется невозможно, он добавит комментарий с TODO.
Это изменение не повлияет на запросы с декораторами ViewChildren и ContentChildren. Они всегда имели динамическое поведение, в новых терминах — в смысле static: false.
Новые фичи ngUpgrade
До сих пор одна из проблем смешанной работы AngularJS 1.X и Angular с ngUpgrade заключалась в том, что маршрутизаторы обоих фреймворков конкурировали из-за URL. Это приводило к трудно объяснимым побочным эффектам. Чтобы избежать этого, была добавлена возможность использования единой службы локации доступа к URL-адресу в обеих версиях.
Для этого команда Angular расширила возможности служб локации Angular и тем самым обеспечила замену $location в AngularJS.
По этой причине в службу локации был добавлен новый метод onUrlChange для отслеживания изменений URL-адресов:
export class AppComponent {
constructor(loc: Location, pLoc: PlatformLocation) {
loc.onUrlChange((url) => console.debug('url change', url));
console.debug('hostname: ', pLoc.hostname);
}
}
Сервис PlatformLocation предлагает дополнительный доступ к отдельным частям URL-адреса. Подробное описание того, как замена $location, основанная на ней, используется для лучшей интеграции фреймворков, можно найти здесь. Кроме того, теперь вы можете найти решение для ленивой загрузки AngularJS, которая основана на вышеупомянутом динамическом импорте ECMAScript.
Заключение
Снова команда Angular сдержала свое слово: переход на новую версию Angular прост и не включает больших изменений. Наоборот некоторые углы были сглажены, сделав работу с SPA-фреймворком от Google еще более комфортной. Дифференциальная загрузка дает возможности для дальнейшей оптимизации размеров пакетов, если старые браузеры либо не поддерживаются, либо поддерживаются отдельными пакетами. Поддержка Web worker показывает, что вычислительно емкие задачи находят путь на обработку в браузере. Энтузиасты теперь могут сделать свои первые шаги с Ivy.
PS: Это мой первый перевод, поэтому замечания, пожелания и ошибки прошу отметить в комментариях.