קודקודייל
  • קודקודייל
  • מי אתם קודקודייל?
  • קורסים בחינם
  • צרו קשר
  • בניית אתרים
    • וורדפרס
  • נגישות אתרים
  • כל הקטגוריות
    • אנגולר
    • HTML
    • CSS
    • Javascript
    • Typescript
    • NodeJs
    • בלוקציין
  • קודקודייל
  • מי אתם קודקודייל?
  • קורסים בחינם
  • צרו קשר
  • בניית אתרים
    • וורדפרס
  • נגישות אתרים
  • כל הקטגוריות
    • אנגולר
    • HTML
    • CSS
    • Javascript
    • Typescript
    • NodeJs
    • בלוקציין
קודקודייל
  • קודקודייל
  • מי אתם קודקודייל?
  • קורסים בחינם
  • צרו קשר
  • בניית אתרים
    • וורדפרס
  • נגישות אתרים
  • כל הקטגוריות
    • אנגולר
    • HTML
    • CSS
    • Javascript
    • Typescript
    • NodeJs
    • בלוקציין
  • קודקודייל
  • מי אתם קודקודייל?
  • קורסים בחינם
  • צרו קשר
  • בניית אתרים
    • וורדפרס
  • נגישות אתרים
  • כל הקטגוריות
    • אנגולר
    • HTML
    • CSS
    • Javascript
    • Typescript
    • NodeJs
    • בלוקציין
ראשי ♦ אנגולר ♦ Angular Microfrontend Module Federation Tutorial – Part 4

Angular Microfrontend Module Federation Tutorial – Part 4

עידן יצחקי 2 ביולי 2022 2 תגובות

פעם קודמת ראינו איך לבנות את הדיירקטיב, הפעם נהפוך אותו לקצת יותר חכם כדי שנוכל להעביר אליו מודולים שלמים ומתוכם להוציא את הרכיב שאנחנו רוצים.

מה אנחנו רוצים שהוא ידע לעשות?

  • למשוך רכיב
  • למשוך רכיב מתוך מודול
  • להתקין תלויות (dependency) במידת הצורך
  • להעביר מידע ל-Input של רכיב במידת הצורך

🛑 מה שנראה כרגע בדיירקטיב עובד רק במצב DEV, אחרי שנראה איך זה עובד נבצע שינוי מסויים בדיירקטיב וב-MFE1 כדי שזה יעבוד גם במצב PROD ואני אסביר למה זה קורה.

SHELL

כך יראה הדיירקטיב בתצורה החדשה

import { loadRemoteModule } from '@angular-architects/module-federation';
import { createNgModuleRef, Directive, Injector, Input, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appCreator]'
})
export class CreatorDirective {
  @Input('appCreator') set data(value: ICompData) {
    this.componentInit(value);
  }
  constructor(private vcr: ViewContainerRef, private injector: Injector) { }
  private async componentInit(componentData: ICompData) {
    Promise.all([
      loadRemoteModule({
        type: 'module',
        remoteEntry: componentData.mfLink,
        exposedModule: './' + componentData.module
      }).then(m => {
        if (m[componentData.compName]) {
          return this.vcr.createComponent(m[componentData.compName], { injector: this.injector });
        } else {
          const moduleRef = createNgModuleRef(m[componentData.module], this.injector);
          const microComp = this.vcr.createComponent<any>(m[componentData.module].ɵmod.exports.find(
            (c: any) => { return c.name === componentData.compName }
          ), { ngModuleRef: moduleRef });
          microComp.instance.data = 'HI :)';
          return microComp;
        }
      }).catch(e => { console.warn(e); })
    ]).catch(e => { console.warn(e); })
  }
}
export interface ICompData {
  module: string,
  mfLink: string,
  compName: string
}

נעבור על השינויים שבוצעו ונסביר אותם:

שורה 8-10: כמו בתצורה הקודמת רק שהפעם במקום להוציא את המידע למשתנה, אנחנו מפעילים פונקציה ושולחים אליה את המידע.

שורה 19: אנחנו בודקים אם המודול שלנו בעל אותו שם של הרכיב, אם כן אז אנחנו מבינים שלא מדובר במודול שמכיל רכיבים לשליפה אלא מה שיש לנו הוא הרכיב עצמו ואפשר להטמיע אותו בדף.

שורה 22: מכינים את המודול והקשרים שלו לצירוף של הרכיב, כך שאם יש קשרים/ תלויות, הם יותקנו יחד עם הרכיב.

שורה 23: שליפה של הרכיב המתאים על ידי בדיקה של exports, אני רק רוצה לציין שאין חובה לשלוף משם, ניתן לבצע את הבדיקה גם על declarations. אבל למען הסדר הטוב בצד ה-MFE1 (שנראה בהמשך), אנחנו נסתכל רק על מה שמוחצן החוצה מהמודול.

אחרי שמצאנו את הרכיב שלנו, נחזיר אותו ליצירת הרכיב בצירוף הקשרים מהמודול.

שורה 26: זו דוגמה לאיך אפשר להעביר מידע לתוך הרכיב במידה שיש לו INPUT. ניתן כמובן להוסיף אותה להתקנת הרכיב בשורה 20 ורק אז לבצע return.

שורה 36: זו התוספת לאינטרפייס כדי שנוכל לשלוף רכיב מתוך מודול שלם.

בהתאם לשינויים נעדכן גם את המידע שנכנס לדיירקטיב.

<app-content [componentData]="main"></app-content>
<app-content [componentData]="side"></app-content>
<app-content [componentData]="partOne"></app-content>
import { Component, OnInit } from '@angular/core';
import { ICompData } from 'src/app/directives/creator.directive';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
  public main: ICompData = { module: 'MainContentComponent', mfLink: 'http://localhost:3001/mfe2.js', compName: 'MainContentComponent' };
  public side: ICompData = { module: 'SideContentComponent', mfLink: 'http://localhost:3001/mfe2.js', compName: 'SideContentComponent' };
  public partOne: ICompData = { module: 'CollectionModule', mfLink: 'http://localhost:3000/mfe1.js', compName: 'PartOneComponent' };
  constructor() { }

  ngOnInit(): void {
  }

}

שימו לב, במקרה שלנו, בגלל שאנחנו מכריחים לשלוח את compName אז במידה ששלחנו רכיב, שם המודול ושם הרכיב יהיו אותו דבר.

במקרה שאנחנו שולחים מודול ורוצים לחלץ ממנו רכיב כמו בשורה 12, אז שם המודול ושם הרכיב שונים.

MFE1

ניצור מודול חדש ב- src > app > components

ng g m collection

ניכנס לספריה שנוצרה (collection), בתוך הספריה יש את המודול collection.module.ts , בספריה ניצור 2 רכיבים חדשים:

ng g c part-one
ng g c part-two

נכנס למודול ונעדכן אותו כך:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PartOneComponent } from './part-one/part-one.component';
import { PartTwoComponent } from './part-two/part-two.component';

const DEC_AND_EXP = [
  PartOneComponent,
  PartTwoComponent
]

@NgModule({
  declarations: [
    [...DEC_AND_EXP]
  ],
  imports: [
    CommonModule
  ],
  exports: [
    [...DEC_AND_EXP]
  ]
})
export class CollectionModule { }

הרעיון כאן הוא, במקום לשכפל את כל הרכיבים שיש ב-declarations ל-exports , אנחנו ניצור משתנה ששם נעדכן את הרכיבים החדשים והעתקים שלו יהיו גם ב-declarations וגם ב-exports.

כדי שהמודול יעבור קימפול, נצרף אותו למודול הראשי

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { SubPageComponent } from './pages/sub-page/sub-page.component';
import { CollectionModule } from './components/collection/collection.module';

@NgModule({
  declarations: [
    AppComponent,
    SubPageComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    CollectionModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

לא משנה איך הקובץ שלכם נראה, השורה שמעניינת אותנו היא שורה 17.

ולבסוף, נעדכן גם את הקובץ שמחצין את המודול החוצה:

  new ModuleFederationPlugin({
      library: { type: "module" },

      // For remotes (please adjust)
      name: "mfe1",
      filename: "mfe1.js",
      exposes: {
        './MainModule': path.resolve(__dirname, 'src/app/pages/main/main.module.ts'),
        './CollectionModule': path.resolve(__dirname, 'src/app/components/collection/collection.module.ts')
      },



      shared: share({
        "@angular/core": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
        "@angular/common": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
        "@angular/common/http": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
        "@angular/router": { singleton: true, strictVersion: true, requiredVersion: 'auto' },

        ...sharedMappings.getDescriptors()
      })

    }),

גם כאן, השורה שמעניינת אותנו היא שורה 9.

לא לשכוח לעצור את הקומפיילר ולהפעיל מחדש.

!!! הבעיה היא שכאשר אנחנו במצב פיתוח, המודול שלנו (collectionModule) מגיע שאכן הוא מאכלס את כל הרכיבים כמו שכותבים אותם במודול עצמו, ולכן שורה 23 בדיירקטייב יכולה לעבוד ולשלוף את הרכיב החוצה.

אבל במצב PROD, שזה המצב שבסופו של דבר תגיעו (בסיום הפיתוח) אין יותר TS ואין יותר חלוקה לרכיבים, אלא כל הקבצים מקומפלים לבלוקים של JS ואם אין הפרדה של הרכיב הספציפי החוצה לקובץ משלו אנחנו צריכים לטעון את כל המודול עם כל הרכיבים בו, כי הם גוש אחד של קוד. !!!

אז מה עושים? כרגע האופציה היחידה שמצאתי שעובדת היא להחצין כל רכיב בנפרד ולהחצין את המודול במידה שאנחנו רוצים להעביר תלויות של רכיבים במודולים שונים שלא בהכרח יהיו זמינים בפרויקט שמושך את הרכיב שלנו.

המצב הסופי של הדיירקטיב

import { loadRemoteModule } from '@angular-architects/module-federation';
import { createNgModuleRef, Directive, Injector, Input, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appCreator]'
})
export class CreatorDirective {
  @Input('appCreator') set data(value: ICompData) {
    this.componentInit(value);
  }
  constructor(private vcr: ViewContainerRef, private injector: Injector) { }
  private componentInit(componentData: ICompData) {
    return Promise.all([
      componentData.module === componentData.compName ?
        loadRemoteModule({
          type: 'module',
          remoteEntry: componentData.mfLink,
          exposedModule: './' + componentData.module
        }).then((m: any) => {
          return this.vcr.createComponent(m[componentData.compName], { injector: this.injector });
        }) :
        loadRemoteModule({
          type: 'module',
          remoteEntry: componentData.mfLink,
          exposedModule: './' + componentData.module
        }).then(m => {
          const moduleRef = createNgModuleRef(m[componentData.module], this.injector);
          Promise.all([
            loadRemoteModule({
              type: 'module',
              remoteEntry: componentData.mfLink,
              exposedModule: './' + componentData.compName
            })
          ]).then((c: any) => {
            const microComp = this.vcr.createComponent<any>(c[0][componentData.compName], { ngModuleRef: moduleRef });
            return microComp;
          })
        }).catch(e => { console.warn(e); })
    ]).catch(e => { console.warn(e); })
  }
}
export interface ICompData {
  module: string,
  mfLink: string,
  compName: string
}

שורה 14: אנחנו בודקים האם שם הרכיב זהה לשם המודול, במידה שכן מדובר ברכיב בודד ללא צורך בתלויות ולכן אפשר ישר למשוך אותו וליצר אותו.

שורה 21: במידה שהם לא שוום זה אומר שיש לנו מודול ואנחנו רוצים להתקין רכיב בתוספת תלויות שיש במודול

ולכן יש 2 קריאות של פרומיס, הראשונה היא עבור המודול, כדי להכין את התלויות. הקריאה השניה היא עבור הרכיב המרוחק וחיבור של תלויות מהמודול אליו לפני ההקמה שלו.

שורה 27: יצירה של משתנה שיחזיק את התלויות עם כל המודול של הרכיב.

שורה 28: הקריאה השניה לרכיב עצמו.

שורה 35: יצירת הרכיב יחד עם התלויות של המודול שלו.

בנוסף צריך לעשות שינוי נוסף ב-MFE1

 exposes: {
        './MainModule': path.resolve(__dirname, 'src/app/pages/main/main.module.ts'),
        './CollectionModule': path.resolve(__dirname, 'src/app/components/collection/collection.module.ts'),
        './PartOneComponent': path.resolve(__dirname, 'src/app/components/collection/part-one/part-one.component.ts')
      },

זהו זה! סיימנו.

אם לא פיספסנו משהו, אנחנו אמורים לראות את part-one על הדף.

בחלק הבא, נראה איך להעביר מידע בין רכיבים ששיכים למיקרו-פרונטאנדים שונים.✨

פוסטים קשורים:

teamworkAngular Microfrontend Module Federation Tutorial – Part 3 angular micro-frontendAngular Microfrontend Module Federation Tutorial – Part 2 micro frontendAngular Microfrontend Module Federation Tutorial – Part 6 microfrontendAngular Microfrontend Module Federation Tutorial – Part 5
angular אנגולר לימודי אנגולר מדריך אנגולר

אודות המחבר

עידן יצחקי להציג את כל הפוסטים של עידן יצחקי


« פוסט קודם
פוסט הבא »

2 תגובות

  1. כהן מרים הגב 15 במרץ 2023 בשעה 22:22

    הי,
    נהניתי מאד מהמאמרים על Module Federation
    תוכל בבקשה לתת לי יותר פירוט איך מיצאים סרוויס ?
    תודה רבה

    • עידן יצחקי הגב 20 במרץ 2023 בשעה 20:49

      היי,
      סרביסים הם פונקציות משותפות לפרויקט, עד כמה שאני יודע, לא ניתן לשתף סרוויס.
      הוא לא יהיה זמין לך בפרויקטים שקוראים לו בגלל שהוא לא קיים באותם פרויקטים.
      אם היה אפשר ליצא סרביס, משמע לשתף אותו בין מספר פרויקטים שונים, אז לא היינו צריכים לשתף מידע באיוונטים או איחסון מקומי.
      כאשר מבצעים קריאה לרכיב והוא תלוי בסרביס (מיבא אותו לתוך הרכיב) אז גם הקוד של הסרביס מגיע ביחד איתו, אבל הוא אינסטאנס חדש ולא משותף עם פרויקטים מיקרו אחרים.
      צריך לזכור שכל פרויקט הוא סוג של אי די סגור , נכון שניתן להעביר אליו נתונים בזמן היצירה שלו בפרויקט היעד , אבל הוא עדיין אי והוא לא משתף מידע עם איים אחרים.

השארת תגובה

ביטול

חיפוש באתר
בחירת העורכים
29 בדצמבר 2023 עידן יצחקי

שדה טקסט עשיר עם תמונות

אתם הולכים להיות מופתעים עד כמה HTML יכול להיות חכם ולבצע משהו כל כך מורכב, שאם אנחנו היינו רוצים ליצור

1 באוקטובר 2021 עידן יצחקי

איך למשוך דינמית favicon של אתרים אחרים ב-JS

בפוסט זה נראה איך אפשר על פי לינקים בדף למשוך את ה-favicon מהדומיין שלהם באופן דינמי, בדיקה של תקינות התמונה

פופולרי
Javascript functions – היכרות עם סוגי פונקציות
Javascript
21 בדצמבר 2024 אין תגובות
Nested routing in angular standalone component
Typescript
15 בנובמבר 2024 תגובה אחת
בחרו לפי תגיות
angular blockchain css ethers express front-end fullstack GQL html javascript next js nextjs nodejs react hooks reactjs solidity webgl אנגולר בלוקציין וורדפרס לימודי אנגולר לימודי וורדפרס לימוד ריאקט מדריך front-end מדריך GQL מדריך אנגולר מדריך וורדפרס מדריך חינם react מדריך ריאקט מפתח בלוק מפתח בלוקציין מתכנת front-end מתכנת בלוקציין מתכנת פרונט סולידיטי קורס front end קורס fullstack קורס nextjs קורס אנגולר קורס בלוקציין קורס בלוקציין בחינם קורס סולידיטי קורס ריאקט קורס תכנות קורס תכנות בחינם
סינון על פי קטגוריות
CSS fullstack HTML IIS Javascript nodeJs SEO Typescript אנגולר בלוקציין בניית אתרים וורדפרס חיפוש עבודה כלים נוספים כללי נגישות קורסים ריאקט תלת מימד תקלות ופתרונות
צור קשר
כל הזכויות שמורות לקודקודייל
ליצירת קשר: @ קודקודייל
גלילה לראש העמוד