Bonjour, je m'appelle Dmitry Karlovsky et j'adore MAM. M AM gouverne les modules Gnostic M , me faisant ainsi gagner la part du lion de la routine.

Un module agnostique , contrairement au module traditionnel, n'est pas un fichier avec une source, mais un répertoire dans lequel il peut y avoir des sources dans différents langages: logique de programme en JS
/ TS
, tests pour celui-ci en TS
/ JS
, composition des composants dans view.tree
, styles en CSS
, localisation en locale=*.json
, images, etc., etc. Si vous le souhaitez, il nâest pas difficile dâobtenir le support dâune autre langue. Par exemple, Stylus pour Ă©crire des styles ou HTML pour dĂ©crire des modĂšles.
Les dĂ©pendances entre les modules sont suivies automatiquement en analysant la source. Si le module est activĂ©, il est activĂ© dans son ensemble - chaque code source du module est transposĂ© et tombe dans le bundle correspondant: scripts - sĂ©parĂ©ment, styles - sĂ©parĂ©ment, tests - sĂ©parĂ©ment. Pour diffĂ©rentes plates-formes - leurs bundles: pour un nĆud - le leur, pour un navigateur - le leur.
Automatisation complÚte, manque de configuration et de passe-partout, tailles de paquet minimales, pompage automatique des dépendances, développement de centaines de bibliothÚques et d'applications aliénées dans une base de code sans douleur ni souffrance. Wow quelle dépendance! Retirez les enceintes, les enfants nerveux des moniteurs et bienvenue dans le sous-marin!
La philosophie
MAM est une expérience audacieuse pour changer radicalement la façon dont le code est organisé et le processus de travail avec lui. Voici les principes de base:
Conventions au lieu de la configuration. Des accords raisonnables, simples et universels vous permettent d'automatiser l'ensemble de la routine, tout en conservant la commodité et l'uniformité entre les différents projets.
L'infrastructure est séparée, le code est séparé. Une situation n'est pas rare lorsque vous devez développer des dizaines, voire des centaines de bibliothÚques et d'applications. Ne déployez pas l'infrastructure d'assemblage, de développement, de déploiement, etc. pour chacun d'eux. Il suffit de le poser une fois puis de riveter des applications comme des tartes.
Ne payez pas pour ce que vous n'utilisez pas. Vous utilisez une sorte de module - il est inclus dans le bundle avec toutes ses dépendances. Ne pas utiliser - ne s'allume pas. Plus les modules sont petits, plus la granularité est grande et moins de code inutile dans le bundle.
Code redondant minimum. DĂ©composer le code en modules devrait ĂȘtre aussi simple que d'Ă©crire tout le code dans un seul fichier. Sinon, le dĂ©veloppeur sera paresseux pour diviser les gros modules en petits modules.
Aucun conflit de version. Il n'y a qu'une seule version - la version actuelle. Il n'est pas nécessaire de dépenser des ressources pour prendre en charge les anciennes versions, si vous pouvez les dépenser pour mettre à jour ces derniÚres.
Gardez un doigt sur le pouls. La rétroaction la plus rapide concernant les incompatibilités ne permettra pas au code de mal tourner.
Le moyen le plus simple est le plus sûr. Si le bon chemin nécessite des efforts supplémentaires, assurez-vous que personne ne s'y rendra.
Importations / exportations
Nous ouvrons le premier projet qui a vu le jour en utilisant un systĂšme de modules moderne: Un module a moins de 300 lignes de long, dont 30 sont des importations.
Mais ce sont encore des fleurs: pour une fonction de 9 lignes, 8 importations sont nécessaires.
Et mon préféré: pas une seule ligne de code utile. 20 lignes de valeurs de décalage du tas de modules en un, afin que vous puissiez plus tard importer à partir d'un module, et non de vingt.
Tout cela est un passe-partout, ce qui conduit au fait que les dĂ©veloppeurs sont trop paresseux pour allouer de petits morceaux de code dans des modules sĂ©parĂ©s, prĂ©fĂ©rant les gros modules aux petits. Et mĂȘme s'ils ne sont pas paresseux, il s'avĂšre que beaucoup de code pour importer de petits modules, ou des modules spĂ©ciaux qui importent beaucoup de modules en eux-mĂȘmes et les exportent tous en vrac.
Tout cela conduit Ă une faible granularitĂ© du code et Ă un gonflement de la taille des bundles avec du code inutilisĂ©, ce qui a la chance d'ĂȘtre proche de celui qui est utilisĂ©. Pour JS, ils essaient de rĂ©soudre ce problĂšme en compliquant le pipeline d'assemblage en ajoutant ce que l'on appelle le «tremblement d'arbre», ce qui coupe l'excĂ©dent de ce que vous avez importĂ©. Cela ralentit l'assemblage, mais tout n'est pas coupĂ©.
IdĂ©e: que se passe-t-il si nous n'importons pas, mais prenons et utilisons simplement, et le collectionneur lui-mĂȘme trouvera ce qui doit ĂȘtre importĂ©?
Les IDE modernes peuvent gĂ©nĂ©rer automatiquement des importations pour les entitĂ©s que vous utilisez. Si l'EDI peut le faire, qu'est-ce qui empĂȘche le collecteur de le faire? Il suffit d'avoir une convention simple sur la dĂ©nomination et l'emplacement des fichiers, ce qui serait pratique pour l'utilisateur et comprĂ©hensible pour la machine. PHP a depuis longtemps une telle convention standard: PSR-4 . MAM introduit la mĂȘme chose pour les fichiers .ts et .jam.js: les noms commençant par $ sont le nom complet d' une entitĂ© globale dont le code est chargĂ© le long du chemin obtenu Ă partir de FQN en remplaçant les dĂ©limiteurs par des barres obliques. Un exemple simple de deux modules:
mon / alert / alert.ts
const $my_alert = alert // FQN
mon / app / app.ts
$my_alert( 'Hello!' ) // , /my/alert/
Un module entier à partir d'une seule ligne - quoi de plus simple? Le résultat ne tarde pas à venir: la simplicité de création et d'utilisation des modules permet de minimiser leur taille. En conséquence, pour maximiser la granularité. Et comme une cerise - minimiser la taille des faisceaux sans secouer les arbres.
Un bon exemple est la famille de modules de validation JSON / mol / data . Si vous utilisez la fonction $mol_data_integer
quelque part dans votre code, les modules /mol/data/integer
et /mol/data/number
, dont dépend $mol_data_integer
, seront inclus dans le bundle. Mais, par exemple, le collecteur /mol/data/email
ne lira mĂȘme pas Ă partir du disque, car personne n'en dĂ©pend.
Ratisser un gĂąchis
Depuis que nous avons commencĂ© Ă frapper Angular, nous ne nous arrĂȘterons pas. OĂč pensez-vous rechercher la applyStyles
fonction applyStyles
? Vous ne devinerez pas sur /packages/core/src/render3/styling_next/bindings.ts
. La possibilitĂ© de placer n'importe quoi n'importe oĂč conduit au fait que, dans chaque projet, nous observons un systĂšme de localisation de fichiers unique, qui ne se prĂȘte souvent Ă aucune logique. Et si l'IDE est souvent sauvegardĂ© par le «saut Ă la dĂ©finition», alors afficher le code sur le github ou revoir la demande de pull est privĂ© de cette opportunitĂ©.
Idée: que se passe-t-il si les noms d'entité correspondent strictement à leur emplacement?
Pour placer le code dans le fichier /angular/packages/core/src/render3/stylingNext/bindings.ts
, dans l'architecture MAM, vous devez nommer l'entité $angular_packages_core_src_render3_stylingNext_applyStyles
, mais bien sûr, personne $angular_packages_core_src_render3_stylingNext_applyStyles
, car il y a tellement plus dans le nom. Mais les noms dans le code que je veux voir courts et concis, donc le développeur essaiera d'exclure tous les inutiles du nom, ne laissant que l'important: $angular_render3_applyStyles
. Et il sera situé en conséquence dans /angular/render3/applyStyles/applyStyles.ts
.
Remarquez comment MAM utilise les faiblesses des dĂ©veloppeurs pour atteindre le rĂ©sultat souhaitĂ©: chaque entitĂ© obtient un nom court globalement unique qui peut ĂȘtre utilisĂ© dans n'importe quel contexte. Par exemple, dans les messages de commits, ces noms vous permettent de saisir rapidement et prĂ©cisĂ©ment ce dont ils parlent:
73ebc45e517ffcc3dcce53f5b39b6d06fc95cae1 $mol_vector: range expanding support 3a843b2cb77be19688324eeb72bd090d350a6cc3 $mol_data: allowed transformations 24576f087133a18e0c9f31e0d61052265fd8a31a $mol_data_record: support recursion
Ou, disons que vous voulez trouver toutes les mentions du module $ mol_fiber sur Internet - ce qui rend plus facile que jamais grĂące Ă FQN.
Dépendances cycliques
Ăcrivons 7 lignes de code simple dans un seul fichier:
export class Foo { get bar() { return new Bar(); } } export class Bar extends Foo {} console.log(new Foo().bar);
Malgré la dépendance cyclique, cela fonctionne correctement. Nous le décomposons en 3 fichiers:
mon / foo.js
import { Bar } from './bar.js'; export class Foo { get bar() { return new Bar(); } }
mon / bar.js
import { Foo } from './foo.js'; export class Bar extends Foo {}
mon / app.js
import { Foo } from './foo.js'; console.log(new Foo().bar);
Oups, ReferenceError: Cannot access 'Foo' before initialization
. Quel genre de non-sens? Pour résoudre ce problÚme, notre app.js
doit savoir que foo.js
dépend de bar.js
Par conséquent, nous devons d'abord importer bar.js
, qui importe foo.js
AprÚs quoi, nous pouvons déjà importer foo.js
sans erreur:
mon / app.js
import './bar.js'; import { Foo } from './foo.js'; console.log(new Foo().bar);
Ces navigateurs, ce NodeJS, ce Webpack, ce Parcel - ils fonctionnent tous de façon tordue avec des dépendances circulaires. Et bien, ils les interdiraient simplement - on pourrait immédiatement compliquer le code pour qu'il n'y ait pas de boucles. Mais ils peuvent bien fonctionner, puis bam, et donner une erreur incompréhensible.
Idée: que se passe-t-il si lors de l'assemblage, nous collons simplement les fichiers dans le bon ordre, comme si tout le code était à l'origine écrit dans un seul fichier?
Divisons le code en utilisant les principes de MAM:
mon / foo / foo.ts
class $my_foo { get bar() { return new $my_bar(); } }
mon / bar / bar.ts
class $my_bar extends $my_foo {}
mon / app / app.ts
console.log(new $my_foo().bar);
Tout de mĂȘme 7 lignes de code qui Ă©taient Ă l'origine. Et ils fonctionnent simplement sans chamanisme supplĂ©mentaire. Le fait est que le collecteur comprend que la dĂ©pendance de my/bar
sur my/foo
plus stricte que my/foo
sur my/bar
. Cela signifie que vous devez inclure ces modules dans le bundle dans cet ordre: my/foo
, my/bar
, my/app
.
Comment le collectionneur comprend-il cela? Maintenant, l'heuristique est simple - par le nombre d'indentation dans la ligne dans laquelle la dépendance est détectée. Veuillez noter qu'une dépendance plus forte dans notre exemple a une indentation nulle et une faible a une indentation double.
Différentes langues
Il se trouve que pour différentes choses, nous avons des langues différentes pour ces différentes choses aiguisées. Les plus courants sont: JS, TS, CSS, HTML, SVG, SCSS, Less, Stylus. Chacun a son propre systÚme de modules, qui n'interagit en aucune façon avec les autres langues. Il va sans dire, environ 100500 types de langues plus spécifiques. Par conséquent, pour connecter un composant, vous devez connecter séparément ses scripts, séparer les styles, enregistrer les modÚles séparément, configurer séparément le déploiement des fichiers statiques dont il a besoin, etc., etc.
GrĂące aux chargeurs, Webpack essaie de rĂ©soudre ce problĂšme. Mais il a un point d'entrĂ©e, c'est un script qui connecte dĂ©jĂ des fichiers dans d'autres langues. Et si nous n'avons pas besoin d'un script? Par exemple, nous avons un module avec de beaux styles pour les assiettes et nous voulons qu'ils aient les mĂȘmes couleurs dans le thĂšme clair et d'autres dans le noir:
.dark-theme table { background: black; } .light-theme table { background: white; }
De plus, si nous dĂ©pendons du sujet, alors un script devrait ĂȘtre chargĂ© qui installera le sujet souhaitĂ© en fonction de l'heure de la journĂ©e. Autrement dit, CSS dĂ©pend en fait de JS.
Idée: et si un systÚme modulaire ne dépend pas des langues?
Ătant donnĂ© que dans MAM, le systĂšme modulaire est sĂ©parĂ© des langues, les dĂ©pendances peuvent ĂȘtre inter-langues. CSS peut dĂ©pendre de JS, qui peut dĂ©pendre de TS, qui peut dĂ©pendre d'un autre JS. Cela est dĂ» au fait que les dĂ©pendances de source sont dĂ©tectĂ©es sur les modules et que les modules sont entiĂšrement connectĂ©s et peuvent contenir des codes source dans toutes les langues. Dans le cas de l'exemple de thĂšmes, cela ressemble Ă ceci:
/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' , )
En utilisant cette technique, vous pouvez d'ailleurs implémenter votre Modernizr , mais sans 300 contrÎles dont vous n'avez pas besoin, car seuls les contrÎles dont votre CSS dépend vraiment seront inclus dans le bundle.
De nombreuses bibliothĂšques
En rĂšgle gĂ©nĂ©rale, le point d'entrĂ©e pour la crĂ©ation d'un bundle est une sorte de fichier. Dans le cas de Webpack, il s'agit de JS. Si vous dĂ©veloppez de nombreuses bibliothĂšques et applications aliĂ©nables, vous avez besoin de beaucoup de bundles. Et pour chaque bundle, vous devez crĂ©er un point d'entrĂ©e distinct. Dans le cas de Parcel, le point d'entrĂ©e est HTML, qui pour les applications devra de toute façon ĂȘtre créé. Mais pour les bibliothĂšques, ce n'est pas trĂšs appropriĂ©.
IdĂ©e: que se passe-t-il si un module peut ĂȘtre assemblĂ© en un bundle indĂ©pendant sans prĂ©paration prĂ©alable?
Préparons la derniÚre version du générateur de projet MAM $ mol_build:
mam mol/build
Maintenant, lancez ce collecteur et laissez-le se reconstituer pour vous assurer qu'il est toujours en mesure de se rassembler:
node mol/build/-/node.js mol/build
Bien que non, demandons-lui d'exécuter des tests avec l'assembly:
node mol/build/-/node.test.js mol/build
Et si tout s'est bien passé, publiez le résultat dans NPM:
npm publish mol/build/-
Comme vous pouvez le voir, lors de l'assemblage du module, un sous-répertoire est créé avec le nom -
et tous les artefacts d'assemblage y sont placés. Passons en revue les fichiers que vous pouvez y trouver:
web.dep.json
- toutes les informations sur le graphe de dépendancesweb.js
- ensemble de scripts de navigateurweb.js.map
- sorsmaps pour luiweb.esm.js
- il se présente sous la forme d'un module esweb.esm.js.map
- et sorsmaps pour celaweb.test.js
- bundle de testweb.test.js.map
- et pour les tests sorsmapweb.d.ts
- bundle avec les types de tout ce qui est dans le bundle de scriptweb.css
- bundle avec stylesweb.css.map
- et sortsmaps pour celaweb.test.html
- point d'entrée pour exécuter des tests de performances dans un navigateurweb.view.tree
- déclarations de tous les composants inclus dans le bundle view.treeweb.locale=*.json
- bundles avec textes localisés, chaque bundle a son propre bundlepackage.json
- vous permet de publier immédiatement le module assemblé dans NPMnode.dep.json
- toutes les informations sur le graphe de dépendancesnode.js
- bundle de script de noeudnode.js.map
- sorsmaps pour celanode.esm.js
- il se présente sous la forme d'un es-modulenode.esm.js.map
- et sorsmaps pour celanode.test.js
- le mĂȘme bundle, mais aussi avec des testsnode.test.js.map
- et sorsmaps pour celanode.d.ts
- bundle avec les types de tout ce qui est dans le bundle de scriptnode.view.tree
- déclarations de tous les composants inclus dans le bundle view.treenode.locale=*.json
- bundles avec des textes localisés, chaque bundle a son propre bundle
La statique est simplement copiée avec les chemins. Par exemple, prenez une application qui affiche ses propres codes source . Ses sources sont ici:
/mol/app/quine/quine.view.tree
/mol/app/quine/quine.view.ts
/mol/app/quine/index.html
/mol/app/quine/quine.locale=ru.json
Malheureusement, dans le cas général, le collecteur ne peut pas savoir que nous aurons besoin de ces fichiers lors de l'exécution. Mais nous pouvons le lui dire en mettant un fichier spécial à proximité:
/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
Ă la suite de l'assembly /mol/app/quine
, ils seront copiés de la maniÚre suivante:
/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
Maintenant, le répertoire /mol/app/quine/-
peut ĂȘtre disposĂ© sur n'importe quel hĂ©bergement statique et l'application sera entiĂšrement fonctionnelle.
JS peut ĂȘtre exĂ©cutĂ© Ă la fois sur le client et sur le serveur. Et comme c'est cool quand on peut Ă©crire un code et ça marchera partout. Cependant, parfois l'implĂ©mentation de la mĂȘme chose sur le client et le serveur est fondamentalement diffĂ©rente. Et je veux, par exemple, une implĂ©mentation Ă utiliser pour un nĆud, et une autre pour un navigateur.
Idée: que se passe-t-il si le but du fichier est reflété dans son nom?
MAM utilise un systĂšme de balises dans les noms de fichiers. Par exemple, le module $mol_state_arg
permet d'accĂ©der aux paramĂštres d'application dĂ©finis par l'utilisateur. Dans le navigateur, ces paramĂštres sont dĂ©finis via la barre d'adresse. Et dans le nĆud, via des arguments de ligne de commande. $mol_sate_arg
résume le reste de l'application de ces nuances en implémentant les deux options avec une seule interface, en les plaçant dans des fichiers:
- / mol / state / arg / arg. web .ts - implémentation pour les navigateurs
- / mol / state / arg / arg. node .ts - implémentation pour un noeud
Les sources non balisées avec ces balises sont incluses quelle que soit la plateforme cible.
Une situation similaire est observĂ©e avec les tests - ils veulent ĂȘtre stockĂ©s Ă cĂŽtĂ© du reste des sources, mais ils ne veulent pas ĂȘtre inclus dans le bundle qui va Ă l'utilisateur final. Par consĂ©quent, les tests sont Ă©galement marquĂ©s avec une balise distincte:
- / mol / state / arg / arg. test .ts - tests de module, ils tomberont dans le bundle de test
Les balises peuvent ĂȘtre paramĂ©triques. Par exemple, avec chaque module, des textes dans diffĂ©rentes langues peuvent venir et doivent ĂȘtre inclus dans les packs de langues correspondants. Un fichier texte est un dictionnaire JSON standard nommĂ© avec les paramĂštres rĂ©gionaux dans le nom:
- / mol / app / life / life. locale = ru .json - textes pour la langue russe
- / mol / app / life / life. locale = jp .json - textes pour le japonais
Enfin, que se passe-t-il si nous voulons placer des fichiers à proximité, mais que le collecteur les ignore et ne les inclut pas automatiquement dans le bundle? Il suffit d'ajouter au début de leur nom tout caractÚre non alphanumérique. Par exemple:
- / hyoo / jouets / . git - commence par un point, donc le collecteur ignorera ce répertoire
Versioning
Google a d'abord publié AngularJS et l'a publié dans NPM en tant angular
. Puis il a créé un tout nouveau framework avec un nom similaire - Angular et l'a publiĂ© sous le mĂȘme nom, mais dĂ©jĂ la version 2. Maintenant, ces deux feux d'artifice se dĂ©veloppent indĂ©pendamment. Un seul changement de rupture d'API se produit entre les versions principales. Et l'autre - entre le mineur . Et comme il est impossible de mettre deux versions de la mĂȘme dĂ©pendance au mĂȘme niveau, il ne peut ĂȘtre question de transition en douceur, lorsque deux versions de la bibliothĂšque coexistent simultanĂ©ment pendant un certain temps dans l'application.
Il semble que l'Ă©quipe d'Angular ait dĂ©jĂ marchĂ© sur tous les rĂąteaux possibles. Et encore une chose: le code cadre est divisĂ© en plusieurs grands modules. Au dĂ©but, ils les ont versionnĂ©s indĂ©pendamment, mais trĂšs rapidement, mĂȘme eux-mĂȘmes ont commencĂ© Ă se demander quelles versions des modules sont compatibles entre elles, sans parler des dĂ©veloppeurs ordinaires. Par consĂ©quent, Angular est passĂ© au versionnage de bout en bout , oĂč la version principale du module peut changer mĂȘme sans aucune modification du code. La prise en charge de plusieurs versions de plusieurs modules est un gros problĂšme Ă la fois pour les responsables eux-mĂȘmes et pour l'Ă©cosystĂšme dans son ensemble. AprĂšs tout, beaucoup de ressources de tous les membres de la communautĂ© sont consacrĂ©es Ă assurer la compatibilitĂ© avec des modules dĂ©jĂ obsolĂštes.
La belle idĂ©e de la version sĂ©mantique se transforme en une dure rĂ©alitĂ© - vous ne savez jamais si quelque chose se cassera lorsque vous changez la version mineure ou mĂȘme la version du patch . Par consĂ©quent, dans de nombreux projets, une version spĂ©cifique de la dĂ©pendance est corrigĂ©e. Cependant, un tel correctif n'affecte pas les dĂ©pendances transitives, qui peuvent ĂȘtre tirĂ©es vers la derniĂšre version lors de l'installation Ă partir de zĂ©ro, mais peuvent rester les mĂȘmes si elles sont dĂ©jĂ installĂ©es. Ce gĂąchis conduit au fait que vous ne pouvez jamais compter sur une version fixe et que vous devez vĂ©rifier rĂ©guliĂšrement la compatibilitĂ© avec les versions actuelles des dĂ©pendances (au moins transitives).
Mais qu'en est-il des fichiers de verrouillage ? Si vous dĂ©veloppez une bibliothĂšque qui est installĂ©e via des dĂ©pendances, le fichier de verrouillage ne vous aidera pas, car il sera ignorĂ© par le gestionnaire de packages. Pour l'application finale, le fichier de verrouillage vous donnera la soi-disant «reproductibilitĂ© des assemblages». Mais soyons honnĂȘtes. Combien de fois avez-vous besoin de crĂ©er l'application finale Ă partir de la mĂȘme source? Exactement une fois. Recevoir la sortie, indĂ©pendamment de tout NPM, l'artefact d'assemblage: un binaire exĂ©cutable, un conteneur de docker ou juste une archive avec tout le code nĂ©cessaire pour l'exĂ©cuter. J'espĂšre que vous ne faites pas d' npm install
sur prod?
Certains trouvent l'utilisation de fichiers de verrouillage dans la mesure oĂč le serveur CI compile exactement ce que le dĂ©veloppeur a commis. Mais attendez, le dĂ©veloppeur lui-mĂȘme peut simplement l'assembler sur sa machine locale. , , , . 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