How does EsJS work in the background?

main
a5013ebRevert "chore(playground): add Vercel deployment button to GitHub sharing content"
Avatar of Enzo NotarioEnzo Notarioposted

¿Cómo funciona EsJS de fondo?

Avatar of Claude Haiku 4.5Claude Haiku 4.5commented

Voy a explorar el repositorio para entender cómo funciona EsJS internamente.

Ahora veamos la estructura principal del core:

Perfecto, ahora veamos el archivo principal:

Ahora veamos los compiladores:

Veamos ahora los plugins:

Ahora veamos el paquete esbabel que parece ser el compilador:

Perfecto. Ahora déjame ver más del generator para entender cómo genera el código JavaScript:

Excelente. Ahora veamos un ejemplo de plugin para entender la transformación completa:

Perfecto. Ahora déjame ver un ejemplo completo de un plugin:

Ahora veamos el contenido completo del archivo consola:

Excelente. Ahora veamos el archivo utils de plugins:

Perfecto. Ahora veamos el archivo principal de utils:

Perfecto. Ahora tengo una buena comprensión. Déjame crear un diagrama mental y una explicación clara. Déjame ver un test para entender mejor el flujo:

Déjame buscar un ejemplo más simple:

Perfecto. Ya tengo suficiente información. Déjame crear una explicación completa y bien estructurada:

##Cómo funciona EsJS de fondo

EsJS es un transpilador (compilador de lenguaje a lenguaje) que convierte código escrito en sintaxis española a JavaScript válido. Aquí te explico cómo funciona internamente:

###🏗️ Arquitectura General

El flujo de compilación tiene dos caminos principales:

Código EsJS → [Compilador] → JavaScript
                    ↓
            [Plugins de transformación]
                    ↓
            JavaScript optimizado

###📦 Componentes Principales

####1. Core (@es-js/core)

Es el punto de entrada principal. Define la función compile() que orquesta todo:

compile(code: string, options?: CompileOptions): string

Opciones disponibles:

  • from: idioma origen ('esjs' o 'js')
  • to: idioma destino ('esjs' o 'js')
  • compiler: 'essucrase' (por defecto) o 'esbabel'

####2. Dos Compiladores Disponibles

A) EsbabelCompiler (más simple)

  • Usa el paquete @es-js/esbabel
  • Realiza una conversión simple y directa

B) EssucraseCompiler (más completo, por defecto)

  • Usa la librería @es-js/compiler (essucrase)
  • Genera un AST (Árbol de Sintaxis Abstracta)
  • Aplica plugins para transformaciones más complejas

###🔄 Flujo de Compilación Detallado

####Paso 1: Tokenización (Lexical Analysis)

El compilador esbabel primero convierte el código en tokens:

// En esbabel/src/lexer.ts
tokenize(code: string): Token[]

Los tokens identificados son:

  • Identificadores (Identifier) - nombres de variables
  • Palabras clave (Keyword) - si, mientras, para, etc.
  • Caracteres especiales (SpecialCharacter) - operadores
  • Literales de cadena (StringLiteral) - texto entre comillas
  • Caracteres simples (LeftParen, RightCurly, Dot, etc.)

Ejemplo:

Código: "var x = 5"
Tokens: [
  { type: 'Keyword', name: 'var' },
  { type: 'Identifier', name: 'x' },
  { type: 'SpecialCharacter', value: '=' },
  { type: 'SpecialCharacter', value: '5' }
]

####Paso 2: Mapeo de Palabras Clave

El compilador mantiene mapeos de diccionarios en esbabel/src/keywords.ts:

// Ejemplos de mapeos disponibles:
const keywordControl = new Map([
  ['si', 'if'],
  ['mientras', 'while'],
  ['para', 'for'],
  ['retornar', 'return'],
  ...
])

const supportFunction = new Map([
  ['consola', 'console'],
  ['Fecha', 'Date'],
  ['Numero', 'Number'],
  ['Promesa', 'Promise'],
  ...
])

####Paso 3: Generación (Code Generation)

El generador convierte los tokens nuevamente a código, pero reemplazando cada palabra clave española por su equivalente en inglés:

// En esbabel/src/generator.ts
generate(tokens: Token[], reverse = false): string

Lógica:

  1. Itera sobre cada token
  2. Si es una palabra clave/identificador, busca en el diccionario
  3. Si no está en el diccionario, mantiene el nombre original
  4. Concatena todo el código generado

Ejemplo:

Tokens → Generador → "const x = 5"

###🔌 Sistema de Plugins

Cuando se usa EssucraseCompiler, después de la compilación base, se aplican plugins que transforman el AST para casos más complejos.

Ubicación: packages/core/src/plugins/

Cada plugin es responsable de transformar un objeto/método específico:

  • consolaconsole.log(), console.error(), etc.
  • matrizArray.isArray(), Array.from(), etc.
  • cadenaString.prototype.includes(), etc.
  • fechaDate.now(), métodos de fecha, etc.
  • numeroNumber.isNaN(), Number.parseFloat(), etc.
  • promesaPromise.all(), .then(), etc.
  • mateMath.floor(), Math.random(), etc.

Estructura de un plugin (ejemplo: consola):

// packages/core/src/plugins/consola/index.ts

export const report = () => 'Converts consola to console'

export const methods = new Map<string, string>([
  ['afirmar', 'assert'],
  ['limpiar', 'clear'],
  ['escribir', 'log'],
  ['advertencia', 'warn'],
  // ... más métodos
])

export const objects = new Map([['consola', 'console']])

export function replace() {
  return {
    ...replaceObjectStaticMethods({
      from: 'consola',
      to: 'console',
      methods,
    }),
  }
}

Cómo funcionan los plugins:

  1. Definen mapeos (methods, properties, staticMethods, etc.)
  2. Usan funciones de utilidad (replaceObjectStaticMethods, etc.)
  3. Generan reglas de transformación que se aplican al AST
  4. El generador putout aplica estas reglas recursivamente

###💫 Funcionalidades Adicionales

####Invertibilidad de Mapeos

Los compiladores pueden trabajar en ambas direcciones:

// EsJS → JavaScript (normal)
compile(code, { from: 'esjs', to: 'js' })

// JavaScript → EsJS (reverse)
compile(code, { from: 'js', to: 'esjs' })

Esto se logra con invertMap() que invierte el diccionario de búsqueda.

####Separación de Imports

Hay lógica para extraer y manejar imports por separado:

// En src/utils.ts
splitCodeImports(code): {
  codeWithoutImports,
  imports,
  hasImports
}

###🎯 Ejemplo Completo de Transformación

Entrada (EsJS):

var x = 5
si (x > 3) {
  consola.escribir("Es mayor")
}

Proceso:

  1. Tokenización → Identifica palabras clave españolas
  2. Mapeovarvar, siif, consola.escribirconsole.log
  3. Generación → Reconstruye el código
  4. Plugins → Aplica transformaciones complejas de métodos

Salida (JavaScript):

var x = 5
if (x > 3) {
  console.log("Es mayor")
}

###📊 Resumen del Flujo

┌─────────────────────────────────────┐
│   Código en EsJS                    │
└────────────┬────────────────────────┘
             │
             ↓
┌─────────────────────────────────────┐
│   ESBABEL COMPILER                  │
│  ├─ Lexer: Tokenización             │
│  ├─ Generator: Conversión básica    │
│  └─ Diccionarios: Mapeos español→en │
└────────────┬────────────────────────┘
             │
             ↓
┌─────────────────────────────────────┐
│   ESSUCRASE COMPILER (opcional)     │
│  ├─ AST Parser                      │
│  ├─ Aplicar Plugins                 │
│  └─ Code generation                 │
└────────────┬────────────────────────┘
             │
             ↓
┌─────────────────────────────────────┐
│   JavaScript válido                 │
└─────────────────────────────────────┘

Esta arquitectura permite que EsJS sea modular, extensible y bidireccional, mientras mantiene la compatibilidad completa con JavaScript moderno.

Avatar of Enzo NotarioEnzo Notariocommented

pero actualmente se utiliza EsSucrase, no EsBabel

Avatar of Claude Haiku 4.5Claude Haiku 4.5commented

Tienes razón, déjame revisar más a fondo cómo funciona actualmente EsSucrase:

Veo que usa @es-js/compiler. Déjame buscar más información sobre qué es essucrase y cómo se usa:

Déjame ver qué es exactamente @es-js/compiler:

Perfecto, ahora entiendo mejor. Déjame ver el repositorio de @es-js/compiler:

Excelente, ahora tengo la información clave. Veamos el flujo real en el EssucraseCompiler:

Perfecto. Ahora veamos un ejemplo real de cómo se usan los plugins. Déjame ver uno más complejo:

Perfecto, ahora tengo toda la información necesaria. Déjame escribir una explicación actualizada y precisa:


##🔧 Cómo funciona EsJS de fondo (con EsSucrase)

EsJS es un transpilador que convierte código escrito en sintaxis española a JavaScript de forma rápida y eficiente. Actualmente utiliza EsSucrase como compilador base, que es un fork especializado de Sucrase.

###📚 La cadena de herramientas

┌─────────────────────────────────────────────────────────────┐
│ @es-js/compiler (EsSucrase)                                 │
│ ↳ Fork de Sucrase (super-fast Babel alternative)           │
│ ↳ Optimizado para EsJS                                      │
│ ↳ Hereda parser de Babel pero simplificado                  │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ @es-js/core (Orquestador)                                   │
│ ├─ EssucraseCompiler (compilador principal)                │
│ ├─ Plugins (transformaciones específicas del dominio)      │
│ └─ Utils (utilidades generales)                            │
└─────────────────────────────────────────────────────────────┘

###🔄 Flujo de Compilación en Detalle

Cuando llamas a compile(code, options), sucede lo siguiente:

####Paso 1: Entrada y opciones

compile(code: string, options?: CompileOptions): string

Las opciones por defecto son:

  • from: 'esjs' - El código viene en sintaxis española
  • to: 'js' - Queremos JavaScript
  • compiler: 'essucrase' - Usar EsSucrase

####Paso 2: Compilación base con EsSucrase

compileEssucrase(code: string, options?: CompileOptions): string
  ↓
transform(code, { 
  transforms: ['esjs'] 
}).code

@es-js/compiler (EsSucrase) realiza:

  1. Parse: Convierte el código en un AST (Árbol de Sintaxis Abstracta)
  2. Transform: Aplica la transformación 'esjs' que:
    • Reemplaza palabras clave españolas por equivalentes en inglés
    • Transforma estructuras de sintaxis
  3. Generate: Regenera el código JavaScript

Este proceso es muy rápido porque:

  • Solo se enfoca en EsJS (no en ES5, IE11, etc. como Babel)
  • Usa un parser optimizado heredado de Babel pero simplificado
  • Evita passes innecesarias

####Paso 3: Aplicación de Plugins

applyPlugins(compiled: string, toEsJS?: boolean): string

Si necesita aplicar transformaciones más complejas:

  1. Parse con putout (herramienta de análisis de código):

    const ast = putout.parse(code, { printer: 'recast' })
    
  2. Aplica plugins: Cada plugin transforma el AST con reglas específicas

  3. Genera código final:

    putout.print(ast, { printer: 'recast' })
    

###🔌 El Sistema de Plugins

Los plugins son el mecanismo para transformar objetos y métodos españoles a JavaScript.

Estructura típica de un plugin:

// packages/core/src/plugins/matriz/index.ts

export const report = () => 'Converts Matriz methods to JavaScript'

// Mapeo de métodos de instancia
export const methods = new Map<string, string>([
  ['posicion', 'at'],
  ['concatenar', 'concat'],
  ['filtrar', 'filter'],
  ['mapear', 'map'],
  // ... más métodos
])

// Mapeo de métodos estáticos
export const staticMethods = new Map<string, string>([
  ['desde', 'from'],
  ['esMatriz', 'isArray'],
])

// Objetos que se transforman
export const objects = new Map<string, string>([
  ['Matriz', 'Array'],
  ['Arreglo', 'Array'],
])

// Función que genera las reglas de transformación
export function replace() {
  return {
    ...replaceObjectStaticMethods({
      from: 'Matriz',
      to: 'Array',
      methods: staticMethods,
    }),
    ...replaceExpressionMethods({
      methods,
    }),
    ...replaceObjects({
      objects,
    }),
  }
}

Cómo funcionan las reglas:

// replaceObjectStaticMethods genera reglas como:
// 'Matriz.desde(__args)' → 'Array.from(__args)'
// 'Matriz.esMatriz(__args)' → 'Array.isArray(__args)'

// replaceExpressionMethods genera:
// '__a.mapear' → '__a.map'
// '__a.filtrar' → '__a.filter'

// replaceObjects genera:
// 'new Matriz()' → 'new Array()'
// 'new Arreglo()' → 'new Array()'

###📦 Lista de Plugins Disponibles

En packages/core/src/plugins/:

PluginTransformaEjemplos
consolaconsoleconsola.escribir()console.log()
matrizArrayMatriz.desde()Array.from()
cadenaStringCadena.incluye()String.includes()
fechaDateFecha.ahora()Date.now()
numeroNumberNumero.esEntero()Number.isInteger()
promesaPromisePromesa.todos()Promise.all()
mateMathMate.piso()Math.floor()
booleanoBooleanTransformaciones booleanas
funcionFunctionMétodos de función
jsonJSONJSON.analizar()JSON.parse()
navegadornavigatornavegador.idiomanavigator.language
ventanawindowventana.abierta()window.open()
documentoDocumentMétodos del DOM
apoderadoProxyApoderadoProxy
simboloSymbolMétodos de símbolo
enterograndeBigIntEnteroGrandeBigInt
tiposType conversionsConversión de tipos
objetosObjectMétodos de objeto
soporteSupport functionsFunciones de soporte

###🔑 Mapeos de Palabras Clave

Definidos en @es-js/esbabel (importados a core):

// Palabras de control
keywordControl:
  ['si', 'if']
  ['mientras', 'while']
  ['para', 'for']
  ['retornar', 'return']
  ['romper', 'break']
  ['continuar', 'continue']
  // ... más

// Constantes del lenguaje
constantLanguage:
  ['verdadero', 'true']
  ['falso', 'false']
  ['nulo', 'null']
  ['indefinido', 'undefined']

// Tipos y almacenamiento
storageType:
  ['const', 'const']
  ['mut', 'let']
  ['var', 'var']
  ['clase', 'class']
  ['funcion', 'function']
  ['asincrono', 'async']

// Funciones de soporte
supportFunction:
  ['consola', 'console']
  ['Fecha', 'Date']
  ['Promesa', 'Promise']
  // ... más

###📊 Ejemplo Completo de Transformación

Entrada (EsJS):

mut numeros = [1, 2, 3, 4, 5]

mut resultado = numeros
  .filtrar(x => x > 2)
  .mapear(x => x * 2)

consola.escribir(resultado)

Proceso:

  1. EsSucrase (transform): Convierte estructura básica

    let numeros = [1, 2, 3, 4, 5]
    let resultado = numeros...
    console...
    
  2. Plugins: Aplica transformaciones del dominio

    • filtrarfilter
    • mapearmap
    • consola.escribirconsole.log

Salida (JavaScript):

let numeros = [1, 2, 3, 4, 5]

let resultado = numeros
  .filter(x => x > 2)
  .map(x => x * 2)

console.log(resultado)

###🔄 Bidireccionalidad

EsJS puede funcionar en ambas direcciones:

// EsJS → JavaScript
compile(code, { from: 'esjs', to: 'js' })

// JavaScript → EsJS (invirtiendo mapeos)
compile(code, { from: 'js', to: 'esjs' })

Internamente, cuando toEsJS = true, se invierten los mapeos:

invertMap(methods) // { 'filter' → 'filtrar', ... }

###Por qué EsSucrase en lugar de Babel

  • Velocidad: Solo enfocado en EsJS, no en compatibilidad ES5
  • Simplicidd: Parser optimizado heredado de Babel
  • Mantenimiento: Fork especializado y controlado
  • Tamaño: Mucho más pequeño que Babel completo
  • Enfoque: Solo transforma lo necesario para EsJS

###🎯 Resumen del Flujo

Código EsJS
    ↓
┌─────────────────────┐
│ @es-js/compiler     │ ← Parse (AST) + Transform + Generate
│ (EsSucrase)         │
└─────────────────────┘
    ↓ (código intermedio)
┌─────────────────────┐
│ Plugins de Putout   │ ← Transformaciones AST especializadas
│ (18 plugins)        │
└─────────────────────┘
    ↓
JavaScript válido ✅

Esta arquitectura permite que EsJS sea rápido, modular, extensible y mantenible, mientras proporciona una experiencia de programación completamente en español.


END OF POST

Log in to add a comment
Claude Haiku 4.5
How does EsJS work in the background? — es-js/esjs