Dejando TypeScript

Cambiando de TypeScript

Chander Ramesh

17 de septiembre de 2025

Nos encanta, TypeScript

Durante casi cinco años, Motion ha operado en un monorepo de TypeScript bastante grande. En su punto máximo, tenía aproximadamente ~2.5 millones de líneas de código después de excluir comentarios, node_modules, etc. Para gestionarlo, utilizamos el sistema de construcción Turborepo de Vercel, bastante excelente.

Este no es un post de blog odiando a TypeScript, ¡todo lo contrario! Motion probablemente ni siquiera habría sobrevivido hasta hoy sin él. Motion ha pivotado más de veinte veces. TypeScript nos permitió validar rápidamente ideas y, lo más importante, permitió que todos nuestros desarrolladores operaran de manera verdaderamente full stack. Podíamos compartir código rápidamente y fácilmente entre el frontend y el backend, unificamos enormemente nuestro entorno de desarrollo y, por supuesto, aprovechamos el extenso y vibrante ecosistema.

Nuestro frontend está escrito en React. ¿Aplicaciones móviles? React Native. ¿Aplicación de escritorio? Electron. ¿Infraestructura como código? Crean o no, TypeScript. Éramos (y seguimos siendo) ¡grandes fans de TypeScript!

Pero con el tiempo, las cosas comenzaron a cambiar.

El sueño de compartir código en todas partes nunca se materializó realmente. Desde el principio, hubo problemas para compartir código entre React Native y el resto del ecosistema. Teníamos una versión diferente de React y Tailwind del resto de la aplicación web, por lo que muchas bibliotecas principales simplemente nunca se compartieron. Cuando logramos compartir código, los desarrolladores a menudo olvidaban lo móvil por completo al realizar cambios en las bibliotecas compartidas, lo que resultaba en un frecuente juego de “quién rompió la aplicación móvil”. Eventualmente, tuvimos que abandonar React Native para ciertas pantallas donde el rendimiento era crítico.

Con el tiempo, lidiamos con todos los puntos dolorosos típicos de una base de código de TypeScript en crecimiento. El servidor de lenguaje se estrellaba con frecuencia, las compilaciones de CI tomaban eternidades (nuestra pipeline algo optimizada regularmente aún toma ~20+ minutos). Una gran parte de esto eventualmente será resuelto por el movimiento a TS-Go — pero este es el problema exacto con TypeScript. Hay mucho en marcha, y no tengo duda de que eventualmente las cosas serán geniales. Pero mientras tanto, como una startup en rápido crecimiento y escalamiento, no estamos en control de nuestro propio destino.

Estamos esperando a que el (increíblemente talentoso) equipo del compilador de TypeScript realice cambios fundamentales. Estamos esperando a que el equipo (muy apasionado) de biome soporte ciertas características para que podamos re-habilitar reglas de lint básicas sin destruir nuestros tiempos de CI.

Tuvimos que esperar a Zodv4 para que nuestro monorepo incluso se compilará porque con v3 nos estábamos encontrando con problemas de profundidad máxima de recursión en el compilador de TypeScript! Para ese asunto, ¿por qué necesitamos Zod en absoluto? Este ceremonial solo es necesario porque TypeScript no tiene tipos en tiempo de ejecución en primer lugar.

La historia no mejora en el lado del ORM. Como descubrimos de la manera difícil, Prisma elimina toda la tabla si tu sentencia where evalúa a undefined. (Nos encontramos con muchos, muchos otros errores en el camino). Drizzle parece mejor, y los patrocinamos durante muchos meses, pero ni siquiera es 1.0 todavía. ¿Quién dice que no tendrá errores durante los primeros años después del lanzamiento?

El ecosistema es grande, vibrante y apasionado. Está lleno de personas que realmente quieren lo mejor para la web y están haciendo lo mejor. Pero al final del día, como CTO, no puedo dejar el destino de mi empresa en las manos de otra persona. La sensación de impotencia que sentía cada vez que nos encontrábamos con uno de estos problemas me estaba carcomiendo — especialmente cuando estos problemas en su mayoría están resueltos en muchos ecosistemas maduros.

No es casualidad que Java y .NET dominen el mundo empresarial. Los tipos en tiempo de ejecución son un hecho. Los tiempos de compilación relativamente rápidos son obligatorios para bases de código que tienen millones de líneas de longitud. Ambos tienen un ORM que ha madurado durante décadas, lo que significa que para operaciones CRUD básicas los desarrolladores simplemente no necesitan pensar en absoluto: solo hay una elección real. Con el multihilo de vuelta en la mesa, no necesitamos iniciar soluciones complejas de orquestación de flujos de trabajo para mantener libre un bucle de eventos de un solo hilo. Y en última instancia, esto significa que los desarrolladores pueden centrarse en resolver problemas novedosos en lugar de encontrar soluciones cada vez más ingeniosas para casos de uso de décadas de antigüedad.

¿Es aburrido? Sí. Pero resolver operaciones CRUD a escala no debería ser emocionante. Debería ser aburrido y seguro. Es 2025 — como industria, deberíamos saber cómo escalar aplicaciones CRUD a cientos de miles de consultas por segundo de manera segura y confiable sin errores, sin cuellos de botella de rendimiento y sin “emoción”.

La puntilla llegó cuando desarrollamos nuestro sistema de flujo de trabajo agentic: necesitábamos un bloque de código que pudiera ejecutar JavaScript arbitrario. Y, por razones de seguridad obvias, esto no podía ejecutarse en un contenedor backend que también ejecutara JavaScript. Esto coincidió con una nueva iniciativa importante (aún no publicitada) en la empresa.

Pero no podemos quedarnos

Entonces, si necesitábamos introducir otro lenguaje para la ejecución de código arbitrario de todos modos… ¿Podríamos simplemente iniciar la nueva iniciativa en el nuevo lenguaje? Pero eso planteó la pregunta de qué lenguaje.

Hace dos años, en un retiro fuera de la ciudad ahora famoso en Chicago, tuvimos un momento similar de bifurcación. TypeScript no tiene y nunca tendrá un sistema de tipos verdaderamente seguro, y incluso en ese entonces teníamos fatiga de TypeScript. Y aunque la mayoría de los desarrolladores backend querían cambiar a un nuevo lenguaje, la guerra de llamas resultante entre las opciones de lenguaje finalmente nos llevó a quedarnos en TypeScript. Esta vez, aprendí la lección: ¡no encuestas! (Broma — en verdad, lo discutí con seis o siete ingenieros, pero no quería tener un foro verdaderamente abierto).

Los criterios eran que quería un lenguaje con recolección de basura, tipado estático que fuera performante y maximizara la productividad. Dado eso, las únicas dos opciones que consideré seriamente fueron C Sharp y Java. Ambos tienen ecosistemas grandes, probados con software extremadamente maduro.

Al final, decidí por C Sharp, incluso aunque nunca lo había usado profesionalmente porque realmente creo que Motion será mucho más productivo:

  1. Entity Framework

Como se mencionó anteriormente, muchos de los problemas frustrantemente simples con los que lidiamos fue la falta de un ORM muy robusto y performante. La mayoría del software B2B se centra en operaciones CRUD básicas, y nosotros enMotion no es una excepción. Después de probarlo yo mismo, quedé absolutamente impresionado por Entity Framework. En Motion, durante mucho tiempo luchamos con los borrados suaves, especialmente con modelos anidados. Pero con los filtros de consulta globales de EF Core, es literalmente una línea de código. Hay muchos más ejemplos de casos de uso realmente avanzados (división de tablas, tokens de concurrencia, y propiedades de navegación). Pero lo que más me impresionó fue lo natural que fue el seguimiento de cambios. A diferencia de Prisma o Drizzle, donde teníamos que pasar las transacciones a cada método en la capa del repositorio, EFCore operaba de esta manera por defecto. Las operaciones simplemente mutaban el contexto, y guardar el contexto realizaba un compromiso transaccional de todos los cambios. En cualquier momento, puedes simplemente crear un nuevo contexto (o guardar el antiguo).

  1. Similitud con TypeScript

Estos dos lenguajes fueron creados por la misma persona, por lo que es natural que se vean y sientan muy similares. Como muchas personas están empezando a darse cuenta (incluso Microsoft lo anuncia), las construcciones del lenguaje son excepcionalmente similares. Ambos tienen async/await, sintaxis similar de funciones anónimas =>, tipos nulos y mucho más. Quizás el mejor sitio web que compara y contrasta los dos lenguajes es este por nuestro propio ingeniero senior Charles Chen

Charles Chen

! La familiaridad fue bastante importante porque significó que el impacto en la velocidad durante esta transición se minimizaría.

  1. Ecosistema

C Sharp es el quinto lenguaje de programación más popular (excluyendo SQL, HTML y Bash). Aunque es menos popular que TypeScript o JavaScript, generalmente no encuentro beneficios continuos linealmente con el tamaño del ecosistema. Más bien, es mucho más similar al crecimiento logarítmico. Después de un cierto punto, se estanca y no hay más utilidad marginal a la biblioteca extra, especialmente cuando la calidad de muchas de las bibliotecas, para ser sinceros, no es tan alta. En ese sentido, es más una prueba binaria: ¿es el ecosistema suficientemente grande — no ¿este es el ecosistema más grande? Para nuestros casos de uso, pasó la prueba con colores. C Sharp tiene una rica historia de event sourcing, soporte de primera clase para el modelo actor, y realmente excelentes buses de mensajes y más.

  1. Codificación de IA

C Sharp y .NET funcionan muy bien con herramientas de codificación de IA. En general, he descubierto que cuanto más fuertes sean las barreras, más lejos puede llegar la IA sin intervención humana. Cuando estaba jugando con F#, que tiene un compilador mucho más estricto que JavaScript o Python, consistentemente encontré que podía enviar a Claude en tareas más largas y complejas sin supervisión y simplemente confiar en el compilador para atrapar la mayoría de los problemas comunes. Este podcast de Jane Street también toca brevemente este mismo fenómeno: cuanto más fuertes sean las barreras, más lejos puede llegar la IA. No es de extrañar que Kotlin y Elixir, ambos muy funcionales, rindan consistentemente mejor con la generación de código (¡C Sharp está en el top 5, aunque!).

Independientemente de la estricción del compilador, C Sharp tiene cientos de reglas de lint bien establecidas y muy eficientes gracias a la muy bien documentada API del compilador Roslyn. Una de esas reglas de lint exige que todos los métodos y campos públicos tengan documentación XML estructurada alrededor de ellos, lo cual es un camino feliz bien conocido para los modelos de IA. Combinado con el enorme volumen de ejemplos bien elaborados de todo el ecosistema .NET, mis hallazgos muy poco científicos, basados en vibraciones, son que los modelos de IA sí escriben mejor código C Sharp que código TypeScript dado un esfuerzo de prompting similar.

Conclusión

.NET no es “cool”, y por una buena razón. Durante muchos años fue solo para Windows, y tenía la reputación de ser bastante arcaico. Cuando Google tomó Silicon Valley (y la web) por asalto, las tecnologías de Mountain View se convirtieron en el estándar de facto.

Pero .NET Core ha estado evolucionando en silencio y de manera constante. Esto es definitivamente no el C Sharp de antaño. Es moderno, Linux-first, y de código abierto. Tiene un rendimiento increíblemente rápido y un runtime maduro y sólido como una roca.

Reconozco que esto es una postura contraria. Pero si eres el tipo de desarrollador que tiene la mente abierta y prefiere un poco de pensamiento en contra de la corriente, entonces creo que Motion es el lugar adecuado para ti. ¡Estamos contratando!

P.D. No somos los únicos que nos damos cuenta de lo productivo que puede ser C#.

via Chander Ramesh (Medium)