En un proyecto grande, puede surgir la tarea de identificar cambios para el usuario final por diferencias en el código front-end de la aplicación. El desarrollador de Yandex.Market Nikita Sidorov @nickshevr contó cómo resolvimos este problema usando la biblioteca Diffector, sobre cómo construir y analizar el gráfico del módulo en las aplicaciones de Node.js y sobre encontrar defectos en el código antes de su lanzamiento.

- Hoy intentaré ser lo más franco posible contigo.
He estado trabajando en Yandex.Market por poco más de un año y medio. Estoy haciendo la misma cantidad de web y comencé a notar cambios en mí mismo, también puedes notarlos. Mi longitud promedio de cabello aumentó y la barba comenzó a aparecer. Y ya sabes, hoy miré a mis colegas: en Sergei Berezhnoy
veged , en Vova Grinenko
tadatuta , y me di cuenta: este es un buen criterio para lo que casi he madurado como desarrollador front-end real.
Y al hacer este juramento, decidí hablar contigo sobre la vida, sobre aquella en la que todos participamos. Principalmente sobre la vida antes del tiempo de ejecución. Ahora explicaré de qué se tratará todo esto.

¿Qué hay de la vida? Sobre la vida del código, por supuesto. El código es lo que hacemos. Permíteme recordarte que decidí ser sincero contigo aquí, así que la primera diapositiva fue lo más simple posible. Tomé la verdad, la primera etapa: la adopción, ya ves, nadie discutirá con este axioma.

Y luego me di cuenta de que tendría que modificarlo, pero para que quedara claro. Que sea algún tipo de aceptación de requisitos. Cualquier código comienza con el hecho de que mira la tarea e intenta aceptar los requisitos que lo establecen.

Después de eso, por supuesto, comenzamos la etapa de escritura: escribimos nuestro código. Luego lo cubrimos con pruebas, verificamos su efectividad nosotros mismos. Después de eso, ya verificamos si nuestra aplicación funciona con nuestro código como un todo. Después de eso, se lo damos al probador, déjelo verificar. ¿Qué piensas después de eso? Te recuerdo, la vida antes del tiempo de ejecución. ¿Crees que el tiempo de ejecución sigue a esto? De hecho, resulta así. Y esto no es un error en la presentación. Muy a menudo en cualquier etapa de los controles, y puede haber muchos más de lo que indiqué, es posible que tenga que llamar para volver a escribir. De acuerdo, esto puede ser un problema bastante grande. Esto puede ralentizar la entrega de algunas características en la producción y, en principio, ralentizarlo como desarrollador, porque el ticket dependerá de usted. Y aquí todo pasa, pasa. Hay algunas M veces más para N comprobaciones, y solo entonces el código llega al usuario en el navegador. Pero este es nuestro objetivo. Nuestro objetivo es escribir código que realmente estará disponible para el usuario y realmente funcionará para su beneficio.
Hoy hablaremos de la primera parte. Sobre lo que sucede antes, pero no sobre las pruebas.

Por cierto, se ve algo así. Tomé nuestro turno en el rastreador, recogí el mío, conté la mediana. Resulta que mis boletos están mucho menos en desarrollo que en cheques. Y como saben, cuanto más tiempo esté bajo control, mayores serán las posibilidades de que aparezca goto al principio o goto al final, y no quiero hacer esto en absoluto.
Y también, si presta atención, hay dos palabras en la diapositiva aquí: desarrollo (esto es lo que estamos haciendo los desarrolladores) y verificación (esto es lo que estamos haciendo, pero también probadores). Por lo tanto, el problema es relevante, de hecho, para los evaluadores.

El objetivo es bastante prosaico. En general, me gusta decir que la vida debe simplificarse: ya trabajamos mucho con usted. El objetivo se parece a esto, pero debes admitir que es bastante efímero, así que resaltemos algunos criterios básicos de los que puede depender el objetivo.
Por supuesto, cuanto menos código, más fácil es para nosotros. Cuanto más rápido tengamos controles de CI, más pronto nos daremos cuenta de si tenemos razón o no. Es decir, localmente, generalmente puede comenzar para siempre. Velocidad de verificación: esto se aplica directamente al probador. Si nuestra aplicación es grande y necesita ser revisada en su totalidad, esta es una gran cantidad de tiempo. La velocidad de liberación depende de todo esto. Incluso es imposible liberarlo hasta que hayamos pasado todas las comprobaciones y hasta que comprendamos que el código es exactamente el que queremos.
Para resolver algunos de los problemas de los que estamos hablando, analicemos el gráfico de dependencia de los módulos en nuestro lenguaje de programación. Y, en realidad, describámoslo.

El gráfico está orientado: tiene aristas con direcciones. En los nodos del gráfico, tendremos solo los módulos del lenguaje del que estamos hablando. Las costillas son un tipo específico de enlace. Hay varios tipos de comunicación.

Veamos un ejemplo común. Hay un archivo A. Aquí, se importa algo del archivo B, y esta es una relación entre los nodos.

Lo mismo sucederá si reemplaza importar con require. De hecho, no todo es tan simple aquí.

Sugiero, ya que estamos hablando del tipo de dependencia, considere al menos dos tipos: acelerar su canalización, acelerar el recorrido del gráfico. Es necesario observar no solo el módulo dependiente, sino también el dependiente. Propongo llamar al módulo A - el padre, B - el niño, y le aconsejo que mantenga los enlaces siempre como una lista de doble enlace. Esto simplificará tu vida, te informo de antemano.
Una vez que hayamos descrito de alguna manera el gráfico, pongámonos de acuerdo sobre cómo lo construiremos.

Hay dos formas Ya sea su herramienta favorita en su lenguaje de programación favorito utilizando el mismo AST (árboles de sintaxis abstracta) o regulares. ¿Cuál es el beneficio aquí? El hecho de que aquí no estás atado a nadie, pero al mismo tiempo tienes que implementar todo tú mismo. Tendrá que describir todos los tipos de conexiones de todas esas cosas y tecnologías que utiliza, ya sea un recopilador CSS separado, algo así. Pero tienes total libertad de vuelo, por así decirlo.
Además, la segunda opción, también la promocionaré un poco, esta es una opción solo para la mayoría de las personas que ya tienen configurado un sistema de compilación. El hecho es que el sistema de ensamblaje recopila un gráfico según el diseño, de forma predeterminada.

Veamos uno de los sistemas de ensamblaje más populares en Yandex, este es un paquete web. Aquí di un ejemplo de cómo puede recopilar todo el resultado del paquete web en un archivo separado, que luego se puede alimentar a nuestro o algún otro analizador. Lo recoge con la ayuda de AST, se utiliza la biblioteca bellota. Puede que la hayas notado cuando algo ha caído. Me he dado cuenta.
Y cuales son las ventajas. El hecho es que cuando describió su sistema de compilación, le pidió la entrada con toda honestidad. Estos son los archivos de los que se desenrollan sus dependencias, los puntos de derivación iniciales. Esto es bueno, porque no tienes que volver a grabarlos. Además, webpack y babel, y todo esto, y bellota, incluido, todavía no es su mantenimiento. Y, por lo tanto, todo tipo de nuevas características del lenguaje, todo tipo de errores y todo lo demás, se corrigen más rápido que si lo hicieras, especialmente si no tienes un equipo muy grande. Sí, incluso si es grande, entonces no es tan grande como el código abierto.
Esto es tanto un plus como un menos, de hecho. Es como si se obtuviera un doble filo (espada de doble filo). El hecho es que este gráfico se construye durante el ensamblaje. Es bueno, es decir, podemos ensamblar el proyecto e inmediatamente reutilizar el resultado del ensamblaje. Pero, ¿qué sucede si no queremos armar un proyecto, pero solo queremos obtener este gráfico?
Y una gran desventaja, de hecho. Si tiene alguna cosa personalizada conectada, hablaremos mucho más tarde sobre las conexiones, entonces el sistema de compilación no le permitirá hacer esto. O bien, deberá integrar esto, como su complemento de paquete web.

Considere un ejemplo específico. Ejecuté un comando en mi proyección, donde solo hay tres archivos, y obtuve esta salida. Y esto solo muestro una clave, que se llama módulos. Solo estamos hablando con usted sobre el gráfico de dependencia de los módulos, por lo que miramos los módulos, todo es lógico.

Mucha información, pero no necesitamos todo. Deja algunos puntos y hablemos de ellos. Supongamos que consideramos el primer módulo. Él tiene un nombre, hay razones. Las razones son solo la conexión, de hecho, con los módulos "dependientes", resulta que los que importan este módulo a sí mismos. Estos son los datos básicos para construir un gráfico sobre él.

Además, preste atención a las exportaciones usadas y las exportaciones proporcionadas. Hablaremos de ellos un poco más tarde. Pero estas también son cosas muy importantes.


Y si describe su decisión, debe hablar sobre los tipos de conexiones que ocurren entre los módulos. Es decir, tenemos, por supuesto, nuestro sistema de módulos dentro de nuestro lenguaje: ya sea cjs-modules o esm-modules. Además, debe aceptar que podemos tener una conexión entre archivos en el sistema de archivos al nivel del sistema de archivos en sí. Estos son algunos tipos de marcos: algún tipo de marco dependerá de cómo sean los papás.

Y un ejemplo tan banal: si escribió el lado del servidor de Node, con bastante frecuencia podría ver un paquete npm tan popular como Config. Le permite definir convenientemente sus configuraciones.

Para usarlo, debe obtener la carpeta de configuración, donde tiene NODE_PATH, y especificar varios archivos JavaScript, solo para presentar la configuración allí para diferentes entornos. Como ejemplo, creé un papá, por defecto especificado, desarrollo y producción.

Y, de hecho, toda la configuración funciona de esta manera. Es decir, cuando escribe require ('config'), solo lee el módulo dentro de sí mismo y toma el nombre del módulo de la variable de entorno. Como comprenderá, no estaba claro allí que estos archivos se usan de alguna manera, porque no hay importación / necesidad directa, webpack ni siquiera lo reconocería.

Hoy también hablamos sobre la inyección de dependencia. No era algo inspirado, pero en apoyo miré una de las bibliotecas aquí. Se llama inversify JS. Como puede ver, proporciona una sintaxis bastante personalizada: lazyInject, nameProvider, y aquí está. Y, debes admitir, no está claro qué tipo de proveedor es, qué tipo de módulo realmente inyecta aquí. Y lo necesitamos, y tenemos que entenderlo. Es decir, nuevamente, el sistema de compilación no se resolverá, y tendremos que hacerlo nosotros mismos.
Supongamos que hemos construido un gráfico, y le sugiero que comience almacenándolo en algún lugar. ¿Qué nos permitirá hacer esto? Esto nos permitirá hacer algún tipo de análisis heurístico, reproducir un poco de Data Science y hacerlo, centrándonos en un segmento de tiempo.

Cual es la idea Aquí, de hecho, está directamente nuestros datos. Recientemente implementamos nuestro sistema de diseño en Yandex.Market y, en particular, implementamos una biblioteca de componentes como parte de este sistema de diseño. Y aquí puede ver: consideramos la cantidad de importaciones, el componente de reacción de nuestra biblioteca, el componente común. Y puedes distribuir en directorios. En este caso, tenemos un repositorio no mono y, por lo tanto, tenemos platform.desktop, platform.touch y src.
¿Qué podemos pensar cuando vemos estos números? Podemos hipotetizar que el comando táctil no parece aumentar el uso de componentes comunes. Esto significa que los componentes son malos para los dispositivos móviles, están mal hechos o el comando táctil es lento. ¿Pero es realmente así?

Si miramos en un período más largo, en un período de tiempo más largo, esto nos permite hacer solo el almacenamiento de gráficos después de cada lanzamiento, entonces entenderemos que, de hecho, todo está bien para tocar, el indicador está creciendo en él. Para src, es aún mejor, para el escritorio, resulta que no.

Todavía había una pregunta de la audiencia sobre cómo explicar la importancia a los gerentes. Aquí está el número total de importaciones de la biblioteca, también por tiempo. ¿A qué gerentes no les gustan los gráficos? Puede crear un calendario de este tipo y ver que el uso de la biblioteca está creciendo, lo que significa que esto es al menos algo útil.
Una de mis partes favoritas. Lo cubriré bastante brevemente. Esta es una búsqueda de defectos en el gráfico. Hoy quería hablar con ustedes acerca de dos tipos de defectos: esta es una dependencia cíclica de módulos y algún módulo no utilizado, es decir, un problema de eliminación de código muerto.

Comencemos con la dependencia circular.

Todo parece ser bastante simple aquí. Ya tiene un gráfico dirigido, solo necesita encontrar un bucle allí. Explicaré por qué estoy hablando de esto. El hecho es que antes de escribir, básicamente, el lado del servidor en Node.js, y no usamos, en principio, ningún paquete web / babel, nada. Es decir, se lanzaron tal cual. Y hubo requerimiento. ¿Quién recuerda cómo la importación difiere de la requerida? Todo esta correcto. Si escribió mal el código, pero realmente lo hice, puede descubrir en su servidor que su módulo está en algún tipo de dependencia cíclica solo cuando alguna solicitud proviene de los usuarios, o algún otro evento funcionará. Ese es un problema bastante global. Hasta el tiempo de ejecución no entiendo. Es decir, la importación es mucho mejor, no habrá tal problema.

Luego simplemente tome cualquier algoritmo que desee. Aquí tomé un algoritmo bastante simple. Necesitamos encontrar un vértice que tenga solo un tipo de aristas, ya sea entrante o saliente. Si existe tal vértice, lo eliminamos, eliminamos los bordes y, de hecho, continuamos este proceso, encontraremos y probaremos que hubo un ciclo de cinco ciclos en este gráfico.
De acuerdo, si lo miró por código, es decir, allí todavía puede encontrar un ciclo de dos o tres longitudes, pero es más poco realista, y realmente tuvimos un ciclo de siete en el proyecto, pero no en la producción.

Sobre módulos no utilizados. También hay un algoritmo bastante trivial. Necesitamos resaltar los componentes conectados en nuestro gráfico, y solo mirar, encontrar aquellos componentes que no incluyen ninguno de los nodos de entrada. En este caso, este es este componente de conectividad, ambos vértices, resulta que ambos nodos. Luego se llama entry.js. En realidad, no importa cómo se llame, esto es lo que ha descrito en los medios de configuración del ensamblado de entrada.

Pero hay otro enfoque. Si no ha recopilado el gráfico y solo tiene un sistema de compilación, ¿cómo es la forma más económica de hacerlo? Marquemos todos los archivos que ingresaron al ensamblado durante el ensamblaje. Etiquetarlos y crear muchos. Después de eso, deberíamos obtener muchos de los archivos que tiene en el proyecto y simplemente restarlos. Esa es una operación muy simple.

Y ahora no solo te digo algo teórico, me inspiré, vine a mi proyecto e hice esto. Y atención! Ni siquiera eliminé node_modules. Esto lo dejé como un punto de crecimiento para la próxima revisión. Y, en resumen, me inspiré tanto que decidí hacer esta diapositiva, reorganizarla. ¡Que se vea así, porque es realmente genial!
Buenos números, ¿te imaginas cómo todo salió bien? Y luego fui llevado a una estepa tal que me sentí como un diseñador, y pensé que este es un logro que me gustaría agregar al marco. Y, como saben, me levanté, miré y me di cuenta de que probablemente no era un diseñador, sino un desarrollador web. Pero no soy tonto. Tomé este marco, agregado a mi sitio para amuletos de SEO.

Puedes usar, incluso el enlace es. Y para que no creas que te estoy engañando, somos francos hoy, realmente miré las críticas. Creo que puedes creerles.

Bueno, para ser honesto, se parecía a esto. Vi una nueva biblioteca de hipopótamos thanos-js, la tomé, creé una solicitud de grupo. En secreto, tengo derechos de administrador en nuestro repositorio. Y tomé y confundí al maestro. Como te gusta eso Bueno, tú y yo somos francos y, de hecho, todo parecía así. Si alguien no lo sabe, thanos-js es una biblioteca que simplemente elimina el 50% de su código al azar.

En realidad, utilicé la biblioteca allí de todos modos, pero la biblioteca se llama de manera diferente. Se llama difector, y ahora hablaremos de eso con usted. Y aquí me gustaría señalar que la solicitud del grupo es bastante significativa, menos 44 mil líneas de código, y se pueden imaginar: pasó la prueba la primera vez. Es decir, de lo que estoy hablando realmente puede funcionar.

Difusor De hecho, está involucrado no solo en la tarea de eliminar módulos no utilizados, buscar defectos en el gráfico, sino también en una tarea más importante. Lo que inicialmente dije fue ayudar al desarrollador y al probador, ahora hablaremos de ello. Y funciona algo como esto.
Obtenemos una lista de archivos modificados utilizando el sistema de control de versiones. Ya hemos construido un gráfico, diffector lo construye. Y para cada archivo modificado, buscamos la ruta de entrada y marcamos la entrada modificada. Y la entrada corresponderá con las páginas de la aplicación que verá el usuario. Pero esto es bastante lógico.
¿Y qué nos da esto? Para pruebas: sabemos qué páginas de la aplicación han cambiado. Podemos decirle al probador que solo vale la pena probarlos. También podemos decirle a nuestro trabajo ci, que ejecuta pruebas automáticas, que solo vale la pena probar estas páginas. Y para los desarrolladores, todo es mucho más simple, porque ahora los evaluadores no te escriben y no te preguntan: "¿Por qué necesitas hacer una prueba?"

Veamos un ejemplo de cómo funciona el difector. Aquí tenemos un cierto directorio, pages.desktop / *. Solo contiene una lista de las páginas en sí. Y las páginas también están descritas por varios archivos. El controlador es el lado del servidor de la página. La vista es algún tipo de parte de reacción. Y deps, esto es de otro sistema de compilación. No solo tenemos webpack, sino también ENB.

E hice algunos cambios en el proyecto, en un archivo vacío, cuya estructura viste. Esto es lo que me da diffector. Lo acabo de iniciar, diffector es una aplicación de línea de comandos. Lo lancé, me dice que he cambiado una página, que se llama BindBonusPage.

También puedo ejecutarlo en modo detallado, ver un informe más detallado y realmente ver que al menos funciona en un caso tan simple. Como vemos, en nuestra página BindBonusPage, el archivo de índice y el controlador han cambiado.
Pero veamos qué sucede si cambiamos algo más.

Cambié algo más. Y el difector me dijo que he cambiado nueve páginas. Y esto ya no me hace feliz, como si realmente no me ayudara.

A ver por qué? Ahora muestra las razones por las cuales esta página se consideró modificada. , . - uikit.

diff. . , diffector . , - , .

, , . , , entry, , , test-scope, . .
. , , , , .

. - , . — i18n, , . , , , . , , - .
? , , , , , .

- . , B , , -2 . . , esm.

.

.

, value, . , , . , .
, AST, , 250 , , . , , - , , .

, - GlobalContext - , . , modify, , ? , - GlobalContext. . . , side effects. , , webpack, , . , webpack sideEffects: true, . .

, - - . , . diffector, . , , . — , . , .
, , diff, expand, log, , , , .

, . D, diff. , , , . , , . , , . .
, , . . . , — , . . . , , , , , . . , .
— diffector ? , , , .

- , , .

, , entry. entry. diffector.

. -. , .

, entry -, .

. diffector. . , , - , -. , . : , BindBonusPage, -, . . , , - . .


— CI. . : , , .

, . 43 — testing, , .

. , , .

, . : , , . , , , , , . , , - .

. , , , . - , - , , , . .
, , , output . , . — . — . Gracias