Reglas para elegir un marco JS

TL; DR

  • Este artículo no trata los marcos JS de la lista TOP-3.
  • Al desarrollar en un marco JS que no sea TOP-3, debe resolver un orden de magnitud más problemas técnicos de lo esperado al comienzo del desarrollo
  • La historia se basa en hechos reales.

La historia comenzó con un mini proyecto, que se desarrolló originalmente en base a las bibliotecas backbone.js y marionette.js. Estas, por supuesto, son las grandes bibliotecas que comenzaron la historia del desarrollo de aplicaciones de una sola página. Pero ya en ese momento tenían más probabilidades de ser de valor histórico que práctico. Solo mencionaré el hecho de que para mostrar una tabla simple fue necesario crear: 1) un módulo con una descripción del modelo, 2) un módulo con una descripción de la colección, 3) un módulo con la definición de un modelo de vista, 4) un módulo con la definición de una colección de vista, 4) una plantilla de fila de tabla, 4) 5) plantilla de tabla; 6) módulo controlador. Tener alrededor de 10 entidades en una pequeña aplicación: en la etapa inicial ya tenía más de cincuenta módulos pequeños. Y esto es solo el comienzo. Pero ahora no se trata de eso.

En algún momento, después de seis meses de la aplicación, todavía no apareció en los resultados de búsqueda. Agregar prerender.io al proyecto (que luego usó el motor phantom.js) ayudó, pero no tan significativamente como se esperaba. Las nubes comenzaron a acumularse sobre la aplicación y sobre mí, después de lo cual me di cuenta de que necesitaba hacer algo de manera muy rápida y eficiente, preferiblemente hoy. El objetivo que establecí fue este: cambiar a la representación del servidor. Con backbone.js y marionettejs, esto es casi imposible. En cualquier caso, el proyecto rendr en backbone.js, que se desarrolló bajo la dirección de Spike Brehm (el autor de la idea de aplicaciones isomorfas / universales), que reunió 58 colaboradores y 4,184 me gusta en github.com, se detuvo en 2015 y claramente no estaba destinado a un bombardeo de un día . Empecé a buscar una alternativa. Excluí el marco TOP-3 JS de la consideración inmediata, porque no tenía una reserva de tiempo para su desarrollo. Después de una breve búsqueda, encontré el framework JS de rápido crecimiento riot.js github.com/riot/riot , que actualmente tiene 13,704 me gusta en github.com y, como esperaba, bien podría haber llegado al primer posición (que, sin embargo, no sucedió).

Y sí, cerré la pregunta (aunque no el mismo día, sino durante los siguientes dos días del día) transfiriendo la aplicación al servidor mediante riot.js. Y el mismo día en que esto sucedió, el sitio pasó a las primeras diez páginas de resultados de búsqueda. Y dentro de una semana fui a la primera página, desde donde no se ha ido desde hace varios años.

Esto termina la historia de éxito y comienza la historia de las derrotas. El siguiente proyecto fue mucho más complicado. Es cierto que hubo un punto positivo en que el 99% de las pantallas de la aplicación estaban en la cuenta personal del usuario, por lo que no era necesario renderizar el servidor. Inspirado por la primera experiencia exitosa de usar riot.js, comencé a promover la idea de consolidar el éxito y aplicar riot.js en la interfaz. Entonces me pareció que, finalmente, se encontró una solución que combinaba simplicidad y funcionalidad, como prometía la documentación de riot.js. ¡Qué equivocado estaba!

El primer problema que encontré cuando fue necesario proporcionar al diseñador de diseño HTML todas las herramientas de desarrollo necesarias. En particular, necesitábamos complementos para el editor de código, un motor en el que sería posible colocar los componentes y observar de inmediato el resultado, incluso con la sobrecarga de componentes (recarga en caliente). Todo esto tenía que darse en la forma lista para la operación industrial en el futuro cercano, y todo esto no fue así. Como resultado, el diseño de la aplicación comenzó en uno de los motores de plantillas tradicionales, lo que condujo a la desagradecida etapa de traducir documentos HTML en componentes riot.js.

Sin embargo, el principal problema que surgió en esta etapa ni siquiera está relacionado con la traducción del diseño del formato de plantilla a los componentes de riot.js. De repente, resultó que riot.js proporciona mensajes completamente informativos sobre errores de compilación de plantillas, así como errores de tiempo de ejecución (esto es cierto para todas las versiones de riot.js hasta la versión 4.0, que se ha rediseñado por completo). No hubo información no solo sobre la línea en la que ocurrió el error, sino incluso sobre el nombre del archivo o componente en el que ocurrió el error. Fue posible buscar un error durante muchas horas seguidas, y aún así no se pudo encontrar. Y luego tuve que revertir todos los cambios al último estado de trabajo.

El siguiente fue un problema con el enrutamiento. El enrutamiento en riot.js viene casi de fábrica github.com/riot/route , al menos del mismo desarrollador. Esto le permitió esperar una operación sin problemas. Pero en algún momento noté que algunas páginas están sobrecargadas de manera impredecible. Es decir, una vez que la transición a una nueva ruta puede ocurrir en el modo de aplicación de una sola página, y otra vez la misma transición sobrecargó todo el documento HTML, como cuando se trabaja con una aplicación web clásica. En este caso, por supuesto, el estado interno se perdió si aún no se había guardado en el servidor. (Actualmente, el desarrollo de esta biblioteca está detenido y no se usa con riot.js 4.0.)

El único componente del sistema que funcionó como se esperaba fue el administrador estatal mínimo de flujo similar github.com/jimsparkman/RiotControl . Es cierto que para trabajar con este componente, era necesario designar y cancelar a los oyentes de los cambios de estado con mucha más frecuencia de la que quisiéramos.

La idea inicial de este artículo fue esta: mostrar, por ejemplo, nuestra propia experiencia con el marco riot.js las tareas que el desarrollador tendría que resolver, quien decidió (decidió) desarrollar una aplicación en un marco JS no de la lista TOP-3. Sin embargo, en el proceso de preparación, decidí actualizar algunas páginas de la documentación de riot.js en mi memoria, y entonces aprendí que se lanzó una nueva versión de riot.js 4.0, que fue completamente rediseñada, que se puede encontrar en el artículo del desarrollador de Riot. js en medium.com: medium.com/@gianluca.guarini/every-revolution-begins-with-a-riot-js-first-6c6a4b090ee . De este artículo aprendí que todos los problemas principales que me preocupaban y de los que iba a hablar en este artículo han sido eliminados. Específicamente, en riot.js versión 4.0:

  • el compilador se reescribe por completo (o mejor dicho, se escribió primero porque el motor solía funcionar en expresiones regulares antes); esto afectó, en particular, el contenido de información de los mensajes de error
  • Además de la representación del servidor, se agregó Hydrate en el cliente, lo que finalmente nos permitió comenzar a escribir aplicaciones universales sin doble representación (la primera vez en el servidor y allí mismo en el cliente debido a la falta de la función Hydrate () en versiones anteriores)
  • complemento agregado para componentes de sobrecarga en caliente github.com/riot/hot-reload
  • y muchos otros cambios útiles

Esto no es publicidad, solo el fondo. De hecho, mis conclusiones serán muy ambiguas en términos de recomendaciones de uso, y el artículo en general no está dedicado a un marco o biblioteca específicos, sino a las tareas que deben resolverse durante el proceso de desarrollo.

Desafortunadamente, el trabajo realizado por los desarrolladores de riot.js aún no ha sido evaluado adecuadamente por la comunidad. Por ejemplo, la biblioteca de representación del servidor github.com/riot/ssr durante los seis meses transcurridos desde el comienzo de su desarrollo ha recopilado tres co-contribuyentes y tres me gusta en github.com (no todos los me gusta son realizados por los contribuyentes, aunque uno es el mismo que el contribuyente).

Por lo tanto, en el transcurso de la obra, cambió la dirección del artículo y, en lugar de las memorias, trató de ir de nuevo, teniendo un poco más de conocimiento y experiencia, versiones más avanzadas de bibliotecas y tiempo libre ilimitado.

Entonces aquí vamos. Por ejemplo, se realizó una implementación de la aplicación github.com/gothinkster/realworld . Este proyecto ha sido discutido más de una vez en Habré. Para aquellos que no están familiarizados con él, describiré brevemente su idea. Los desarrolladores de este proyecto con la ayuda de diferentes lenguajes y marcos de programación (o sin ellos) resuelven el mismo problema: el desarrollo de un motor de blog, en una funcionalidad similar a la versión simplificada de medium.com. Este es un compromiso entre la complejidad de las aplicaciones reales que tenemos que desarrollar a diario y todo.app, que no siempre nos permite apreciar realmente el trabajo con la biblioteca o el marco. Este proyecto es respetado por los desarrolladores. En confirmación de lo anterior, puedo decir que hay incluso una implementación de Rich Harris (desarrollador principal de sveltejs) github.com/sveltejs/realworld .

Entorno de desarrollo


Por supuesto que estás listo para la batalla, pero piensa en los desarrolladores que te rodean. En este caso, concéntrese en la pregunta en qué entorno de desarrollo trabajan sus colegas. Si no hay complementos para los principales entornos de desarrollo y editores de código de programa para el marco con el que va a trabajar, entonces es poco probable que reciba soporte. Por ejemplo, uso el editor Atom para el desarrollo. Para él hay un plugin antidisturbios github.com/riot/syntax-highlight/tree/legacy , que no se ha actualizado en los últimos tres años. Y en el mismo repositorio hay un complemento para sublime github.com/riot/syntax-highlight : es actual y admite la versión actual de riot.js 4.0.

Sin embargo, el componente riot.js es un fragmento válido de un documento HTML en el que el código JS está contenido en el cuerpo del elemento de script. Entonces, todo funciona si agrega un tipo de documento html para la extensión * .riot. Por supuesto, esta es una decisión forzada, ya que de lo contrario hubiera sido simplemente imposible continuar más aquí.

Obtuvimos resaltado de sintaxis en un editor de texto, y ahora necesitamos una funcionalidad más avanzada, lo que estamos acostumbrados a obtener de eslint. En nuestro caso, el código JS del componente está contenido en el cuerpo del elemento del script, esperaba encontrar y encontrar un complemento para extraer el código JS del documento HTML: github.com/BenoitZugmeyer/eslint-plugin-html . Después de eso, mi configuración de eslint comenzó a verse así:

{ "parser": "babel-eslint", "plugins": [ "html" ], "settings": { "html/html-extensions": [".html", ".riot"] }, "env": { "browser": true, "node": true, "es6": true }, "extends": "standard", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" }, "parserOptions": { "ecmaVersion": 2018, "sourceType": "module" }, "rules": { } } 

La presencia de complementos para resaltar sintaxis y eslint probablemente no sea lo primero en lo que un desarrollador comienza a pensar al elegir un marco JS. Mientras tanto, sin estas herramientas, puede encontrar oposición de colegas y su éxodo masivo por "buenas" razones del proyecto. Aunque la razón única y verdaderamente válida es que se sienten incómodos trabajando sin un arsenal completo de desarrolladores. En el caso de riot.js, el problema se resolvió mediante el método de Columbus. En el sentido de que en realidad no hay complementos para riot.js, pero debido a las peculiaridades de la sintaxis de la plantilla riot.js, que parece un fragmento de un documento HTML normal, cubrimos el 99% de la funcionalidad necesaria utilizando herramientas para trabajar con un documento HTML.

Además de los medios para escribir y validar el código que acabamos de examinar, las herramientas del desarrollador incluyen herramientas para el montaje rápido y la depuración del proyecto, la recarga en caliente de componentes y módulos en un navegador web al realizar cambios en el proyecto. Consideraremos esta parte en la siguiente sección.

Asamblea de proyecto


Logramos acostumbrarnos a la mayoría de las características que se necesitan al construir el proyecto, e incluso dejamos de pensar en lo que podría ser diferente. Pero podría ser de otra manera. Y, si ha elegido un nuevo marco JS, es aconsejable asegurarse primero de que todo funcione como espera. Por ejemplo, como ya mencioné, el mayor problema al desarrollar versiones anteriores de riot.js fue la falta de mensajes de error de compilación e información de tiempo de ejecución sobre el componente en el que se produjo este error. También es importante la velocidad de compilación. Como regla general, para acelerar la velocidad de compilación, en un marco construido correctamente solo se recompila la parte modificada, como resultado, el tiempo de reacción a los cambios de texto componente es mínimo. Bueno, es muy bueno si se admite una recarga en caliente de componentes sin una recarga completa de la página en el navegador web.

Por lo tanto, intentaré enumerar la lista de verificación, a lo que debe prestar especial atención al analizar las herramientas de compilación del proyecto:

1. La presencia de un modo de desarrollo y una aplicación de trabajo.
En modo desarrollador:
2. Mensajes informativos sobre errores de compilación del proyecto (nombre del archivo fuente, número de línea en el archivo fuente, descripción del error)
3. Mensajes informativos sobre errores de tiempo de ejecución (nombre del archivo fuente, número de línea en el archivo fuente, descripción del error)
4. Reensamblaje rápido de módulos modificados
5. Sobrecarga en caliente de componentes en el navegador
En modo de funcionamiento:
6. La presencia de versiones en el nombre del archivo (por ejemplo, 4a8ee185040ac59496a2.main.js)
7. El diseño de pequeños módulos en uno o más módulos (fragmentos)
8. El código se divide en fragmentos mediante la importación dinámica

En riot.js versión 4.0, apareció el módulo github.com/riot/webpack-loader , que corresponde completamente a la lista de verificación dada. No enumeraré todas las características de la configuración del ensamblaje. Lo único que me gustaría llamar su atención es que en el proyecto en cuestión uso módulos para express.js: webpack-dev-middleware y webpack-hot-middleware, que le permiten trabajar en un servidor completamente funcional de inmediato, desde el momento de la composición. Esto, en particular, permite el desarrollo de aplicaciones web universales / isomorfas. Le llamo la atención sobre el hecho de que el módulo de sobrecarga en caliente del módulo es válido solo para un navegador web. Al mismo tiempo, el componente representado en el lado del servidor permanece sin cambios. Por lo tanto, es necesario escuchar sus cambios y, en el momento adecuado, eliminar todo el código almacenado en caché por el servidor y cargar el código de los módulos modificados. Cómo hacer esto durante mucho tiempo, así que solo proporcionaré un enlace a la implementación: github.com/apapacy/realworld-riotjs-effector-universal-hot/blob/master/src/dev_server.js

Enrutamiento


Parafraseando un poco a Leo Tolstoi, podemos decir que todos los motores de los frameworks JS son similares entre sí, mientras que todas las rutas que están unidas a ellos funcionan a su manera. A menudo me encuentro con la clasificación condicional de enrutadores en dos tipos: declarativa e imperativa. Ahora intentaré descubrir cómo se justifica dicha clasificación.

Hagamos una pequeña excursión a la historia. En los albores de Internet, las URL / URI coincidían con el nombre del archivo alojado en el servidor. Pasamos varias páginas de la historia a la vez y aprenderemos sobre el advenimiento de la Arquitectura Modelo 2 (MVC). En esta arquitectura, aparece un controlador frontal, que realiza la función de enrutamiento. Me preguntaba quién decidió primero desde el controlador frontal seleccionar la función de enrutamiento en un bloque separado, que luego envía una solicitud a uno de los muchos controladores y aún no ha encontrado una respuesta. Parece que comenzaron a hacerlo todo a la vez.

Es decir, el enrutamiento determina la acción que se debe realizar en el servidor y (transitivamente a través del controlador) la vista que generará el controlador. Al transferir el enrutamiento al lado del cliente (navegador web), ya se formó una idea de la función de enrutamiento en la aplicación. Y aquellos que se centraron principalmente en el hecho de que el enrutamiento determina la acción, desarrollaron el enrutamiento imperativo, y aquellos que prestaron atención a que, al final, el enrutamiento determina la opinión que se debe mostrar al usuario, desarrollaron el enrutamiento declarativo.

Es decir, al transferir de un servidor a un cliente para el enrutamiento, "colgaron" dos funciones que eran características del enrutamiento del servidor (seleccionar una acción y seleccionar una vista). Además, han surgido nuevas tareas: esta es la navegación a través de una aplicación de una sola página sin volver a cargar completamente el documento HTML, trabajar con el historial de visitas y mucho más. Para ilustrar, proporcionaré extractos de la documentación del enrutador de un marco mega-popular:

... facilita la creación de aplicaciones SPA. Incluye las siguientes características

  • Rutas anidadas / Vistas
  • Configuración de enrutador modular
  • Acceso a parámetros de ruta, consulta, comodines
  • Ver animación de transición basada en Vue.js
  • Conveniente control de navegación
  • Fijación automática de clase CSS activa para enlaces
  • Modos de historial HTML5 o hash, con cambio automático en IE9
  • Comportamiento de desplazamiento personalizado

En esta opción, el enrutamiento está claramente sobrecargado de funcionalidad y necesita repensar sus tareas en el lado del cliente. Empecé a buscar una solución adecuada para mi tarea. Como criterio principal, tomé en cuenta ese enrutamiento:

  1. debería funcionar igual tanto en el lado del cliente web como en el lado del servidor web para aplicaciones web universales / isomorfas;
  2. debería funcionar con cualquier marco (incluido el elegido) o sin él.

Y encontré una biblioteca de este tipo, esta es github.com/kriasoft/universal-router . Si describimos brevemente la idea de esta biblioteca, configura rutas que toman una cadena de URL en la entrada y llaman a una función asíncrona en la salida, que pasa la URL analizada como un parámetro real. Honestamente, quería preguntar: ¿eso es todo? ¿Y cómo deberían todos trabajar con esto? Y luego encontré un artículo en medium.com medium.com/@ippei.tanaka/universal-router-history-react-97ec79464573 , en el que se propuso una opción bastante buena, con la excepción de quizás reescribir el método de historial push (), que no lo hizo Necesito y que eliminé de mi código. Como resultado, la operación del enrutador en el lado del cliente se define aproximadamente así:

 const routes = new UniversalRouter([ { path: '/sign-in', action: () => ({ page: 'login', data: { action: 'sign-in' } }) }, { path: '/sign-up', action: () => ({ page: 'login', data: { action: 'sign-up' } }) }, { path: '/', action: (req) => ({ page: 'home', data: { req, action: 'home' } }) }, { path: '/page/:page', action: (req) => ({ page: 'home', data: { req, action: 'home' } }) }, { path: '/feed', action: (req) => ({ page: 'home', data: { req, action: 'feed' } }) }, { path: '/feed/page/:page', action: (req) => ({ page: 'home', data: { req, action: 'feed' } }) }, ... { path: '(.*)', action: () => ({ page: 'notFound', data: { action: 'not-found' } }) } ]) const root = getRootComponent() const history = createBrowserHistory() const render = async (location) => { const route = await router.resolve(location) const component = await import(`./riot/pages/${route.page}.riot`) riot.register(route.page, component.default || component) root.update(route, root) } history.listen(render) 

Ahora cualquier llamada a history.push () iniciará el enrutamiento. Para navegar dentro de la aplicación, también debe crear un componente que envuelva el elemento HTML estándar a (ancla), sin olvidar cancelar su comportamiento predeterminado:

 <navigation-link href={ props.href } onclick={ action }> <slot/> <script> import history from '../history' export default { action (e) { e.preventDefault() history.push(this.props.href) if (this.props.onclick) { this.props.onclick.call(this, e) } e.stopPropagation() } } </script> </navigation-link> 

Aplicación de gestión del estado


Inicialmente, incluí la biblioteca mobx en el proyecto. Todo funcionó como se esperaba. Excepto que no se correspondía exactamente con la tarea: el estudio que establecí al comienzo del artículo. Entonces cambié a github.com/zerobias/effector . Este es un proyecto muy poderoso. Ofrece el 100% de la funcionalidad redux (solo sin grandes gastos generales) y el 100% de la funcionalidad mobx (aunque en este caso será necesario codificar un poco más, pero aún menos si se compara con mobx sin decoradores)

La descripción de la tienda se ve así:

 import { createStore, createEvent } from 'effector' import { request } from '../agent' import { parseError } from '../utils' export default class ProfileStore { get store () { return this.profileStore.getState() } constructor () { this.success = createEvent() this.error = createEvent() this.updateError = createEvent() this.init = createEvent() this.profileStore = createStore(null) .on(this.init, (state, store) => ({ ...store })) .on(this.success, (state, data) => ({ data })) .on(this.error, (state, error) => ({ error })) .on(this.updateError, (state, error) => ({ ...state, error })) } getProfile ({ req, author }) { return request(req, { method: 'get', url: `/profiles/${decodeURIComponent(author)}` }).then( response => this.success(response.data.profile), error => this.error(parseError(error)) ) } follow ({ author, method }) { return request(undefined, { method, url: `/profiles/${author}/follow` }).then( response => this.success(response.data.profile), error => this.error(parseError(error)) ) } } 

Esta biblioteca utiliza reductores completos (se denominan en la documentación de effector.js), que muchas personas carecen de mobx, pero con mucho menos esfuerzo de codificación que redux. Pero lo principal ni siquiera es eso. Habiendo recibido el 100% de la funcionalidad de redux y mobx, utilicé solo una décima parte de la funcionalidad inherente a effector.js. De lo cual podemos concluir que su uso en proyectos complejos puede enriquecer significativamente los fondos de los desarrolladores.

Prueba


Todo

Conclusiones


Entonces, el trabajo está completo. El resultado se presenta en el repositorio github.com/apapacy/realworld-riotjs-effector-universal-hot y en este artículo sobre Habré.
Sitio de demostración en ahora realworld-riot-effector-universal-hot-pnujtmugam.now.sh

Y al final compartiré mis impresiones sobre el desarrollo. Desarrollar en riot.js versión 4.0 es bastante conveniente. Muchas construcciones son más fáciles de escribir que en React. Se tardó exactamente dos semanas en desarrollarse sin fanatismo en las horas posteriores y los fines de semana. Pero ... Uno pequeño pero ... El milagro no volvió a suceder. La representación del servidor en React es 20-30 veces más rápida. Las corporaciones vuelven a ganar. Sin embargo, se probaron dos interesantes bibliotecas de enrutamiento y administrador de estado.

apapacy@gmail.com
17 de junio de 2019

Source: https://habr.com/ru/post/456448/


All Articles