├── micro-fe-app.jpg ├── micro-frontends.gif ├── micro-frontends.md ├── micro-frontends.mp4 ├── ngft-email-client ├── .editorconfig ├── .gitignore ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── tasks.json ├── README.md ├── angular.json ├── package-lock.json ├── package.json ├── projects │ ├── calendar │ │ ├── .browserslistrc │ │ ├── karma.conf.js │ │ ├── src │ │ │ ├── app │ │ │ │ ├── app.component.html │ │ │ │ ├── app.component.scss │ │ │ │ ├── app.component.spec.ts │ │ │ │ ├── app.component.ts │ │ │ │ ├── app.module.ts │ │ │ │ └── calendar │ │ │ │ │ ├── calendar-home │ │ │ │ │ ├── calendar-home.component.html │ │ │ │ │ ├── calendar-home.component.scss │ │ │ │ │ ├── calendar-home.component.spec.ts │ │ │ │ │ └── calendar-home.component.ts │ │ │ │ │ └── calendar.module.ts │ │ │ ├── assets │ │ │ │ └── .gitkeep │ │ │ ├── bootstrap.ts │ │ │ ├── environments │ │ │ │ ├── environment.prod.ts │ │ │ │ └── environment.ts │ │ │ ├── favicon.ico │ │ │ ├── index.html │ │ │ ├── main.ts │ │ │ ├── polyfills.ts │ │ │ ├── styles.scss │ │ │ └── test.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.spec.json │ │ ├── webpack.config.js │ │ └── webpack.prod.config.js │ ├── mailbox │ │ ├── .browserslistrc │ │ ├── karma.conf.js │ │ ├── src │ │ │ ├── app │ │ │ │ ├── app.component.html │ │ │ │ ├── app.component.scss │ │ │ │ ├── app.component.spec.ts │ │ │ │ ├── app.component.ts │ │ │ │ ├── app.module.ts │ │ │ │ └── mailbox │ │ │ │ │ ├── mailbox-home │ │ │ │ │ ├── mailbox-home.component.html │ │ │ │ │ ├── mailbox-home.component.scss │ │ │ │ │ ├── mailbox-home.component.spec.ts │ │ │ │ │ └── mailbox-home.component.ts │ │ │ │ │ └── mailbox.module.ts │ │ │ ├── assets │ │ │ │ └── .gitkeep │ │ │ ├── bootstrap.ts │ │ │ ├── environments │ │ │ │ ├── environment.prod.ts │ │ │ │ └── environment.ts │ │ │ ├── favicon.ico │ │ │ ├── index.html │ │ │ ├── main.ts │ │ │ ├── polyfills.ts │ │ │ ├── styles.scss │ │ │ └── test.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.spec.json │ │ ├── webpack.config.js │ │ └── webpack.prod.config.js │ └── shell │ │ ├── .browserslistrc │ │ ├── karma.conf.js │ │ ├── src │ │ ├── app │ │ │ ├── app.component.html │ │ │ ├── app.component.scss │ │ │ ├── app.component.spec.ts │ │ │ ├── app.component.ts │ │ │ └── app.module.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── bootstrap.ts │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.scss │ │ ├── test.ts │ │ └── types.d.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.spec.json │ │ ├── webpack.config.js │ │ └── webpack.prod.config.js └── tsconfig.json └── readme.md /micro-fe-app.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tieppt/micro-frontends-demo/ae1389d4a5fc6d5c67fbcf72654720fde969e999/micro-fe-app.jpg -------------------------------------------------------------------------------- /micro-frontends.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tieppt/micro-frontends-demo/ae1389d4a5fc6d5c67fbcf72654720fde969e999/micro-frontends.gif -------------------------------------------------------------------------------- /micro-frontends.md: -------------------------------------------------------------------------------- 1 | # Thực hành Micro Frontends 2 | 3 | > Good frontend development is hard. Scaling frontend development so that many teams can work simultaneously on a large and complex product is even harder. 4 | 5 | > source: [https://martinfowler.com/articles/micro-frontends.html](https://martinfowler.com/articles/micro-frontends.html) 6 | 7 | ## Micro Frontends là gì? 8 | Hiện nay, các ứng dụng Single Page Apps (SPAs) cực kỳ phổ biến, chúng có nhiều tính năng và cũng rất phức tạp và thường được kết hợp với kiến trúc Microservices ở tầng backend. Sau một thời gian phát triển, các ứng dụng SPAs này trở nên cồng kềnh, và khó hơn cho việc maintain và chúng được gọi là Frontend Monolith. 9 | 10 | Trong những năm trở lại đây, việc áp dụng những concepts từ Microservices vào các ứng dụng Frontend được nhắc đến khá thường xuyên. Ý tưởng của Micro Frontends đó là sẽ phân tách các ứng dụng này thành các phần kết hợp của các tính năng, mỗi tính năng có thể được phát triển bới một team độc lập. 11 | 12 | > "An architectural style where independently deliverable frontend applications are composed into a greater whole" 13 | 14 | > source: [https://martinfowler.com/articles/micro-frontends.html](https://martinfowler.com/articles/micro-frontends.html) 15 | 16 | ### Monolithic Frontends 17 | 18 | ![Monolithic Frontends](https://micro-frontends.org/ressources/diagrams/organisational/monolith-frontback-microservices.png) 19 | 20 | > source: [https://micro-frontends.org](https://micro-frontends.org) 21 | 22 | ### Micro Frontends 23 | 24 | ![Micro Frontends](https://micro-frontends.org/ressources/diagrams/organisational/verticals-headline.png) 25 | 26 | > source: [https://micro-frontends.org](https://micro-frontends.org) 27 | 28 | ## Phương pháp để áp dụng Micro Frontends 29 | 30 | - Iframe 31 | 32 | Phương pháp này dễ để áp dụng nhưng có chứa nhiều giới hạn như việc navigation, thực thi các đoạn JavaScript từ Host App, ... 33 | - Proxy like nginx 34 | 35 | Phương pháp này yêu cầu việc phát triển các ứng dụng phải độc lập, ví dụ `/mailbox`, `/calendar` là các app Frontend khác nhau. Phương pháp này có một vấn đề là khi navigate từ app này sang app khác thì bạn sẽ bị reload giống như ứng dụng client-server thông thường. 36 | - Web Components 37 | 38 | Đây là một công nghệ không quá mới trong thời gian trở lại đây. Các framework áp dụng hoặc tạo ra Custom Elements khá nhiều. Ví dụ [Angular Elements](https://angular.io/guide/elements), [Stencil](https://stenciljs.com). 39 | Chúng có ưu điểm là bạn có thể tạo ra được các element và có thể sử dụng như là một thẻ html thông thường ở bất cứ framework nào (Framework Agnostic) 40 | - Orchestrator Frameworks 41 | 42 | Webpack 5 and Module Federation, [piral](https://piral.io), [luigi](https://luigi-project.io/), [single-spa](https://single-spa.js.org/) 43 | 44 | ## Develop Email Client Micro Frontends 45 | 46 | Code demo có tại repo sau: [https://github.com/tieppt/micro-frontends-demo](https://github.com/tieppt/micro-frontends-demo) 47 | 48 | 49 | ![Email Client Micro Frontends](./assets/micro-fe-app.jpg) 50 | 51 | Từ hình mô tả trên chúng ta có thể thấy rằng, ứng dụng email client của chúng ta sẽ có thể được phát triển bởi 2 team cho 2 chức năng là **mailbox** và **calendar**. Trong đó, team **calendar** có thể phát triển thêm một widget để có thể nhúng vào page của **mailbox**. Việc tạo ra các widget có thể được thực hiện thông qua Custom Elements. 52 | 53 | ### Shell or Host app 54 | Để các micro app có thể chạy trên cùng một app, chúng ta cần có một shell (có thể được gọi là host). Shell sẽ setup một số thứ như routing, shared state, ... Việc tạo ra shell app có thể ảnh hưởng đến công nghệ cần áp dụng cho các micro app. 55 | 56 | Ví dụ: nếu chúng ta lựa chọn Angular hay React làm shell app, thì các micro app sẽ phải có tầng wrapper để có thể chạy được các app đó. Vì routing của các framework trên là specific cho từng framework. Nên để route được, và render đúng component thì phải tuân thủ theo ràng buộc của framework đó. 57 | 58 | ### Chuẩn bị 59 | Trong demo này, chúng ta sẽ sử dụng Webpack 5, trong bản release mới nhất nó đã giới thiệu một advanced API là Module Federation. Điều này giúp chúng ta dễ dàng phát triển được Micro Frontend. 60 | Ngoài ra, chúng ta sẽ dùng Angular v11 (thời điểm này đang là RC) để tạo các app. 61 | 62 | Đầu tiên, chúng ta cần tạo một shell app bằng lệnh sau. 63 | ```sh 64 | npx @angular/cli@14 new ngft-email-client --create-application=false 65 | ``` 66 | 67 | Sau khi tạo xong project, chúng ta sẽ tạo thêm 3 applications nữa: 1 shell app, và 2 remote apps (mailbox, calendar). 68 | 69 | Lưu ý: chúng ta dùng Router và scss cho cả 3 apps cho thống nhất. 70 | 71 | ``` 72 | npx ng generate application shell 73 | 74 | # ? Would you like to add Angular routing? Yes 75 | # ? Which stylesheet format would you like to use? SCSS 76 | 77 | npx ng generate application mailbox 78 | 79 | # ? Would you like to add Angular routing? Yes 80 | # ? Which stylesheet format would you like to use? SCSS 81 | 82 | npx ng generate application calendar 83 | 84 | # ? Would you like to add Angular routing? Yes 85 | # ? Which stylesheet format would you like to use? SCSS 86 | 87 | ``` 88 | 89 | Ngoài ra, chúng ta cần dùng đến custom webpack config nên chúng ta cần install thêm một package là `@angular-builders/custom-webpack`. 90 | 91 | ```sh 92 | npm i -D @angular-builders/custom-webpack@14 93 | ``` 94 | 95 | File `package.json` của chúng ta sẽ có dạng như sau: 96 | 97 | 98 | ```json 99 | { 100 | "name": "acme-email-client", 101 | "scripts": { 102 | "start:shell": "ng serve --project=shell", 103 | "start:mailbox": "ng serve --project=mailbox", 104 | "start:calendar": "ng serve --project=calendar" 105 | }, 106 | "dependencies": { 107 | "@angular/animations": "^14.2.0", 108 | "@angular/common": "^14.2.0", 109 | "@angular/compiler": "^14.2.0", 110 | "@angular/core": "^14.2.0", 111 | "@angular/forms": "^14.2.0", 112 | "@angular/platform-browser": "^14.2.0", 113 | "@angular/platform-browser-dynamic": "^14.2.0", 114 | "@angular/router": "^14.2.0", 115 | "rxjs": "~7.5.0", 116 | "tslib": "^2.3.0", 117 | "zone.js": "~0.11.4" 118 | }, 119 | "devDependencies": { 120 | "@angular-builders/custom-webpack": "^14.0.1", 121 | "@angular-devkit/build-angular": "^14.2.3", 122 | "@angular/cli": "~14.2.3", 123 | "@angular/compiler-cli": "^14.2.0", 124 | "typescript": "~4.7.2" 125 | } 126 | } 127 | ``` 128 | 129 | Chúng ta sẽ cài đặt các port để chạy `ng serve` cho từng ứng dụng trong file `angular.json`. 130 | 131 | Ví dụ shell sẽ chạy ở port 5200 thì chúng ta sẽ thêm như sau. 132 | ```json 133 | { 134 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 135 | "version": 1, 136 | "newProjectRoot": "projects", 137 | "projects": { 138 | "shell": { 139 | "projectType": "application", 140 | "schematics": { 141 | "@schematics/angular:component": { 142 | "style": "scss" 143 | } 144 | }, 145 | "root": "projects/shell", 146 | "sourceRoot": "projects/shell/src", 147 | "prefix": "app", 148 | "architect": { 149 | "build": {}, 150 | "serve": { 151 | "builder": "@angular-devkit/build-angular:dev-server", 152 | "options": { 153 | "port": 5200 154 | }, 155 | "configurations": { 156 | "production": { 157 | "browserTarget": "shell:build:production" 158 | }, 159 | "development": { 160 | "browserTarget": "shell:build:development" 161 | } 162 | }, 163 | "defaultConfiguration": "development" 164 | }, 165 | } 166 | } 167 | } 168 | } 169 | ``` 170 | 171 | Sau đó, chúng ta làm tương tự cho mailbox (5300) và calendar (5400). 172 | 173 | 174 | 175 | ### Bật tính năng Module Federation 176 | Để bật được tính năng này chúng ta cần sử dụng custom webpack như sau: 177 | Bạn tạo ra các file webpack config, sau đó thay thế builder mặc định ở trong `angular.json`. 178 | 179 | Ví dụ chúng ta tạo ra 2 files `webpack.config.js` và `webpack.prod.config.js` để sử dụng cho 2 môi trường là development và production trong folder `projects/shell`. 180 | Sau đó chúng ta sẽ thay thế trong `angular.json`: 181 | - Thay `@angular-devkit/build-angular` bằng `@angular-builders/custom-webpack`. 182 | - Thêm config của webpack mà chúng ta vừa tạo 183 | - Trong mục `serve.options` chúng ta sẽ cần thêm `publicHost` để dùng cho Module Federation 184 | 185 | Dưới đây là một phần của file `angular.json`. 186 | 187 | ```json 188 | { 189 | "projects": { 190 | "shell": { 191 | "architect": { 192 | "build": { 193 | "builder": "@angular-builders/custom-webpack:browser", 194 | "options": { 195 | "customWebpackConfig": { 196 | "path": "projects/shell/webpack.config.js" 197 | } 198 | }, 199 | "configurations": { 200 | "production": { 201 | "customWebpackConfig": { 202 | "path": "projects/shell/webpack.prod.config.js" 203 | } 204 | } 205 | } 206 | }, 207 | "serve": { 208 | "builder": "@angular-builders/custom-webpack:dev-server", 209 | "options": { 210 | "port": 5200, 211 | "publicHost": "http://localhost:5200/" 212 | }, 213 | "configurations": { 214 | "production": { 215 | "browserTarget": "shell:build:production" 216 | }, 217 | "development": { 218 | "browserTarget": "shell:build:development" 219 | } 220 | }, 221 | "defaultConfiguration": "development" 222 | }, 223 | } 224 | }, 225 | } 226 | } 227 | ``` 228 | 229 | Sau đó chúng ta sẽ tạo tương tự cho các project `mailbox` và `calendar`. 230 | 231 | #### Config Shell 232 | 233 | Chúng ta cần config shell như sau để bật Module Federation: 234 | 235 | ```js 236 | const { ModuleFederationPlugin } = require('webpack').container; 237 | 238 | /** @type {require('webpack').Configuration} */ 239 | module.exports = { 240 | output: { 241 | publicPath: 'auto', // we setup the `publicHost` in `angular.json` file 242 | uniqueName: 'shell', 243 | }, 244 | optimization: { 245 | runtimeChunk: false, 246 | }, 247 | experiments: { 248 | // Allow output javascript files as module source type. 249 | outputModule: true, 250 | }, 251 | plugins: [ 252 | new ModuleFederationPlugin({ 253 | name: 'shell', 254 | library: { 255 | // because Angular v14 will output ESModule 256 | type: 'module', 257 | }, 258 | remotes: { 259 | mailbox: 'http://localhost:5300/remoteEntry.js', 260 | calendar: 'http://localhost:5400/remoteEntry.js', 261 | }, 262 | /** 263 | * shared can be an object of type SharedConfig 264 | * you can change this to select something can be shared 265 | */ 266 | shared: ['@angular/core', '@angular/common', '@angular/router'], 267 | // shared: { 268 | // "@angular/animations": { 269 | // singleton: true, 270 | // strictVersion: true, 271 | // requiredVersion: "^14.2.0", 272 | // }, 273 | // "@angular/animations/browser": { 274 | // singleton: true, 275 | // strictVersion: true, 276 | // requiredVersion: "^14.2.0", 277 | // }, 278 | // "@angular/common": { 279 | // eager: true, 280 | // singleton: true, 281 | // strictVersion: true, 282 | // requiredVersion: "^14.2.0", 283 | // }, 284 | // "@angular/common/http": { 285 | // singleton: true, 286 | // strictVersion: true, 287 | // requiredVersion: "^14.2.0", 288 | // }, 289 | // "@angular/core": { 290 | // eager: true, 291 | // singleton: true, 292 | // strictVersion: true, 293 | // requiredVersion: "^14.2.0", 294 | // }, 295 | // "@angular/platform-browser": { 296 | // eager: true, 297 | // singleton: true, 298 | // strictVersion: true, 299 | // requiredVersion: "^14.2.0", 300 | // }, 301 | // "@angular/platform-browser/animations": { 302 | // singleton: true, 303 | // strictVersion: true, 304 | // requiredVersion: "^14.2.0", 305 | // }, 306 | // "@angular/router": { 307 | // singleton: true, 308 | // strictVersion: true, 309 | // requiredVersion: "^14.2.0", 310 | // }, 311 | // "@angular/platform-browser-dynamic": { 312 | // eager: true, 313 | // singleton: true, 314 | // strictVersion: true, 315 | // requiredVersion: "^14.2.0", 316 | // }, 317 | // }, 318 | }), 319 | ], 320 | }; 321 | ``` 322 | 323 | Shell sẽ chạy ở port 5200, và chúng ta cần một unique name cho mỗi app. Ngoài ra, do shell sẽ trỏ đến 2 remotes appa, nên bạn sẽ thấy chúng ta config tương ứng cho 2 app remote luôn ở đây. 324 | 325 | Do đang dùng các micro app bằng Angular, nên chúng ta có thể share các phần code, như config phía trên, chúng ta đã share 3 packages. 326 | 327 | Giờ đây chúng ta có thể thêm config cho routing của shell để trỏ đến 2 micro app kia: 328 | 329 | ```ts 330 | const routes: Routes = [ 331 | { 332 | path: 'mailbox', 333 | loadChildren: () => import('mailbox/MailboxModule').then(m => m.MailboxModule) 334 | }, 335 | { 336 | path: 'calendar', 337 | loadChildren: () => import('calendar/CalendarModule').then(m => m.CalendarModule) 338 | } 339 | ]; 340 | ``` 341 | 342 | Có một vấn đề phát sinh lúc này đó là 2 đướng dẫn trên không thật sự tồn tại trong app shell, nó là đường dẫn ảo, do đó chúng ta cần bảo cho TypeScript biết rằng chúng có thật sự tồn tại. 343 | 344 | Chúng ta chỉ cần tạo một file `types.d.ts` trong thư mục `src` là sẽ được. 345 | 346 | ```ts 347 | declare module 'mailbox/MailboxModule'; 348 | declare module 'calendar/CalendarModule'; 349 | ``` 350 | 351 | Giờ đây bạn có thể chạy shell để xem kết quả. 352 | 353 | ```sh 354 | yarn start:shell 355 | ``` 356 | 357 | Nhưng app của chúng ta khi chạy sẽ báo lỗi như sau: `Uncaught Error: Shared module is not available for eager consumption`. 358 | Điều này xảy ra do chúng ta đang shared các packages. Do đó chúng ta cần config thêm 1 số thứ để bootstrap được ứng dụng. Dựa theo bài viết này [https://medium.com/dev-genius/module-federation-advanced-api-inwebpack-5-0-0-beta-17-71cd4d42e534](https://medium.com/dev-genius/module-federation-advanced-api-inwebpack-5-0-0-beta-17-71cd4d42e534) chúng ta thấy có gợi ý như sau: 359 | 360 | > **The recommended solution to eager imports** 361 | 362 | >Methods mentioned above work, but can have some limits or drawbacks. 363 | 364 | >At Webpack, we strongly recommend a dynamic import of a bootstrap file. Doing so will not create any additional Round Trips, it’s also more performant in general as initialization code is split out of a larger chunk. 365 | 366 | Webpack khuyến cáo chúng ta tạo ra 1 file chứa phần import đó, và sẽ gọi dynamic import. 367 | 368 | **bootstrap.ts** 369 | ```ts 370 | import { enableProdMode } from '@angular/core'; 371 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 372 | 373 | import { AppModule } from './app/app.module'; 374 | import { environment } from './environments/environment'; 375 | 376 | if (environment.production) { 377 | enableProdMode(); 378 | } 379 | 380 | platformBrowserDynamic().bootstrapModule(AppModule) 381 | .catch(err => console.error(err)); 382 | ``` 383 | **main.ts** 384 | ```ts 385 | import('./bootstrap').catch(err => console.error(err)); 386 | ``` 387 | 388 | Vậy là ứng dụng đã chạy được thành công. 389 | 390 | #### Config Remotes app 391 | Nếu chúng ta muốn navigate vào 2 micro app kia thì cũng cần config tương tự, nhưng cần một số thay đổi, vì những app đó là remotes app. 392 | 393 | Config dưới đây là dành cho mailbox app. 394 | ```js 395 | const { ModuleFederationPlugin } = require('webpack').container; 396 | 397 | /** @type {require('webpack').Configuration} */ 398 | module.exports = { 399 | output: { 400 | publicPath: 'auto', // we setup the `publicHost` in `angular.json` file 401 | uniqueName: 'mailbox', 402 | }, 403 | optimization: { 404 | runtimeChunk: false, 405 | }, 406 | experiments: { 407 | // Allow output javascript files as module source type. 408 | outputModule: true, 409 | }, 410 | plugins: [ 411 | new ModuleFederationPlugin({ 412 | name: 'mailbox', 413 | filename: 'remoteEntry.js', 414 | library: { 415 | // because Angular v14 will output ESModule 416 | type: 'module', 417 | }, 418 | exposes: { 419 | './MailboxModule': 'projects/mailbox/src/app/mailbox/mailbox.module.ts', 420 | }, 421 | /** 422 | * shared can be an object of type SharedConfig 423 | * you can change this to select something can be shared 424 | */ 425 | shared: ['@angular/core', '@angular/common', '@angular/router'], 426 | // shared: { 427 | // "@angular/animations": { 428 | // singleton: true, 429 | // strictVersion: true, 430 | // requiredVersion: "^14.2.0", 431 | // }, 432 | // "@angular/animations/browser": { 433 | // singleton: true, 434 | // strictVersion: true, 435 | // requiredVersion: "^14.2.0", 436 | // }, 437 | // "@angular/common": { 438 | // eager: true, 439 | // singleton: true, 440 | // strictVersion: true, 441 | // requiredVersion: "^14.2.0", 442 | // }, 443 | // "@angular/common/http": { 444 | // singleton: true, 445 | // strictVersion: true, 446 | // requiredVersion: "^14.2.0", 447 | // }, 448 | // "@angular/core": { 449 | // eager: true, 450 | // singleton: true, 451 | // strictVersion: true, 452 | // requiredVersion: "^14.2.0", 453 | // }, 454 | // "@angular/platform-browser": { 455 | // eager: true, 456 | // singleton: true, 457 | // strictVersion: true, 458 | // requiredVersion: "^14.2.0", 459 | // }, 460 | // "@angular/platform-browser/animations": { 461 | // singleton: true, 462 | // strictVersion: true, 463 | // requiredVersion: "^14.2.0", 464 | // }, 465 | // "@angular/router": { 466 | // singleton: true, 467 | // strictVersion: true, 468 | // requiredVersion: "^14.2.0", 469 | // }, 470 | // "@angular/platform-browser-dynamic": { 471 | // eager: true, 472 | // singleton: true, 473 | // strictVersion: true, 474 | // requiredVersion: "^14.2.0", 475 | // }, 476 | // }, 477 | }), 478 | ], 479 | }; 480 | ``` 481 | 482 | Như các bạn cũng thấy, chúng ta config phần `output` giống như app shell vừa rồi. Phần khác biệt nhất là ở config cho `ModuleFederationPlugin`. 483 | 484 | Chúng ta cần config một số fields như `name`, `library`, và đặc biệt là `fileName` cần giống với phần shell chúng ta đã config (ở đây là `remoteEntry.js`) và phần `exposes`. 485 | 486 | Phần `exposes` cho phép chúng ta config những gì sẽ được public ra bên ngoài. Mỗi key của nó nên tuân theo [ESM syntax inside Node 14](https://medium.com/dev-genius/module-federation-advanced-api-inwebpack-5-0-0-beta-17-71cd4d42e534). 487 | 488 | 489 | **Standalone-Mode cho Remotes app**: Ở đây chúng ta cũng có đề cập đến các package được shared. Do đó để chạy được mode này, tức là các micro app sẽ có thể chạy như app độc lập, chúng ta cũng sẽ áp dụng kỹ thuật tương tự đó là dùng dynamic import phần bootstrap như shell ở trên. 490 | 491 | Các micro apps lúc này hoàn toàn có thể có phần config routing riêng tùy ý. 492 | 493 | ```ts 494 | export const MAILBOX_ROUTES: Routes = [ 495 | { 496 | path: '', 497 | component: MailboxHomeComponent, 498 | } 499 | ]; 500 | 501 | @NgModule({ 502 | declarations: [ 503 | MailboxHomeComponent 504 | ], 505 | imports: [ 506 | CommonModule, 507 | RouterModule.forChild(MAILBOX_ROUTES), 508 | ] 509 | }) 510 | export class MailboxModule { } 511 | ``` 512 | 513 | Tương tự chúng ta có thể config cho calendar app như sau: 514 | 515 | ```js 516 | const { ModuleFederationPlugin } = require('webpack').container; 517 | 518 | /** @type {require('webpack').Configuration} */ 519 | module.exports = { 520 | output: { 521 | publicPath: 'auto', // we setup the `publicHost` in `angular.json` file 522 | uniqueName: 'calendar', 523 | }, 524 | optimization: { 525 | runtimeChunk: false, 526 | }, 527 | experiments: { 528 | // Allow output javascript files as module source type. 529 | outputModule: true, 530 | }, 531 | plugins: [ 532 | new ModuleFederationPlugin({ 533 | name: 'calendar', 534 | library: { 535 | // because Angular v14 will output ESModule 536 | type: 'module', 537 | }, 538 | filename: 'remoteEntry.js', 539 | exposes: { 540 | './CalendarModule': 'projects/calendar/src/app/calendar/calendar.module.ts', 541 | }, 542 | /** 543 | * shared can be an object of type SharedConfig 544 | * you can change this to select something can be shared 545 | */ 546 | shared: ['@angular/core', '@angular/common', '@angular/router'], 547 | // shared: { 548 | // "@angular/animations": { 549 | // singleton: true, 550 | // strictVersion: true, 551 | // requiredVersion: "^14.2.0", 552 | // }, 553 | // "@angular/animations/browser": { 554 | // singleton: true, 555 | // strictVersion: true, 556 | // requiredVersion: "^14.2.0", 557 | // }, 558 | // "@angular/common": { 559 | // eager: true, 560 | // singleton: true, 561 | // strictVersion: true, 562 | // requiredVersion: "^14.2.0", 563 | // }, 564 | // "@angular/common/http": { 565 | // singleton: true, 566 | // strictVersion: true, 567 | // requiredVersion: "^14.2.0", 568 | // }, 569 | // "@angular/core": { 570 | // eager: true, 571 | // singleton: true, 572 | // strictVersion: true, 573 | // requiredVersion: "^14.2.0", 574 | // }, 575 | // "@angular/platform-browser": { 576 | // eager: true, 577 | // singleton: true, 578 | // strictVersion: true, 579 | // requiredVersion: "^14.2.0", 580 | // }, 581 | // "@angular/platform-browser/animations": { 582 | // singleton: true, 583 | // strictVersion: true, 584 | // requiredVersion: "^14.2.0", 585 | // }, 586 | // "@angular/router": { 587 | // singleton: true, 588 | // strictVersion: true, 589 | // requiredVersion: "^14.2.0", 590 | // }, 591 | // "@angular/platform-browser-dynamic": { 592 | // eager: true, 593 | // singleton: true, 594 | // strictVersion: true, 595 | // requiredVersion: "^14.2.0", 596 | // }, 597 | // }, 598 | }), 599 | ], 600 | }; 601 | ``` 602 | 603 | ### Khỏi chạy ứng dụng 604 | 605 | Giờ đây bạn có thể chạy cả 3 ứng dụng: 606 | ```sh 607 | npm run start:shell 608 | npm run start:mailbox 609 | npm run start:calendar 610 | ``` 611 | 612 | Sau đó truy cập vào các địa chỉ sau: 613 | [http://localhost:5200/](http://localhost:5200/), [http://localhost:5300/](http://localhost:5300/), [http://localhost:5400/](http://localhost:5400/) 614 | 615 | Dưới đây là kết quả có được. Chúng ta có thể chạy standalone cho từng micro apps hoặc chạy chính từ shell app. 616 | 617 | ![Micro Frontends Angular](./assets/micro-frontends.gif) 618 | 619 | ## Lời kết 620 | Như vậy với việc dùng Webpack 5 Module Federation chúng ta đã có thể tự tạo một ứng dụng Micro Frontend. 621 | 622 | Trong bài tiếp theo chúng ta sẽ tìm hiểu cách sử dụng Custom Elements để tạo ra các widget giúp dễ dàng nhúng vào các micro app khác. 623 | 624 | ## Code sample 625 | 626 | - https://github.com/tieppt/micro-frontends-demo 627 | 628 | ## References 629 | 630 | Các bạn có thể đọc thêm ở các bài viết sau: 631 | 632 | - https://medium.com/dev-genius/module-federation-advanced-api-inwebpack-5-0-0-beta-17-71cd4d42e534 633 | - https://www.angulararchitects.io/aktuelles/the-microfrontend-revolution-part-2-module-federation-with-angular/ 634 | - https://martinfowler.com/articles/micro-frontends.html 635 | -------------------------------------------------------------------------------- /micro-frontends.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tieppt/micro-frontends-demo/ae1389d4a5fc6d5c67fbcf72654720fde969e999/micro-frontends.mp4 -------------------------------------------------------------------------------- /ngft-email-client/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /ngft-email-client/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /ngft-email-client/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /ngft-email-client/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "pwa-chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /ngft-email-client/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /ngft-email-client/README.md: -------------------------------------------------------------------------------- 1 | # NgftEmailClient 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.2.3. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /ngft-email-client/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "shell": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | } 12 | }, 13 | "root": "projects/shell", 14 | "sourceRoot": "projects/shell/src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-builders/custom-webpack:browser", 19 | "options": { 20 | "outputPath": "dist/shell", 21 | "index": "projects/shell/src/index.html", 22 | "main": "projects/shell/src/main.ts", 23 | "polyfills": "projects/shell/src/polyfills.ts", 24 | "tsConfig": "projects/shell/tsconfig.app.json", 25 | "inlineStyleLanguage": "scss", 26 | "assets": [ 27 | "projects/shell/src/favicon.ico", 28 | "projects/shell/src/assets" 29 | ], 30 | "styles": [ 31 | "projects/shell/src/styles.scss" 32 | ], 33 | "scripts": [], 34 | "customWebpackConfig": { 35 | "path": "projects/shell/webpack.config.js" 36 | } 37 | }, 38 | "configurations": { 39 | "production": { 40 | "budgets": [ 41 | { 42 | "type": "initial", 43 | "maximumWarning": "500kb", 44 | "maximumError": "1mb" 45 | }, 46 | { 47 | "type": "anyComponentStyle", 48 | "maximumWarning": "2kb", 49 | "maximumError": "4kb" 50 | } 51 | ], 52 | "fileReplacements": [ 53 | { 54 | "replace": "projects/shell/src/environments/environment.ts", 55 | "with": "projects/shell/src/environments/environment.prod.ts" 56 | } 57 | ], 58 | "outputHashing": "all", 59 | "customWebpackConfig": { 60 | "path": "projects/shell/webpack.prod.config.js" 61 | } 62 | }, 63 | "development": { 64 | "buildOptimizer": false, 65 | "optimization": false, 66 | "vendorChunk": true, 67 | "extractLicenses": false, 68 | "sourceMap": true, 69 | "namedChunks": true 70 | } 71 | }, 72 | "defaultConfiguration": "production" 73 | }, 74 | "serve": { 75 | "builder": "@angular-builders/custom-webpack:dev-server", 76 | "options": { 77 | "port": 5200, 78 | "publicHost": "http://localhost:5200/" 79 | }, 80 | "configurations": { 81 | "production": { 82 | "browserTarget": "shell:build:production" 83 | }, 84 | "development": { 85 | "browserTarget": "shell:build:development" 86 | } 87 | }, 88 | "defaultConfiguration": "development" 89 | }, 90 | "extract-i18n": { 91 | "builder": "@angular-builders/custom-webpack:extract-i18n", 92 | "options": { 93 | "browserTarget": "shell:build" 94 | } 95 | }, 96 | "test": { 97 | "builder": "@angular-builders/custom-webpack:karma", 98 | "options": { 99 | "main": "projects/shell/src/test.ts", 100 | "polyfills": "projects/shell/src/polyfills.ts", 101 | "tsConfig": "projects/shell/tsconfig.spec.json", 102 | "karmaConfig": "projects/shell/karma.conf.js", 103 | "inlineStyleLanguage": "scss", 104 | "assets": [ 105 | "projects/shell/src/favicon.ico", 106 | "projects/shell/src/assets" 107 | ], 108 | "styles": [ 109 | "projects/shell/src/styles.scss" 110 | ], 111 | "scripts": [] 112 | } 113 | } 114 | } 115 | }, 116 | "mailbox": { 117 | "projectType": "application", 118 | "schematics": { 119 | "@schematics/angular:component": { 120 | "style": "scss" 121 | } 122 | }, 123 | "root": "projects/mailbox", 124 | "sourceRoot": "projects/mailbox/src", 125 | "prefix": "app", 126 | "architect": { 127 | "build": { 128 | "builder": "@angular-builders/custom-webpack:browser", 129 | "options": { 130 | "outputPath": "dist/mailbox", 131 | "index": "projects/mailbox/src/index.html", 132 | "main": "projects/mailbox/src/main.ts", 133 | "polyfills": "projects/mailbox/src/polyfills.ts", 134 | "tsConfig": "projects/mailbox/tsconfig.app.json", 135 | "inlineStyleLanguage": "scss", 136 | "assets": [ 137 | "projects/mailbox/src/favicon.ico", 138 | "projects/mailbox/src/assets" 139 | ], 140 | "styles": [ 141 | "projects/mailbox/src/styles.scss" 142 | ], 143 | "scripts": [], 144 | "customWebpackConfig": { 145 | "path": "projects/mailbox/webpack.config.js" 146 | } 147 | }, 148 | "configurations": { 149 | "production": { 150 | "budgets": [ 151 | { 152 | "type": "initial", 153 | "maximumWarning": "500kb", 154 | "maximumError": "1mb" 155 | }, 156 | { 157 | "type": "anyComponentStyle", 158 | "maximumWarning": "2kb", 159 | "maximumError": "4kb" 160 | } 161 | ], 162 | "fileReplacements": [ 163 | { 164 | "replace": "projects/mailbox/src/environments/environment.ts", 165 | "with": "projects/mailbox/src/environments/environment.prod.ts" 166 | } 167 | ], 168 | "outputHashing": "all", 169 | "customWebpackConfig": { 170 | "path": "projects/mailbox/webpack.prod.config.js" 171 | } 172 | }, 173 | "development": { 174 | "buildOptimizer": false, 175 | "optimization": false, 176 | "vendorChunk": true, 177 | "extractLicenses": false, 178 | "sourceMap": true, 179 | "namedChunks": true 180 | } 181 | }, 182 | "defaultConfiguration": "production" 183 | }, 184 | "serve": { 185 | "builder": "@angular-builders/custom-webpack:dev-server", 186 | "options": { 187 | "port": 5300, 188 | "publicHost": "http://localhost:5300/" 189 | }, 190 | "configurations": { 191 | "production": { 192 | "browserTarget": "mailbox:build:production" 193 | }, 194 | "development": { 195 | "browserTarget": "mailbox:build:development" 196 | } 197 | }, 198 | "defaultConfiguration": "development" 199 | }, 200 | "extract-i18n": { 201 | "builder": "@angular-builders/custom-webpack:extract-i18n", 202 | "options": { 203 | "browserTarget": "mailbox:build" 204 | } 205 | }, 206 | "test": { 207 | "builder": "@angular-builders/custom-webpack:karma", 208 | "options": { 209 | "main": "projects/mailbox/src/test.ts", 210 | "polyfills": "projects/mailbox/src/polyfills.ts", 211 | "tsConfig": "projects/mailbox/tsconfig.spec.json", 212 | "karmaConfig": "projects/mailbox/karma.conf.js", 213 | "inlineStyleLanguage": "scss", 214 | "assets": [ 215 | "projects/mailbox/src/favicon.ico", 216 | "projects/mailbox/src/assets" 217 | ], 218 | "styles": [ 219 | "projects/mailbox/src/styles.scss" 220 | ], 221 | "scripts": [] 222 | } 223 | } 224 | } 225 | }, 226 | "calendar": { 227 | "projectType": "application", 228 | "schematics": { 229 | "@schematics/angular:component": { 230 | "style": "scss" 231 | } 232 | }, 233 | "root": "projects/calendar", 234 | "sourceRoot": "projects/calendar/src", 235 | "prefix": "app", 236 | "architect": { 237 | "build": { 238 | "builder": "@angular-builders/custom-webpack:browser", 239 | "options": { 240 | "outputPath": "dist/calendar", 241 | "index": "projects/calendar/src/index.html", 242 | "main": "projects/calendar/src/main.ts", 243 | "polyfills": "projects/calendar/src/polyfills.ts", 244 | "tsConfig": "projects/calendar/tsconfig.app.json", 245 | "inlineStyleLanguage": "scss", 246 | "assets": [ 247 | "projects/calendar/src/favicon.ico", 248 | "projects/calendar/src/assets" 249 | ], 250 | "styles": [ 251 | "projects/calendar/src/styles.scss" 252 | ], 253 | "scripts": [], 254 | "customWebpackConfig": { 255 | "path": "projects/calendar/webpack.config.js" 256 | } 257 | }, 258 | "configurations": { 259 | "production": { 260 | "budgets": [ 261 | { 262 | "type": "initial", 263 | "maximumWarning": "500kb", 264 | "maximumError": "1mb" 265 | }, 266 | { 267 | "type": "anyComponentStyle", 268 | "maximumWarning": "2kb", 269 | "maximumError": "4kb" 270 | } 271 | ], 272 | "fileReplacements": [ 273 | { 274 | "replace": "projects/calendar/src/environments/environment.ts", 275 | "with": "projects/calendar/src/environments/environment.prod.ts" 276 | } 277 | ], 278 | "outputHashing": "all", 279 | "customWebpackConfig": { 280 | "path": "projects/calendar/webpack.config.js" 281 | } 282 | }, 283 | "development": { 284 | "buildOptimizer": false, 285 | "optimization": false, 286 | "vendorChunk": true, 287 | "extractLicenses": false, 288 | "sourceMap": true, 289 | "namedChunks": true 290 | } 291 | }, 292 | "defaultConfiguration": "production" 293 | }, 294 | "serve": { 295 | "builder": "@angular-builders/custom-webpack:dev-server", 296 | "options": { 297 | "port": 5400, 298 | "publicHost": "http://localhost:5400/" 299 | }, 300 | "configurations": { 301 | "production": { 302 | "browserTarget": "calendar:build:production" 303 | }, 304 | "development": { 305 | "browserTarget": "calendar:build:development" 306 | } 307 | }, 308 | "defaultConfiguration": "development" 309 | }, 310 | "extract-i18n": { 311 | "builder": "@angular-builders/custom-webpack:extract-i18n", 312 | "options": { 313 | "browserTarget": "calendar:build" 314 | } 315 | }, 316 | "test": { 317 | "builder": "@angular-builders/custom-webpack:karma", 318 | "options": { 319 | "main": "projects/calendar/src/test.ts", 320 | "polyfills": "projects/calendar/src/polyfills.ts", 321 | "tsConfig": "projects/calendar/tsconfig.spec.json", 322 | "karmaConfig": "projects/calendar/karma.conf.js", 323 | "inlineStyleLanguage": "scss", 324 | "assets": [ 325 | "projects/calendar/src/favicon.ico", 326 | "projects/calendar/src/assets" 327 | ], 328 | "styles": [ 329 | "projects/calendar/src/styles.scss" 330 | ], 331 | "scripts": [] 332 | } 333 | } 334 | } 335 | } 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /ngft-email-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngft-email-client", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test", 10 | "start:shell": "ng serve --project=shell", 11 | "start:mailbox": "ng serve --project=mailbox", 12 | "start:calendar": "ng serve --project=calendar" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/animations": "^14.2.0", 17 | "@angular/common": "^14.2.0", 18 | "@angular/compiler": "^14.2.0", 19 | "@angular/core": "^14.2.0", 20 | "@angular/forms": "^14.2.0", 21 | "@angular/platform-browser": "^14.2.0", 22 | "@angular/platform-browser-dynamic": "^14.2.0", 23 | "@angular/router": "^14.2.0", 24 | "rxjs": "~7.5.0", 25 | "tslib": "^2.3.0", 26 | "zone.js": "~0.11.4" 27 | }, 28 | "devDependencies": { 29 | "@angular-builders/custom-webpack": "^14.0.1", 30 | "@angular-devkit/build-angular": "^14.2.3", 31 | "@angular/cli": "~14.2.3", 32 | "@angular/compiler-cli": "^14.2.0", 33 | "@types/jasmine": "~4.0.0", 34 | "jasmine-core": "~4.3.0", 35 | "karma": "~6.4.0", 36 | "karma-chrome-launcher": "~3.1.0", 37 | "karma-coverage": "~2.2.0", 38 | "karma-jasmine": "~5.1.0", 39 | "karma-jasmine-html-reporter": "~2.0.0", 40 | "typescript": "~4.7.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, '../../coverage/calendar'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 304 | 305 | 306 | 327 | 328 |
329 | 330 | 331 |
332 | 333 | 334 | Rocket Ship 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | {{ title }} app is running! 345 | 346 | 347 | Rocket Ship Smoke 348 | 349 | 350 | 351 |
352 | 353 | 354 |

Resources

355 |

Here are some links to help you get started:

356 | 357 | 388 | 389 | 390 |

Next Steps

391 |

What do you want to do next with your app?

392 | 393 | 394 | 395 |
396 | 400 | 401 | 405 | 406 | 410 | 411 | 415 | 416 | 420 | 421 | 425 |
426 | 427 | 428 |
429 |
ng generate component xyz
430 |
ng add @angular/material
431 |
ng add @angular/pwa
432 |
ng add _____
433 |
ng test
434 |
ng build
435 |
436 | 437 | 438 | 454 | 455 | 456 | 468 | 469 | 470 | Gray Clouds Background 471 | 472 | 473 | 474 |
475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tieppt/micro-frontends-demo/ae1389d4a5fc6d5c67fbcf72654720fde969e999/ngft-email-client/projects/calendar/src/app/app.component.scss -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async () => { 7 | await TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | }); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'calendar'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.componentInstance; 26 | expect(app.title).toEqual('calendar'); 27 | }); 28 | 29 | it('should render title', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.nativeElement as HTMLElement; 33 | expect(compiled.querySelector('.content span')?.textContent).toContain('calendar app is running!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { 9 | title = 'calendar'; 10 | } 11 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { RouterModule, Routes } from '@angular/router'; 4 | 5 | import { AppComponent } from './app.component'; 6 | export const APP_ROUTES: Routes = [ 7 | { 8 | path: '', 9 | redirectTo: 'calendar', 10 | pathMatch: 'full' 11 | }, 12 | { 13 | path: 'calendar', 14 | loadChildren: () => import('./calendar/calendar.module').then(m => m.CalendarModule), 15 | } 16 | ]; 17 | 18 | @NgModule({ 19 | declarations: [ 20 | AppComponent 21 | ], 22 | imports: [ 23 | BrowserModule, 24 | RouterModule.forRoot(APP_ROUTES) 25 | ], 26 | providers: [], 27 | bootstrap: [AppComponent] 28 | }) 29 | export class AppModule { } 30 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/app/calendar/calendar-home/calendar-home.component.html: -------------------------------------------------------------------------------- 1 |

calendar-home works!

2 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/app/calendar/calendar-home/calendar-home.component.scss: -------------------------------------------------------------------------------- 1 | 2 | :host { 3 | display: block; 4 | min-height: 200px; 5 | width: 100%; 6 | border: 5px dashed goldenrod; 7 | } 8 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/app/calendar/calendar-home/calendar-home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CalendarHomeComponent } from './calendar-home.component'; 4 | 5 | describe('CalendarHomeComponent', () => { 6 | let component: CalendarHomeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CalendarHomeComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(CalendarHomeComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/app/calendar/calendar-home/calendar-home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-calendar-home', 5 | templateUrl: './calendar-home.component.html', 6 | styleUrls: ['./calendar-home.component.scss'] 7 | }) 8 | export class CalendarHomeComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/app/calendar/calendar.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule, Routes } from '@angular/router'; 4 | import { CalendarHomeComponent } from './calendar-home/calendar-home.component'; 5 | 6 | export const CALENDAR_ROUTES: Routes = [ 7 | { 8 | path: '', 9 | component: CalendarHomeComponent, 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | declarations: [ 15 | CalendarHomeComponent 16 | ], 17 | imports: [ 18 | CommonModule, 19 | RouterModule.forChild(CALENDAR_ROUTES), 20 | ] 21 | }) 22 | export class CalendarModule { } 23 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tieppt/micro-frontends-demo/ae1389d4a5fc6d5c67fbcf72654720fde969e999/ngft-email-client/projects/calendar/src/assets/.gitkeep -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tieppt/micro-frontends-demo/ae1389d4a5fc6d5c67fbcf72654720fde969e999/ngft-email-client/projects/calendar/src/favicon.ico -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Calendar 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/main.ts: -------------------------------------------------------------------------------- 1 | import('./bootstrap').catch(err => console.error(err)); 2 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().forEach(context); 27 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { ModuleFederationPlugin } = require('webpack').container; 2 | 3 | /** @type {require('webpack').Configuration} */ 4 | module.exports = { 5 | output: { 6 | publicPath: 'auto', // we setup the `publicHost` in `angular.json` file 7 | uniqueName: 'calendar', 8 | }, 9 | optimization: { 10 | runtimeChunk: false, 11 | }, 12 | experiments: { 13 | // Allow output javascript files as module source type. 14 | outputModule: true, 15 | }, 16 | plugins: [ 17 | new ModuleFederationPlugin({ 18 | name: 'calendar', 19 | library: { 20 | // because Angular v14 will output ESModule 21 | type: 'module', 22 | }, 23 | filename: 'remoteEntry.js', 24 | exposes: { 25 | './CalendarModule': 'projects/calendar/src/app/calendar/calendar.module.ts', 26 | }, 27 | /** 28 | * shared can be an object of type SharedConfig 29 | * you can change this to select something can be shared 30 | */ 31 | shared: ['@angular/core', '@angular/common', '@angular/router'], 32 | // shared: { 33 | // "@angular/animations": { 34 | // singleton: true, 35 | // strictVersion: true, 36 | // requiredVersion: "^14.2.0", 37 | // }, 38 | // "@angular/animations/browser": { 39 | // singleton: true, 40 | // strictVersion: true, 41 | // requiredVersion: "^14.2.0", 42 | // }, 43 | // "@angular/common": { 44 | // eager: true, 45 | // singleton: true, 46 | // strictVersion: true, 47 | // requiredVersion: "^14.2.0", 48 | // }, 49 | // "@angular/common/http": { 50 | // singleton: true, 51 | // strictVersion: true, 52 | // requiredVersion: "^14.2.0", 53 | // }, 54 | // "@angular/core": { 55 | // eager: true, 56 | // singleton: true, 57 | // strictVersion: true, 58 | // requiredVersion: "^14.2.0", 59 | // }, 60 | // "@angular/platform-browser": { 61 | // eager: true, 62 | // singleton: true, 63 | // strictVersion: true, 64 | // requiredVersion: "^14.2.0", 65 | // }, 66 | // "@angular/platform-browser/animations": { 67 | // singleton: true, 68 | // strictVersion: true, 69 | // requiredVersion: "^14.2.0", 70 | // }, 71 | // "@angular/router": { 72 | // singleton: true, 73 | // strictVersion: true, 74 | // requiredVersion: "^14.2.0", 75 | // }, 76 | // "@angular/platform-browser-dynamic": { 77 | // eager: true, 78 | // singleton: true, 79 | // strictVersion: true, 80 | // requiredVersion: "^14.2.0", 81 | // }, 82 | // }, 83 | }), 84 | ], 85 | }; 86 | -------------------------------------------------------------------------------- /ngft-email-client/projects/calendar/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./webpack.config'); 2 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, '../../coverage/mailbox'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 304 | 305 | 306 | 327 | 328 |
329 | 330 | 331 |
332 | 333 | 334 | Rocket Ship 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | {{ title }} app is running! 345 | 346 | 347 | Rocket Ship Smoke 348 | 349 | 350 | 351 |
352 | 353 | 354 |

Resources

355 |

Here are some links to help you get started:

356 | 357 | 388 | 389 | 390 |

Next Steps

391 |

What do you want to do next with your app?

392 | 393 | 394 | 395 |
396 | 400 | 401 | 405 | 406 | 410 | 411 | 415 | 416 | 420 | 421 | 425 |
426 | 427 | 428 |
429 |
ng generate component xyz
430 |
ng add @angular/material
431 |
ng add @angular/pwa
432 |
ng add _____
433 |
ng test
434 |
ng build
435 |
436 | 437 | 438 | 454 | 455 | 456 | 468 | 469 | 470 | Gray Clouds Background 471 | 472 | 473 | 474 |
475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tieppt/micro-frontends-demo/ae1389d4a5fc6d5c67fbcf72654720fde969e999/ngft-email-client/projects/mailbox/src/app/app.component.scss -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async () => { 7 | await TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | }); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'mailbox'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.componentInstance; 26 | expect(app.title).toEqual('mailbox'); 27 | }); 28 | 29 | it('should render title', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.nativeElement as HTMLElement; 33 | expect(compiled.querySelector('.content span')?.textContent).toContain('mailbox app is running!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { 9 | title = 'mailbox'; 10 | } 11 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { RouterModule, Routes } from '@angular/router'; 4 | 5 | import { AppComponent } from './app.component'; 6 | export const APP_ROUTES: Routes = [ 7 | { 8 | path: '', 9 | pathMatch: 'full', 10 | redirectTo: 'mailbox', 11 | }, 12 | { 13 | path: 'mailbox', 14 | loadChildren: () => import('./mailbox/mailbox.module').then(m => m.MailboxModule) 15 | } 16 | ]; 17 | @NgModule({ 18 | declarations: [ 19 | AppComponent 20 | ], 21 | imports: [ 22 | BrowserModule, 23 | RouterModule.forRoot(APP_ROUTES) 24 | ], 25 | providers: [], 26 | bootstrap: [AppComponent] 27 | }) 28 | export class AppModule { } 29 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/app/mailbox/mailbox-home/mailbox-home.component.html: -------------------------------------------------------------------------------- 1 |

mailbox-home works!

2 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/app/mailbox/mailbox-home/mailbox-home.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | min-height: 200px; 4 | width: 100%; 5 | border: 5px dashed deeppink; 6 | } 7 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/app/mailbox/mailbox-home/mailbox-home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { MailboxHomeComponent } from './mailbox-home.component'; 4 | 5 | describe('MailboxHomeComponent', () => { 6 | let component: MailboxHomeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ MailboxHomeComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(MailboxHomeComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/app/mailbox/mailbox-home/mailbox-home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-mailbox-home', 5 | templateUrl: './mailbox-home.component.html', 6 | styleUrls: ['./mailbox-home.component.scss'] 7 | }) 8 | export class MailboxHomeComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/app/mailbox/mailbox.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { MailboxHomeComponent } from './mailbox-home/mailbox-home.component'; 4 | import { RouterModule, Routes } from '@angular/router'; 5 | 6 | export const MAILBOX_ROUTES: Routes = [ 7 | { 8 | path: '', 9 | component: MailboxHomeComponent, 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | declarations: [ 15 | MailboxHomeComponent 16 | ], 17 | imports: [ 18 | CommonModule, 19 | RouterModule.forChild(MAILBOX_ROUTES), 20 | ] 21 | }) 22 | export class MailboxModule { } 23 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tieppt/micro-frontends-demo/ae1389d4a5fc6d5c67fbcf72654720fde969e999/ngft-email-client/projects/mailbox/src/assets/.gitkeep -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tieppt/micro-frontends-demo/ae1389d4a5fc6d5c67fbcf72654720fde969e999/ngft-email-client/projects/mailbox/src/favicon.ico -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mailbox 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/main.ts: -------------------------------------------------------------------------------- 1 | import('./bootstrap').catch(err => console.error(err)); 2 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().forEach(context); 27 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { ModuleFederationPlugin } = require('webpack').container; 2 | 3 | /** @type {require('webpack').Configuration} */ 4 | module.exports = { 5 | output: { 6 | publicPath: 'auto', // we setup the `publicHost` in `angular.json` file 7 | uniqueName: 'mailbox', 8 | }, 9 | optimization: { 10 | runtimeChunk: false, 11 | }, 12 | experiments: { 13 | // Allow output javascript files as module source type. 14 | outputModule: true, 15 | }, 16 | plugins: [ 17 | new ModuleFederationPlugin({ 18 | name: 'mailbox', 19 | filename: 'remoteEntry.js', 20 | library: { 21 | // because Angular v14 will output ESModule 22 | type: 'module', 23 | }, 24 | exposes: { 25 | './MailboxModule': 'projects/mailbox/src/app/mailbox/mailbox.module.ts', 26 | }, 27 | /** 28 | * shared can be an object of type SharedConfig 29 | * you can change this to select something can be shared 30 | */ 31 | shared: ['@angular/core', '@angular/common', '@angular/router'], 32 | // shared: { 33 | // "@angular/animations": { 34 | // singleton: true, 35 | // strictVersion: true, 36 | // requiredVersion: "^14.2.0", 37 | // }, 38 | // "@angular/animations/browser": { 39 | // singleton: true, 40 | // strictVersion: true, 41 | // requiredVersion: "^14.2.0", 42 | // }, 43 | // "@angular/common": { 44 | // eager: true, 45 | // singleton: true, 46 | // strictVersion: true, 47 | // requiredVersion: "^14.2.0", 48 | // }, 49 | // "@angular/common/http": { 50 | // singleton: true, 51 | // strictVersion: true, 52 | // requiredVersion: "^14.2.0", 53 | // }, 54 | // "@angular/core": { 55 | // eager: true, 56 | // singleton: true, 57 | // strictVersion: true, 58 | // requiredVersion: "^14.2.0", 59 | // }, 60 | // "@angular/platform-browser": { 61 | // eager: true, 62 | // singleton: true, 63 | // strictVersion: true, 64 | // requiredVersion: "^14.2.0", 65 | // }, 66 | // "@angular/platform-browser/animations": { 67 | // singleton: true, 68 | // strictVersion: true, 69 | // requiredVersion: "^14.2.0", 70 | // }, 71 | // "@angular/router": { 72 | // singleton: true, 73 | // strictVersion: true, 74 | // requiredVersion: "^14.2.0", 75 | // }, 76 | // "@angular/platform-browser-dynamic": { 77 | // eager: true, 78 | // singleton: true, 79 | // strictVersion: true, 80 | // requiredVersion: "^14.2.0", 81 | // }, 82 | // }, 83 | }), 84 | ], 85 | }; 86 | -------------------------------------------------------------------------------- /ngft-email-client/projects/mailbox/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./webpack.config'); 2 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, '../../coverage/shell'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 304 | 305 | 306 | 327 | 328 |
329 | 330 | 331 |
332 | 333 | 334 | Rocket Ship 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | {{ title }} app is running! 345 | 346 | 347 | Rocket Ship Smoke 348 | 349 | 350 | 351 |
352 | 353 | 354 |

Resources

355 |

Here are some links to help you get started:

356 | 357 | 396 | 397 | 398 |

Next Steps

399 |

What do you want to do next with your app?

400 | 401 | 402 | 403 |
404 | 408 | 409 | 413 | 414 | 418 | 419 | 423 | 424 | 428 | 429 | 433 |
434 | 435 | 436 |
437 |
ng generate component xyz
438 |
ng add @angular/material
439 |
ng add @angular/pwa
440 |
ng add _____
441 |
ng test
442 |
ng build
443 |
444 | 445 | 446 | 462 | 463 | 464 | 476 | 477 | 478 | Gray Clouds Background 479 | 480 | 481 | 482 |
483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tieppt/micro-frontends-demo/ae1389d4a5fc6d5c67fbcf72654720fde969e999/ngft-email-client/projects/shell/src/app/app.component.scss -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async () => { 7 | await TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | }); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'shell'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.componentInstance; 26 | expect(app.title).toEqual('shell'); 27 | }); 28 | 29 | it('should render title', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.nativeElement as HTMLElement; 33 | expect(compiled.querySelector('.content span')?.textContent).toContain('shell app is running!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { 9 | title = 'shell'; 10 | } 11 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { RouterModule, Routes } from '@angular/router'; 4 | 5 | import { AppComponent } from './app.component'; 6 | 7 | const routes: Routes = [ 8 | { 9 | path: 'mailbox', 10 | loadChildren: () => import('mailbox/MailboxModule').then(m => m.MailboxModule) 11 | }, 12 | { 13 | path: 'calendar', 14 | loadChildren: () => import('calendar/CalendarModule').then(m => m.CalendarModule) 15 | } 16 | ]; 17 | 18 | @NgModule({ 19 | declarations: [ 20 | AppComponent 21 | ], 22 | imports: [ 23 | BrowserModule, 24 | RouterModule.forRoot(routes) 25 | ], 26 | providers: [], 27 | bootstrap: [AppComponent] 28 | }) 29 | export class AppModule { } 30 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tieppt/micro-frontends-demo/ae1389d4a5fc6d5c67fbcf72654720fde969e999/ngft-email-client/projects/shell/src/assets/.gitkeep -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tieppt/micro-frontends-demo/ae1389d4a5fc6d5c67fbcf72654720fde969e999/ngft-email-client/projects/shell/src/favicon.ico -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Shell 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/src/main.ts: -------------------------------------------------------------------------------- 1 | import('./bootstrap').catch(err => console.error(err)); 2 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().forEach(context); 27 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/src/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'mailbox/MailboxModule'; 2 | declare module 'calendar/CalendarModule'; 3 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { ModuleFederationPlugin } = require('webpack').container; 2 | 3 | /** @type {require('webpack').Configuration} */ 4 | module.exports = { 5 | output: { 6 | publicPath: 'auto', // we setup the `publicHost` in `angular.json` file 7 | uniqueName: 'shell', 8 | }, 9 | optimization: { 10 | runtimeChunk: false, 11 | }, 12 | experiments: { 13 | // Allow output javascript files as module source type. 14 | outputModule: true, 15 | }, 16 | plugins: [ 17 | new ModuleFederationPlugin({ 18 | name: 'shell', 19 | library: { 20 | // because Angular v14 will output ESModule 21 | type: 'module', 22 | }, 23 | remotes: { 24 | mailbox: 'http://localhost:5300/remoteEntry.js', 25 | calendar: 'http://localhost:5400/remoteEntry.js', 26 | }, 27 | /** 28 | * shared can be an object of type SharedConfig 29 | * you can change this to select something can be shared 30 | */ 31 | shared: ['@angular/core', '@angular/common', '@angular/router'], 32 | // shared: { 33 | // "@angular/animations": { 34 | // singleton: true, 35 | // strictVersion: true, 36 | // requiredVersion: "^14.2.0", 37 | // }, 38 | // "@angular/animations/browser": { 39 | // singleton: true, 40 | // strictVersion: true, 41 | // requiredVersion: "^14.2.0", 42 | // }, 43 | // "@angular/common": { 44 | // eager: true, 45 | // singleton: true, 46 | // strictVersion: true, 47 | // requiredVersion: "^14.2.0", 48 | // }, 49 | // "@angular/common/http": { 50 | // singleton: true, 51 | // strictVersion: true, 52 | // requiredVersion: "^14.2.0", 53 | // }, 54 | // "@angular/core": { 55 | // eager: true, 56 | // singleton: true, 57 | // strictVersion: true, 58 | // requiredVersion: "^14.2.0", 59 | // }, 60 | // "@angular/platform-browser": { 61 | // eager: true, 62 | // singleton: true, 63 | // strictVersion: true, 64 | // requiredVersion: "^14.2.0", 65 | // }, 66 | // "@angular/platform-browser/animations": { 67 | // singleton: true, 68 | // strictVersion: true, 69 | // requiredVersion: "^14.2.0", 70 | // }, 71 | // "@angular/router": { 72 | // singleton: true, 73 | // strictVersion: true, 74 | // requiredVersion: "^14.2.0", 75 | // }, 76 | // "@angular/platform-browser-dynamic": { 77 | // eager: true, 78 | // singleton: true, 79 | // strictVersion: true, 80 | // requiredVersion: "^14.2.0", 81 | // }, 82 | // }, 83 | }), 84 | ], 85 | }; 86 | -------------------------------------------------------------------------------- /ngft-email-client/projects/shell/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./webpack.config'); 2 | -------------------------------------------------------------------------------- /ngft-email-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "downlevelIteration": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "target": "es2020", 20 | "module": "es2020", 21 | "lib": [ 22 | "es2020", 23 | "dom" 24 | ] 25 | }, 26 | "angularCompilerOptions": { 27 | "enableI18nLegacyMessageIdFormat": false, 28 | "strictInjectionParameters": true, 29 | "strictInputAccessModifiers": true, 30 | "strictTemplates": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Micro Frontends with Angular and Webpack Federation 2 | 3 | Bài viết: [micro-frontends.md](./micro-frontends.md) 4 | 5 | Code demo: [ngft-email-client](./ngft-email-client) 6 | 7 | Scripts: 8 | 9 | ```sh 10 | npm run start:shell 11 | npm run start:mailbox 12 | npm run start:calendar 13 | ``` 14 | --------------------------------------------------------------------------------