Hola, mi nombre es Dmitry Karlovsky y adoro a MAM. M AM gobierna los módulos Gnósticos M , ahorrándome la mayor parte de la rutina.

El módulo agnóstico , a diferencia del tradicional, no es un archivo fuente, sino un directorio dentro del cual puede haber códigos fuente en una variedad de idiomas: lógica de programa en JS
/ TS
, pruebas para él en TS
/ JS
, composición de componentes en view.tree
, estilos en CSS
, localización en locale=*.json
, imágenes, etc., etc. Si lo desea, no es difícil asegurar el soporte de cualquier otro idioma. Por ejemplo, Stylus para escribir estilos o HTML para describir plantillas.
Las dependencias entre módulos se rastrean automáticamente mediante el análisis de la fuente. Si el módulo se enciende, se enciende como un todo: cada código fuente del módulo se transpone y cae en el paquete correspondiente: scripts - por separado, estilos - por separado, pruebas - por separado. Para diferentes plataformas, sus paquetes: para un nodo, el suyo, para un navegador, el suyo.
Automatización total, falta de configuración y repetitivo, tamaños de paquete mínimos, bombeo automático de dependencias, desarrollo de cientos de bibliotecas y aplicaciones enajenadas en una base de código sin dolor ni sufrimiento. Wow que adicción! ¡Saquen a los niños embarazadas, nerviosos, de los monitores y bienvenidos al submarino!
Filosofía
MAM es un experimento audaz para cambiar radicalmente la forma en que se organiza el código y el proceso de trabajar con él. Aquí están los principios básicos:
Convenciones en lugar de configuración. Los acuerdos razonables, simples y universales le permiten automatizar toda la rutina, manteniendo la comodidad y la uniformidad entre los diferentes proyectos.
La infraestructura está separada, el código está separado. Una situación no es infrecuente cuando necesita desarrollar docenas, o incluso cientos de bibliotecas y aplicaciones. No despliegue la infraestructura de ensamblaje, desarrollo, despliegue, etc. para cada uno de ellos. Es suficiente pedirlo una vez y luego remachar aplicaciones como pasteles.
No pague por lo que no usa. Utiliza algún tipo de módulo: está incluido en el paquete con todas sus dependencias. No utilizar: no se enciende. Cuanto más pequeños son los módulos, mayor es la granularidad y menos código innecesario en el paquete.
Código mínimo redundante. Romper el código en los módulos debería ser tan simple como escribir todo el código en un solo archivo. De lo contrario, el desarrollador será perezoso para dividir los módulos grandes en pequeños.
No hay conflictos de versiones. Solo hay una versión: la actual. No es necesario gastar recursos en el soporte de versiones antiguas, si puede gastarlas en actualizar las últimas.
Mantenga un dedo en el pulso. Los comentarios más rápidos con respecto a las incompatibilidades no permitirán que el código salga mal.
La forma más fácil es la más segura. Si el camino correcto requiere un esfuerzo adicional, asegúrese de que nadie vaya a ellos.
Importaciones / Exportaciones
Abrimos el primer proyecto que surgió utilizando un sistema de módulos moderno: un módulo tiene menos de 300 líneas de largo, 30 de las cuales son importaciones.
Pero estas siguen siendo flores: para una función de 9 líneas, se requieren 8 importaciones.
Y mi favorito: ni una sola línea de código útil. 20 líneas de cambio de valores del montón de módulos a uno, para que luego pueda importar desde un módulo, y no desde veinte.
Todo esto es repetitivo, lo que lleva al hecho de que los desarrolladores son demasiado vagos para asignar pequeños fragmentos de código en módulos separados, prefiriendo módulos grandes a pequeños. E incluso si no son perezosos, resulta mucho código para importar módulos pequeños o módulos especiales que importan muchos módulos en sí mismos y los exportan a granel.
Todo esto conduce a una baja granularidad del código y a inflar el tamaño de los paquetes con código no utilizado, lo cual es lo suficientemente afortunado como para estar cerca del que se usa. Para JS, están tratando de resolver este problema complicando la tubería de ensamblaje, agregando la llamada "sacudida del árbol", que corta el exceso de lo que importó. Esto ralentiza el montaje, pero no todo está cortado.
Idea: ¿Qué sucede si no importamos, sino que solo tomamos y usamos, y el recolector mismo descubrirá qué necesita importarse?
Los IDE modernos pueden generar automáticamente importaciones para las entidades que utiliza. Si el IDE puede hacer esto, ¿qué impide que el recopilador haga esto? Es suficiente tener un acuerdo simple sobre el nombre y la ubicación de los archivos, lo que sería conveniente para el usuario y comprensible para la máquina. PHP ha tenido durante mucho tiempo una convención estándar: PSR-4 . MAM introduce lo mismo para los archivos .ts y .jam.js: los nombres que comienzan con $ son el Nombre completo de alguna entidad global cuyo código se carga a lo largo de la ruta obtenida de FQN reemplazando los delimitadores con barras inclinadas. Un ejemplo simple de dos módulos:
my / alert / alert.ts
const $my_alert = alert // FQN
my / app / app.ts
$my_alert( 'Hello!' ) // , /my/alert/
Un módulo completo de una línea: ¿qué podría ser más simple? El resultado no se hace esperar: la simplicidad de crear y usar módulos lleva a minimizar su tamaño. Como resultado, para maximizar la granularidad. Y como una cereza, minimiza el tamaño de los bultos sin sacudir los árboles.
Un buen ejemplo es la familia de módulos de validación JSON / mol / data . Si utiliza la función $mol_data_integer
en algún lugar de su código, los módulos /mol/data/integer
y /mol/data/number
, de los que depende $mol_data_integer
, se incluirán en el paquete. Pero, por ejemplo, el recopilador /mol/data/email
ni siquiera leerá desde el disco, ya que nadie depende de él.
Rastrillar un desastre
Desde que comenzamos a patear Angular, no nos detendremos. ¿Dónde piensa buscar la applyStyles
función applyStyles
? No adivinará en /packages/core/src/render3/styling_next/bindings.ts
. La capacidad de colocar cualquier cosa en cualquier lugar conduce al hecho de que en cada proyecto observamos un sistema único de ubicación de archivos, a menudo no susceptible de ninguna lógica. Y si el "salto a la definición" a menudo guarda el IDE, entonces ver el código en el github o revisar la solicitud de extracción se ven privados de esta oportunidad.
Idea: ¿Qué pasa si los nombres de las entidades corresponden estrictamente a su ubicación?
Para colocar el código en el archivo /angular/packages/core/src/render3/stylingNext/bindings.ts
, en la arquitectura MAM deberá nombrar la entidad $angular_packages_core_src_render3_stylingNext_applyStyles
, pero, por supuesto, nadie actuará, porque hay mucho más en el nombre. Pero los nombres en el código quiero ver cortos y concisos, por lo que el desarrollador intentará excluir todo lo innecesario del nombre, dejando solo lo importante: $angular_render3_applyStyles
. Y se ubicará en consecuencia en /angular/render3/applyStyles/applyStyles.ts
.
Observe cómo MAM utiliza las debilidades de los desarrolladores para lograr el resultado deseado: cada entidad obtiene un nombre corto globalmente único que puede usarse en cualquier contexto. Por ejemplo, en los mensajes de confirmaciones, estos nombres le permiten captar de manera rápida y precisa de lo que están hablando:
73ebc45e517ffcc3dcce53f5b39b6d06fc95cae1 $mol_vector: range expanding support 3a843b2cb77be19688324eeb72bd090d350a6cc3 $mol_data: allowed transformations 24576f087133a18e0c9f31e0d61052265fd8a31a $mol_data_record: support recursion
O supongamos que desea encontrar todas las menciones del módulo $ mol_fiber en Internet , lo que lo hace más fácil que nunca gracias a FQN.
Dependencias cíclicas
Escribamos 7 líneas de código simple en un archivo:
export class Foo { get bar() { return new Bar(); } } export class Bar extends Foo {} console.log(new Foo().bar);
A pesar de la dependencia cíclica, funciona correctamente. Lo dividimos en 3 archivos:
my / foo.js
import { Bar } from './bar.js'; export class Foo { get bar() { return new Bar(); } }
my / bar.js
import { Foo } from './foo.js'; export class Bar extends Foo {}
my / app.js
import { Foo } from './foo.js'; console.log(new Foo().bar);
¡Vaya! Error de ReferenceError: Cannot access 'Foo' before initialization
. ¿Qué tipo de tonterías? Para solucionar esto, nuestra app.js
necesita saber que foo.js
depende de bar.js
Por lo tanto, primero tenemos que importar bar.js
, que importa foo.js
Después de lo cual ya podemos importar foo.js
sin error:
my / app.js
import './bar.js'; import { Foo } from './foo.js'; console.log(new Foo().bar);
Esos navegadores, ese NodeJS, ese paquete web, ese paquete, todos funcionan de manera torcida con dependencias circulares. Y bueno, simplemente los prohibirían: uno podría complicar de inmediato el código para que no haya bucles. Pero pueden funcionar bien, y luego bam, y dar un error incomprensible.
Idea: ¿Qué sucede si durante el ensamblaje simplemente pegamos los archivos en el orden correcto, como si todo el código se hubiera escrito originalmente en un archivo?
Dividamos el código usando los principios de MAM:
my / foo / foo.ts
class $my_foo { get bar() { return new $my_bar(); } }
my / bar / bar.ts
class $my_bar extends $my_foo {}
my / app / app.ts
console.log(new $my_foo().bar);
Todas las mismas 7 líneas de código que originalmente. Y simplemente funcionan sin chamanismo adicional. El caso es que el coleccionista entiende que la dependencia de my/bar
de my/foo
más estricta que my/foo
de my/bar
. Esto significa que debe incluir estos módulos en el paquete en este orden: my/foo
, my/bar
, my/app
.
¿Cómo entiende esto el coleccionista? Ahora la heurística es simple: por el número de sangría en la línea en la que se detecta la dependencia. Tenga en cuenta que una dependencia más fuerte en nuestro ejemplo tiene cero sangría, y una débil tiene doble sangría.
Diferentes idiomas
Dio la casualidad de que para diferentes cosas tenemos diferentes idiomas para estas diferentes cosas agudizadas. Los más comunes son: JS, TS, CSS, HTML, SVG, SCSS, Less, Stylus. Cada uno tiene su propio sistema de módulos, que no interactúa con otros idiomas de ninguna manera. No hace falta decir que hay unos 100500 tipos de idiomas más específicos. Como resultado, para conectar un componente, debe conectar por separado sus scripts, estilos separados, registrar plantillas por separado, configurar por separado la implementación de los archivos estáticos que necesita, etc., etc.
Gracias a los cargadores, Webpack está tratando de resolver este problema. Pero tiene un punto de entrada es un script que ya conecta archivos en otros idiomas. ¿Y si no necesitamos un script? Por ejemplo, tenemos un módulo con hermosos estilos para platos y queremos que tengan los mismos colores en el tema claro y otros en la oscuridad:
.dark-theme table { background: black; } .light-theme table { background: white; }
Además, si dependemos del tema, se debe cargar un script que instalará el tema deseado según la hora del día. Es decir, CSS en realidad depende de JS.
Idea: ¿Qué pasa si un sistema modular no depende de idiomas?
Dado que en MAM el sistema modular está separado de los idiomas, las dependencias pueden ser entre idiomas. CSS puede depender de JS, que puede depender de TS, que puede depender de otro JS. Esto se logra debido al hecho de que las dependencias de origen se detectan en los módulos, y los módulos están conectados por completo y pueden contener códigos fuente en cualquier idioma. En el caso del ejemplo de temas, se ve así:
/my/table/table.css
[my_theme="dark"] table { background: black; } [my_theme="light"] table { background: white; }
/my/theme/theme.js
document.documentElement.setAttribute( 'my_theme' , ( new Date().getHours() + 15 ) % 24 < 12 ? 'light' : 'dark' , )
Utilizando esta técnica, por cierto, puede implementar su Modernizador , pero sin 300 comprobaciones que no necesita, porque solo se incluirán en el paquete las comprobaciones de las que realmente depende su CSS.
Muchas bibliotecas
Por lo general, el punto de entrada para crear un paquete es algún tipo de archivo. En el caso de Webpack, este es JS. Si desarrolla muchas bibliotecas y aplicaciones enajenables, entonces necesita muchos paquetes. Y para cada paquete necesita crear un punto de entrada separado. En el caso de Parcel, el punto de entrada es HTML, que para las aplicaciones deberá crearse de todos modos. Pero para las bibliotecas, esto de alguna manera no es muy adecuado.
Idea: ¿Qué pasa si cualquier módulo puede ensamblarse en un paquete independiente sin preparación previa?
Armemos la última versión del generador de proyectos $ mol_build MAM:
mam mol/build
Ahora ejecute este colector y deje que se vuelva a armar para asegurarse de que todavía puede hacerlo:
node mol/build/-/node.js mol/build
Aunque, no, vamos a pedirle que ejecute pruebas junto con la asamblea:
node mol/build/-/node.test.js mol/build
Y si todo salió bien, publique el resultado en NPM:
npm publish mol/build/-
Como puede ver, al ensamblar el módulo, se crea un subdirectorio con el nombre -
y todos los artefactos de ensamblaje se colocan allí. Veamos los archivos que puedes encontrar allí:
web.dep.json
: toda la información sobre el gráfico de dependenciaweb.js
- paquete de script del navegadorweb.js.map
- sorsmaps para élweb.esm.js
: tiene la forma de un módulo esweb.esm.js.map
- y sorsmaps para elloweb.test.js
- paquete de pruebaweb.test.js.map
- y para pruebas sorsmapweb.d.ts
- paquete con tipos de todo lo que está en el paquete de scriptweb.css
- paquete con estilosweb.css.map
- y clasifica mapas para elloweb.test.html
: punto de entrada para ejecutar pruebas de rendimiento en un navegadorweb.view.tree
: declaraciones de todos los componentes incluidos en el paquete view.treeweb.locale=*.json
- paquetes con textos localizados, cada paquete tiene su propio paquetepackage.json
: le permite publicar inmediatamente el módulo ensamblado en NPMnode.dep.json
: toda la información sobre el gráfico de dependencianode.js
- paquete de script de nodonode.js.map
- sorsmaps para ellonode.esm.js
: tiene la forma de un módulo esnode.esm.js.map
- y sorsmaps para ellonode.test.js
: el mismo paquete, pero también con pruebasnode.test.js.map
- y sorsmaps para ellonode.d.ts
: paquete con tipos de todo lo que está en el paquete de scriptnode.view.tree
: declaraciones de todos los componentes incluidos en el paquete view.treenode.locale=*.json
- paquetes con textos localizados, cada paquete tiene su propio paquete
La estática simplemente se copia junto con las rutas. Como ejemplo, tome una aplicación que muestre sus propios códigos fuente . Sus fuentes están aquí:
/mol/app/quine/quine.view.tree
/mol/app/quine/quine.view.ts
/mol/app/quine/index.html
/mol/app/quine/quine.locale=ru.json
Desafortunadamente, en el caso general, el recopilador no puede saber que necesitaremos estos archivos en tiempo de ejecución. Pero podemos decirle esto poniendo un archivo especial cerca:
/mol/app/quine/quine.meta.tree
deploy \/mol/app/quine/quine.view.tree deploy \/mol/app/quine/quine.view.ts deploy \/mol/app/quine/index.html deploy \/mol/app/quine/quine.locale=ru.json
Como resultado del ensamblado /mol/app/quine
, se copiarán de las siguientes maneras:
/mol/app/quine/-/mol/app/quine/quine.view.tree
/mol/app/quine/-/mol/app/quine/quine.view.ts
/mol/app/quine/-/mol/app/quine/index.html
/mol/app/quine/-/mol/app/quine/quine.locale=ru.json
Ahora el directorio /mol/app/quine/-
se puede diseñar en cualquier alojamiento estático y la aplicación será completamente funcional.
JS se puede ejecutar tanto en el cliente como en el servidor. Y qué bueno es cuando puedes escribir un código y funcionará en todas partes. Sin embargo, a veces la implementación de lo mismo en el cliente y el servidor es fundamentalmente diferente. Y quiero, por ejemplo, que se use una implementación para un nodo y otra para un navegador.
Idea: ¿Qué sucede si el propósito del archivo se refleja en su nombre?
MAM usa un sistema de etiquetas en los nombres de archivo. Por ejemplo, el módulo $mol_state_arg
proporciona acceso a los parámetros de aplicación definidos por el usuario. En el navegador, estos parámetros se establecen a través de la barra de direcciones. Y en el nodo, a través de argumentos de línea de comando. $mol_sate_arg
el resto de la aplicación de estos matices mediante la implementación de ambas opciones con una única interfaz, colocándolas en archivos:
- / mol / state / arg / arg. web .ts - implementación para navegadores
- / mol / state / arg / arg. nodo .ts: implementación para un nodo
Las fuentes no etiquetadas con estas etiquetas se incluyen independientemente de la plataforma de destino.
Se observa una situación similar con las pruebas: desean almacenarse junto al resto de las fuentes, pero no quieren incluirse en el paquete que va al usuario final. Por lo tanto, las pruebas también están marcadas con una etiqueta separada:
- / mol / state / arg / arg. test .ts - pruebas de módulo, caerán en el paquete de prueba
Las etiquetas pueden ser paramétricas. Por ejemplo, con cada módulo pueden venir textos en varios idiomas y deben incluirse en los paquetes de idiomas correspondientes. Un archivo de texto es un diccionario JSON normal nombrado con la configuración regional en el nombre:
- / mol / app / life / life. locale = ru .json - textos para el idioma ruso
- / mol / app / life / life. locale = jp .json - textos para japonés
Finalmente, ¿qué pasa si queremos poner archivos cerca, pero queremos que el recopilador los ignore y no los incluya automáticamente en el paquete? Es suficiente agregar al comienzo de su nombre cualquier carácter no alfanumérico. Por ejemplo:
- / hyoo / juguetes / . git: comienza con un punto, por lo que el recopilador ignorará este directorio
Versionado
Google lanzó AngularJS por primera vez y lo publicó en NPM como angular
. Luego creó un marco completamente nuevo con un nombre similar: Angular y lo publicó con el mismo nombre, pero ya la versión 2. Ahora estos dos fuegos artificiales se están desarrollando de forma independiente. Solo se produce un cambio de ruptura de API entre las versiones principales. Y el otro, entre el menor . Y dado que es imposible poner dos versiones de la misma dependencia en el mismo nivel, no se puede hablar de una transición fluida, cuando dos versiones de la biblioteca coexisten simultáneamente durante algún tiempo en la aplicación.
Parece que el equipo de Angular ya ha pisado todos los rastrillos posibles. Y una cosa más: el código marco está dividido en varios módulos grandes. Al principio, los versionaron de forma independiente, pero muy rápidamente, incluso ellos mismos comenzaron a confundirse sobre las versiones de los módulos que son compatibles entre sí, y mucho menos los desarrolladores comunes. Por lo tanto, Angular cambió a versiones de extremo a extremo , donde la versión principal del módulo puede cambiar incluso sin ningún cambio en el código. El soporte para múltiples versiones de múltiples módulos es un gran problema tanto para los propios mantenedores como para el ecosistema en su conjunto. Después de todo, se gastan muchos recursos de todos los miembros de la comunidad para garantizar la compatibilidad con módulos ya obsoletos.
La hermosa idea de las versiones semánticas se convierte en una realidad dura: nunca se sabe si algo se romperá cuando cambie la versión menor o incluso la versión del parche . Por lo tanto, en muchos proyectos, se repara una versión específica de la dependencia. Sin embargo, una solución de este tipo no afecta las dependencias transitivas, que pueden ser atraídas a la última versión cuando se instala desde cero, pero pueden seguir siendo las mismas si ya lo son. Este desastre lleva al hecho de que nunca puede confiar en una versión fija y necesita verificar regularmente la compatibilidad con las versiones actuales de dependencias (al menos transitivas).
¿Pero qué pasa con los archivos de bloqueo ? Si está desarrollando una biblioteca que se instala a través de dependencias, el archivo de bloqueo no lo ayudará, ya que el administrador de paquetes lo ignorará. Para la aplicación final, el archivo de bloqueo le dará la llamada "reproducibilidad de ensamblajes". Pero seamos honestos. ¿Cuántas veces necesitas construir la aplicación final desde la misma fuente? Exactamente una vez. Al recibir la salida, independientemente de cualquier NPM, el artefacto de ensamblaje: un binario ejecutable, un contenedor de acoplador o simplemente un archivo con todo el código necesario para ejecutarlo. Espero que no npm install
haciendo npm install
en prod?
Algunos encuentran el uso de archivos de bloqueo para que el servidor CI reúna exactamente lo que el desarrollador ha comprometido. Pero espere, el desarrollador mismo puede simplemente ensamblarlo en su máquina local. , , , . Continuous Integration , , , , - . CI , .
, , . , Angular@4 ( 3). , , " " " ". Angular@4 , Angular@5. Angular@6, . Angular TypeScript . . , 2 , … , business value , , , , .
, , , , 2 . : , — , — . 3 React, 5 jQuery, 7 lodash.
: — ?
. - . , . , . , . , . , . , , . : issue, , workaround, pull request, , . , , . . .
, . , , . . . : , , -. - — . , , - . , , , NPM . , . .
, ? — . mobx
, mobx2
API . — , : , . mobx
mobx2
, API. API, .
. — . , :
var pages_count = $mol_atom2_sync( ()=> $lib_pdfjs.getDocument( uri ).promise ).document().numPages
mol_atom2_sync
lib_pdfjs
, :
npm install mol_atom2_sync@2.1 lib_pdfjs@5.6
, , — , . ? — , *.meta.tree
, :
/.meta.tree
pack node git \https://github.com/nin-jin/pms-node.git pack mol git \https://github.com/eigenmethod/mol.git pack lib git \https://github.com/eigenmethod/mam-lib.git
. .
NPM
MAM — NPM . , — . , , NPM .
NPM , $node. , - -:
/my/app/app.ts
$node.portastic.find({ min : 8080 , max : 8100 , retrieve : 1 }).then( ( ports : number[] ) => { $node.express().listen( ports[0] ) })
, . - lib
NPM . , NPM- pdfjs-dist
:
/lib/pdfjs/pdfjs.ts
namespace $ { export let $lib_pdfjs : typeof import( 'pdfjs-dist' ) = require( 'pdfjs-dist/build/pdf.min.js' ) $lib_pdfjs.disableRange = true $lib_pdfjs.GlobalWorkerOptions.workerSrc = '-/node_modules/pdfjs-dist/build/pdf.worker.min.js' }
/lib/pdfjs/pdfjs.meta.tree
deploy \/node_modules/pdfjs-dist/build/pdf.worker.min.js
, .
. create-react-app
angular-cli
, . , , eject
. . , , .
: ?
MAM . .
MAM MAM , :
git clone https://github.com/eigenmethod/mam.git ./mam && cd mam npm install npm start
8080 . , — MAM.
( — acme
) ( — hello
home
):
/acme/acme.meta.tree
pack hello git \https://github.com/acme/hello.git pack home git \https://github.com/acme/home.git
npm start
:
npm start acme/hello acme/home
. — . , , . — : https://t.me/mam_mol