Lenguajes - meme JS

Para facilitar la programación y el mantenimiento de nuestro software, en el proyecto de meme.js propongo 3 lenguajes con los que pretendo facilitar el uso de HTML, CSS, JavaScript y su interoperabilidad.

Con estos lenguajes, logramos obtener funcionalidades completamente nuevas, con una gran potencia y robustez, aunado a una facilidad de modularización e independencia que no podrás creer.

Los lenguajes que se proponen en meme.js son: meme-html, meme-css y meme-js, los tres son lenguajes que simplifican y enriquecen HTML, CSS y JS.

En este artículo pretendo mostrarte las bases de los lenguajes, para que puedas comenzar a desenvolverse con meme.js y todas las innovaciones que trae consigo.

introducción

A diferencia de mh y mc, meme-js (en adelante mj), no es una reimplementación o una simplificación de JavaScript, mejor dicho, mj es una simplificación de las clases JavaScript, la herencia de estas y el manejo de plataformas de ejecución y como tal, trata de ser lo menos intrusivo posible con el lenguaje. De igual forma, al ser un superset para escribir componentes de meme.js, solo describiré las peculiaridades de mj y no explicaré el JavaScript que lleguemos a ver.

mj tiene la flexibilidad que se requiera, sin embargo, el superset está enfocado a la programación orientada a objetos, programación reactiva mediante eventos y la programación imperativa, que sumado a la modularización natural de meme.js. Se vuelve una herramienta de desarrollo que nos facilitará enormemente la programación de sistemas robustos con alto grado de mantenibilidad.

Componente meme.js

En meme.js conceptualizamos un componente como una clase mj, este componente es del tipo universal, por lo que se ejecutará tanto del lado del cliente, como del lado del servidor. Un componente puede contener todas las APIS e interfaces de usuario necesarias para su funcionamiento. Por esto mismo un componente de meme.js puede ser completamente reutilizable, tanto en el mismo proyecto, organización o incluso tener una distribución global, de forma sencilla y sin intermediarios.

Como lo mencioné, un componente de meme.js equivale a una clase mj, y solo puede existir una clase por archivo, por ende, cada archivo .mj, representará a un solo componente en nuestro proyecto.

Esqueleto de la clase de meme.js

Si bien la clase de meme.js se comporta como una clase JavaScript ordinaria, no lo es del todo, y por ejemplo, podemos verlo incluso de la forma en que se nombra esta clase (el nombre puede existir o no), entre muchas otras cosas.

Por eso hay que tener en consideración los siguientes puntos, en las clases de meme.js:

  1. Las clases meme.js usan el paradigma Single File Components, esto quiere decir que todo está en la misma clase, terraforms, back, front, vista, estilos, etc. Puede sonar engorroso, pero la forma en que meme.js lo resuelve, es muy intuitivo y fácil de usar. Aun así, meme.js no te obliga a hacer nada y puedes optar por separar todo en diferentes archivos.
  2. En el interior de la clase, solo se pueden declarar, variables, métodos, getters y setters.
  3. No se pueden definir constantes, todas las variables que se definan serán del tipo let y no se usará palabras claves de declaración como: let, var o const.
  4. Al definir un método, no se usa la palabra reservada function y no se podrán definir métodos de flecha.
  5. Cualquier método o función de la clase, puede ser definido como asíncrono.
  6. Las variables y métodos son de ámbito global dentro de la clase, por lo que no requieren de la palabra clave this, para acceder a estos y podemos accederlos desde cualquier parte de la clase.
  7. Las variables y métodos privados, son realmente privados e inaccesibles desde cualquier parte externa de la clase.
  8. Las variables y métodos públicos son accesibles desde cualquier código externo como propiedades y métodos de la instancia.

Nombre

Cuando un componente es compilado, no se toma en cuenta ni su localización o profundidad en el sistema de archivos (la carpeta de nuestro proyecto), por lo que podremos colocar el componente en donde nos plazca. De lo único que hay que estar consciente, es que solo puede haber un componente con un nombre x, en todo el proyecto. Así que se recomienda que tengamos una buena política de nombres en la organización, para poder administrar todos los componentes que podamos compartir transversalmente entre proyectos. Hay que ser muy conscientes de este hecho, ya que meme.js es altamente escalable y con un correcto nombrado de componentes, lo mismo dará contar con 10 o 1 millón de componentes en una organización.

El nombre del componente se puede adquirir de dos lugares: del nombre del archivo o del nombre de la clase, por ejemplo, Si tenemos un archivo con el nombre mi_comp_1 y dentro de él tenemos el código:
class {}
, ya que la clase no tiene un nombre, el nombre del componente, sera: mi_comp_1, en caso de que la clase tuviera un nombre, el nombre que se tomaría sería el de la clase, por ejemplo, si tuviéramos el mismo nombre de archivo, pero con el siguiente contenido:
class mi_nombre_de_clase {}
, el nombre del componente sería: mi_nombre_de_clase.

Caracteres del nombre

El nombre del componente sólo puede empezar con letras, y puede contener números, letras y caracteres especiales como: _, sin embargo, cualquier carácter que no sea una letra, y este al principio del nombre, será ignorado a la hora de que el compilador parse el nombre del componente, por ejemplo, si tenemos el nombre: 01.- mi_comp_1, El nombre que el compilador parseara, será: mi_comp_1.

Todos los caracteres especiales posteriores que haya en el nombre, serán reemplazados por _, y el nombre será parseado a minúsculas, por ejemplo, si tenemos el nombre: Mi comp 1., este nombre será parseado por el compilador como: mi_comp_1_.

Herencia

En meme.js, solo la parte front-end, puede heredar funcionalidades, y todos los componentes heredan de HTMLElement, por lo que todos los componentes son elementos HTML nativos con todas las características de estos. Y de igual forma podemos heredar de componentes que nosotros hagamos, mediante la palabra reservada extends seguido del componente del cual queremos heredar, por ejemplo:
class mi_heredero extends mi_componente {...}

Al heredar de otro componente, este mismo heredará todos los elementos en su vista, todos los estilos, todas sus referencias y todos sus métodos públicos.

Herencia desde otras etiquetas

Igualmente meme.js tiene la herencia de etiquetas, a través del atributo is de HTML, mediante la siguiente sintaxis:
class mi_boton_heredero extends HTMLButtonElement:button {...}

Como podemos ver en el ejemplo, mi_boton_heredero, está heredando directamente del elemento HTMLButtonElement, y le estamos indicando la etiqueta HTML que se escribe en el DOM button, de esta forma podremos inyectar un comportamiento especial a un elemento estándar de HTML.

Zonas

Las zonas de código son una característica única e innovadora de meme.js. Y su propósito es dividir el componente en diferentes tipos de comportamientos y entornos de ejecución.

Para establecer estas zonas se usa un comentario reservado, todo lo que sea posterior a este comentario, será parte de esta zona, hasta que nuevamente cambié la zona con otro comentario reservado, por ejemplo:

class mi_componente {
	// front-private
	mi_variable_privada = 1; // <-variable privada

	mi_funcion_privada() {...} // <-método privado

	// front-public
	mi_variable_publica = 1; // <-variable pública

	mi_funcion_publica() {...} // <-método público
}

Como podemos ver en este ejemplo, tenemos una zona privada, identificada así por el comentario reservado // front-private, y como podemos observar, todo lo que se encuentre después de este comentario, será tomado como un elemento privado en el front, hasta que aparece otro comentario reservado que cambia la zona a pública. Cabe mencionar que por defecto, la zona inicial de los componentes es de tipo // front-public, por lo que cambiará solo si existe un comentario reservado.

Tipos de zonas

Actualmente existen 6 tipos diferentes de zonas en meme.js y cada una otorga un comportamiento diferente a los elementos contenidos en ella, y estas zonas serían:

  • back-private: comprende todos los elementos que tendrán el carácter de privado del lado del servidor, es decir, aquí podremos poner, todas las variables o métodos privados del servidor.
  • back-public: comprende todos los elementos públicos del servidor.
  • back-rest: comprende todas las funciones de servidor tipo http, cabe mencionar que esta zona únicamente acepta métodos, cualquier variable introducida en esta zona, será ignorada por el compilador.
  • back-socket: comprende todas las funciones de servidor de tipo web-socket. Al igual que la zona anterior, esta zona solo acepta métodos.
  • front-private: comprende todos los elementos privados que tendrá nuestro componente, del lado del cliente.
  • front-public: comprende todos los elementos públicos de nuestro componente del lado del cliente.

En cuanto a las zonas privadas y públicas, no se explicarán más a fondo debido a que son conceptos populares que hemos usado continuamente en la programación, es decir el tener variables y métodos públicos o privados en una clase, la única diferencia es que se establece si serán públicas o privadas, para el servidor o el cliente.

En el caso de las zonas nuevas como son back-rest y back-socket, se explicarán más adelante debido a que necesitamos saber previamente lo que es una función remota, otro concepto innovador que nos trae a la mesa meme.js.

Peculiaridades de la clase de meme.js

Si bien las clases de meme.js se escriben como cualquier otra clase JS, tienen ciertas peculiaridades que nos facilitaran la tarea a la hora de crear, manejar y mantener nuestros componentes, por lo que tenemos que tener en cuenta los siguientes puntos:

  • Ámbito de las funciones: todas las funciones de la clase tienen el mismo ámbito, por lo que no se requiere de this para ejecutar otras funciones, acceder a otras variables y por supuesto, this siempre tendrá el mismo valor en toda la clase, y el valor que tendrá es la referencia a nuestro componente padre.
  • Objeto especial refs: como ya vimos en el lenguaje mh, puedes crear referencias a un elemento del DOM, pues bien, dentro de la clase, todas las referencias que sean creadas, se encontraran dentro de este objeto JS, y podremos accederlas en cualquier lado de nuestra clase.
  • Objeto especial props: este es otro objeto JS que tenemos disponible, en un inicio, este objeto trae cargados todos los atributos HTML de nuestro componente con los valores crudos, sin embargo, estos valores no están vinculados a nuestros atributos, por lo que cualquier acción sobre este objeto no afectará el funcionamiento de nuestro componente.

Funciones remotas

Te imaginas que pudieras tener desarrollos robustos, modulares, que se programaran con una interoperabilidad única, al punto de que deje de existir el concepto de back-end y front-end distintamente? Bueno, pues eso es lo que trae a la mesa meme.js, una forma de que el back y el front se unan de forma que sean casi indistinguibles uno del otro y con una facilidad enorme.

Las funciones remotas son parte de este nuevo concepto y son fáciles de comprender: son una función como cualquier otra función de JS y es usada como cualquier otra, pero con una ejecución remota o en un contexto diferente. Por ejemplo:

class {
	// back-rest
	Suma( ...parametros ) {
		return parametros.reduce( ( r, v ) => r + v );
	}

	// back-public
	async Create() {
		const result = await Suma( 1, 2, 3 );

		console.log( result                             ); //? 6
		console.log( await Suma( 9, 8, 7, 6, 5 )        ); //? 35
		console.log( await Suma( 'hola', ' ', 'mundo' ) ); //? 'hola mundo'
	}
}

Como podemos ver en el ejemplo anterior, tenemos una función remota llamada Suma, que nos sumara todos los parámetros que le enviemos. Para usar Suma del lado del cliente, solo tenemos que llamarla como una función pública cualquiera. Cabe mencionar que todas las funciones remotas son asíncronas, sin importar la forma en que se definan en la zona back-rest.

El canal o lugar donde se ejecuta una función remota dependerá de la zona. La zona back-rest, va sobre el protocolo http(s/2), y la zona back-socket va sobre el protocolo ws(s).

Funciones remotas, por socket

A diferencia de las funciones back-rest, para poder usar una función de servidor que use el protocolo websocket, necesitamos agregar un paso más. El cual sería conectarse al socket antes de cualquier llamada a las funciones, esto es algo que hace automaticamente la libreria protocol en el primer llamado a funciones de este tipo, pero de igual forma lo podemos hacer manualmente y con una sola instrucción, que sería:
window.socket.Connect()

Otra función de la zona back-socket, es que aunque las funciones por protocolo websocket funcionan de forma similar a las funciones back-rest. Tienen una peculiaridad extra, la cual es que pueden ser ejecutadas mediante el disparo de eventos desde el cliente y de igual forma el servidor puede disparar eventos que se recibirán en el cliente. Con esto, podemos tener sistemas de comunicación bidireccional y completamente reactivos, por ejemplo:

class {
	// back-socket
	suma( event, ...parametros ) {
		const result = parametros.reduce( ( r, v ) => r + v );

		this.Trigger( 'respuesta', result );

		// o en caso de que necesitemos que todos
		// los clientes conectados reciban el
		// resultado
		this.TriggerAll( 'respuesta', result );
	}

	// front-public
	View() {
		! (respuesta)=onRespuesta // <-observamos el evento "respuesta"
	}

	// front-public
	async Create() {
		await socket.Connect(); // <-completamente opcional, por que ya lo hace el componente de protocolo

		this.Trigger( 'suma', 1, 2, 3 );
		this.Trigger( 'suma', 9, 8, 7, 6, 5 );
	}

	// front-private
	onRespuesta( _, respuesta ) {
		console.log( respuesta );
	}
}

En este ejemplo:

  1. Nos conectamos al socket al crear el componente, en la función Create.
  2. Igualmente colocamos un observador global (!), en nuestra vista, con esto haremos que cada vez que se presente el evento respuesta en el DOM, se ejecute la función privada onRespuesta.
  3. Inmediatamente disparamos dos eventos llamados suma (el evento es el nombre de la función remota), pasándole los parámetros de la función.
  4. Ahora en el servidor: Se recibe el evento (ejecutando la función correspondiente), y se procesa la información enviada por el cliente.
  5. Y en una primera instancia, mostramos como disparamos el evento respuesta, en el cliente, pasándole el resultado de nuestro proceso en los parámetros del evento.
  6. Luego, mostramos cómo podríamos disparar el mismo evento, a todos los clientes conectados a este servidor (en caso de que necesitemos esto).
  7. Devuelta al cliente, recibimos y pintamos en la consola, el resultado.

Contexto en las funciones remotas

Aunque estas funciones se usan como cualquier otra función JS, hay que tener en cuenta que son parte de un metaprotocolo que va sobre protocolos ya existentes, como lo son http o ws, y como tal, son peticiones como cualquier otra, con lo que tendremos todas las características de una petición de su tipo. En el caso de una petición http, tendremos cosas como query params, cabeceras, cuerpo, etc. Todos estos datos sumados a otros referentes a la compilación (que no se explicaran en esta ocasión), se encontrarán en el contexto de la función.

Contexto en funciones rest

Dentro del ámbito de las funciones rest, podemos distinguir dos modalidades de ejecución:

  1. Ejecución como API: Esta modalidad se efectúa cuando invocamos a una función al estilo de cualquier servicio web.
  2. Ejecución como Función Local: Se realiza mediante el uso del protocolo definido en meme.js.

Es crucial comprender estas diferencias, especialmente para saber cómo se nos proporcionará el contexto durante la ejecución:

  • Al ejecutar nuestra función en modo API, recibimos el contexto como el primer y único argumento de la función.
  • Cuando la función se ejecuta en modo local, el contexto se pasa como un objeto accesible a través de la palabra clave this dentro de la función.

Este entendimiento es esencial para manejar adecuadamente el contexto en las diversas situaciones de ejecución de nuestras funciones rest.

Forma del contexto rest

Como mencioné antes, el contexto es un objeto que recibimos y contiene toda la información procesada de la petición, y otros extras como la compilación (información que ignoramos de momento).

El objeto contexto tiene la siguiente apariencia:

{
	host: "0.0.0.0:8080",
	method: "POST",
	raw_url: "/function/mi_componente/MiFuncion",
	params: {},
	headers: {
		host: "0.0.0.0:8080",
		connection: "keep-alive",
		contentLength: "0",
		pragma: "no-cache",
		cacheControl: "no-cache",
		userAgent: "xxxxxxxx/x.x.x",
		contentType: "application/json",
		idSession: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
		accept: "*/*",
		secGPC: "1",
		acceptLanguage: "es-419,es",
		origin: "http://0.0.0.0:8080",
		referer: "http://0.0.0.0:8080/",
		acceptEncoding: "gzip, deflate",
	},
	protocol: "http",
	url: "/function/mi_componente/MiFuncion",
	path: [
		"function",
		"mi_componente",
		"MiFuncion",
	],
	petition: {
		request: {...},
		response: {...},
	},
	cookies: {...},
	file: {...},
	files: {...},
	config: {...},
	compilers: {...},
	url_method: "MiFuncion",
	stream: {...},
	Send: ...,
	GetBodyBuffer: () => Promise,
	GetBodyText: () => Promise,
	GetBodyJson: () => Promise,
}

A continuación, se proporciona una descripción breve de las propiedades más relevantes y sus valores:

  • host<string>: Indica el host desde el cual el cliente ha realizado la petición.
  • method<string>: Muestra el método HTTP utilizado para realizar la petición.
  • raw_url<string>: Presenta la URL completa con la que se realizó la petición, incluyendo los parámetros de consulta.
  • params<object>: Contiene los parámetros de consulta enviados con la petición. Si un parámetro no tiene valor, no se incluirá en este objeto.
  • headers<object>: Muestra las cabeceras de la petición, donde la clave está en formato camelCase y el valor corresponde al de la cabecera.
  • protocol<string>: Indica el protocolo utilizado en la petición, que puede ser http o http2.
  • url<string>: Solo contiene la ruta (path), de la petición.
  • path<array>: Es un arreglo que descompone la ruta de la petición en sus diferentes partes.
  • petition<object>: Aquí se encuentran los objetos de solicitud (request) y respuesta (response) originales de Node.js.
  • url_method<string>: En este parámetro podemos ver el nombre de la función que se está ejecutando.
  • GetBodyBuffer<function>: Este parámetro es una función asíncrona que al ejecutarla nos devuelve el cuerpo de la petición en formato de buffer.
  • GetBodyText<function>: Este parámetro es una función asíncrona que al ejecutarla nos devuelve el cuerpo de la petición en formato texto plano.
  • GetBodyJson<function>: Este parámetro es una función asíncrona que al ejecutarla nos devuelve el cuerpo de la petición en formato JSON.

Forma del contexto socket

Dado que una petición socket es una petición HTTP “upgradeada”, el contexto de una función socket es igual que un contexto de una función HTTP, salvo que se agregan propiedades que nos sirven para manejar los sockets. Por lo que solo describiré las propiedades que se agregan al contexto:

  • id<integer>: Aquí veremos un identificador único para el socket actual.
  • socket<object>: Este objeto es el socket actual.
  • sockets<array>: Es un array que contiene todos los sockets conectados al servidor.
  • Trigger<function>: Con esta función podremos ejecutar eventos en el cliente conectado a este socket.
  • TriggerAll<function>: Con esta función podremos ejecutar eventos en todos los clientes conectados al servidor.

Retorno de una función remota

Recordemos que las funciones remotas son una abstracción de una peticion HTTP, y como tal, también tenemos una respuesta HTTP, salvo que al ser meme.js, responder es tan fácil como retornar algún valor en la ejecución de la función. Podemos retornar toda clase de valores, objetos, arreglos, enteros, cadenas, etc. Y la respuesta será un código 200 con nuestro valor en el cuerpo.

Respuesta específica

En caso de que necesitemos tener una respuesta más específica, podemos retornar un objeto con propiedades especiales, de esta forma, podemos indicar, el código de respuesta, cabeceras, etc.

Las propiedades especiales que tenemos disponibles, serían:

  • _code: especifica el código de la respuesta HTTP.
  • _type: con esta propiedad podemos especificar el tipo de valor en el body.
  • _body: el cuerpo de la respuesta.
  • _headers: en esta propiedad podemos indicar las cabeceras de la petición.
  • _stream: en este parámetro podemos poner datos muy grandes, para que se transmitan en streaming, lo que puede acelerar la transmisión de datos, sobre todo con el protocolo HTTP/2.

Responder sólo un código HTTP

Adicionalmente, si solo necesitamos contestar con un código de respuesta, sin importar el cuerpo, podemos anteponer el hash al código HTTP de respuesta, por ejemplo:
return #201
.

Ciclo de vida del componente

Los componentes de meme.js son una especie de componentes completamente nuevos en el mundo del desarrollo web. Por esto mismo su ciclo de vida es más extenso y ocurre durante diferentes tiempos del desarrollo. Lo que nos dará más flexibilidad en la creación de toda clase de soluciones.

Todas las etapas del ciclo de vida se usan en forma de funciones en la clase de nuestro componente, por ejemplo:

class {
	Load() {}
	Tag() {}
	End() {}
	View() {}
	Style() {}
	Create() {}
	Destroy() {}
}

Las funciones que tenemos disponibles y las etapas en las que estas ocurren son:

  • Load

    Esta función se ejecuta en tiempo de compilación y cuando todos los componentes han sido cargados y parseados por el compilador de meme.js, y en este momento aún no se han transpilado los componentes.

  • Tag

    Esta función se ejecuta en tiempo de compilación y cuando el compilador está escribiendo la etiqueta de nuestro custom element en cualquier otro componente o página.

  • End

    Esta función se ejecuta en tiempo de compilación y cuando el compilador ha terminado de cargar y transpilar todos los componentes de nuestro proyecto.

  • View

    Esta función se ejecuta en tiempo de ejecución, del lado del navegador antes de la creación de nuestro componente justo en el momento que se está creando lo que será el template HTML de nuestro componente, cabe mencionar que dentro de esta función sólo se puede escribir código meme-HTML y es el template de nuestro componente.

  • Style

    Esta función se ejecuta en tiempo de ejecución, del lado del navegador antes de la creación de nuestro componente justo en el momento que se está creando lo que será el template HTML de nuestro componente, cabe mencionar que dentro de esta función sólo se puede escribir código meme-CSS y es el estilo que tendrá nuestro componente.

  • Create

    Esta función se ejecuta en tiempo de ejecución y depende de la zona en la que se encuentre; si la función se encuentra en una de las zonas back, se ejecutará justo cuando se crean los servicios de nuestro componente. Si por el contrario se encuentra en alguna de las zonas front, se ejecutará del lado del navegador y justo cuando nuestro componente ya ha sido montado en el DOM.

  • Drestroy

    Esta función se ejecuta en tiempo de ejecución, del lado del navegador y ocurre justo cuando nuestro componente es desmontado del DOM.