3 дня назад
Алекс Рикабо и Марк Тексон
[Alex] Я присоединился к команде Angular в 2015 году, примерно во время первоначального релиза Angular 2.0. Одной из моих первых задач был перевод исходного кода с JavaScript на относительно новый (в то время) язык от Microsoft, известный как TypeScript. TypeScript впоследствии стал как отраслевым стандартом, так и одной из главных сильных сторон Angular, обеспечив структуру и безопасность, необходимые команде для масштабирования фреймворка. Мы создали инновационный компилятор с опережающей компиляцией (AOT) для Angular поверх API компилятора TypeScript. Хотя компиляция веб-приложений не была чем-то новым в Google, в то время это было редкостью для более широкой веб-экосистемы.
Перенесемся в сегодняшний день — TypeScript помог Angular масштабироваться до уровня одних из крупнейших веб-приложений в мире. Инструментарий веб-экосистемы также значительно эволюционировал за последние 10 лет. Недавно мы начали наблюдать стремление к созданию более высокопроизводительных компиляторов JavaScript, сборщиков и других инструментов с использованием нативного кода. TypeScript 7 — это невероятная демонстрация потенциала в этой области, и мы хотели бы поздравить команду TypeScript с их стабильным релизом и потрясающей производительностью, которую он обеспечивает! Если вы еще не видели их пост в блоге, обязательно ознакомьтесь с ним, особенно с показателями производительности.
Компилятор Angular имеет одну из самых сложных интеграций с TypeScript в веб-экосистеме, и мы знали, что эта глубокая интеграция не может работать с версией TypeScript, скомпилированной в Go. Помимо накладных расходов на преодоление языкового барьера, компилятор Angular реализует собственные преобразования кода через API ts.Transformer, который недоступен при кросс-языковом взаимодействии. Чтобы предоставить разработчикам Angular те же преимущества нативного инструментария и TypeScript 7, нам нужно мыслить нестандартно. В начале этого года мы начали составлять наш план: отделить компиляцию Angular от API компилятора TypeScript и создать новый компилятор, специфичный для Angular, который обрабатывает компоненты, директивы и т. д., и выводит преобразованный код TypeScript, готовый для обработки конвейером сборки или любым другим инструментом компиляции. Это хорошо проторенный путь, и многие другие фреймворки в веб-пространстве аналогичным образом решили отделить генерацию кода от проверки типов. Более того, многие из них используют одну и ту же базовую библиотеку для выполнения этих преобразований: Oxc.
Oxc, Oxidation Compiler, — это нативный набор инструментов для парсинга и компиляции JavaScript, разработанный Void Zero. Он написан на Rust и обладает стабильным и зрелым API для таких операций, как парсинг, обход AST, семантический анализ и преобразования кода. Oxc — это движок, на котором работает их популярный инструмент сборки Vite. Так что, как бы банально это ни звучало… мы переписываем компилятор Angular на Rust 🦀 (частично).
По крайней мере, в процессе разработки мы называем этот новый инструмент Angular Preprocessor (ngp), чтобы отличать его от существующего компилятора на базе TypeScript (ngc). Давайте посмотрим, как это будет выглядеть на верхнем уровне:
За кулисами
У ngp есть две основные задачи: компилировать декораторы Angular (@Component, @Pipe и т. д.) и любые связанные шаблоны для эффективного рендеринга во время выполнения, а также способствовать проверке типов выражений в шаблонах компонентов с помощью компилятора TypeScript. Для каждого входного исходного файла (например, dashboard.ts) в вашем проекте ngp генерирует два выходных файла, по одному для каждой задачи:
- Файл dashboard.ng.ts, который содержит ваш код, но с декораторами Angular, замененными на их скомпилированные версии. Этот файл может быть передан в TypeScript или напрямую в сборщик, такой как esbuild. Это код для ваших компонентов, который загружается и выполняется в браузере.
- Файл dashboard.ngtypecheck.ts, который содержит трансляцию выражений и типов в любых шаблонах компонентов, что позволяет TypeScript выполнять проверку типов и предоставлять высококачественную диагностику.
Для обоих файлов генерируются карты исходного кода (source maps), которые позволяют любым последующим инструментам (например, TypeScript) сообщать об ошибках в контексте вашего исходного файла.
Нажмите Enter или кликните, чтобы просмотреть изображение в полном размере
Обратите внимание, что для повышения производительности эти выходные файлы обычно создаются только в оперативной памяти и не записываются на диск.
Гибридная архитектура
Хотя в конечном итоге мы хотим заменить всю цепочку компиляции нативный кодом на Rust, у Angular есть сложный конвейер компиляции шаблонов, написанный на TypeScript. Переписывание этой части потребовало бы дополнительного времени. Поэтому для ngp мы используем гибридный подход: Rust для фронтенда компилятора и TypeScript для бэкенда.
Фронтенд компилятора — это библиотека на Rust, которую мы называем анализатором. Используя oxc, она парсит ваш код, находит классы с декораторами Angular, сопоставляет связи между NgModule и их компонентами/директивами, извлекает информацию о зависимостях из библиотек в формате Angular Package Format и создает список задач компиляции. Поскольку Rust и Oxc имеют мощную поддержку многопоточности, наш анализатор считывает ваш исходный код параллельно и передает единицы работы по компиляции на бэкенд.
Бэкенд — это часть, которая выполняет эти задачи, вызывая существующий компилятор шаблонов Angular для генерации выходного кода TypeScript, либо для выполнения, либо для проверки типов. Он получает задачи компиляции от анализатора, генерирует код и записывает выходные файлы и их карты исходного кода. Бэкенд ngp написан на TypeScript.
Получайте истории об Angular на свою почту
Присоединяйтесь к Medium бесплатно, чтобы получать обновления от этого автора.
Запомнить меня для более быстрого входа
Мы используем инструмент под названием napi-rs, который позволяет упаковывать библиотеки Rust в виде нативных плагинов для приложений Node или, альтернативно, в виде бандлов WebAssembly. Используя napi-rs, когда ngp загружает код анализатора, он может загрузить его либо как нативную библиотеку, если она существует для вашей архитектуры и платформы, либо использовать бандл WASM в качестве резервного варианта. Поддержка WASM также позволяет загружать анализатор на базе Rust в браузерных средах, что полезно для онлайн-инструментов разработки, которым необходимо компилировать Angular в браузере.
Текущий статус
ngp приближается к стадии MVP в качестве компилятора. Мы тестируем его на большом корпусе приложений Angular от Google, чтобы итеративно улучшать его корректность. Мы также создаем прототип его интеграции в систему сборки CLI, а также у нас есть прототип интеграции языковой службы. Мы планируем выпустить экспериментальную версию, которую вы сможете протестировать в своих проектах, позднее в этом году. А пока дайте нам знать, какие вопросы у вас есть по поводу этого захватывающего нового компилятора.
Часто задаваемые вопросы
Стал ли новый компилятор быстрее? Получит ли он такое же 10-кратное ускорение, как TypeScript 7?
Пока рано говорить. Компиляция TypeScript — это лишь часть всего процесса проверки типов, транспиляции, минификации и сборки. Мы воздержимся от каких-либо выводов, пока не сможем провести бенчмарк полного процесса сборки с помощью Angular CLI в прямом сравнении, но мы провели несколько специальных тестов, и результаты обнадеживают.
Что насчет языковой службы?
Мы также сможем использовать новый движок ngp для работы Angular language service, и у нас уже есть рабочий прототип этой интеграции.
Будут ли внесены критические изменения?
Мы тестируем новый компилятор на всей кодовой базе Angular в Google и исправляем все обнаруженные проблемы совместимости. Тем не менее, нам известно о нескольких пограничных случаях, когда проверка типов с новым выводом работает немного иначе, что может привести к появлению новых ошибок типизации. Они встречаются крайне редко, но мы все равно задокументируем их как критические изменения для полноты картины. В самом TypeScript 7 есть несколько подобных различий в поведении.
Почему бы не использовать API взаимодействия TypeScript для переноса существующего компилятора?
Мы планируем использовать API взаимодействия TS 7.1 для проверки типов и диагностики как часть нашего решения. Мы тесно сотрудничаем с командой TypeScript в Microsoft, чтобы гарантировать, что API TS 7.1 смогут поддерживать наши сценарии использования, и мы благодарны им за сотрудничество!
Мы рассматривали возможность использования уровня API взаимодействия для всего конвейера компилятора, но отказались от этого по соображениям производительности. Компилятор Angular выполняет гораздо более обширный обход и обработку AST, чем другие потребители, и мы (как и команда TypeScript) посчитали, что преимущества обработки в нативном коде слишком велики, чтобы их игнорировать.
Почему не Go, как выбрала Microsoft?
Команда TypeScript проделала фантастическую работу, documenting объяснив свои причины выбора Go. По большей части это сводится к тому, что Go гораздо лучше подходит для переноса сложной кодовой базы с другого языка со сборкой мусора. Для Angular это не было ограничением, поскольку наш компилятор имеет гораздо более простые структуры данных для управления. Вместо этого основным решающим фактором для нас стала доступность высококачественного, хорошо поддерживаемого инструментария JavaScript/TypeScript: библиотеки с парсером, AST, семантическим связывателем и преобразователем кода. Пересечение этого требования и нашего желания создавать нативный код привело нас к oxc и Rust.
Примечание: сам TypeScript 7 — это инструментарий для парсинга и преобразования TypeScript на Go, но его API по замыслу являются закрытыми (по крайней мере, в первоначальном выпуске). В настоящее время не существует публичной библиотеки, которая реализует парсер TypeScript и AST на Go.
Разве VoidZero уже не создала компилятор Angular на Rust/oxc?
Yes! Но с некоторыми оговорками. oxc-angular-compiler фокусируется на транспиляции исходного кода Angular (задача №1 компилятора), но не реализует ни проверку типов шаблонов (задача №2), ни межфайловые оптимизации, которые необходимы для сохранения небольшого размера бандлов приложений, не использующих standalone-компоненты. Нам необходимо поддерживать обе эти операции.
В долгосрочной перспективе мы заинтересованы в адаптации порта нашего движка парсинга и компиляции шаблонов из oxc-angular-compiler, чтобы перенести больше работы ngp на Rust.











