Assemblage d'une bibliothèque de composants angulaires en tant que composants Web

De nombreux articles sont écrits sur Angular Elements et lisent régulièrement des rapports. Comme, vous n'avez plus besoin de déployer plusieurs angulaires à part entière - il suffit de collecter les composants Web et de les utiliser sur votre page.

Mais, en règle générale, ces matériaux se limitent à considérer une situation plutôt utopique: nous faisons un projet séparé, créons un composant angulaire, configurons le projet pour assembler Elements et, enfin, compilons plusieurs fichiers JS, en les connectant à une page régulière nous donnerons le résultat souhaité. Hourra, le composant fonctionne! ..

image

En pratique, le besoin se fait sentir de retirer plusieurs composants d'un projet angulaire prêt à l'emploi, de plus, de préférence afin de ne pas affecter son développement et son utilisation actuels. Cet article s'est avéré juste à cause de l'une de ces situations: je voulais non seulement assembler des éléments individuels du projet, mais faire du processus de compilation de la bibliothèque d'interface utilisateur entière sur Angular en un ensemble de fichiers avec des composants Web natifs.

Préparation du module


Rappelons d'abord à quoi devrait ressembler le module de compilation des éléments angulaires.

@NgModule({ imports: [BrowserModule], entryComponents: [SomeComponent], }) export class AppModule { constructor(readonly injector: Injector) { const ngElement = createCustomElement(SomeComponent, { injector, }); customElements.define('some-component', ngElement); } ngDoBootstrap() {} } 

Nous avons besoin de:

  1. Ajoutez à l'entréeComponents le composant que nous prévoyons de faire un élément angulaire, importez les modules nécessaires pour le composant.
  2. Créez un élément angulaire à l'aide de createCustomElement et d'un injecteur.
  3. Déclarez un composant Web dans les éléments personnalisés de votre navigateur.
  4. Remplacez la méthode ngDoBootstrap pour la vider.

Le premier élément est la désignation du composant lui-même et de ses dépendances, et les trois autres sont le processus nécessaire à l'apparition du composant Web dans le navigateur. Cette séparation vous permet de placer la logique de création d'un élément séparément, dans une superclasse abstraite:

 export abstract class MyElementModule { constructor(injector: Injector, component: InstanceType<any>, name: string) { const ngElement = createCustomElement(component, { injector, }); customElements.define(`${MY_PREFIX}-${name}`, ngElement); } ngDoBootstrap() {} } 

La superclasse est capable d'assembler un composant natif à part entière à partir de l'injecteur, du composant et de le nommer et de l'enregistrer. Le module de création d'un élément spécifique ressemblera à ceci:

 @NgModule({ imports: [BrowserModule, MyButtonModule], entryComponents: [MyButtonComponent], }) export class ButtonModule extends MyElementModule { constructor(injector: Injector) { super(injector, MyButtonComponent, 'button'); } } 

Dans cet exemple, nous collectons le module de notre bouton dans les métadonnées NgModule et déclarons le composant de ce module dans entryComponents, et obtenons également l'injecteur à partir du mécanisme d'injection de dépendance angulaire.

Le module est prêt à être assemblé et nous fournira un ensemble de fichiers JS qui peuvent être pliés dans un composant Web séparé. De cette façon, nous pouvons créer plusieurs modules et assembler à tour de rôle les composants Web.

Assembler quelques composants


Nous devons maintenant configurer le processus d'amorçage des modules résultants. Surtout j'aime l'idée de mettre cette logique dans un fichier exécutable séparé, qui sera responsable de la compilation d'un module particulier.

La structure des éléments ressemble à ceci:

image

Un fichier de compilation séparé dans la version la plus simple ressemblerait à ceci:

 enableProdMode(); platformBrowserDynamic() .bootstrapModule(ButtonModule) .catch(err => console.error(err)); 

Cette approche aidera à contourner facilement les modules préparés et à maintenir la structure du projet avec des éléments clairs et simples.

Dans les paramètres de construction angular.json, nous spécifions le chemin du fichier collecté vers un certain dossier temporaire à l'intérieur de dist:

 "outputPath": "projects/elements/dist/tmp" 

Il y aura un ensemble de fichiers de sortie après l'assemblage du module.

Pour l'assemblage lui-même, utilisez la commande de construction habituelle dans angular-cli:

 ng run elements:build:production --main='projects/elements/src/${project}/${component}/compile.ts' 

Un élément distinct sera le produit final, nous activons donc les drapeaux de production avec Ahead-of-Time-compilation, puis nous substituons le chemin d'accès au fichier exécutable, qui se compose du projet et du nom du composant.

Nous allons maintenant collecter le résultat dans un fichier séparé, qui sera le dernier ensemble de notre composant Web distinct. Pour ce faire, utilisez le chat habituel:

 cat dist/tmp/runtime.js dist/tmp/main.js > dist/tmp/my-${component}.js 

Il est important de noter ici que nous ne mettons pas le fichier polyfills.js dans le bundle de chaque composant, car nous obtenons une duplication si nous utilisons plusieurs composants sur la même page à l'avenir. Bien sûr, vous devez désactiver l'option outputHashing dans angular.json.

Nous transférerons l'ensemble résultant d'un dossier temporaire vers un dossier pour stocker les composants. Par exemple, comme ceci:

 cp dist/tmp/my-${component}.js dist/components/ 

Il ne reste plus qu'à tout assembler - et le script de compilation est prêt:

 // compileComponents.js projects.forEach(project => { const components = fs.readdirSync(`src/${project}`); components.forEach(component => compileComponent(project, component)); }); function compileComponent(project, component) { const buildJsFiles = `ng run elements:build:production --aot --main='projects/elements/src/${project}/${component}/compile.ts'`; const bundleIntoSingleFile = `cat dist/tmp/runtime.js dist/tmp/main.js > dist/tmp/my-${component}.js`; const copyBundledComponent = `cp dist/tmp/my-${component}.js dist/components/`; execSync(`${buildJsFiles} && ${bundleIntoSingleFile} && ${copyBundledComponent}`); } 

Maintenant, nous avons un père soigné avec un ensemble de composants Web:

image

Connectez des composants à une page standard


Nos composants Web assemblés peuvent être insérés indépendamment dans la page, connectant leurs bundles JS selon les besoins:

 <my-elements-input id="input"> </<my-elements-input> <script src="my-input.js"></script> 

Afin de ne pas faire glisser tout le fichier zone.js avec chaque composant, nous le connectons une fois au début du document:

 <script src="zone.min.js"></script> 

Le composant est affiché sur la page, et tout va bien.

Et ajoutons un bouton:

 <my-elements-button size="l" onclick="onClick()"></my-elements-button> <script src="my-button.js"></script> 

Nous lançons la page et ...

image

Oh, c'est cassé!

Si nous regardons dans le paquet, nous trouvons là une telle ligne discrète:

 window.webpackJsonp=window.webpackJsonp||[] 

image

Fenêtre des correctifs Webpack, afin de ne pas dupliquer le chargement des mêmes modules. Il s'avère que seul le premier composant ajouté à la page peut s'ajouter à customElements.

Pour résoudre ce problème, nous devons utiliser custom-webpack:

  1. Ajoutez un pack Web personnalisé au projet avec des éléments:

    ng add @angular-builders/custom-webpack --project=elements

  2. Configuration de angular.json:

     "builder": "@angular-builders/custom-webpack:browser", "options": { "customWebpackConfig": { "path": "./projects/elements/elements-webpack.config.js" }, ... 

  3. Créez un fichier de configuration de pack Web personnalisé:

     module.exports = { output: { jsonpFunction: 'myElements-' + uuidv1(), library: 'elements', }, }; 

    Dans ce document, nous devons générer un identifiant unique pour chaque assemblage de toute manière pratique. J'ai profité de uuid.

Vous pouvez réexécuter le script de génération - les nouveaux composants s'entendent bien sur la même page.

Apporter la beauté


Nos composants utilisent des variables CSS globales qui spécifient le thème de couleur et la taille des composants.

Dans les applications angulaires utilisant notre bibliothèque, elles sont placées dans le composant racine du projet. Avec des composants Web indépendants, cela n'est pas possible, il vous suffit donc de compiler les styles et de vous connecter à la page où les composants Web sont utilisés.

 // compileHelpers.js compileMainTheme(); function compileMainTheme() { const pathFrom = `../../main-project/styles/themes`; const pathTo = `dist/helpers`; execSync( `lessc ${pathFrom}/theme-default-vars.less ${pathTo}/main-theme.css`,; ); } 

Nous utilisons moins, alors compilez simplement nos variables lessc et placez le fichier résultant dans le dossier helpers.

Cette approche vous permet de contrôler le style de tous les composants Web de la page sans avoir à les recompiler.

Script final


En fait, l'ensemble du processus d'assemblage des éléments décrits ci-dessus peut être réduit à un ensemble d'actions:

 #!/bin/sh rm -r -f dist/ && mkdir -p dist/components && node compileElements.js && node compileHelpers.js && rm -r -f dist/tmp 

Il ne reste plus qu'à appeler ce script à partir du package principal.json pour réduire l'ensemble du processus de compilation des composants angulaires réels au lancement d'une seule commande.

Tous les scripts décrits ci-dessus, ainsi que des démos pour l'utilisation de composants angulaires et natifs, peuvent être trouvés sur github .

Total


Nous avons organisé un processus dans lequel l'ajout d'un nouveau composant Web ne prend que quelques minutes, tout en conservant la structure des principaux projets angulaires dont ils sont issus.

Tout développeur peut ajouter un composant à un ensemble d'éléments et les assembler dans un ensemble de bundles JS distincts de composants Web sans plonger dans les spécificités du travail avec Angular Elements.

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


All Articles