Angular v6부터 Web Components에 대한 지원으로 @angular/elements 기능이 추가되어 Custom HTML Tag을 만들 수 있도록 지원한다. 본 글은 해당 사이트의 글을 Nx.dev 환경과 통합하여 개발하는 과정을 설명한다. Nx 환경은 mono repository 기반으로 multi application을 개발 할 수 있는 환경을 제공한다. Angular/CLI기반이지만 Angular, React, Node.js 개발까지 하나의 Git Repository안에서 개발하고 번들링 할 수 있도록 지원한다. 따라서 micro frontend에서 multi application 개발 잇점을 갖는다.
블로그 소스 [GitHub]
NX 환경 준비
Angular v9.*
@angular/cli v9.1.6
RxJS v6.5.*
Typescript v3.8.*
NodeJS v12.16.*
Node Version Manager(nvm)를 통해 로컬환경에 여러 Node버전을 관리하자.
// NodeJS
$ nvm install 12.16.2
$ nvm alias default 12.16.2
$ nvm use 12.16.2
// Angular/CLI 최신버전 사용
$ npm i -g @angular/cli@latest
$ npm i -g @nrwl/cli@latest
$ npm i -g yarn@latest
// local 설치
$ yarn add
NX workspace를 생성한다.
$ npx create-nx-workspace@latest
// 선택 및 입력
? Workspace name (e.g., org name) micro-demo
? What to create in the new workspace angular [a workspace with a single Angular application]
? Application name app-container
? Default stylesheet format SASS(.scss) [ http://sass-lang.com ]
Web Components 개발 환경 설정
@angular/elements 를 설치한다.
$ yarn add @angular/elements
UI Component로 ng-antd v9.1.* 를 사용한다. yarn 이 아니라 angular/cli의 'ng' 명령을 사용한다. Yes와 sidemenu 형태 선택한다.
$ ng add ng-zorro-antd
선택하기
? Enable icon dynamic loading [ Detail: https://ng.ant.design/components/icon/en ] Yes
? Set up custom theme file [ Detail: https://ng.ant.design/docs/customize-theme/en ] Yes
? Choose your locale code: en_US
? Choose template to create project: sidemenu
설치 후에 Nx workspace와 ng-zorro-antd의 불일치를 해결한다.
- apps/app-container/theme.less의 첫줄의 import 문구 수정
- @import "../../../node_modules/ng-zorro-antd/ng-zorro-antd.less";
- apps/app-container/index.html의 root tag 수정
- <app-root></app-root>
설정 수정후 실행을 하면 sidemenu가 있는 환경이 자동 셋업되어 아래와 같이 보인다. 자세한 설치방법은 사이트를 참조한다.
$ ng serve --open
Monitor Web Components 개발 및 번들링
monitor 애플리케이션을 신규 생성한다. 모니터 애플리케이션을 Web Components로 만들어 app-container 애플리케이션에서 동적으로 로딩해 본다.
$ ng g app monitor
선택
? Which stylesheet format would you like to use? SASS(.scss) [ http://sass-lang.com ]
? Would you like to configure routing for this application? No
apps/monitor/src/app/app.component.html과 app.component.ts 를 변경한다.
// app.component.html
{{title}} Application
// app.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'micro-demo-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
@Input() title = 'monitor';
}
apps/monitor/src/app/app.module.ts에서 Web Comopennts를 등록한다.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [],
// 동적으로 생성하므로 entryComponents에 등록
entryComponents: [AppComponent],
// 정적 bootstrap을 사용하지 않음
// bootstrap: [AppComponent],
})
export class AppModule {
constructor(private injector: Injector) {}
ngDoBootstrap() {
// createCustomElement를 통해 Web Components 스펙에 맞는 객체로 반환
const monitorApp = createCustomElement(AppComponent, { injector: this.injector });
// browser window객체에 잇는 customElements를 통해 Web Components 등록
customElements.define('monitor-app', monitorApp);
// 사용방법: @Input() title이 있으므로 attribute 설정가능
// <monitor-app title="Monitor Application"></monitor-app>
}
}
monitor 애플리케이션을 번들링하면 여러개의 파일로 나오는데 번들링 파일을 최소화한다. ngDoBootstrap() 은 정적 bootstrap이 아닌 실행타임에 외부 컴포넌트를 동적으로 로딩할 때 애플케이션 root를 결정할 수 있게 한다. ngDoBootstrap에 대한 설명을 참조하자.
$ ng build monitor --prod --output-hashing=none
수행할 경우 main, polyfill, runtime등의 파일이 생성된다. 파일 최소화를 위해 ngx-build-plus 패키지를 이용한다.
ng add 명령으로 ngx-build-plus를 설치하고 애플리케이션은 monitor를 지정한다.
$ ng add ngx-build-plus --project=monitor
ng add 로 수행을 하면 angular.json 파일의 설정을 자동으로 적용해 준다. builder의 명령어를 자동 수정함.
"monitor": {
"projectType": "application",
"schematics": {
"@nrwl/angular:component": {
"style": "scss"
}
},
"root": "apps/monitor",
"sourceRoot": "apps/monitor/src",
"prefix": "micro-demo",
"architect": {
"build": {
"builder": "ngx-build-plus:browser", <== builder가 자동 변경됨
"options": {
...
다시 명령으로 monitor 애플리케이션을 번들링한다. ngx-build-plus로 확장한 옵션인 --single-bundle true를 추가한다.
$ ng build monitor --prod --output-hashing=none --single-bundle true
main과 polyfill을 합쳐주는 스크립트를 등록한다. Mac/Linux기준 명령이다. micro-demo 폴더 밑에 buildSingle.sh 파일을 생성한다. 번들링파일 합치는 명령을 넣는다.
#!/bin/sh
ng build monitor --prod --output-hashing=none --single-bundle true && cat dist/apps/monitor/main-es5.js dist/apps/monitor/polyfills-es5.js > apps/app-container/src/assets/monitor-es5.js
buildSingle.sh를 수행한 결과 파일은 app-container의 apps/app-container/src/assets/monitor-es5.js 쪽으로 copy된다.
컨테이너 애플리케이션에서 동적로딩
Web Components 스펙을 기준으로 monitor 애플리케이션을 번들링 했기때문에 프레임워크의 종류에 상관없이 <monitor-app> 태그를 사용할 수 있다. 정적으로 사용하는 방법을 살펴보자.
apps/app-container/src/app/pages/welcome/welcome.component.ts에서 monitor-es5.js파일을 import한다.
import { Component, OnInit } from '@angular/core';
// 소스 import
import '../../../assets/monitor-es5.js';
@Component({
selector: 'app-welcome',
templateUrl: './welcome.component.html',
styleUrls: ['./welcome.component.scss']
})
export class WelcomeComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
테스트를 위해 welcome.component.html 에 <monitor-app>태그를 설정해 보자.
<monitor-app title="Hi Monitor"></monitor-app>
여기까지하고 수행을 하면 <monitor-app> 태그를 해석할 수 없다고 Angular가 에러를 뱃는다. <monitor-app> 은 Angular가 해석하는 것이 아니라 Browser에서 해석되는 Web Components이므로 무시하도록 welcome.module.ts에 CUSTOM_ELEMENTS_SCHEMA를 설정한다.
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { WelcomeRoutingModule } from './welcome-routing.module';
import { WelcomeComponent } from './welcome.component';
@NgModule({
imports: [WelcomeRoutingModule],
declarations: [WelcomeComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
exports: [WelcomeComponent]
})
export class WelcomeModule { }
<monitor-app>태그를 설정하지 않고 Javascript를 이용하여 로딩해 본다.
apps/app-container/src/app/monitor.service.ts 파일을 생성한다.
- monitor-es5.js 파일 동적 로딩
- <monitor-app> DOM 동적 추가
import { Injectable } from '@angular/core';
@Injectable({providedIn: 'root'})
export class MonitorLoaderService {
loaded = false;
constructor() { }
// script 동적 로딩
loadMonitorScript(): void {
if (this.loaded) {
return;
}
const script = document.createElement('script');
script.src = 'assets/monitor-es5.js';
document.body.appendChild(script);
this.loaded = true;
}
// <monitor-app> 태그 추가
addMonitorApp(): void {
const tile = document.createElement('monitor-app');
// @Input() 내용은 setAttribute로 추가 가능
tile.setAttribute('title', 'Dynamic Load Monitor');
const content = document.getElementById('content');
content.appendChild(tile);
}
}
위의 경우 monitor-es5.js 파일을 별도로 다운로드받아 동적 로딩을 수행한다.
공통 파일 빼고 번들링하기
만일 app-container과 monitor 에서 사용하는 공통 패키지의 버전이 같다면, app-container 애플리케이션과 monitor 애플리케이션이 공통으로 사용하는 파일중, monitor 애플리케이션을 번들링할 때 공통파일을 제거하는 방법에 대해 알아보자. 제거를 통해 monitor 애플리케이션의 번들링 사이즈를 줄일 수 있다. 이는 Network payload time을 줄여주는 결과를 갖는다.
ngx-build-plus를 이용해서 @angular/cli의 webpack externals 을 자동 생성한다. (참조)
$ ng g ngx-build-plus:externals --project monitor
수행을 하면 angular.json 파일에 별도 환경이 추가되고, apps/monitor/webpack.externals.js 파일이 생성된다. angular.json 내용중
"node_modules/@angular/elements/bundles/elements.umd.js", 내용은 제거한다. elements.umd.js를 공통파일로 빼서 사용하는데 오류가 있다.
angular.json의 monitor 애플리케이션으 "scripts" 설정 내역 => scripts.js 파일에 설정한 *.umd.js 파일을 합친다.
// angular.json 내의 monitor 애플리케이션 설정
// "scripts"에 externals로 참조하는 파일 설정이 자동으로 입력되어 진다.
"monitor": {
"projectType": "application",
"schematics": {
"@nrwl/angular:component": {
"style": "scss"
}
},
"root": "apps/monitor",
"sourceRoot": "apps/monitor/src",
"prefix": "micro-demo",
"architect": {
"build": {
"builder": "ngx-build-plus:browser",
"options": {
"outputPath": "dist/apps/monitor",
"index": "apps/monitor/src/index.html",
"main": "apps/monitor/src/main.ts",
"polyfills": "apps/monitor/src/polyfills.ts",
"tsConfig": "apps/monitor/tsconfig.app.json",
"aot": true,
"assets": [
"apps/monitor/src/favicon.ico",
"apps/monitor/src/assets"
],
"styles": [
"apps/monitor/src/styles.scss"
],
"scripts": [
"node_modules/rxjs/bundles/rxjs.umd.js",
"node_modules/@angular/core/bundles/core.umd.js",
"node_modules/@angular/common/bundles/common.umd.js",
"node_modules/@angular/common/bundles/common-http.umd.js",
"node_modules/@angular/compiler/bundles/compiler.umd.js",
"node_modules/@angular/elements/bundles/elements.umd.js", // <-- 제거한다
"node_modules/@angular/platform-browser/bundles/platform-browser.umd.js",
"node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js"
]
},
apps/monitor/webpack.externals.js 파일내역에서 ng.elements도 주석처리한다. => monitor-es5.js 파일에서 제거되는 파일목록이다.
const webpack = require('webpack');
module.exports = {
"externals": {
"rxjs": "rxjs",
"@angular/core": "ng.core",
"@angular/common": "ng.common",
"@angular/common/http": "ng.common.http",
"@angular/platform-browser": "ng.platformBrowser",
"@angular/platform-browser-dynamic": "ng.platformBrowserDynamic",
"@angular/compiler": "ng.compiler",
// "@angular/elements": "ng.elements", <-- 주석처리한다.
// Uncomment and add to scripts in angular.json if needed
// "@angular/router": "ng.router",
// "@angular/forms": "ng.forms"
}
}
buildSingle.sh 내용을 수정한다. scripts.js 파일은 window.ng.core 또는 window.ng.common과 같은 global 객체가 담겨있는 파일이다. 따라서 scripts.js는 app-container 애플리케이션에서 최초 한번만 로딩하면 되고, 이후 monitor 애플리케이션과 같은 web components는 번들 파일은 자신의 내용만을 포함한다.
monitor app size including common library - 129KB
monitor app size excluding common library - 19KB
#!/bin/sh
ng build monitor --prod --extra-webpack-config=apps/monitor/webpack.externals.js --output-hashing=none --single-bundle true && cat dist/apps/monitor/main-es5.js dist/apps/monitor/polyfill-es5.js > apps/app-container/src/assets/monitor-es5.js
cat dist/apps/monitor/scripts.js > apps/app-container/src/assets/scripts.js
scripts.js 파일은 angular.json 의 app-container 애플리케이션 "scripts" 옵션에 추가한다.
"app-container": {
"projectType": "application",
"schematics": {
"@nrwl/angular:component": {
"style": "scss"
}
},
"root": "apps/app-container",
"sourceRoot": "apps/app-container/src",
"prefix": "micro-demo",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/apps/app-container",
"index": "apps/app-container/src/index.html",
"main": "apps/app-container/src/main.ts",
"polyfills": "apps/app-container/src/polyfills.ts",
"tsConfig": "apps/app-container/tsconfig.app.json",
"aot": true,
"assets": [
"apps/app-container/src/favicon.ico",
"apps/app-container/src/assets",
{
"glob": "**/*",
"input": "./node_modules/@ant-design/icons-angular/src/inline-svg/",
"output": "/assets/"
}
],
"styles": [
"apps/app-container/src/theme.less",
"apps/app-container/src/styles.scss"
],
"scripts": [
"apps/app-container/src/assets/scripts.js" <== 추가
]
},
수행하면 네트워크에서 scripts.js 파일은 한번만 로딩되고, 이후 다양한 Web Components들은 자신의 애플리케이션 내역만 포함한 파일로 번들링하여 파일 사이즈를 줄일 수 있다. (참조)
참조
https://www.angulararchitects.io/aktuelles/angular-elements-part-i/
https://ng.ant.design/docs/getting-started/en
https://medium.com/angular-in-depth/how-to-manually-bootstrap-an-angular-application-9a36ccf86429
https://www.angulararchitects.io/aktuelles/your-options-for-building-angular-elements/
https://indepth.dev/tiny-angular-application-projects-in-nx-workspaces/
https://nstudio.io/blog/custom-web-elements-with-angular-and-react
'React > Architecture' 카테고리의 다른 글
[MS-2] React & Nest 기반 애플리케이션 및 Micro Service 통신 설정 (0) | 2021.09.20 |
---|---|
[MS-1] Micro Service 환경 구축 (0) | 2021.09.20 |
[NX-Micro Frontend] NX기반 Enterprise Angular Monorepo Patterns (0) | 2021.08.05 |
[Micro Frontend] 마이크로 프론트앤드 - 개념 (0) | 2020.05.18 |
[React] 주요 개념 (Main Concept) 정리 (0) | 2020.04.23 |