Video Summary and Transcription
Tobias Koppers presenta TurboPack y TurboEngine, abordando las limitaciones de Webpack. Demuestra codificación en vivo para mostrar la optimización de la validación de caché y la eficiencia de compilación. La charla cubre agregar registro y memorización, optimizar la ejecución y rastrear dependencias, implementar invalidación y observador, y almacenar y eliminar invalidadores. También se discute la compilación incremental, la integración con otras herramientas de monorepo, la visualización de errores y la posibilidad de un sistema de complementos para Toolpag. Por último, se menciona la comparación con el Constructor de Bunn.
1. Introducción a TurboPack y TurboEngine
Soy Tobias Koppers, el creador de TurboPack y TurboEngine. Estoy aquí para demostrar programación en vivo en JavaScript, enfocándome en el núcleo de TurboEngine. La motivación detrás de TurboPack es abordar las limitaciones de Webpack en el manejo de aplicaciones grandes y compilaciones incrementales. TurboPack introduce una nueva arquitectura para optimizar la validación de caché y mejorar la eficiencia de compilación. Mostraré una aplicación simple que copia archivos JavaScript basados en el grafo de dependencias, con la adición de un encabezado de derechos de autor. A través de la programación en vivo, explicaré el proceso y demostraré cómo TurboEngine mejora las compilaciones incrementales. ¡Comencemos!
Gracias por recibirme. Estoy intentando algo nuevo. Hoy estoy intentando hacer programación en vivo, así que espero que funcione. Mi nombre es Tobias Koppers y trabajé en Webpack durante 10 años y ahora me uní a Universal y trabajo en TurboPack tratando de hacer algo nuevo, algo mejor y sí, estoy tratando de enfocarme, como dije, en un aspecto de TurboPack y trato de explicar un poco cómo funciona TurboPack o el núcleo de TurboEngine en detalle, así que estoy tratando de demostrar algo con eso, así que estoy tratando de hacer programación en vivo en JavaScript un poco del núcleo de TurboEngine.
La motivación de eso es que en las aplicaciones de Webpack vimos que las aplicaciones crecen y crecen, cada vez más grandes y la arquitectura de Webpack no está diseñada para eso, las compilaciones incrementales tienden a volverse más lentas a medida que la aplicación crece. No es un problema enorme, pero sí puede convertirse en un problema en unos años cuando las aplicaciones tengan millones de módulos o algo así y algunos problemas que aislamos fueron que hacemos muchas búsquedas en caché, tenemos que calcular muchas cosas para validar la caché, como verificar si los archivos siguen siendo los mismos, hacer hash de cosas y ese es el problema porque todo este sobrecosto, lo pagas en cada compilación incremental y queremos hacer algo nuevo, una nueva arquitectura para abordar este problema. Y por eso creamos TurboEngine y TurboPack, es una nueva arquitectura y puedo explicar un poco haciendo programación en vivo.
Lo que quiero mostrar es una especie de aplicación pequeña que es súper simple, no es un empaquetador pero algo similar a un empaquetador, toma cualquier aplicación JavaScript y simplemente copia la aplicación siguiendo el grafo de dependencias a otra carpeta y al hacerlo también agrega un encabezado de derechos de autor solo para demostrar algo. Con eso, comienzo con la aplicación básica escrita en JavaScript y la explico más adelante y luego trato de agregar algo similar a TurboEngine para hacerlo más eficiente, para hacer compilaciones incrementales posibles de una manera similar a cómo funciona en TurboPack, en Rust y con TurboEngine. Para eso, preparé esta pequeña aplicación, es realmente simple, es solo un montón de archivos de Node.js. Usamos Acron para obtener el grafo de dependencias de algo, la ruta allí, los módulos.
Y recorro un poco la aplicación para que la entiendas. El proceso principal es realmente simple, obtenemos el directorio base, como el directorio de origen, tenemos un directorio de salida y luego tenemos un archivo de entrada que es en realidad este archivo en el que estamos mirando. Así que en realidad estamos copiando nuestra propia aplicación a otra carpeta. Y luego comenzamos a seguir el grafo de dependencias desde ese punto de entrada y copiamos eso desde la capa base a la capa de salida. Y para complicarlo un poco más, agrego este archivo de encabezado que básicamente es, déjame mostrarlo, es como una declaración de derechos de autor que se debe agregar a cada archivo para que sea un poco más interesante. Luego invocamos esta función llamada copiar grafo, que básicamente calcula la salida del archivo actual simplemente reubicándolo. Llamando a la función de copia que copia el archivo, súper simple. Y luego llama a otras dos funciones que se llaman obtener referencias que veremos más adelante, es como obtener las referencias, como todos los archivos que se han importado desde un archivo y luego recorrer eso y llamarse a sí mismo de forma recursiva para copiar toda la aplicación. Sí. Así que copiar también es bastante simple, leer el encabezado, leer el archivo y escribirlo en otro archivo. Nada super complicado aquí. Obtener referencias es un poco más complicado pero sí, no es realmente necesario entenderlo. Es como llamar a analizar para obtener un AST del módulo y como recorrer o hacer algo de magia para extraer las declaraciones de importación y devolver una lista de todos los archivos referenciados por ese tipo de archivo. Analizar también es bastante simple, llamando a Akon, que es una biblioteca de análisis de JavaScript. También leyendo el archivo obviamente y luego devuelve el AST. Y después de eso, inicio todo y eso debería copiar la aplicación a la nueva carpeta. Así que vamos a intentarlo. Oh. Algunas cosas que quiero explicar. También tengo esta función de tarea que en realidad no está haciendo nada actualmente.
2. Agregando Registro y Memorización
Entonces, básicamente, solo estamos agregando un registro para que puedas ver lo que está haciendo la aplicación. Más adelante agregaremos más lógica a eso. El primer paso es agregar algún tipo de sistema de memorización que funcione como una caché. Almacenamos la caché en algún lugar utilizando un mapa. Ahora deberíamos tener este tipo de memorización, es bastante simple en realidad.
Entonces, básicamente, solo estamos agregando un registro para que puedas ver lo que está haciendo la aplicación. De lo contrario, no imprime nada, eso es bastante aburrido. Así que solo tiene registro y lo que hago es llamar básicamente a la función con registro. Lo ves, pero no es algo directo, no está haciendo nada especial.
Agregaremos más lógica a eso más adelante. Entonces, lo que terminarás viendo es toda esta aplicación en ejecución, por lo que se llama a main, se llama a copy graph, se llama a copy y se llaman a todas estas funciones en una especie de tres tipos de metales. Esto es básicamente una traza de pila.
Pero también verás muchos problemas con esta aplicación. Por ejemplo, estamos leyendo el encabezado varias veces, como aquí y aquí y aquí. Y también estamos llamando a copy graph varias veces. Estamos llamando a fs copy graph desde taskKey, porque se referencia desde fs. Y estamos llamando a copy graph desde task. Estamos haciendo mucho trabajo duplicado que no queremos hacer porque eso es lo que queremos hacer.
El primer paso es agregar algún tipo de sistema de memorización que funcione como una caché. Entonces, si ejecutas la misma función dos veces, simplemente devolvemos un resultado existente. Así que agreguemos eso. Para agregar una caché, almacenamos la caché en algún lugar. Y en JavaScript, podemos usar simplemente un mapa para eso. Y lo que queremos hacer es obtener la tarea del mapa como primer paso, la función de la caché. En realidad, queremos obtener la función y todos estos argumentos. Porque puedes llamar a la misma función con diferentes argumentos, que es básicamente una tarea diferente. Y luego, si tenemos una tarea, simplemente, si no tenemos una tarea, simplemente podemos crear una. Lo que significa que creamos un nuevo objeto que tiene algún resultado, que es indefinido para el y luego establecemos el resultado, que es básicamente lo que estábamos haciendo antes, así que copiamos eso aquí. Y luego, en cualquier caso, devolvemos el resultado. Así que ahora deberíamos tener algún tipo de sistema de memorización.
Me faltó algo, así que en realidad tengo que establecer la caché, sí, así. Y hay un error, probablemente lo veas si eres un desarrollador de JavaScript, el mapa no funciona con matrices porque se almacena por identidad, así que lo que realmente necesitamos hacer es almacenarlo por un tipo de valor de eso, así que para eso preparé algo que es como un TupleMap. Que necesito importar. Copiar carga, no lo hagas mal. Y ahora deberíamos tener este tipo de sistema de memorización, es bastante simple en realidad.
3. Optimizando la Ejecución y Rastreando Dependencias
Llamar a una función varias veces es ineficiente, por lo que lo optimizamos ejecutando los encabezados solo una vez. Para la ejecución incremental, tomamos un enfoque de abajo hacia arriba, calculando las funciones afectadas a partir de los archivos modificados. Para rastrear las dependencias, utilizamos un grafo para almacenar las tareas conectadas. Al establecer una tarea actual, podemos invalidar las tareas dependientes y rastrear la ejecución. La función Invalidate se utiliza para invalidar y calcular las tareas, junto con sus dependencias.
Y con eso, al llamarlo nuevamente, nunca ejecutas una función dos veces, por lo que los encabezados solo se llaman una vez, y si copias algo nuevamente, no lee el encabezado nuevamente y puedes ver que no invoca copy graph de la tarea varias veces. Así que optimizamos eso, y necesitamos lo mismo más adelante para nuestras partes incrementales.
Entonces, el siguiente paso. El siguiente paso es agregar algún tipo de sistema de ejecución incremental. Lo que no queremos hacer es simplemente comenzar main nuevamente y buscar todo desde la caché, porque eso es lo que hemos hecho con Webpack, y el costo de buscar todo en la caché es real y es un problema. Entonces, lo que queremos hacer es un nuevo enfoque, y el enfoque es similar, hacerlo de manera opuesta, por lo que no llamamos a las funciones de arriba hacia abajo. De hecho, queremos ir de abajo hacia arriba, por lo que queremos obtener, por ejemplo, desde el archivo modificado, averiguar qué funciones se ven afectadas y volver a calcularlas. Entonces, queremos ir desde los archivos modificados, desde las lecturas de archivos, invalidar eso y luego propagar todo el sistema para volver al estado correcto.
Para hacer eso, en realidad necesitamos algún tipo de grafo para almacenar qué tareas están conectadas a otras tareas. Queremos saber que cuando invalidamos, por ejemplo, una llamada, tenemos que invalidar nuestro método de copia o algo así. Entonces, lo que queremos hacer es almacenar algo, como una tarea dependiente, y tal vez un conjunto sea suficiente. Almacenar una lista de tareas en las que esta tarea depende. Que depende de esa tarea. Y queremos llenar esto para establecer la tarea, la tarea dependiente, en la tarea actual. El problema es que no conocemos la tarea actual. Entonces, lo que queremos hacer, y puede que no haya una tarea actual para la aplicación principal, es agregarla solo si tenemos una tarea actual. Y luego queremos rastrear cuál es la tarea actual. Entonces, eso en realidad no es muy complicado. Podemos tener una variable que también sea una tarea actual, y luego tener una función, envolver nuestras llamadas con algún tipo de estado donde tenemos esta tarea actual. Así que tenemos este tipo de método de tarea actual. Tarea actual, donde llamas a una función con esta tarea actual establecida en algo. Tal vez Copilot pueda hacer eso. Sí, puede hacerlo. Entonces, lo que queremos hacer es, como, establecerlo antes de la función y restaurarlo al valor anterior después. Y luego usamos esta nueva función para envolver nuestra llamada en esta tarea actual. Entonces, ahora sabemos que estamos dentro de esa tarea y podemos rastrearla en todos sus hijos. Ahora tenemos este tipo de árbol de ejecuciones, que vemos en la salida almacenada en nuestra estructura de tareas. Entonces, ¿cómo lo usamos? Queremos hacer esta especie de invalidación y luego propagarla a la tarea dependiente. Entonces, queremos tener algún tipo de función llamada Invalidate, que invalide una tarea y la calcule e invalide también la tarea dependiente. Primero, queremos registrar eso porque es agradable ver qué está sucediendo.
4. Implementando la Invalidación y el Observador
Necesitamos almacenar los nombres y otra información para la invalidación. El primer paso es recalcular la tarea, seguido de invalidar las dependencias. Después de recalcular las tareas, necesitamos invalidar todas las tareas dependientes. Para implementar esto, agregamos funciones para obtener y agregar invalidadores. Podemos usar el sistema de invalidación en nuestra implementación del sistema de archivos. Obtenemos el invalidador en una función específica y lo llamamos cuando sea necesario. Usamos un observador para detectar cambios en el directorio. Por último, almacenamos los invalidadores en una lista.
Eso es solo para imprimir lo que está sucediendo para que puedas verlo. Y necesitamos almacenar los nombres. Así que tenemos que almacenar algunas cosas, como el nombre, la función y los argumentos. Para poder acceder a ellos y hacer la invalidación.
Y luego, el primer paso es recalcular la tarea. Y el segundo paso es invalidar la dependencia. Ya sabes, Copyload ya sabe lo que queremos hacer. De acuerdo. Así que lo que queremos hacer es simplemente ejecutar la tarea nuevamente haciendo eso. En realidad, no es correcto. Así que queremos ejecutar la tarea nuevamente y almacenar el nuevo resultado. Así que queremos envolverlo nuevamente con la tarea actual, pero luego simplemente invocar la función de la tarea con los argumentos de la tarea. Así que lo recalculamos, pero después de recalcular las tareas, el resultado podría cambiar o probablemente haya cambiado, con suerte. Y luego queremos recorrer todas las tareas dependientes e invalidarlas también para que puedan recalcularse también. Entonces, si tenemos una tarea dependiente, queremos recorrerlas e invalidarlas. Y esto no se llama en ningún lugar, así que queremos llamarlo, por ejemplo, desde un observador. Así que agregamos algún tipo de función a este tipo de cosa que es obtenerInvalidador, agregarInvalidador, que devuelve la función de invalidación para la tarea actual. Entonces, si no tenemos una tarea actual, queremos lanzar un error, pero al final, queremos devolver una función que te permita invalidar la tarea actual. Y esta función se puede usar en nuestra implementación del sistema de archivos, que actualmente se ve así. Entonces, si borras algo, llamamos a leer archivo. Si escribes algo, llamamos a escribir archivo. Sí, es bastante simple.
Entonces, ¿cómo aprovechamos nuestro sistema de invalidación? Podemos obtener el invalidador en este tipo de función. Ahora, lo tenemos, pero ¿cuándo lo llamamos? Necesitamos tener algún tipo de observador. Afortunadamente, hay una implementación de observador en Node.js. Y para simplificar, simplemente observamos todo el directorio de forma recursiva. Y en algunos casos extremos, no hay sistema de archivos debido a Node.js. Ahora queremos invalidar todas las llamadas de lectura que se ven afectadas por este tipo de archivo que ha cambiado. Así que lo que queremos hacer es almacenar nuestro invalidador en algún lugar. Así que tenemos una lista de invalidadores.
5. Almacenando y Eliminando Invalidadores
Y luego almacenamos el invalidador. En el conjunto de validadores. Lo almacenamos en este tipo de matriz. Así que tenemos acceso a todos los invalidadores de todas las llamadas de lectura, por lo que simplemente podemos llamarlos. Pero eso lleva a, como, porque un archivo cambió, es decir, los atributos cambiaron, se agregó un archivo, múltiples sistemas. Eventualmente, esto lo activaría varias veces. Queremos retrasarlo un poco para que sea legible en la salida. Establecemos un tiempo de espera. Y llamamos al invalidador. También queremos bloquear algo. Así que tal vez creemos algo como archivo cambiado. En realidad, necesitamos eliminar el invalidador. Así que lo eliminamos. Porque una vez invalidado, no tiene sentido que la función se invalide múltiples veces. Porque después de la invalidación, la llamamos nuevamente y luego tenemos un nuevo invalidador que está en el mapa nuevamente.
Y luego almacenamos el invalidador. En el conjunto de validadores. Lo almacenamos en este tipo de matriz. Así que tenemos acceso a todos los invalidadores de todas las llamadas de lectura, por lo que simplemente podemos llamarlos. Y eso es lo que realmente queremos hacer. Obtenemos el invalidador. De los invalidadores, a través del sistema de archivos. En realidad, eso está mal. Porque el sistema de archivos es relativo y del equipo humano. Así que lo hacemos absoluto. Y lo obtenemos de la lista. Y si lo tenemos, si tenemos un invalidador, podemos llamarlo.
Pero eso lleva a, como, porque un archivo cambió, es decir, los atributos cambiaron, se agregó un archivo, múltiples sistemas. Eventualmente, esto lo activaría varias veces. Así que queremos retrasarlo un poco para que sea legible en la salida. Establecemos un tiempo de espera. Y llamamos al invalidador. Tal vez lo pongamos en 100 milisegundos. Y sí. También queremos bloquear algo. Así que tal vez creemos algo como archivo cambiado. Así que realmente veamos qué está sucediendo. Sí. Oops. Genial. Funciona más o menos, pero hay un error.
En realidad, necesitamos eliminar el invalidador. Así que lo eliminamos. Porque una vez invalidado, no tiene sentido que la función se invalide múltiples veces. Porque después de la invalidación, la llamamos nuevamente y luego tenemos un nuevo invalidador que está en el mapa nuevamente.
6. Invalidación y Bubbling
Así que solo queremos invalidarlo una vez. Comparamos el resultado antiguo con el nuevo resultado y solo invalidamos los padres si algo ha cambiado realmente. La nueva implementación ahora solo invalida la nueva función al guardar el archivo. Si algo cambió pero no necesitamos volver a calcularlo, imprimimos 'sin cambios' y detenemos el bubbling.
Así que sí, debería funcionar. Bueno, con suerte. Vamos a probarlo si me falta algo.
De acuerdo, necesitamos importar la función getInvalidator. Así que funciona. La compilación inicial es básicamente la misma. Secuenciamos todas las cosas que hemos puesto antes, pero ahora básicamente no se sale porque estamos observando algo. Técnicamente, si cambiamos el archivo, debería volver a calcular las cosas y volver al estado de 'status'. Así que tal vez solo pueda guardar este archivo. De acuerdo, invalidador. De acuerdo, intentemos de nuevo. Guardando el archivo. Oh, ahora funciona. De acuerdo, ahora vemos que básicamente invalida la función de lectura y vuelve a calcular la función de lectura y luego hace 'bubbling' en el gráfico de ejecución. Entonces, copy llama a read, por lo que también se vuelve a calcular copy y copy es llamado por copy graph y se va 'bubbling' hasta la función principal. Hay algunos problemas, como que se va 'bubbling' varias veces, como copy graph, copy graph se repite aquí porque read se usa en parse y en copy. Y también, es bastante inútil, si solo guardo el archivo, volver a calcular todo porque no cambié nada en el archivo. ¿Por qué necesitamos volver a calcular copy graph si las referencias no cambiaron en absoluto? Entonces, lo que queremos agregar es algún tipo de barrera de detención para detener el 'bubbling' si el resultado es el mismo. Así que el reloj está mal. De acuerdo, tengo dos minutos, así que agregaré esto. Sí, quiero invalidar solo si algo ha cambiado realmente, así que puedo agregar algún tipo de código simple para que esto funcione. Así que queremos comparar el resultado antiguo con el nuevo resultado. Así que si el resultado antiguo no es igual, no deberíamos hacer eso con Stringify, pero funciona por ahora. Así que solo si algo ha cambiado realmente, queremos invalidar los padres. Ahora lo intentaré de nuevo con la nueva implementación, y si solo guardo este archivo, ahora solo invalida la nueva función, tal vez agreguemos algún tipo de registro. Así que si algo cambió, si el registro no cambia, perfecto. Así que si algo cambió pero no necesitamos volver a calcularlo, simplemente podemos imprimir 'sin cambios', así que podemos detener el 'bubbling'. Si read cambió pero nada más cambió, pero si realmente cambiamos algo, como el AST, como agregar un comentario, tenemos que hacer 'bubbling' más, como read se va a 'bubbling' hacia arriba, copia el archivo nuevamente y luego analiza el archivo nuevamente y obtiene las referencias porque las referencias del archivo no cambiaron, no es necesario volver a calcularlo.
7. Compilación Incremental y Cambios Granulares
Cuando reordenamos las importaciones, el sistema puede seguir el grafo y solo volver a calcular lo que se necesita. Un ejemplo más grande con 10,000 archivos demuestra la capacidad del sistema para realizar cambios granulares en los archivos. La compilación incremental en TurboEngine permite volver a calcular solo los archivos afectados, lo que hace que el sistema sea independiente del tamaño de la aplicación.
En el caso de que hagamos algo como reordenar las importaciones, entonces también subimos y obtenemos las referencias y bajamos por el árbol. Así que todo el sistema es realmente genial porque luego podemos ir desde el evento de observación e invalidar solo ciertas partes de nuestra aplicación, independientemente de cuán grande sea la aplicación, por lo que podemos seguir el grafo y solo volver a calcular lo que se necesita.
Así que probémoslo con un ejemplo más grande. He preparado un directorio aquí con muchos archivos, 10,000 archivos, y básicamente todos se importan desde este tipo de función. Así que lleva un tiempo compilar eso inicialmente porque tenemos que, oh espera, detente, quería desactivar el registro para eso, por eso aquí hay una fuente que imprime millones de líneas de código. Se tarda 10 segundos en compilar eso, pero luego podemos usar el mismo sistema para hacer estos cambios granulares en nuestros archivos, como esperar a que se hagan.
Así que si solo cambio este archivo pequeño aquí, entonces hará lo mismo si lo guardo, será muy rápido en leer un archivo y verlo igual. Y si cambio el archivo de índice, es un poco más lento, porque analizar el archivo ya lleva un segundo más o menos. Así que tenemos que agregar algo aquí, lleva un poco más de tiempo, pero no tiene que copiar todos los archivos, porque eso es básicamente lo mismo. Y si agrego algunos modules más al archivo aquí, entonces volverá a calcular eso y copiará los archivos que son nuevos, y ese tipo de cosas. Hay un sistema de compilación incremental en TurboEngine, es un poco simplificado, es un poco más complejo en la práctica, pero creo que entiendes la idea de cómo puedes abordar el problema de abajo hacia arriba y volver a calcular solo los archivos afectados, y hacer que el sistema sea independiente del tamaño de la aplicación, y solo dependiendo del tamaño del cambio.
8. Integración con otras herramientas de Monorepo
Gracias por tu interés en la integración de TurboPack con otras herramientas de Monorepo. TurboPack está diseñado para ser integrable con todas las herramientas, ofreciendo funcionalidad de línea de comandos y herramientas de integración para la caché. Nos enfocamos especialmente en la integración profunda con TurboRepo, nuestra herramienta, para compartir cachés y crear una experiencia más integrada. Al igual que nuestra integración con NX, comenzar con una colaboración cercana nos permite construir una prueba de concepto y asegurar la usabilidad de TurboPack para otras herramientas. Trabajar con un equipo familiar facilita la comunicación y comprensión de los requisitos de la herramienta.
Gracias. Parece que lo he entendido en la mente de las personas. ¿Qué hay de la integración con otras herramientas de Monorepo como NX, aparte de Turbo? TurboPack es integrable en todas las herramientas, puedes llamarlo una cosa de línea de comandos, y probablemente ofrecemos algunas herramientas de integración para la caché, pero también queremos trabajar un poco más profundamente con TurboRepo, porque esa es nuestra herramienta, y nuestra idea es poder compartir las cachés entre TurboRepo y TurboPack, para que sea más integrado, pero creo que ofrecemos las mismas capacidades de integración para otras herramientas de Monorepo. Creo que tiene sentido, como ir con algo con lo que estás familiarizado, y puedes trabajar juntos para construir esta prueba de concepto. Sí, es similar a lo que hicimos con NX. Construimos TurboPack con una integración profunda con NxJS, porque ahí es donde podemos trabajar juntos y llegar a un estado en el que tengamos todo lo que sabemos que se necesita para TurboPack, y luego lanzar TurboPack como una cosa independiente, para que también sea utilizable para otras cosas. Pero comenzar inicialmente con algo que conoces y con un equipo con el que puedes trabajar es mucho más fácil de manejar que comunicarse con herramientas externas, y no saber qué se necesita para la herramienta y sí. Sí, no puedes exigir cosas de la misma manera. No puedes decir, hey, realmente necesitamos impulsar esto. Es mucho más difícil negociar eso. Sí. Muy bien, genial.
9. Error Display and TurboPack Reliability
¿Cómo se muestra el mensaje de error en la misma línea donde se produjo en tu IDE? ¿Cuándo puedo usar TurboPack de manera confiable? Nuestra prioridad es hacer que Next funcione. Queremos hacer de TurboPack una herramienta independiente, más simple y fácil de usar. No admitimos complementos de Webpack tal como están actualmente.
Entonces, el siguiente. ¿Cómo se muestra el mensaje de error en la misma línea donde se produjo en tu IDE? ¿Qué? Sí, eso es un complemento, creo. No estoy seguro cuál, pero uno de estos. Quien haya hecho esta pregunta, puedes encontrar a Tobias más tarde y, ya sabes, exponer tu caso.
De acuerdo. ¿Cuándo puedo usar TurboPack de manera confiable sin... Sí. Sí. Como dije, nuestra prioridad es hacer que Next funcione, lo cual debería ser pronto. Tan pronto como el caso ya esté funcionando para muchas aplicaciones, pero hemos descubierto algunos casos excepcionales y más cosas modernas de Next.js. Y luego queremos... Nuestro objetivo es hacerlo una herramienta independiente porque queremos tener una herramienta similar en comparación a Webpack donde se pueda usar de forma independiente. Pero hay muchas cosas que resolver, como cómo tomar una configuración, cuál es la API. Y no queremos cometer los mismos errores, esta configuración como Webpack, así que queremos hacerlo más simple, más fácil de usar. Así que tenemos que pensar en eso. Y técnicamente, existe el independiente. Es el CLI de dos paquetes. Puedes compilarlo tú mismo en nuestro repositorio, pero no es realmente... No tiene configuración. Es solo... Tal vez no mucho todavía. No es algo que hagamos público todavía, pero planeamos hacerlo y llevará un poco de tiempo. Pero lo lograremos.
Sí, por supuesto. Y sabías que esto iba a llegar. Entonces, ¿qué pasa con las aplicaciones que usan complementos de Webpack? ¿Cuál es el plan de migración? ¿Qué consejos tienes? Sí. No admitiremos complementos de Webpack tal como están actualmente. Así que no es... Porque los complementos de Webpack pueden integrarse profundamente en Webpack y modificar cada pequeño detalle de eso. Y queremos ofrecer un sistema de complementos similar, tal vez no tan profundo como los complementos de JavaScript.
10. Sistema de complementos de Toolpag
Queremos ofrecer un sistema de complementos para Toolpag, pero debido a la arquitectura diferente, requiere la adaptación de los complementos de Webpack. Estamos explorando opciones para importar complementos y considerando colaboraciones con Copilot y ZPT.
Entonces, al igual que la API de complementos de Rust, realmente podemos profundizar en Toolpag. Pero queremos ofrecer algún tipo de sistema de complementos. Y probablemente debas adaptar tu complemento de Webpack a un complemento de Toolpag. Pero eso es algo que queremos hacer. Debido a la arquitectura diferente. No es algo que podamos hacer fácilmente. Hacer que el complemento de Webpack funcione. Tal vez los más simples. Pero debido a la arquitectura diferente, queremos tener algún tipo de sistema de complementos diferente. Y sí. De acuerdo. Sí. Tal vez también podamos importar a través de eso. Como a través de la documentación o algo así. Sí. Sí. Tal vez podamos hacer que Copilot lo adapte. Y hacer que ZPT lo adapte.
11. Comparación con el constructor de Bunn
Todavía no hemos comparado los tiempos de compilación con el nuevo constructor de Bunn. Dado que nos enfocamos en la experiencia de compilación incremental y tenemos un servidor de desarrollo, mientras que Bunn se enfoca en compilaciones rápidas para producción, es difícil hacer una comparación directa. Sin embargo, el autor de Bunn mencionó en Twitter que harán una comparación. Veremos cómo resulta.
Sí. Por supuesto. Y luego creo que probablemente nuestra última pregunta para esta vez. ¿Comparaste los tiempos de compilación con el nuevo constructor de Bunn? Aún no hemos comparado eso. Pero creo que el autor de Bunn lo hizo. Pero este problema. Porque actualmente solo tenemos un servidor de desarrollo. Bunn solo tiene un sistema de compilación para producción. Por lo tanto, es difícil comparar en este momento. Porque nos enfocamos en la experiencia de compilación incremental. La experiencia de desarrollo. Y ellos se enfocaron en compilaciones rápidas para producción. Así que creo que eventualmente habrá una comparación. Pero probablemente nosotros no lo hagamos. Al menos no públicamente. Y tal vez ellos probablemente lo hagan. Al menos dijeron en Twitter que lo harán. Así que veremos. Sí. Por supuesto. Y tal vez rápidamente. ¿Por qué eligieron enfocarse en el lado de la developer experience? Creemos que esto es lo que la gente más desea. Pasan la mitad del día enfrentando la developer experience. La experiencia de producción solo ocurre después de hacer la solicitud de extracción. Y en su mayoría es asíncrona. Eso realmente no es un gran problema para ustedes. Entonces, el punto problemático es la developer experience. Y por eso nos enfocamos en eso. Sí. Por supuesto. Genial. Bueno, gracias. Y todos saben dónde encontrar a Tobias más tarde. Y por favor, otro aplauso. Gracias.
Comments