블로그 이미지
Peter Note
Web & LLM FullStacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

2021. 8. 25. 15:40 [App FullStacker]/Start React

NXAngular/CLI를 확장하여 Typescript기반의 멀티 애플리케이션 및 노드 패키지개발을 위한 환경을 제공한다. 또한 Plugin 기반으로 React, NextJS, NestJS와 같은 프레임워크와 노드환경 확장을 통해 FullStack개발을 지원한다. 

 

목적

  • Case1: SPA/CSR의 React 애플리케이션 생성 + NestJS 기반 노드서버 생성
  • Case2: SSR을 위한 NextJS기반 애플리케이션 생성
  • Case1과 2의 두가지 애플리케이션에 대해 비교 테스트 진행하여 성능 이점과 차이점을 비교한다.

 

로컬에 새로운 환경 구성하기

NodeJS기반 테스트 환경 구축시 NodeJS버전을 변경하며 사용할 수 있도록 Local PC에 nvm (Node Version Manager)를 설치한다. Windows는 관련 링크를 참조하여 설치한다.

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash

NodeJS LTS버전을 nvm을 통해 설치하고 사용 설정한다. 

$> nvm install v14.18.0
$> nvm use 14.18.0

소스 폴더로 이동을 하면 지정한 node version으로 nvm을 통해 switching 하고 싶다면 폴더에 .nvmrc 파일을 생성하고 "14.18.0" 버전을 명시하고, 로그인 사용자 .zshrc 또는 .bashrc 에 하기 사항을 설정한다. Gist 소스 참조

#! /usr/bin/env zsh

# Ref: https://github.com/creationix/nvm#calling-nvm-use-automatically-in-a-directory-with-a-nvmrc-file
# place this after nvm initialization!

autoload -Uz add-zsh-hook

# Function: load-nvmrc
load-nvmrc() {
    local _CUR_NODE_VER="$(nvm version)"
    local _NVMRC_PATH="$(nvm_find_nvmrc)"

    if [[ -n "${_NVMRC_PATH}" ]]; then
        local _NVMRC_NODE_VER="$(nvm version "$(cat "${_NVMRC_PATH}")")"

        if [[ "${_NVMRC_NODE_VER}" == 'N/A' ]]; then
            local compcontext='yn:yes or no:(y n)'
            vared -cp "Install the unmet version ($(cat "${_NVMRC_PATH}")) in nvm (y/n) ?" _ANSWER
            if [[ "${_ANSWER}" =~ '^y(es)?$' ]] ; then
                nvm install
            fi
        elif [[ "${_NVMRC_NODE_VER}" != "${_CUR_NODE_VER}" ]]; then
            nvm use
        fi
    elif [[ "${_CUR_NODE_VER}" != "$(nvm version default)" ]]; then
        echo -e "Reverting to the default version in nvm"
        nvm use default
    fi
}

add-zsh-hook chpwd load-nvmrc
load-nvmrc

 

NX 개발환경 구성을 위한 글로벌 패키지를 설치한다. Typescript는 v4.3.5 이상을 사용한다.

$> npm i -g @angular/cli@latest
$> npm i -g @nrwl/cli@latest
$> npm i -g yarn@latest

 

NX 개발환경 생성하기

npx 명령으로 개발환경 생성. RNN Stack에서 RNN은 React NestJS NextJS 을 합친 것이다. React Application의 명칭은 "tube-csr" 이다.

반드시 latest 버전으로 설치한다. 

$> npx create-nx-workspace@latest

선택하기
  ✔ Workspace name (e.g., org name)     · rnn-stack
  ✔ What to create in the new workspace · react
  ✔ Application name                    · tube-csr
  ✔ Default stylesheet format           · scss
  ✔ Use Nx Cloud? (It's free and doesn't require registration.) · No
   
$> cd rnn-stack

 

"tube-csr" 애플리케이션을 위한 Node서버로 NestJS 플로그인를 설치하고, "tube-api" 이름으로 서버를 생성한다.

Nx의 NestJS 플러그인 설치
$> yarn add -D @nrwl/nest@latest

생성
$> nx generate @nrwl/nest:app tube-api

 

다음으로 NX의 NextJS 플러그인을 설치한다. NextJS Application은 "tube-ssr" 이다.

설치
$> yarn add -D @nrwl/next@latest

생성
$> nx generate @nrwl/next:app tube-ssr
또는
$> nx g @nrwl/next:app tube-ssr

✔ Which stylesheet format would you like to use? · scss

 

NextJS 애플리케이션을 설치하면 sass-node버전을 v5.0.0이 설치된다. v4.14.1로 변경 사용한다. 5.0으로 사용시 컴파일 오류 발생하여 향후 패치되면 버전 업그레이드 함.

$> npm uninstall node-sass
$> npm install -D node-sass@4.14.1

tube-ssr 애플리케이션의 개발 서버 포트는 4300 으로 변경한다. workspace.json의 "tube-ssr"의 serve options설정에 포트 정보를 수정한다.

 

 

Nx 환경파일 재구성

애플리케이션과 라이브러리를 많이 만들다 보면 rnn-stack/workspace.json 파일안에 설정이 계속 추가된다. 가독성을 위하여 애플리케이션(라이브러리 포함) Nx의 환경설정을 프로젝트별 별도 파일로 분리한다. 분리후에는 애플리케이션이나 라이브러리 생성시 자동으로 "project.json" 파일로 분리가 된다.

 

각 애플리케이션 root 폴더에 "project.json" 파일을 생성하고 workspace.json의 프로젝트별 설정 정보를 옮긴다. workspace.json에는 애플리케이션의 위치정보로 수정하면 컨벤션에 의해 project.json을 인지한다.

workspace.json은 애플리케이션 위치를 표현한다. 

 

테스트하기

"tube"라는 React 애플리케이션을 Dev Server기반으로 실행하고, "realtime"이라는 NextJS 프렘워크기반 노드 서버를 실행한다. 

  • React Application: http://localhost:4200/ 
  • NextJS Application: http://localhost:4300/
React Single Page Application
$> nx serve tube-csr

NextJS Application with Server
$> nx serve tube-ssr

 

Prettier 코드 포멧터 설정하기

MS Code 편집기를 기준으로 prettier를 설정한다. 

  • rnn-stack 루트 폴더에 .prettierrc파일을 생성한다.
  • MS Code를 위한 Prettier Plugin을 설치한다. 
  • .vscode/settings.json 에 prettier 옵션을 설정한다. settings.json 파일이 존재하지 않다면 생성후 설정한다.
  • .vscode/extensions.json의 recommandation으로 prettier를 설정한다.

.prettierrc 내역

{
    "printWidth": 120,
    "singleQuote": true,
    "useTabs": false,
    "tabWidth": 2,
    "semi": true,
    "bracketSpacing": true
}

. vscode/settings.json 내역

{
    "editor.formatOnPaste": true,
    "editor.formatOnType": true,
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "[typescript]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    "[javascript]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    "[scss]": {
        "editor.suggest.insertMode": "replace",
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    "[html]": {
        "editor.suggest.insertMode": "replace",
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    }
}

.vscode/extensions.json 내역

{
  "recommendations": [
    "esbenp.prettier-vscode",
    "nrwl.angular-console",
    "firsttris.vscode-jest-runner",
    "dbaeumer.vscode-eslint"
  ]
}

 

소스

https://github.com/ysyun/rnn-stack/releases/tag/hh-2

 

참조

posted by Peter Note
2021. 8. 25. 10:48 [App FullStacker]/Start React

리액트 프로젝트를 위해 조사해봐야할 항목에 대해서 정리해 본다. 하기 구성에 대해 소스레벨의 준비과정을 작성해 본다.

 

개발환경

  • micro-frontend를 지원하는 환경
  • mono-repo기반 multi application을 개발할 수 있는 환경
  • Nx.dev for React
    • 멀티 애플리케이션 개발 및 애프리케이션별 번들링
    • 라이브러리의 위치 투명성 보장 및 배포

 

구조 설계

  • 1Layer: 애플리케이션
    • Layout: Feature 를 다루는 영역을 분할
    • Feature: UI + Data-Access 를 조합하여 업무 단위 화면을 구성
  • 2Layer: 업무 라이브러리
    • UI: Layout, Feature의 부분을 담당하는 업무 컴포넌트
    • Data-Access: API 호출 및 Data Caching
    • Data-Type: Frontend - Backend간 데이터 타입 및 vadliation
    • State: UI 상태 저장 및 전파
  • 3Layer: 공용 라이브러
    • Component: 사용자 정의 요소 컴포넌트 및 랩퍼
    • Chart: 차트 및 랩퍼
    • Utils: 유틸리티들
  • 4Layer: package.json 
    • AntD, PrimeReact, MaterialUI, Boostrap와 같은 CSS + Component Framework
    • Redux, Mobx, Recoil 과 같은 state
    • moment, date-fns 같은 date
    • immer 같은 immutable state 
    • react-use 같은 Hooks
    • react-query 같은 data api
    • rxjs 같은 reactiv library

 

고려 사항

  • Elagant Code Base 유지
    • Code Convention
    • TSLint
    • Typescript
  • Unit Test Code 
    • commit 전에 Unit Test 수행
  • Loose Coupling을 위한 UI Biz Feature, UI Componnet, Library 관계 정립

 

 

to be continue...

posted by Peter Note
2021. 8. 5. 09:29 [App FullStacker]/Architecture

NX + Angular 기반 개발시 엔터프라이즈 애플리케이션 개발방법에 대한 글을 정리한다. Micro Frontend 개발방법이라기 보다는 애플리케이션을 개발하며 지속적으로 확장할 필요가 있을 때 관심사들을 어떻게 분리하여(Bounded Context) 개발할 수 있을지 보여준다.

 

 

https://indepth.dev/the-shell-library-patterns-with-nx-and-monorepo-architectures/

 

Variations of the Shell Library pattern with Nx & Angular Monorepos

Compare shell library patterns to pick the right one for your Angular monorepo. Nrwl feature shell, Manfred Steyer shell and Composite shell libraries.

indepth.dev

https://connect.nrwl.io/app/books/enterprise-angular-monorepo-patterns

 

Nrwl Connect

Directly leverage Nrwl's Angular expertise. Ship more features in less time.

connect.nrwl.io

 

 

nx.dev를 이용한 라이브러리 관리

Nx는 하나의 레파지토리에서 멀티 애플리케이션과 라이브러리를 개발할 수 있는 환경을 제공한다. @angular/cli를 확장한 형태로 Angular, React, NodeJS를 함께 개발하고 별개로 번들링 배포할 수 있다. Nx 설치는 이전 글을 참조한다.

 

workspace/libs 하위로 폴더를 구분하여 업무를 개발할 수 있고, workspace/apps 의 애플리케이션에서 libs의 내용을 통합하여 사용하는 방법을 제시한다. 

  • application
    • application 안에서 sub-domain을 import하거나, routing 한다.
    • sub-domain을 통합하는 방식이다. 
    • 애플리케이션, web, desktop(using Eletron), mobile(Ionic)을 이용하여 sub-domain을 사용한다.
  • sub-domain
    • 업무 도메인이다.
    • sub-domain은 Bounded Context 이다.
    • Bounded Context는 업무 논리적으로 분리될 수 있는 단위이다.
    •  feature-shell
      • 업무 도메인의 페이지를 통합하고 routing을 설정한다. 
      • sub-domain 안에 1개 존재한다. 
      • shell은 sub-domain을 접근토록 하는 entry point 모듈이다.
      • shell orachestrate the routes of "Bounded Context"
    • feature-<bizName>
      • 일반적으로 웹 화면(Page) 하나라고 정의하자.
      • sub-domain 안에는 feature-<bizName>가 여러개 존재할 수 있다.

 

Nx applications과 libraries 폴더 구조

NX Workspace 의 apps, libs 폴더 구성 예

 

Feature-shell에서 sub-doamin(Bounded Context) 라우팅하기

shell에서 Bounded Context 즉, sub-domain을 routing한다.

 

애플리케이션에서 Feature-shell 라우팅하기. 애플리케이션에서 feature-shell을 조합하여 사용한다.

애플리케이션에서 feature-shell  라우팅

 

 

NX에서 Library 분류 및 개발 방법

nx workspace의 libs/ 밑으로 라이브러리를 개발할 때 다음과 같이 분류하여 개발한다. 

  • Feature-Shell library
    • 업무 sub-domain을 통합하여 애플리케이션에서 사용할 수 있도록 한다.
  • UI library
    • presentation component로써 버튼, 모달같은 업무 로직이 없는 컴포넌트들
  • Shared library
    • Data Access library
      • REST API 서비스
      • WebSocket 통신
      • Ngrx/Store 기반 State 관리
    • Utils library
      • 유틸리티 서비스

 

NX에서 tag 구분 keywords

  • scope
    • sub-domain 
    • grouping folder 
  • type
    • feature-shell
    • ui
    • data-cess
  • platform
    • desktop
    • mobile

예) scope:shared, type:feature,platform:desktop

 

각 라이브러리의 특성에 맞게 libs/ 밑으로 폴더/폴더 형태의 grouping folder 구조로 개발한다. 

예로 booking 폴더 밑에 feature-shell libary가 존재

 

feature-shell의 모듈 명칭은 "feature-shell" 으로 한다.

nx를 이용하여 library 생성시 옵션들

  • --directory=<grouping folder name>
  • --routing: forChild routing 자동 설정
  • --lazy: application에 lazy routing 자동 설정, 반드시 parent-module을 지정함.
  • --parent-module=<application module path and file name>: lazy 옵션 설정시 lazy routing 설정할 module 위치를 지정함.
  • --tags=<tag>,<tag>: nx.json에 포함됨, nx.json에서 직접 수정 및 추가 가능 예) --tags=scope:shared,type:feature
  • --publishable: 특별히 npm registry에 publish 할 수 있는 library를 만들고 싶을 때 사용한다. ng-packagr 기반이다.

//Command
nx g lib feature-<sub-domain name> --routing --directory=<grouping folder name> --lazy --parent-module=apps/<application>/src/app/app.module.ts

//Sample
$ nx g lib feature-shell --routing --directory=admin --lazy --parent-module=apps/app-container/src/app/app.module.ts
? Which stylesheet format would you like to use? SASS(.scss)  [ http://sass-lang.com   ]
CREATE libs/admin/feature-shell/README.md (162 bytes)
CREATE libs/admin/feature-shell/tsconfig.lib.json (459 bytes)
CREATE libs/admin/feature-shell/tslint.json (276 bytes)
CREATE libs/admin/feature-shell/src/index.ts (50 bytes)
CREATE libs/admin/feature-shell/src/lib/admin-feature-shell.module.ts (367 bytes)
CREATE libs/admin/feature-shell/src/lib/admin-feature-shell.module.spec.ts (432 bytes)
CREATE libs/admin/feature-shell/tsconfig.json (138 bytes)
CREATE libs/admin/feature-shell/jest.config.js (405 bytes)
CREATE libs/admin/feature-shell/tsconfig.spec.json (258 bytes)
CREATE libs/admin/feature-shell/src/test-setup.ts (30 bytes)
UPDATE angular.json (15807 bytes)
UPDATE nx.json (1098 bytes)
UPDATE tsconfig.json (863 bytes)

 

 

품질 및 일관성 유지 방법

  • Single Version을 apps와 libs에 적용한다. 
    • package.json에서 관리한다. 
  • Code Formatting은 prettier를 사용한다.
  • Library Dependencies 관리
    • library 순환 참조는 안된다. 
    • library가 application을 import하거나 의존하지 않는다. 
    • npm run dep-graph 또는 npm run affected:dep-graph 수행하면 라이브러리 의존관계도가 자동생성되어 웹브라우져에서 확인할 수 있다.
    • MS Code Editor의 Plugin으로 "Nx Console"을 설치하면 Editor에서 바로 수행해 볼 수도 있다.

의존성 그래프를 웹에서 확인

 

Nx Console이 Left Menu 하단에 N> 아이콘으로 있고, 명령어 목록을 클릭한다.

 

 

Nx Schematics 관리

Nx에 Built-in 된 schematics

  • ngrx: Ngrx/store 기반으로 reducer, action, selector, facade를 자동 생성함
  • node: Express node application의 경우 
  • jest: unit test
  • cypress: E2E test
  • etc..

Custom schematic 만들기

  • 개발자들이 샘플코드를 copy & paste하지 않고 schematic을 통해 업무 개발을 위한 파일을 생성토록할 때 사용한다.
  • nx g workspace-schematic <custom-schematic-name> 명령을 수행하면 workspace/tools/ 폴더 밑에 schematic파일이 생성됨.
    • 예제 파일 생성: nx g workspace-schematic data-access-lib 수행
    • 예제 파일 수행: npm run workspace-schematic data-access-lib <name>

index.ts에서 개발함

//index.ts에 ngrx를 추가로 적용한 예제

import { chain, externalSchematic, Rule } from '@angular-devkit/schematics';
import * as path from 'path';

export default function (schema: any): Rule {
    if (!schema.name.startsWith('data-access-')) {
        throw new Error(`Data-access lib names should start with 'data-access-'`);
    }
    const stateName = schema.name.replace('data-access-', '');
    return chain([
        externalSchematic('@nrwl/workspace', 'lib', {
            name: schema.name,
            tags: 'type:data-access',
        }),
        externalSchematic('@nrwl/schematics', 'ngrx', {
            name: stateName,
            module: path.join('libs', schema.name, 'src', 'lib', `${schema.name}.module.ts`),
        }),
    ]);
}

 

관련소스: github.com/ysyun/blog-5730-micro-demo.git

 

 

참조

https://indepth.dev/the-shell-library-patterns-with-nx-and-monorepo-architectures/

 

Variations of the Shell Library pattern with Nx & Angular Monorepos

Compare shell library patterns to pick the right one for your Angular monorepo. Nrwl feature shell, Manfred Steyer shell and Composite shell libraries.

indepth.dev

NX에서 Eletron, Ionic, NativeScript 통합 개발하기

https://nstudio.io/xplat/fundamentals/architecture

 

nstudio | xplat xplat/fundamentals/architecture - cross platform tools for Nx workspaces

Passionate about implementing creative solutions for you. Technology, Consulting, Audio/Video Production. Web/Mobile apps, Angular/NativeScript plugins, Vue/NativeScript, product development, team training and help with project features/objectives.

nstudio.io

 

posted by Peter Note
2021. 8. 5. 09:28 Angular/Concept

Nx.dev 기반개발에서 Angular 모듈을 Router기반으로 Lazy Loading하기와 Router를 사용하지 않고 Lazy Loading하는 방법에 대해 알아보자. 알단 Lazy Loading 설정을 하게되면 Angular 컴파일시에 자동으로 모듈단위 Code Splitting하여 번들링한다. 따라서 필요한 시점에 필요한 파일만 다운로드를 받기때문에  TTI(Time ot interactive)시간 즉, 애플리케이션 응답성능을 높일 수 있다. 참고로 Webpack 5 (핸재 beta) 에서 Federation Module 개념을 구현하였는데, Angular에서 이와 비슷한 기능을 Angular Module단위 개발로 제공한다.

 

NX 환경 설정

  • NodeJS v12.* 사용
  • @angular/cli와 @nwrl/ci 설치
  • create-nx-workspace 이용하여 nx 기반 환경 생성
  • yarn통한 node modules 설치
// NVM 설치 (https://github.com/nvm-sh/nvm)
$ nvm install 12.16.2
$ nvm alias default 12.16.2
$ nvm use 12.16.2

// 최신버전들 설치
$ npm i -g @angular/cli@latest
$ npm i -g @nrwl/cli@latest
$ npm i -g yarn@latest
$ npm install typescript@3.8.3 --save-dev --save-exact

// create-nx-workspace
$ npx create-nx-workspace@latest
  ? Worspace name   demo
  ...
$ cd demo
$ yarn install

 

 

Router 를 통한 Lazy Loading

  • nx 명령을 통해 Lazy loading할 library를 생성한다.
  • nx 명령을 통해 Lazy loading되어 보여질 component를 생성한다.
  • libs/lazyview/src/lib/lazyview.module.ts 에 RouterModule.forChild 설정
  • lazyview모듈을 로딩하는 "app-container" 애플리케이션의 tsconfig.app.json에 "include" path 설정
  • apps/app-container/src/app/app-routing.module.ts 안에 loadChildren 을 통해 lazyview 모듈을 import 한다.

libs/lazyview 밑에 라이브러리 생성

// 1) libs/lazyview 라이브러리 생성 - 라이브러리는 모듈단위
$ nx g lib --routing --lazy lazyview


// 2) 컴포넌트 생성
$ nx g c lazypage --project=lazyview


// 3) libs/lazyview/src/lib/lazyview.module.ts에 forChild 설정
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { LazypageComponent } from './lazypage/lazypage.component';
@NgModule({
    imports: [
        CommonModule,

        RouterModule.forChild([
            { path: '', pathMatch: 'full', component: LazypageComponent }
        ]),
    ],
    declarations: [LazypageComponent],
})
export class LazyviewModule {}


// 4) apps/app-container/src/tsconfig.app.json 에 include/exclude 설정
{
    "extends": "./tsconfig.json",
    "compilerOptions": {
        "outDir": "../../dist/out-tsc",
        "types": []
    },
    "include": ["**/*.ts", "../../libs/lazyview/src/index.ts"],
    "exclude": ["src/test-setup.ts", "**/*.spec.ts", "src/environments/*.ts"]
}


// 5) app-routing.module.ts 에 loadChildren으로 '@micro-demo/lazyview' 설정
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
    { path: '', pathMatch: 'full', redirectTo: '/welcome' },
    { path: 'lazyview', loadChildren: () => import('@micro-demo/lazyview').then((m) => m.LazyviewModule) },
    { path: 'welcome', loadChildren: () => import('./pages/welcome/welcome.module').then((m) => m.WelcomeModule) },
];
@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule],
})
export class AppRoutingModule {}

"nx serve"  수행한 후 "http://localhost:4200/lazyview" 로 호출한다.

micro-demo-lazyview.js 파일을 code splitt되어 호출시 다운로드된다.

소스: https://github.com/ysyun/blog-5730-micro-demo

 

ysyun/blog-5730-micro-demo

https://mobicon.tistory.com/573 블로그 소스. Contribute to ysyun/blog-5730-micro-demo development by creating an account on GitHub.

github.com

 

 

 

참조

https://juristr.com/blog/2019/04/state-lazy-loading-components-angular/#manual-lazy-loading-of-modules

 

Lazy load Angular Components

Or better..how to lazy load Angular Modules. Learn about the state of lazy loading and lazy loading on steroids with Angular Elements

juristr.com

https://juristr.com/blog/2019/10/lazyload-module-ivy-viewengine/

 

Manually Lazy Load an Angular Module with ViewEngine and Ivy

Find out how to lazy load an NgModule compatible with ViewEngine and Ivy

juristr.com

https://juristr.com/blog/2019/08/ngperf-route-level-code-splitting/

 

Angular Performance: Route Level Code Splitting

Learn about route-level code splitting and lazy loading with Angular

juristr.com

https://medium.com/angular-in-depth/lazy-load-components-in-angular-596357ab05d8

 

Lazy load components in Angular

Lazy load Angular components with Ivy and Angular 9

medium.com

https://indepth.dev/webpack-5-module-federation-a-game-changer-in-javascript-architecture/

 

Webpack 5 Federation. A Game-changer to Javascript architecture.

Module federation — The Javascript equivalent of what Apollo did with GraphQL. Multiple Webpack builds work together, sharing the dependency graph at runtime. Multiple bundles working as an SPA

indepth.dev

 

posted by Peter Note
2021. 8. 5. 09:28 Angular/Concept

Angular router의 loadChildren 을 사용하지 않고 Lazy loading하는 방법을 알아본다. 

 

Angular Module 에 대한 Lazy Loading

Angular의 Lazy Loading 방식

  • NgModule 단위로 lazy loading한다.
  • Angular v8부터 import(<module path>).then(mod => mod.<moduleName>) 방식으로 로딩한다. import 구문을 사용하면 자동으로 code splitting이 된다.
  • Angular v7버전 이하는 ViewEngine 이라는 Template엔진을 사용했고, 8부터 선택적으로 9부터는 디폴트로 Ivy를 Template엔진으로 사용한다.
  • Module을 컴파일/로딩하고 Component를 인스턴스화하기위해 ComponentFactoryResolver를 사용하고, ViewContainer에 인스턴스화된 컴포넌트를 렌더링한다.

우선 manual lazy loading을 위한 모듈과 컴포넌트를 생성한다. 

// 라이브러리 생성, nx 또는 ng 둘 다 사용가능하다
$ nx g lib manuallazy

// 컴포넌트 생성
$ nx g c manual --project=manuallazy
CREATE libs/manuallazy/src/lib/manual/manual.component.scss (0 bytes)
CREATE libs/manuallazy/src/lib/manual/manual.component.html (21 bytes)
CREATE libs/manuallazy/src/lib/manual/manual.component.spec.ts (628 bytes)
CREATE libs/manuallazy/src/lib/manual/manual.component.ts (283 bytes)
UPDATE libs/manuallazy/src/lib/manuallazy.module.ts (266 bytes)

lazyload하는 service를 "app-container" 애플리케이션에 생성한다.

$ nx g s lazy-loader --project=app-container
CREATE apps/app-container/src/app/lazy-loader.service.spec.ts (378 bytes)
CREATE apps/app-container/src/app/lazy-loader.service.ts (139 bytes)

서비스에 loadModule 메소드를 추가한다. 8전부터 Ivy 사용만 대응한다. 7버전 이하는 NgModuleFactoryLoader를 사용한다. (참조)

import { Compiler, Injectable, Injector, NgModuleFactory, Type } from '@angular/core';

@Injectable({
    providedIn: 'root',
})
export class LazyLoaderService {
    constructor(private compiler: Compiler, private injector: Injector) {}

    loadModule(path: any) {
        (path() as Promise<Type<any>>)
            .then((elementModule) => {
                    try {
                        return this.compiler.compileModuleAsync(elementModule);
                    } catch (err) {
                        throw err;
                    }
            })
            .then((moduleFactory) => {
                try {
                    const elementModuleRef = moduleFactory.create(this.injector);
                    const moduleInstance = elementModuleRef.instance;

                    // instantiate component dynamically.
                } catch (err) {
                    throw err;
                }
            });
    }
}

 

 

컴포넌트 인스턴스화 및 ViewContainer에 렌더링하기

다음 순서로 컴포넌트를 인스턴스화 한다. 

  • ManuallazyComponent를 manuallazy 라이브러리에 생성한다.
  • MauallazyModule에 초기 인스턴스화할 Component를 리턴한다.
  • LazyLoaderService에서 ComponentFactoryResolver를 통해 인스턴스화하고 ViewContainer에 할당한다. 
  • "app-container" 애플리케이션의 tsconfig.app.json에 "include"에 MauallazyModule의 index.ts를 추가 설정한다. 
  • "app-container" 애플리케이션에서 LazyLoaderService를 loadModule을 호출한다. 

ManulComponent를 생성한다. 

$ ng g c manual --project=manuallazy

// libs/manuallazy/lib/src/manual/manual.component.html
<p>manual lazy component</p>

 

ManuallazyModule에 ComponentType을 리턴한다.

// libs/manuallazy/src/lib/manuallazy.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ManualComponent } from './manual/manual.component';

@NgModule({
    imports: [CommonModule],
    declarations: [ManualComponent],
})
export class ManuallazyModule {
    bootstrapComponent(): any {
        return ManualComponent;
    }
}

 

LazyLoaderService에 컴포넌트를 로딩하는 "addComponent" 메소드을 추가한다. 

// apps/app-container/src/app/lazy-loader.service.ts
import {
    Compiler,
    Injectable,
    Injector,
    NgModuleFactory,
    Type,
    ViewContainerRef,
    ComponentRef,
    ComponentFactory,
    ComponentFactoryResolver,
} from '@angular/core';

@Injectable({
    providedIn: 'root',
})
export class LazyLoaderService {
    constructor(private compiler: Compiler, private injector: Injector, private resolver: ComponentFactoryResolver) {}

    loadModule(path: any, viewContainer: ViewContainerRef): void {
        (path() as Promise<Type<any>>)
            .then((elementModule) => {
                try {
                    return this.compiler.compileModuleAsync(elementModule);
                } catch (err) {
                    throw err;
                }
            })
            .then((moduleFactory) => {
                try {
                    const elementModuleRef = moduleFactory.create(this.injector);
                    const moduleInstance = elementModuleRef.instance;

                    // instantiate component dynamically.
                    this.addComponent(moduleInstance.bootstrapComponent(), viewContainer);
                } catch (err) {
                    throw err;
                }
            });
    }

    addComponent(cmpType: any, viewContainer: ViewContainerRef): ComponentRef<any> {
        const factory: ComponentFactory<any> = this.resolver.resolveComponentFactory(cmpType);
        let cmp: ComponentRef<any>;
        if (viewContainer) {
            viewContainer.clear();
            cmp = viewContainer.createComponent(factory);
        }
        return cmp;
    }
}

 

"app-container" 애플리케이션의 tsconfig.app.json 에 MauallazyModule의 index.ts 를 추가 설정한다.

// apps/app-container/src/tsconfig.app.json 
{
    "extends": "./tsconfig.json",
    "compilerOptions": {
        "outDir": "../../dist/out-tsc",
        "types": []
    },
    "include": ["**/*.ts", "../../libs/lazyview/src/index.ts", "../../libs/manuallazy/src/index.ts"],
    "exclude": ["src/test-setup.ts", "**/*.spec.ts", "src/environments/*.ts"]
}

 

"app-container" 애플리케이션에서 ManullazyModule을 호출한다. 

// apps/app-container/src/app/app.component.html 일부분
<li nz-menu-item nzMatchRouter>
  <a (click)="manualLazyLoading($event)">Monitor</a>
</li>
... 중략 ...
<div class="inner-content">
   <router-outlet></router-outlet>
   <div style="padding-top: 100px;" #InnerContent></div>
</div>


// apps/app-container/src/app/app.component.ts
import { LazyLoaderService } from './lazy-loader.service';
import { Component, ViewChild, ViewContainerRef } from '@angular/core';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss'],
})
export class AppComponent {
    isCollapsed = false;
    @ViewChild('InnerContent', { read: ViewContainerRef }) innerContent: ViewContainerRef;

    constructor(private readonly lazyLoaderServie: LazyLoaderService) {}

    manualLazyLoading(event: MouseEvent): void {
        // import 할 때 위치투명하게 nx library를 호출한다.
        const manualLoad = () => import('@micro-demo/manuallazy').then((m) => m.ManuallazyModule);
        this.lazyLoaderServie.loadModule(manualLoad, this.innerContent);
    }
}

"Monitor" 메뉴를 클릭하면 "micro-demo-mauallazy.js"파일이 다운로드되고 ViewContainer위치에 컴퍼넌트를 렌더링한다

구현 소스: https://github.com/ysyun/blog-5730-micro-demo

 

ysyun/blog-5730-micro-demo

https://mobicon.tistory.com/573 블로그 소스. Contribute to ysyun/blog-5730-micro-demo development by creating an account on GitHub.

github.com

 

라우팅으로 통한 Angular 모듈 Lazy loading과 메뉴얼 Lazy loading 방식의 단점

  • 둘다 tsconfig.app.json과 같은 사전 설정작업이 필요하다.
  • "app-container"라는 Lazy loading된 모듈을 사용하는 애플리케이션과 함께 컴파일이 되어야 한다.

장점은 import구문을 통해 자동 Code Splitting 기능이다. 위이 단점을 해결하기 위해서 Web Components를 지원하는 @angular/elements를 사용한다. 이에 대한 글은 다음을 참조한다. 

https://mobicon.tistory.com/573

 

[Micro Frontend] Web Components 기반 마이크로 프론앤드

Angular v6부터 Web Components에 대한 지원으로 @angular/elements 기능이 추가되어 Custom HTML Tag을 만들 수 있도록 지원한다. 본 글은 해당 사이트의 글을 Nx.dev 환경과 통합하여 개발하는 과정을 설명한다...

mobicon.tistory.com

 

참조

https://juristr.com/blog/2019/10/lazyload-module-ivy-viewengine/

 

Manually Lazy Load an Angular Module with ViewEngine and Ivy

Find out how to lazy load an NgModule compatible with ViewEngine and Ivy

juristr.com

https://juristr.com/blog/2017/07/ng2-dynamic-tab-component/

 

Create a dynamic tab component with Angular

Learn about advanced topics such as dynamic components, ComponentFactoryResolver, ViewContainerRef, ngTemplateOutlet and much more...

juristr.com

https://juristr.com/blog/2019/04/state-lazy-loading-components-angular/#manual-lazy-loading-of-modules

 

Lazy load Angular Components

Or better..how to lazy load Angular Modules. Learn about the state of lazy loading and lazy loading on steroids with Angular Elements

juristr.com

 

posted by Peter Note