00:00:00(бодрая музыка) - Отлично, всем спасибо, здравствуйте.
00:00:07Меня зовут Люк Сандберг.
00:00:09Я инженер-программист в Vercel, работаю над Turbo Pack.
00:00:12Я работаю в Vercel около шести месяцев,
00:00:14и этого времени мне как раз хватило,
00:00:16чтобы выйти на эту сцену и рассказать вам обо всей той замечательной работе,
00:00:21которую я не делал.
00:00:23До Vercel я работал в Google,
00:00:25где занимался нашими внутренними веб-инструментами и делал странные вещи,
00:00:30например,
00:00:31создавал компилятор TSX в байт-код Java и работал над компилятором Closure.
00:00:37Так что,
00:00:38когда я пришел в Vercel,
00:00:39это было похоже на попадание на другую планету,
00:00:43все было по-другому.
00:00:45И я был очень удивлен всем,
00:00:47что мы делали в команде,
00:00:49и нашими целями.
00:00:50Сегодня я поделюсь несколькими проектными решениями,
00:00:53которые мы приняли в Turbo Pack,
00:00:54и расскажу,
00:00:55как,
00:00:55по моему мнению,
00:00:56они позволят нам продолжать развивать ту фантастическую производительность,
00:01:00которую мы уже имеем.
00:01:01Чтобы это обосновать, вот наша общая цель проектирования.
00:01:06Из этого сразу можно сделать вывод,
00:01:09что мы,
00:01:09вероятно,
00:01:10приняли несколько трудных решений.
00:01:14Итак, что насчет холодных сборок?
00:01:17Они важны,
00:01:18но одна из наших идей заключается в том,
00:01:20что вы вообще не должны с ними сталкиваться.
00:01:22И именно на этом будет сосредоточен этот доклад.
00:01:24В основном докладе вы немного слышали о том,
00:01:27как мы используем инкрементальность для улучшения производительности сборки.
00:01:31Ключевая идея,
00:01:32которую мы используем для инкрементальности,
00:01:34связана с кэшированием.
00:01:35Мы хотим сделать кэшируемым буквально все,
00:01:37что делает сборщик,
00:01:38чтобы при каждом изменении нам приходилось переделывать только работу,
00:01:41связанную с этим изменением.
00:01:43Или,
00:01:43возможно,
00:01:44другими словами,
00:01:45стоимость вашей сборки должна масштабироваться в зависимости от размера или сложности вашего изменения,
00:01:51а не от размера или сложности вашего приложения.
00:01:53И именно так мы можем гарантировать,
00:01:55что Turbo Pack будет продолжать обеспечивать разработчикам хорошую производительность,
00:01:59независимо от того,
00:01:59сколько библиотек иконок вы импортируете.
00:02:01Итак,
00:02:02чтобы помочь понять и обосновать эту идею,
00:02:04давайте представим самый простой в мире сборщик,
00:02:07который,
00:02:08возможно,
00:02:08выглядит так.
00:02:09Итак, вот наш «малыш»-сборщик.
00:02:12И это,
00:02:12возможно,
00:02:13немного слишком много кода для одного слайда,
00:02:15но дальше будет хуже.
00:02:17Итак, здесь мы парсим каждую точку входа.
00:02:20Мы отслеживаем их импорты,
00:02:21разрешаем их ссылки рекурсивно по всему приложению,
00:02:25чтобы найти все,
00:02:26от чего вы зависите.
00:02:28Затем,
00:02:28в конце,
00:02:29мы просто собираем все,
00:02:30от чего зависит каждая точка входа,
00:02:32и помещаем это в выходной файл.
00:02:35Ура, у нас есть «малыш»-сборщик.
00:02:38Очевидно,
00:02:39это наивно,
00:02:39но если мы посмотрим на это с инкрементальной точки зрения,
00:02:42ни одна часть этого не является инкрементальной.
00:02:45Так что мы определенно будем парсить определенные файлы по несколько раз,
00:02:49возможно,
00:02:50в зависимости от того,
00:02:51сколько раз вы их импортируете,
00:02:52это ужасно.
00:02:53Мы определенно будем разрешать импорт React сотни или тысячи раз.
00:02:57Так что, знаете, больно.
00:03:01Итак,
00:03:01если мы хотим,
00:03:02чтобы это было хотя бы немного более инкрементальным,
00:03:05нам нужно найти способ избежать избыточной работы.
00:03:08Итак, давайте добавим кэш.
00:03:10Итак, вы можете представить, что это наша функция парсинга.
00:03:15Она довольно проста.
00:03:15И она,
00:03:16вероятно,
00:03:16является своего рода рабочей лошадкой нашего сборщика.
00:03:19Знаете, очень просто.
00:03:19Мы читаем содержимое файла,
00:03:22передаем его SWC,
00:03:23чтобы получить AST.
00:03:25Итак, давайте добавим кэш.
00:03:27Хорошо, это, очевидно, приятная и простая победа.
00:03:31Но,
00:03:32знаете,
00:03:32я уверен,
00:03:33что некоторые из вас уже писали код для кэширования.
00:03:36Возможно, здесь есть некоторые проблемы.
00:03:38Например, что, если файл изменится?
00:03:41Это, очевидно, то, что нас волнует.
00:03:46И,
00:03:46знаете,
00:03:47что,
00:03:47если файл на самом деле не файл,
00:03:50а три символические ссылки в плаще?
00:03:52Многие менеджеры пакетов организуют зависимости именно так.
00:03:55И мы используем имя файла в качестве ключа кэша.
00:03:59Этого достаточно?
00:04:00Например, мы собираем для клиента и сервера.
00:04:03Одни и те же файлы попадают и туда, и туда.
00:04:04Это работает?
00:04:05Мы также храним AST и возвращаем его.
00:04:08Так что теперь нам нужно беспокоиться о мутациях.
00:04:11Так что,
00:04:11знаете,
00:04:12и наконец,
00:04:13разве это не очень наивный способ парсинга?
00:04:16Я знаю,
00:04:16что у всех есть огромные конфигурации для компилятора.
00:04:21Например, что-то из этого должно попасть сюда.
00:04:23Так что, да, это все отличные замечания.
00:04:27И это очень наивный подход.
00:04:32И на это, конечно, я бы сказал: да, это не сработает.
00:04:36Так что же нам делать, чтобы исправить эти проблемы?
00:04:39Пожалуйста, исправьте и не делайте ошибок.
00:04:44Итак, хорошо.
00:04:46Так что, возможно, это немного лучше.
00:04:49Знаете,
00:04:49здесь вы видите,
00:04:50что у нас есть некоторые преобразования.
00:04:52Нам нужно выполнять индивидуальные действия с каждым файлом,
00:04:55например,
00:04:55понижать уровень или реализовывать использование кэша.
00:04:58У нас также есть некоторая конфигурация.
00:05:00И поэтому,
00:05:01конечно,
00:05:01нам нужно включить это в наш ключ для кэша.
00:05:04Но, возможно, вы сразу же подозрительны.
00:05:08Например, это правильно?
00:05:09Например,
00:05:10достаточно ли на самом деле идентифицировать преобразование по имени?
00:05:13Не знаю, возможно, у него есть своя сложная конфигурация.
00:05:16И,
00:05:17хорошо,
00:05:17и,
00:05:18например,
00:05:19это значение JSON действительно охватят все,
00:05:23что нас волнует?
00:05:24Будут ли разработчики поддерживать это?
00:05:26Насколько большими будут эти ключи кэша?
00:05:29Сколько копий конфигурации у нас будет?
00:05:31Так что я лично видел код именно такой,
00:05:33и мне кажется,
00:05:34что его почти невозможно осмыслить.
00:05:37Хорошо,
00:05:37мы также попытались решить эту другую проблему,
00:05:40связанную с инвалидациями.
00:05:43Итак, мы добавили API обратного вызова для чтения файла.
00:05:46Это отлично,
00:05:47так что если файл изменится,
00:05:49мы можем просто удалить его из кэша,
00:05:51чтобы не продолжать выдавать устаревшее содержимое.
00:05:55Хорошо,
00:05:55но это на самом деле довольно наивно,
00:05:57потому что,
00:05:57конечно,
00:05:58нам нужно удалить наш кэш,
00:05:59но наш вызывающий объект также должен знать,
00:06:01что ему нужна новая копия.
00:06:03Итак, хорошо, давайте начнем передавать колбэки.
00:06:06Хорошо, мы это сделали.
00:06:09Мы передали колбэки вверх по стеку.
00:06:12Здесь вы видите,
00:06:13что мы позволяем нашему вызывающему объекту подписываться на изменения.
00:06:16Мы можем просто перезапустить всю сборку,
00:06:18если что-то изменится,
00:06:20и если файл изменится,
00:06:21мы вызываем это.
00:06:22Отлично, у нас есть реактивный сборщик.
00:06:25Но это все еще едва ли инкрементально.
00:06:28Так что,
00:06:29если файл изменится,
00:06:31нам нужно снова пройтись по всем модулям и сгенерировать все выходные файлы.
00:06:37Так что,
00:06:38знаете,
00:06:38мы сэкономили кучу работы благодаря нашему кэшу парсинга,
00:06:43но этого на самом деле недостаточно.
00:06:45И затем наконец, есть вся эта другая избыточная работа.
00:06:49Например, мы определенно хотим кэшировать импорты.
00:06:52Мы можем находить файл множество раз,
00:06:53и нам постоянно нужны его импорты,
00:06:55поэтому мы хотим поместить туда кэш.
00:06:57И,
00:06:58знаете,
00:06:58результаты разрешения на самом деле довольно сложны,
00:07:01поэтому мы определенно должны кэшировать их,
00:07:03чтобы мы могли повторно использовать работу,
00:07:05которую мы проделали при разрешении React.
00:07:08Но, хорошо, теперь у нас есть другая проблема.
00:07:11Ваши результаты разрешения меняются,
00:07:13когда вы обновляете зависимости или добавляете новые файлы,
00:07:16поэтому нам нужен еще один колбэк там.
00:07:18И мы определенно также хотим,
00:07:19например,
00:07:20кэшировать логику для генерации выходных данных,
00:07:23потому что,
00:07:23если подумать,
00:07:24в сессии HMR вы редактируете одну часть приложения,
00:07:27так почему мы каждый раз переписываем все выходные данные?
00:07:31И также,
00:07:32вы можете,
00:07:32например,
00:07:33удалить выходной файл,
00:07:34поэтому нам,
00:07:35вероятно,
00:07:36следует отслеживать изменения и там.
00:07:39Хорошо,
00:07:39возможно,
00:07:40мы решили все эти проблемы,
00:07:42но у нас все еще есть эта проблема: каждый раз,
00:07:45когда что-то меняется,
00:07:46мы начинаем с нуля.
00:07:48Так что,
00:07:48своего рода,
00:07:49весь поток управления этой функцией не работает,
00:07:51потому что,
00:07:52если изменится один файл,
00:07:53мы бы действительно хотели перескочить в середину этого цикла `for`.
00:07:56И наконец,
00:07:57наш API для нашего вызывающего объекта также безнадежно наивен.
00:08:03Они,
00:08:03вероятно,
00:08:04на самом деле хотят знать,
00:08:05какой файл изменился,
00:08:05чтобы они могли,
00:08:06например,
00:08:07отправлять обновления клиенту.
00:08:07Так что, да.
00:08:11Так что этот подход на самом деле не работает.
00:08:13И даже если бы мы каким-то образом передали все колбэки во все эти места,
00:08:17как вы думаете,
00:08:18вы смогли бы на самом деле поддерживать этот код?
00:08:21Как вы думаете,
00:08:22вы смогли бы,
00:08:23например,
00:08:23добавить в него новую функцию?
00:08:24Я нет.
00:08:25Я думаю, это просто рухнет и сгорит.
00:08:28И, знаете, на это я бы сказал: да.
00:08:34Итак, еще раз, что нам делать?
00:08:36Знаете,
00:08:37как и при общении с LLM,
00:08:39сначала нужно знать,
00:08:41чего вы хотите.
00:08:43А затем вы должны быть предельно ясны в этом.
00:08:48Итак, чего же мы вообще хотим?
00:08:50Итак,
00:08:50знаете,
00:08:51мы рассмотрели множество различных подходов,
00:08:54и многие люди в команде на самом деле имели большой опыт работы со сборщиками.
00:08:59Итак, мы пришли к этим своего рода общим требованиям.
00:09:02Итак,
00:09:02мы определенно хотим иметь возможность кэшировать каждую дорогую операцию в сборщике.
00:09:05И это должно быть очень легко сделать.
00:09:08Например,
00:09:09вы не должны получать 15 комментариев к своему обзору кода каждый раз,
00:09:11когда добавляете новый кэш.
00:09:12И я на самом деле не очень доверяю разработчикам писать правильные ключи кэша или отслеживать входы или зависимости вручную.
00:09:24Так что мы должны это обрабатывать.
00:09:26Мы определенно должны сделать это надежным.
00:09:30Далее, нам нужно обрабатывать изменяющиеся входы.
00:09:33Это большая идея в HMR, но даже между сессиями.
00:09:36Так что,
00:09:36в основном,
00:09:37это будут файлы,
00:09:37но это также могут быть такие вещи,
00:09:39как настройки конфигурации.
00:09:40А с кэшем файловой системы это на самом деле оказывается такими вещами,
00:09:43как переменные окружения,
00:09:44тоже.
00:09:45Итак, мы хотим быть реактивными.
00:09:47Мы хотим иметь возможность пересчитывать вещи,
00:09:50как только что-то изменится,
00:09:52и мы не хотим передавать колбэки повсюду.
00:09:54Наконец,
00:09:55нам просто нужно использовать преимущества современных архитектур,
00:09:59быть многопоточными и просто быстрыми в целом.
00:10:02Итак,
00:10:03возможно,
00:10:04вы смотрите на этот набор требований,
00:10:07и некоторые из вас думают: какое это имеет отношение к сборщику?
00:10:12И на это я бы сказал,
00:10:13конечно,
00:10:14моя команда менеджеров в зале,
00:10:17так что нам не нужно об этом говорить.
00:10:20Но на самом деле,
00:10:21я предполагаю,
00:10:21многие из вас пришли к гораздо более очевидному выводу.
00:10:24Это очень похоже на сигналы.
00:10:28И да, я описываю систему, которая звучит как сигналы.
00:10:31Это способ компоновать вычисления,
00:10:33отслеживать зависимости с некоторой степенью автоматической мемоизации.
00:10:37И я должен отметить,
00:10:38что мы черпали вдохновение из всевозможных систем,
00:10:41особенно из компилятора Rust и системы под названием Salsa.
00:10:45И существует даже академическая литература по этим концепциям,
00:10:49называемым Adaptons,
00:10:50если вам интересно.
00:10:51Хорошо,
00:10:51давайте посмотрим,
00:10:52что,
00:10:53давайте посмотрим,
00:10:54как это выглядит на практике,
00:10:56а затем мы сделаем очень резкий скачок от примеров кода на JavaScript к Rust.
00:11:01Итак, вот пример инфраструктуры, которую мы построили.
00:11:05Функция TurboTask — это кэшируемая единица работы в нашем компиляторе.
00:11:12Итак,
00:11:13мы можем,
00:11:13как только вы аннотируете такую функцию,
00:11:16отслеживать ее,
00:11:17мы можем построить ключ кэша из ее параметров,
00:11:20и это позволяет нам как кэшировать ее,
00:11:23так и повторно выполнять ее,
00:11:25когда нам это нужно.
00:11:28Эти типы VC здесь,
00:11:29вы можете думать о них как о сигналах,
00:11:32это реактивное значение,
00:11:33VC означает «ячейка значения»,
00:11:35но «сигнал» может быть немного лучшим названием.
00:11:39Когда вы объявляете параметр таким образом,
00:11:41вы говорите,
00:11:42что это может измениться,
00:11:44я хочу повторно выполнить его,
00:11:46когда он изменится.
00:11:47И как мы это узнаем?
00:11:49Итак, мы читаем эти значения через `await`.
00:11:52Как только вы ожидаете реактивное значение таким образом,
00:11:55мы автоматически отслеживаем зависимость.
00:11:58И затем,
00:11:58наконец,
00:11:59конечно,
00:12:00мы выполняем фактическое вычисление,
00:12:03которое хотели сделать,
00:12:05и сохраняем его в ячейке.
00:12:07Итак,
00:12:07поскольку мы автоматически отслеживали зависимости,
00:12:11мы знаем,
00:12:11что эта функция зависит как от содержимого файла,
00:12:15так и от значения конфигурации.
00:12:17И каждый раз,
00:12:18когда мы сохраняем новый результат в ячейку,
00:12:21мы можем сравнить его с предыдущим,
00:12:23и затем,
00:12:23если он изменился,
00:12:24мы можем распространять уведомления всем,
00:12:27кто прочитал это значение.
00:12:29Итак,
00:12:29эта концепция изменения является ключевой в нашем подходе к инкрементальности.
00:12:33И да, опять же, самый простой случай прямо здесь.
00:12:37Если файл изменится,
00:12:39Turbo Pack это заметит,
00:12:40аннулирует выполнение этой функции и немедленно перевыполнит ее.
00:12:45И затем,
00:12:45если мы случайно сгенерируем тот же AST,
00:12:48мы просто остановимся на этом,
00:12:50потому что мы вычисляем ту же ячейку.
00:12:53Теперь,
00:12:54что касается парсинга файла,
00:12:55едва ли можно внести в него какое-либо изменение,
00:12:58которое на самом деле не изменит AST.
00:13:00Но мы можем использовать фундаментальную компонуемость функций Turbo Pack,
00:13:06чтобы пойти дальше.
00:13:07Итак,
00:13:08здесь мы видим еще одну функцию кэша Turbo Pack,
00:13:12извлекающую импорты из модуля.
00:13:15Вы можете представить,
00:13:17что это очень распространенная задача,
00:13:18которую мы выполняем в сборщике.
00:13:20Нам нужно извлекать импорты просто для того,
00:13:22чтобы найти все модули в вашем приложении.
00:13:25Мы используем их,
00:13:26чтобы выбрать лучший способ группировки модулей в чанки.
00:13:29И,
00:13:29конечно,
00:13:30граф импортов важен для таких базовых задач,
00:13:33как tree shaking.
00:13:34И поскольку существует так много различных потребителей данных импортов,
00:13:39кэш имеет большой смысл.
00:13:41Так что эта реализация на самом деле не особенная.
00:13:44Это то, что вы найдете в любом сборщике.
00:13:46Мы обходим AST,
00:13:47собираем импорты в какую-то специальную структуру данных,
00:13:51которая нам нравится,
00:13:53а затем возвращаем их.
00:13:55Но ключевая идея здесь в том,
00:13:56что мы сохраняем их в другую ячейку.
00:13:58Так что,
00:13:58если модуль изменится,
00:14:00нам действительно нужно перезапустить эту функцию,
00:14:03потому что мы ее прочитали.
00:14:05Но если подумать о тех изменениях,
00:14:07которые вы вносите в модули,
00:14:09очень немногие из них на самом деле влияют на импорты.
00:14:12Итак,
00:14:12вы меняете модуль,
00:14:14обновляете тело функции,
00:14:16строковый литерал,
00:14:18любую деталь реализации.
00:14:20Это аннулирует эту функцию,
00:14:22и затем мы вычислим тот же набор импортов.
00:14:25И затем мы не аннулируем ничего, что прочитало это.
00:14:29Так что,
00:14:29если вы подумаете об этом в сессии HMR,
00:14:32это означает,
00:14:32что нам действительно нужно перепарсить ваш файл,
00:14:35но нам на самом деле больше не нужно думать о том,
00:14:38как принимать решения о чанках.
00:14:40Нам не нужно думать о каких-либо результатах tree shaking,
00:14:43потому что мы знаем,
00:14:44что они не изменились.
00:14:45Так что мы можем немедленно перейти от парсинга файла,
00:14:48выполнения этого простого анализа,
00:14:50а затем сразу перейти к генерации выходных данных.
00:14:53И это один из способов,
00:14:54благодаря которому у нас очень быстрое время обновления.
00:14:57Так что это довольно императивно.
00:15:02Другой способ осмыслить эту базовую идею — это как граф узлов.
00:15:06Итак, здесь слева вы можете представить холодную сборку.
00:15:12Изначально нам действительно приходится читать каждый файл,
00:15:14парсить их все,
00:15:15анализировать все импорты.
00:15:17И как побочный эффект этого,
00:15:18мы собрали всю информацию о зависимостях из вашего приложения.
00:15:21И затем,
00:15:22когда что-то меняется,
00:15:23мы можем использовать этот граф зависимостей,
00:15:25который мы построили,
00:15:27чтобы распространять инвалидации,
00:15:28возвращаться вверх по стеку и повторно выполнять функции Turbo Pack.
00:15:32И если они производят новое значение,
00:15:34мы останавливаемся там.
00:15:35В противном случае мы продолжаем распространять инвалидацию.
00:15:37Так что отлично.
00:15:41Знаете,
00:15:41это на самом деле своего рода массовое упрощение того,
00:15:44что мы делаем на практике,
00:15:46как вы можете себе представить.
00:15:47Итак,
00:15:48в Turbo Pack сегодня существует около 2500 различных функций Turbo task.
00:15:53И в типичной сборке у нас могут быть буквально миллионы различных задач.
00:15:58Так что это действительно выглядит,
00:16:00возможно,
00:16:00немного больше так.
00:16:01Теперь,
00:16:02я на самом деле не ожидаю,
00:16:03что вы сможете это прочитать.
00:16:04Не смог уместить это на слайде.
00:16:06Так что, возможно, нам стоит уменьшить масштаб.
00:16:08Хорошо, так что это, очевидно, не очень полезно.
00:16:14В действительности у нас есть лучшие способы отслеживать и визуализировать то,
00:16:19что происходит внутри Turbo Pack.
00:16:21Но фундаментально,
00:16:22это работает,
00:16:23отбрасывая подавляющее большинство информации о зависимостях.
00:16:26И теперь я предполагаю,
00:16:27что некоторые из вас,
00:16:29возможно,
00:16:29на самом деле имеют опыт работы с сигналами,
00:16:32возможно,
00:16:33плохой опыт.
00:16:34Знаете,
00:16:35я,
00:16:35например,
00:16:36на самом деле люблю трассировки стека и возможность входить и выходить из функций в отладчике.
00:16:41Так что, возможно, вы подозрительны, что это полная панацея.
00:16:45Например, очевидно, что это сопряжено с компромиссами.
00:16:47И да,
00:16:48на это я бы,
00:16:49конечно,
00:16:50сказал,
00:16:50что,
00:16:51знаете,
00:16:51на самом деле я бы сказал,
00:16:54что вся разработка программного обеспечения — это управление компромиссами.
00:17:01Мы не всегда решаем проблемы точно,
00:17:03но мы действительно выбираем новые наборы компромиссов для предоставления ценности.
00:17:08Итак,
00:17:09чтобы достичь наших проектных целей в отношении инкрементальных сборок в Turbo Pack,
00:17:14мы поставили все наши фишки на эту инкрементальную реактивную модель программирования.
00:17:19И это,
00:17:19конечно,
00:17:20имело некоторые очень естественные последствия.
00:17:23Так что,
00:17:24знаете,
00:17:24возможно,
00:17:25мы на самом деле действительно решили проблему систем кэширования,
00:17:30написанных вручную,
00:17:31и громоздкой логики инвалидации.
00:17:33Взамен нам приходится управлять сложной инфраструктурой кэширования.
00:17:39И,
00:17:39конечно,
00:17:40знаете,
00:17:40для меня это звучит как очень хороший компромисс.
00:17:42Мне нравится сложная инфраструктура кэширования,
00:17:45но всем нам приходится жить с последствиями.
00:17:48Итак,
00:17:48первое,
00:17:49конечно,
00:17:49это просто основные накладные расходы этой системы.
00:17:54Знаете,
00:17:55если подумать,
00:17:56в данной сборке или сессии HMR вы на самом деле не очень много меняете.
00:18:04Так что мы отслеживаем всю информацию о зависимостях между,
00:18:07например,
00:18:07каждым импортом и каждым результатом разрешения в вашем приложении,
00:18:10но вы на самом деле измените лишь несколько из них.
00:18:13Так что большая часть информации о зависимостях,
00:18:15которую мы собираем,
00:18:15на самом деле никогда не нужна.
00:18:16Так что,
00:18:17знаете,
00:18:17чтобы управлять этим,
00:18:19нам пришлось много сосредоточиться на стимулировании,
00:18:21на улучшении производительности этого слоя кэширования,
00:18:24чтобы снизить накладные расходы и позволить нашей системе масштабироваться до все более крупных приложений.
00:18:30И следующее, самое очевидное, это просто память.
00:18:34Знаете,
00:18:35кэши всегда фундаментально являются компромиссом между временем и памятью.
00:18:38И наш в этом смысле ничем не отличается.
00:18:41Наша простая цель состоит в том,
00:18:43чтобы размер кэша масштабировался линейно с размером вашего приложения.
00:18:49Но опять же,
00:18:49мы должны быть осторожны с накладными расходами.
00:18:51Следующий момент немного тонок.
00:18:54Итак,
00:18:55у нас есть множество алгоритмов в сборщике,
00:18:57как вы могли бы ожидать.
00:18:58И некоторые из них требуют понимания чего-то глобального о вашем приложении.
00:19:03Что ж,
00:19:03это проблема,
00:19:04потому что всякий раз,
00:19:05когда вы зависите от глобальной информации,
00:19:07это означает,
00:19:07что любое изменение может аннулировать эту операцию.
00:19:10Поэтому мы должны быть осторожны в том,
00:19:12как мы проектируем эти алгоритмы,
00:19:14тщательно компонуем вещи,
00:19:15чтобы мы могли сохранить инкрементальность.
00:19:17И наконец, это, возможно, немного личная претензия.
00:19:24Все асинхронно в Turbo Pack.
00:19:27И это отлично подходит для горизонтальной масштабируемости,
00:19:29но опять же,
00:19:29это вредит нашим фундаментальным целям,
00:19:31таким как,
00:19:31знаете,
00:19:32отладка и профилирование производительности.
00:19:38Так что я уверен,
00:19:39что многие из вас имеют опыт отладки асинхронного кода,
00:19:42например,
00:19:43в инструментах разработчика Chrome.
00:19:46И это, как правило, довольно приятный опыт.
00:19:48Не всегда идеально.
00:19:49И я уверяю вас, Rust с LLDB отстает на световые годы.
00:19:53Так что для управления этим нам пришлось инвестировать в пользовательские инструменты визуализации,
00:19:59инструментирования и трассировки.
00:20:01И посмотрите на это,
00:20:02еще один инфраструктурный проект,
00:20:04который не является сборщиком.
00:20:07Хорошо, давайте посмотрим, сделали ли мы правильную ставку.
00:20:11Итак,
00:20:11в Vercel у нас есть очень большое производственное приложение.
00:20:17Мы думаем,
00:20:17что это,
00:20:18возможно,
00:20:18одно из крупнейших в мире,
00:20:19но,
00:20:20знаете,
00:20:20мы на самом деле не знаем.
00:20:21Но в нем около 80 000 модулей.
00:20:23Итак, давайте посмотрим, как Turbo Pack справляется с ним.
00:20:26Что касается быстрого обновления,
00:20:28мы действительно доминируем над тем,
00:20:30что может предложить Web Pack.
00:20:32Но это своего рода старые новости.
00:20:33Turbo Pack для разработки уже некоторое время доступен,
00:20:36и я очень надеюсь,
00:20:37что все хотя бы используют его в разработке.
00:20:39Но,
00:20:39знаете,
00:20:40новая вещь здесь сегодня,
00:20:41конечно,
00:20:41в том,
00:20:42что сборки стабильны.
00:20:42Итак, давайте посмотрим на сборку.
00:20:44И здесь вы можете увидеть существенную победу над Web Pack для этого приложения.
00:20:49Эта конкретная сборка на самом деле работает с нашим новым экспериментальным слоем кэширования файловой системы.
00:20:53Так что около 16 из этих 94 секунд — это просто очистка кэша в конце.
00:20:59И это то,
00:20:59над чем мы будем работать,
00:21:00улучшая по мере того,
00:21:01как кэширование файловой системы станет стабильным.
00:21:04Но,
00:21:04конечно,
00:21:05дело в холодных сборках в том,
00:21:06что они холодные,
00:21:07ничего не инкрементально.
00:21:07Итак, давайте посмотрим на фактическую теплую сборку.
00:21:10Итак,
00:21:11используя кэш из холодной сборки,
00:21:13мы можем увидеть это.
00:21:14Так что это просто взгляд на то, где мы находимся сегодня.
00:21:17Поскольку у нас есть эта мелкозернистая система кэширования,
00:21:20мы можем фактически просто записать кэш на диск,
00:21:22а затем при следующей сборке прочитать его обратно,
00:21:24выяснить,
00:21:24что изменилось,
00:21:25и завершить сборку.
00:21:26Хорошо,
00:21:27это выглядит довольно хорошо,
00:21:28но многие из вас думают,
00:21:30что,
00:21:30возможно,
00:21:31у меня лично нет самого большого приложения Next.js в мире.
00:21:34Итак, давайте посмотрим на меньший пример.
00:21:37Веб-сайт react.dev значительно меньше.
00:21:41Это также своего рода интересно,
00:21:42потому что это компилятор React.
00:21:44Неудивительно,
00:21:44что это один из первых,
00:21:46кто внедрил компилятор React.
00:21:47А компилятор React реализован в Babel.
00:21:49И это своего рода проблема для нашего подхода,
00:21:51потому что это означает,
00:21:52что для каждого файла в приложении нам нужно просить Babel обработать его.
00:21:55Так что,
00:21:55и фундаментально,
00:21:57я бы сказал,
00:21:57мы,
00:21:58или я,
00:21:58я не могу сделать компилятор React быстрее.
00:22:01Это не моя работа.
00:22:02Моя работа — Turbo Pack.
00:22:03Но мы можем выяснить, когда именно его вызывать.
00:22:07Итак,
00:22:08глядя на время быстрого обновления,
00:22:10я был на самом деле немного разочарован этим результатом.
00:22:13И оказывается,
00:22:14что около 130 из этих 140 миллисекунд приходится на компилятор React.
00:22:18И Turbo Pack, и Web Pack делают это.
00:22:22Но с Turbo Pack мы можем,
00:22:23после того как компилятор React обработал это изменение,
00:22:27увидеть: о,
00:22:27импорты не изменились.
00:22:29Закинуть это в выходные данные и продолжать.
00:22:31И снова,
00:22:31на холодных сборках мы видим такую последовательную 3-кратную победу.
00:22:37И чтобы было ясно, это на моей машине.
00:22:39Но опять же, никакой инкрементальности в холодной сборке.
00:22:44А в теплой сборке мы видим гораздо лучшее время.
00:22:47Итак,
00:22:47опять же,
00:22:48с теплой сборкой у нас уже есть кэш на диске.
00:22:52Все,
00:22:52что нам нужно сделать,
00:22:53это,
00:22:54по сути,
00:22:54после запуска выяснить,
00:22:55какие файлы в приложении изменились,
00:22:57повторно выполнить эти задачи,
00:22:59а затем повторно использовать все остальное из предыдущей сборки.
00:23:01Итак, основной вопрос: мы уже Turbo?
00:23:05Да.
00:23:06Так что да, это, конечно, обсуждалось в основном докладе.
00:23:09Turbo Pack стабилен начиная с Next 16.
00:23:12И мы даже являемся сборщиком по умолчанию для Next.
00:23:14Так что, знаете, миссия выполнена, пожалуйста.
00:23:17Но. (смех) (аплодисменты)
00:23:23И если вы заметили ту отмену в основном докладе,
00:23:27это я пытался сделать Turbo Pack по умолчанию.
00:23:30Потребовалось всего три попытки.
00:23:31Но то,
00:23:32что я действительно хочу оставить вам,
00:23:34опять же,
00:23:35это вот что.
00:23:35Знаете, потому что мы еще не закончили.
00:23:37Нам еще многое предстоит сделать в плане производительности и завершить работу над слоем кэширования файловой системы.
00:23:42Я предлагаю всем попробовать это в разработке.
00:23:44И это все.
00:23:46Большое спасибо.
00:23:47Пожалуйста, найдите меня, задайте вопросы.
00:23:49(аплодисменты) (бодрая музыка) (бодрая музыка)