Video Summary and Transcription
Esta charla discute el concepto de validación en tiempo de ejecución en TypeScript y cómo une la brecha entre la expresividad de TypeScript y las capacidades en tiempo de ejecución. El orador explica la evolución del análisis ascendente y el analizador de reducción de desplazamiento que hizo posible la validación en tiempo de ejecución. Se destacan los beneficios de la validación en tiempo de ejecución en términos de flexibilidad, escalabilidad y eficiencia. Se enfatiza la integración de la validación y el sistema de tipos, junto con las capacidades de validación mejoradas y las nuevas características ofrecidas por el marco Archetype.
1. Introducción a la Validación en Tiempo de Ejecución en TypeScript
Hola a todos, mi nombre es David. Estoy aquí para hablar sobre la validación en tiempo de ejecución en TypeScript. Hay excelentes soluciones disponibles, pero existe una brecha entre la expresividad de TypeScript y sus capacidades en tiempo de ejecución. Me pregunté cómo podríamos expresar un tipo de TypeScript para su uso en tiempo de ejecución. La respuesta es simple: aprovechar las mismas estructuras que JavaScript. Con algunos ajustes, podemos lograr una correspondencia uno a uno entre TypeScript y la validación en tiempo de ejecución.
Tengo mucha suerte de estar aquí hoy y tener la oportunidad de hablarles sobre uno de mis temas favoritos en el ecosistema de TypeScript, del cual hay muchos. Pero como muchos de ustedes sabrán, uno de ellos es quizás el más cercano y querido para mí, que es la validación en tiempo de ejecución. Esto es algo que se ha discutido con mucha frecuencia en la comunidad en el pasado y ha sido resuelto muchas veces por algunos ingenieros fantásticos. Así que hay algunas soluciones excelentes disponibles para esto.
Pero cuando estaba analizando este problema, no pude evitar sentir que había una brecha entre la expresividad y el poder de TypeScript y su sistema de tipos y su sintaxis en comparación con lo que estaba disponible en tiempo de ejecución a través de alguna combinación de métodos de construcción u otras cosas así. Así que hace un par de años me hice esta pregunta muy peligrosa de cuál es lo más cercano que podríamos llegar a expresar un tipo de TypeScript de esta manera para usarlo en tiempo de ejecución. Sorprendentemente, la respuesta es bastante simple. Y no creo que haya tanta ambigüedad como cuando se responde la mayoría de los problemas de diseño como este. Afortunadamente, TypeScript aprovechó muchas de las mismas estructuras para sus literales de objetos, literales de tuplas, etc. que están integradas en JavaScript.
Así que podemos hacer lo mismo. Podemos decir que el nombre es una cadena. Claro. De acuerdo. Así que tenemos que incrustar esto. Tenemos un dispositivo. Tenemos un objeto anidado aquí. Plataforma. Esto será un poco complicado porque ya están en una cadena. Probablemente tengamos que hacer alguna especie de comillas anidadas o algo así. Para que sepamos que todavía estamos en un literal de cadena ya que Android e iOS no son palabras clave. Y solo hay un par de formas en las que podríamos hacer esto. Pero vamos a ir con esto. Así que creo que esto es lo más cercano que podríamos llegar a una correspondencia uno a uno aquí, si comparamos estas dos cosas, miren esto. Saben, aquí no tenemos un as const, pero básicamente estas dos tienen una correspondencia muy fuerte, ¿verdad? Entonces la pregunta es, ¿esta estructura es algo que podríamos teóricamente usar para la validación en tiempo de ejecución de una manera que capture la esencia de lo que hace que los índices de TypeScript sean tan poderosos y extienda eso para algunas de las necesidades fundamentales de un validador en tiempo de ejecución.
Bien, avanzando rápido, más o menos unos meses. Básicamente, lo que tengo aquí es que necesitamos alguna forma de tomar esa estructura original que se ve exactamente como un tipo de TypeScript pero inferir de vuelta el tipo de TypeScript original sin toda esa sintaxis incrustada en tiempo de ejecución que está diseñada para encajar dentro de JavaScript. Así que esencialmente, después de algunas iteraciones, llegué a esta solución inicial.
2. Evolución del Análisis Sintáctico de Arriba hacia Abajo
Puedes ver que lo llamé parse type, que eventualmente evolucionó a arc type. Fue el comienzo de mi iteración en tipos, type iterate. Tenía algunas limitaciones inherentes. Agregué capacidades de inferencia cíclica. Agregué análisis de funciones por alguna razón. Este enfoque de arriba hacia abajo tenía poco control sobre la precedencia y otros problemas. Eventualmente, me di cuenta de que no iba a funcionar para una solución fundamentalmente escalable.
Puedes ver que lo llamé parse type, que eventualmente, como probablemente puedas adivinar, evolucionó a arc type. Y fue solo un proceso simple de un par de años y algunas iteraciones. Como puedes imaginar, este es el comienzo de mi iteración en tipos, type iterate. Y ha sido un tema desde entonces. Pero hay algunas etapas intermedias y podrás ver un poco sobre cómo evolucionó esto.
Entonces, este es mi intento inicial en las cosas. Tiene algunos tipos bastante complejos. Es este enfoque de análisis sintáctico de arriba hacia abajo que realiza muchas coincidencias de patrones. Es bastante... diría familiar. Quiero decir, esto todavía es un poco loco. Pero en términos de lo que se había hecho en TypeScript en el pasado para el análisis sintáctico, es como, ¿coincide esto con esta expresión de plantilla? Si lo hace, infiere esta parte de la sintaxis. De lo contrario, haz lo mismo. Así que algo sencillo. Pero descubrí que tenía algunas limitaciones inherentes.
Obtuve algunos mensajes de error agradables. Impresionantemente, una de las primeras cosas que agregué fueron estas capacidades de inferencia cíclica. Así que puede hacer eso. Eso es genial. Agregué análisis de funciones por alguna razón, que es inútil para la validación en tiempo de ejecución. Creo que simplemente pensé que era genial o algo así. No estoy seguro de por qué estaba ahí. Este problema de precedencia seguiría siendo un dolor de cabeza para mí. Porque tenía muy poco control con este método de arriba hacia abajo en términos de asegurar, por ejemplo, que el operador de matriz tuviera una precedencia mayor que el operador de unión. Y esa fue realmente la manifestación más directa de este problema. A medida que avanzaba, descubriría otros, como tratar de representar literales de cadena como esto, sí o no. Bueno, debes asegurarte de que no se interprete como el literal de cadena que contiene ese operador de unión. Así que este enfoque de arriba hacia abajo, ya sabes, voy a seguir intentando solucionarlo durante un tiempo. Pero eventualmente me daré cuenta de que simplemente no va a funcionar. Este caso fue el específico que me hizo darme cuenta, está claro que no podré usar este enfoque en absoluto si quiero una solución fundamentalmente escalable.
3. Analizador Shift-Reduced y Mejora de Eficiencia
Tanto en términos de rendimiento como en términos de capacidad para analizar una sintaxis más compleja. La idea de un analizador shift-reduced es un avance fundamental que hizo posible arctype. En lugar de un enfoque de arriba hacia abajo, llevamos un registro de un estado con varias ramas y datos para analizar una cadena. TypeScript maneja esto de manera eficiente, lo que nos permite representar una sintaxis compleja y escribir tipos cercanos al código de ejecución imperativo.
Entonces, ¿a dónde vamos desde aquí? ¿Cómo solucionamos este problema? Este tipo ridículo con el operador de unión y dos literales de cadena, ¿cómo lo vamos a hacer? El emparejamiento de patrones está un poco limitado aquí en términos de lo que vamos a obtener. Entonces, ¿a dónde vamos?
De acuerdo. Volvamos al presente. Estamos de vuelta en nuestro editor. Fue ese problema el que realmente me llevó al primer avance fundamental que hizo posible arctype. Es la idea de un analizador shift-reduced. Es bastante diferente del enfoque de arriba hacia abajo que teníamos antes. Veamos si puedo encontrar esto.
Básicamente, en lugar de tener este enfoque de arriba hacia abajo donde estamos emparejando varias expresiones en toda la cadena, esto, ya sabes, hay mucho sucediendo aquí. Pero lo esencial que realmente necesitas entender es que en realidad estamos llevando un registro de un estado que tiene varias ramas y grupos y otrosdata varios que necesitamos para analizar una cadena. Y es solo un algoritmo simple donde hacemos un bucle, verificamos si tenemos algo en qué operar. Por ejemplo, tal vez sea una cadena o un número o el literal cinco. O no lo tenemos y necesitamos obtener algo en qué operar. Así que seguimos haciendo un bucle así. Si no tenemos nada en qué operar, obtenemos algo en qué operar. Y si lo tenemos, averiguamos qué se está operando en él. Así que en realidad es muy sencillo. Y se adapta muy bien a este problema.
TypeScript maneja esto de manera increíblemente eficiente, como veremos un poco más adelante. Básicamente, utilizando este método, pude representar una sintaxis mucho más compleja de lo que hubiera podido hacer de otra manera. Pero también permitió que TypeScript lo hiciera de manera mucho más eficiente, lo cual fue realmente sorprendente para mí. Porque la lógica no es trivial. En mi opinión, esta es una de las partes más hermosas de la implementación a nivel de tipo aquí. Puedes ver una especie de paralelismo entre la implementación a nivel de runtime del analizador y la implementación estática del analizador. Y ciertamente, ya sabes, en este caso, definitivamente me esforcé un poco para crear ese paralelismo, pero realmente demuestra, en mi opinión, que con algunos de los patrones correctos en mente, se pueden escribir tipos que realmente se acercan mucho al código de runtime imperativo en términos de su expresividad y sus capacidades. Por supuesto, eventualmente llegarás a los límites del sistema de tipos de TypeScript mucho más rápido de lo que llegarás a los límites de tu lenguaje en general. Pero para algo como esto, funciona sorprendentemente bien. Puedes ver, nuevamente, algunos de los patterns aquí donde estamos verificando la parte restante de la cadena, simplemente obteniendo el siguiente token y, en función de eso, tomando algunas decisiones sobre qué hacer con el estado y cómo continuar.
4. Beneficios de la Validación en Tiempo de Ejecución en TypeScript
Esta es una forma notablemente efectiva de analizar cadenas en TypeScript. Proporciona flexibilidad para manejar casos complejos y puede escalar según sea necesario. Es eficiente y tiene el potencial de revolucionar la creación de tipos para la validación en tiempo de ejecución.
Entonces, una vez más, esta es realmente una forma notablemente efectiva de analizar cadenas en TypeScript. Todos deberían usarlo por completo, si estás analizando algo más que trivial, esta es absolutamente la forma de hacerlo. Manteniendo el estado actual, obteniendo los siguientes caracteres, y te brinda tanta flexibilidad como necesites para manejar estos casos potencialmente muy complejos donde tal vez tengas que desambiguar entre varios operadores o cosas así, cosas que nunca podrías hacer con un enfoque de arriba hacia abajo. Así que realmente puede escalar tanto como lo necesites, lo cual fue sorprendente para mí descubrir. Pensé que había llegado al final del camino en términos de mucha de la utilidad del proyecto cuando me encontré con ese último problema, pensando que esta implementación estaría fuera de alcance en términos de rendimiento para el compilador y todo eso. Pero al contrario, en realidad es mucho más eficiente. Y este fue el avance que tuve, donde pensé, wow, esto realmente tiene mucho potencial. Si puedo analizar sintaxis arbitraria, y es realmente eficiente hacerlo, y luego puedo inferir eso uno a uno como tipos, ¿quién sabe realmente a dónde podría llegar esto? Esto podría ser increíble para crear nuestros tipos para tiempo de ejecución, obtener validación en vivo y todo eso. Así que estaba realmente, realmente emocionado.
5. TypeScript Type Safety and Performance
Si eres un veterano experimentado del ecosistema de TypeScript, es posible que te preocupes al ver tipos como este. Las bibliotecas con tipos similares a menudo hacen que los servidores de TypeScript se bloqueen. Para solucionar esto, creamos un marco llamado una prueba que evalúa la cantidad de instancias contribuidas por cualquier expresión. Garantizamos una inferencia precisa y eficiente, incluso para tipos complejos. Las capacidades del analizador a nivel de tipo son avanzadas y brindan una experiencia de edición similar a un IDE. Las cadenas ahora son seguras en el sistema de tipos de TypeScript.
Entonces, si eres un veterano experimentado del ecosistema de TypeScript y ves un tipo que se ve como este, supongo que es probable que te preocupes. Porque a pesar de mi entusiasmo, tu experiencia podría ser que cuando las bibliotecas tienen tipos como este, es muy frecuente que, por ejemplo, bloqueen tu servidor de TypeScript. Esa es quizás la reacción más frecuente que recibo cuando alguien ve Archetype por primera vez, multiplicada por tres o cuatro. Pero ten la seguridad de que el problema en sí mismo necesitaba que encontráramos algunas soluciones mejores para las pruebas a nivel de tipo, incluidas las pruebas de rendimiento.
Entonces creamos un marco llamado una prueba que puede evaluar a nivel de tipo la cantidad de instancias contribuidas por cualquier expresión. Por lo tanto, puedes ver que tenemos esta métrica muy detallada para cada tipo de expresión en Archetype. Podemos garantizar que se infiere de manera eficiente y precisa, incluso para tipos muy complejos como este. Sabemos que hará exactamente lo que esperas y lo hará muy rápidamente. Por lo tanto, no notarás ningún retraso en absoluto.
Y en términos de afirmaciones funcionales, por ejemplo, podemos ver nuestras pruebas de divisibilidad aquí. Un poco más de espacio. Puedes ver que los tipos de afirmaciones que estamos haciendo aquí a nivel de tipo tienen todo tipo de errores sintácticos y semánticos. En este punto, las capacidades del analizador a nivel de tipo son realmente muy avanzadas y prácticamente están a la par con una experiencia de edición nativa de un IDE. Por ejemplo, digamos que quieres definir algún tipo. Digamos que es un número. Por supuesto, obtienes autocompletado para todo en tu ámbito, incluyendo todas las palabras clave. Y tal vez sea divisible por dos. Eso no afectará la inferencia. Es una restricción en tiempo de ejecución. Pero si escribo, oh, ves que falta un operando allí. Oh, cero. Obtienes un error específico para eso. Digamos que intento dividir por desconocido. Básicamente, podemos reducir exactamente cuál es el problema cada vez que algo sale mal como esto. La idea de que las cadenas no sean seguras en cuanto al tipo, eso también se desecha. Nuevamente, esto realmente está a la par con la experiencia nativa de un IDE, simplemente desde el sistema de tipos de TypeScript. Así que este es un punto realmente emocionante en el que estamos con todo esto. Y he estado tan emocionado de compartirlo con todos ustedes durante tanto tiempo. Pero incluso con todo esto implementado, había algo que me molestaba mientras me preparaba para lanzar esto hace bastante tiempo. Y era algo sobre la forma en que los tipos se componían y se reducían juntos que no se sentía del todo correcto.
6. Enhancing Type Comparisons and Runtime Constraints
Quería la capacidad de comparar tipos arbitrarios entre sí, reducir intersecciones y uniones, y tener un sistema de tipos en tiempo de ejecución. TypeScript ahora trata las restricciones en tiempo de ejecución, como divisores, rangos y expresiones regulares, con la misma rigurosidad que las comparaciones entre otros tipos.
No podía ponerle nombre porque realmente trabajé mucho con sistemas de tipos en el pasado. Pero finalmente descubrí lo que me faltaba y lo que quería era la capacidad de comparar tipos arbitrarios entre sí, la capacidad de reducir intersecciones y uniones de manera completa, ir más allá de las capacidades de validación superficial y tener verdaderamente un sistema de tipos en tiempo de ejecución. Y puedes ver parte de eso después de una iteración muy significativa que finalmente se ha hecho realidad, donde además de las restricciones estándar que TypeScript cubre, trata las restricciones en tiempo de ejecución, como divisores, rangos, expresiones regulares, etc., con la misma rigurosidad con la que se tratan las comparaciones entre números, cadenas, objetos y cosas así.
7. Integration of Validation and Type System
Todo está integrado en un único sistema seguro de tipos que puedes utilizar, por ejemplo, para hacer más que simplemente definir validadores. Esencialmente, es un sistema de tipos completo en este punto, además de todas las capacidades de validación.
Todo está integrado en un único sistema seguro de tipos que puedes utilizar, por ejemplo, para hacer más que simplemente definir validadores. Puedes compararlos entre sí. Puedes decir, sabes, ¿este tipo numérico extiende a algún otro tipo numérico que tal vez sea un entero o sea divisible solo por uno. Y lo hará, porque es más específico que eso. Así que esencialmente, es un sistema de tipos completo en este punto, además de todas lasvalidation capacidades, y realmente fue un problema increíble en el que trabajar. Me dio una comprensión mucho más profunda del tipo de trabajo que hace el equipo de TypeScript, y realmente fue simplemente
8. Validation Capabilities and New Features
Archetype ofrece mejoras significativas en las capacidades de validación, como la discriminación automática de uniones y procesos de validación en tiempo de ejecución optimizados. Supera a otros validadores en términos de rendimiento, especialmente en casos donde puede aprovechar su profundo conocimiento del sistema de tipos. La próxima versión de Archetype introduce nuevas características como capacidades de inferencia genérica y la capacidad de definir palabras clave personalizadas dentro de un ámbito. Estas características mejoran la flexibilidad y el poder de la validación en tiempo de ejecución en TypeScript.
Hay mucha belleza en tener la oportunidad de trabajar con este tipo de tipos. En general, encontrar la representación más simple posible que permita componer tipos juntos, compararlos entre sí y reducirlos completamente fue realmente un problema increíble en el que trabajar, y condujo a muchas capacidades nuevas y significativas dentro de la validación también.
Al tener esta información de tipo, por ejemplo, podemos hacer cosas como discriminar uniones automáticamente. Entonces, por ejemplo, en Archetype, puedes definir una unión directamente de esta manera, y básicamente identificaremos dentro de nuestro sistema de tipos el discriminante óptimo, o conjunto de discriminantes, en caso de que requiera múltiples comprobaciones para determinar en qué rama te encuentras, y optimizaremos implícitamente el proceso de validación en tiempo de ejecución aprovechando esas comprobaciones para que la mayoría de las uniones se verifiquen en tiempo constante, sin que ni siquiera tengas que pensarlo, mientras que otros validadores existentes sin esa información del sistema de tipos simplemente lo verificarán de forma lineal o requerirán que optes por especificarlo manualmente a través de una sintaxis como esta. Zod, creo que en realidad recientemente estaba teniendo algunos problemas para mantener sus tipos de unión discriminados y, incluso al definirlo explícitamente, es realmente costoso a nivel de tipo. Puedes ver que hay una gran disparidad de aproximadamente 20 veces menos instancias para la unión aquí, y por supuesto, también es un tipo mucho más sencillo de definir y leer. Pasas el ratón por encima, ves exactamente cuál es la unión, mientras que aquí, quiero decir, no uso constantemente Zod, pero es mucho más difícil saber qué está sucediendo exactamente sin poder ver muchos de los parámetros, y tal vez tengas que extraerlo con Zod.infer. Pero hay algunas disparidades bastante significativas, y en gran medida provienen de las dos áreas principales que hemos discutido. La capacidad de utilizar el sistema de tipos en tiempo de ejecución para optimizar el proceso de validación, por lo que no necesitamos depender de información proporcionada por el usuario como esta, así como las optimizaciones en el sistema de tipos de TypeScript para garantizar que esto se pueda analizar de manera mucho más eficiente.
En términos de rendimiento en tiempo de ejecución en general, Archetype ha sido muy optimizado y, para el caso base de verificar propiedades simples en un objeto, será básicamente idéntico a algunos de los validadores de tiempo de ejecución más rápidos que existen, como Tibia y TypeBox. Pero donde realmente brilla es en los casos en los que puede aprovechar su profundo conocimiento del sistema de tipos, por ejemplo, para discriminar implícitamente una gran unión. Podría resultar fácilmente en velocidades de 20 o 30 veces más rápidas que incluso los validadores más eficientes en casos donde, nuevamente, puede, a través de múltiples pasos, identificar qué comprobaciones debe realizar secuencialmente para determinar en qué rama de una unión se encuentra, y a menudo podremos verificar esa unión en tiempo constante sin que ni siquiera tengas que pensarlo. Mientras que, nuevamente, las alternativas generalmente son no tener esa opción en absoluto o tener que construir esa lógica manualmente y mantenerla a medida que cambia tu tecnología. Y como punto de referencia, esos casos base de los que hablaba para los validadores de rendimiento existentes ya son aproximadamente 400 veces más rápidos que Zot para este tipo de escenarios. Entonces, ciertamente, si necesitas optimizar el rendimiento, también habrá grandes ganancias en esa área.
Sé que estamos quedando sin tiempo aquí, solo quería demostrar algunas de mis características favoritas de la próxima versión, que espero, supongo, es la versión actual ahora que estás viendo este video. Ha sido difícil llegar a este punto, hay tantas cosas que quería cubrir. Definitivamente, el alcance creció mucho más de lo que había anticipado en términos de las mejoras para la versión beta. Pero espero que, si estás viendo esto y pude envolver todo de la manera que pretendía, ahora puedas probar todas estas cosas. Pero de todos modos, quería mostrar algunas de estas capacidades de inferencia genérica, que creo que son muy interesantes. Entonces, hay una nueva característica en beta que te permite definir estos tipos genéricos. Puedes definir una firma como esta y luego hacer referencia a un parámetro de tipo y luego instanciarlo más adelante y obtendrás esta inferencia uno a uno. Nuevamente, es como en TypeScript. Y luego, funcionará también para la validación en tiempo de ejecución. Puedes definirlos dentro de un ámbito, que es básicamente una forma de vincular palabras clave personalizadas a los tipos que desees. Esta es probablemente la más sorprendente dentro del sistema de tipos de TypeScript que hay aquí. Puedes ver que tenemos este genérico alternativo que toma A y B y se llama a sí mismo, y luego intercambia A y B. Esto es simplemente un genérico recursivo clásico de TypeScript, pero en Archetype esto realmente se puede inferir y puedes ver que, de hecho, obtienes estas entradas alternas. Si lo instanciamos más tarde, lo pasamos de encendido a apagado en lugar de 01, obtienes el resultado esperado y esos dos se alternan. Así que el hecho de que todo esto termine siendo posible dentro de TypeScript fue realmente increíble de descubrir. Nuevamente, felicitaciones al equipo de TypeScript por crear un analizador tan increíble, una herramienta increíble que admitiría probablemente de manera no intencional este nivel de profundidad solo dentro de su propio sistema de tipos. Creo que es increíble.
9. Manejo de Morfos y Transformaciones en Archetype
La última característica que quiero mostrar son los morfos, que manejan las transformaciones en Archetype. Están integrados en el sistema de tipos y permiten un manejo detallado de las transformaciones a nivel de propiedad. Esto evita el problema de coloreado de funciones y proporciona una representación visual de las transformaciones. Es una característica única y útil.
Entonces, la última característica que quería mostrar rápidamente, que en realidad existe en alpha, pero porque es una parte realmente importante de lo que Archetype hace además del ámbito, sobre lo que apenas tuve la oportunidad de hablar, pero desafortunadamente solo tengo 20 minutos, es muy poco tiempo, hay mucho aquí, pero por supuesto estoy emocionado de seguir con todos ustedes. Los morfos son cómo Archetype maneja las transformaciones. Lo que es realmente único de ellos es que están integrados en el sistema de tipos. Entonces, en lugar de marcar todo superficialmente como un efecto donde ya no se puede combinar con tipos, se manejan de manera detallada para que solo en la propiedad particular donde ocurre esta transformación se considere un morfo y no se pueda combinar con otro morfo. Pero, en general, evita el problema de coloreado de funciones donde, de manera más amplia, el tipo aún se puede usar como un tipo que se intersecta con otros tipos, se une con otros tipos, etc., etc. Y obtienes esta representación visual realmente agradable de exactamente qué tipos de transformaciones ocurrirán. Así que creo que también es una característica muy agradable y espero que les guste. Entonces, para resumir en este punto, supongo que estamos volviendo al principio aquí. Tenemos esta estructura y tal vez incluso intentemos importar directamente y ver qué obtenemos, y con suerte, obtendremos un buen resultado. Entonces, puedo hacerlo directamente desde el nombre del paquete aquí, y si simplemente envolvemos esto, eso es agradable, también aplica el resaltado desde la extensión. Pero ahí lo tienes. Es exactamente lo que anticipamos, exactamente como el tipo original, y realmente tengo que decir que la experiencia del desarrollador para esto es bastante asombrosa en comparación con las cosas que hay por ahí. Siento que solo obtener este autocompletado inmediato, nuevamente incluso dentro de las expresiones obtendrás estas completaciones completas que funcionan, obtienes errores inmediatos muy receptivos. Si hay algún problema con la expresión que se está analizando, realmente es una experiencia increíble que está absolutamente a la par, e incluso a menudo se siente mejor que editar nativamente este tipo de tipos en tu IDE. Estoy muy emocionado de compartir esto con todos ustedes. Realmente ha sido un viaje llegar hasta aquí, como tal vez hayas inferido de la charla, pero realmente espero seguir adelante. Espero que tengan algunas preguntas para mí y podamos hablar más al respecto durante la sesión de preguntas y respuestas y en el futuro. Muchas gracias por tomarse el tiempo para escuchar mi charla. Nuevamente, realmente lo aprecio. Agradezco todo el amor y apoyo que he recibido de la comunidad que me ha permitido llegar hasta este punto porque ha sido mucho, pero también ha sido una de las cosas más increíbles que he tenido la suerte de hacer. Muchas gracias y espero poder responder sus preguntas pronto.
Comments