블로그 이미지
윤영식
Full Stacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

2021. 8. 5. 09:29 React/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 윤영식
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 윤영식
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 윤영식
2020. 5. 22. 17:15 Angular/Concept

Angular에서 schematic은 복잡한 로직을 지원하는 템플릿 기반의 코드 생성기이다. @angular/cli에서 코드 생성은 schematics를 사용하는 것이다. 해당 패키지는 @schematics/angular 이다. 본글에서는 나만의 schematic을 만들어 본다. 하기 문서를 참조하여 NX 기반으로 개발한다.

 

https://medium.com/@tomastrajan/total-guide-to-custom-angular-schematics-5c50cf90cdb4

 

Total Guide To Custom Angular Schematics

Schematics are great! They enable us to achieve more in shorter amount of time! But most importantly, we can think less about mundane…

medium.com

 

Schematic 생성

 NX환경이 아니라면 @angular-devkit/schematics-cli 를 글로벌로 설치해서 schematics 명령을 사용해 초기 schematic파일을 자동 생성할 수 있다. 그러나 NX를 사용하면 @angular-devkit/schematics-cli설치 없이 nx명령으로 초기 파일을 생성할 수 있다. NX의 schematics을 사용하겠다는 정의는 루트의 angular.json에 정의되어 있다. 

    ...
    "schematics": {
        "@nrwl/angular:application": {
            "unitTestRunner": "jest",
            "e2eTestRunner": "cypress"
        },
        "@nrwl/angular:library": {
            "unitTestRunner": "jest"
        }
    },
    ...

 

명령어 예

//schematic-cli 설치
$ npm install -g @angular-devkit/schematics-cli

//schematics 명령 사용 경우
$ schematics blank hello


//NX 환경일 경우
$ nx g workspace-schematic hello
CREATE tools/schematics/hello/index.ts (250 bytes)
CREATE tools/schematics/hello/schema.json (348 bytes)

nx 명령을 생성된 schematic 파일은 tools/hello 폴더 밑에 존재한다. schema.json은 hello schematic의 정의 파일이고, index.ts가 동작하는 시작 파일이다. 

 

 

Schematic 키워드

  • Tree: virtual file system을 표현한다. base 라는 이미 존재하는 파일 묶음과 staging area 라는 base에서 변경될 것들 목록으로 구성된다. base 는 변경할 수 없고, staging area 만 변경가능 하다.
  • Rule: Tree를 받아서 변경점을 적용하고 새로운 Tree를 반환하는 함수이다. 메인 파일인 index.ts에 해당 함수를 정의한다.
  • Action: Action을 변환(Transformation)으로 표현되고, action 타입은 Create, Rename, Overwrite, Delete 4가지 유형이 있다.
  • 참조 원문

실제 사용자 정의 작업을 해야하는 Rule의 정의이다. 

export declare type Rule = (tree: Tree, context: SchematicContext) => Tree | Observable<Tree> | Rule | Promise<void> | Promise<Rule> | void;

 

모든 Schematic은 context안에서 구동되어야 하고, SchematicContext 객체로 표현된다. nx가 아닌 schematics 명령으로 생성된 index.ts 의 코드를 보자.

  • hello함수는 RuleFactory로써 Rule을 반환하는 고차함수(a higher-order function)를 반환한다.
  • hello함수안에서 나의 template이 어떻게 합쳐지고, 변경되어 지는 것인지 정의한다.
//schematic 명령으로 생성된 index.ts 내역

import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';

// You don't have to export the function as default. You can also have more than one rule factory
// per file.
export function hello(_options: any): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    return tree;
  };
}

nx로 생성된 schematic 의 index.ts는 약간 틀린 구조이다. 

  • default함수로 RuleFactory를 정의한다.
  • Rule을 반환한다. 
import { chain, externalSchematic, Rule } from '@angular-devkit/schematics';

export default function (schema: any): Rule {
    return chain([
        externalSchematic('@nrwl/workspace', 'lib', {
            name: schema.name,
        }),
    ]);
}

 

 

Schematic 옵션 설정

  • nx로 생성했을 경우: schema.json 파일에 옵션을 설정한다.
  • schema.json의 properties 는 Schematic 수행시 prompt 로 작동하여 사용자 입력값을 처리한다. 
  • properties안에 사용자로 부터 입력받고자하는 옵션을 설정한다. 입력받을 수 있는 옵션은 schema.json에 정의되어 있다.
  • schema.json 전체 명세서

 

//nx 명령으로 생성된 schema.json 내역
{
    "$schema": "http://json-schema.org/schema",
    "id": "hello",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Library name",
            "$default": {
                "$source": "argv",
                "index": 0
            }
        }
    },
    "required": ["name"]
}

 

 

옵션의 Input "type"  

  • confirmation: boolean 옵션
  • input: text 또는 number 옵션
  • list: 미리 정의한 목록을 선택

schema.json에 정의하는 type

"style"을 정의한 예

//schema.json 예
"style": {
  "description": "The file extension or preprocessor to use for style files.",
  "type": "string",
  "default": "css",
  "enum": [
    "css",
    "scss",
    "sass",
    "less",
    "styl"
  ],
  "x-prompt": {
    "message": "Which stylesheet format would you like to use?",
    "type": "list",
    "items": [
      { "value": "css",  "label": "CSS" },
      { "value": "scss", "label": "SCSS   [ https://sass-lang.com/documentation/syntax#scss                ]" },
      { "value": "sass", "label": "Sass   [ https://sass-lang.com/documentation/syntax#the-indented-syntax ]" },
      { "value": "less", "label": "Less   [ http://lesscss.org                                             ]" },
      { "value": "styl", "label": "Stylus [ http://stylus-lang.com                                         ]" }
    ]
  },
},

x-prompt 필드를 통해 긴 입력 문구 조합이 가능하다. 

//x-prompt schema
{
    "oneOf": [
        { "type": "string" },
        {
            "type": "object",
            "properties": {
                "type": { "type": "string" },
                "message": { "type": "string" },
                "items": {
                    "type": "array",
                    "items": {
                        "oneOf": [
                            { "type": "string" },
                            {
                                "type": "object",
                                "properties": {
                                    "label": { "type": "string" },
                                    "value": { }
                                },
                                "required": [ "value" ]
                            }
                        ]
                    }
                }
            },
            "required": [ "message" ]
        }
    ]
}

 

 

Schematic 실행

Schematics CLI로 생성했을 경우

  • index.ts를 컴파일한다. 
  • 명령어: schematics <path-to-schematics-project>:<schematics-name> --<required-option>=<value>
//schematics 으로 생성한 폴더로 이동후 컴파일 수행
$ cd hello
$ npm run build

//schematics 수행
$ schematics .:hello
Nothing to be done.

nx 명령으로 생성했을 경우

  • 명령어: npm run workspace-schematic <customized schematic name> <option>
$ npm run workspace-schematic hello hi

> micro-demo@0.0.0 workspace-schematic /Users/dowonyun/mobicon/projects/bistel/src/2019/micro-demo
> nx workspace-schematic "hello" "hi"

>  NX  Executing your local schematic: hello

CREATE libs/hi/tslint.json (91 bytes)
CREATE libs/hi/README.md (158 bytes)
CREATE libs/hi/tsconfig.json (135 bytes)
CREATE libs/hi/tsconfig.lib.json (190 bytes)
CREATE libs/hi/src/index.ts (26 bytes)
CREATE libs/hi/src/lib/hi.ts (0 bytes)
CREATE libs/hi/jest.config.js (246 bytes)
CREATE libs/hi/tsconfig.spec.json (269 bytes)
UPDATE tsconfig.json (731 bytes)
UPDATE angular.json (16683 bytes)

 

 

참조

Angular Schematic 개념: https://angular.io/guide/schematics

 

Angular

 

angular.io

스키마 명세: https://github.com/angular/angular-cli/blob/7.0.x/packages/schematics/angular/application/schema.json

 

angular/angular-cli

CLI tool for Angular. Contribute to angular/angular-cli development by creating an account on GitHub.

github.com

 

posted by 윤영식
2019. 4. 10. 16:35 Angular/Architecture

두번째 Multi Application에 대한 Plugin 방식을 구성 테스트해 보자.

 

 

Library기반 Plugin 파일 만들기

플랫폼에 있는 플러그인이 아니라 플랫폼과 별개의 서비스에서 운영되는 플로그인을 플랫폼상에 렌더링하기 위해서는 UMD(Universal Module Definition) 방식으로 플러그인이 번들링 되어야 한다. UMD에 대해서는 본 블로그의 글을 참조한다.

$ ng g library plugin2 --publishable

 

plugin2 로 별도 번들링이 가능한 라이브러리 파일을 생성한다. 최종 npm scope를 jm으로 주었기 때문에 npm으로 설치했을 때 node_modules최종 명칭은 @jm/plugin2가 된다. libs/plugins/package.json의 설정 참조. build가 되는지 실행해 본다.

$ ng build plugin2

dist/libs/plugin2에 npm repository로 publish할 수 있는 형태의 다양한 포멧으로 번들링이 되었다. 해당 번들링은 ng-packagr를 기본으로 한 Angular Package Format에 따른다.

plugin2 라이브러리에 plugin2라는 이름의 컴포넌트를 생성하고 여기에 ngx-echart를 사용하는 샘플을 넣어보자.

// 컴포넌트 생성
$ ng g component plugin2 --project=plugin2

// echart 관련 라이브러리 및 echart angular wrapper 설치
$ npm i -S echarts ngx-echarts
$ npm i -D @types/echarts

plugin2/plugin2.component.html과 .ts에 ngx-echart의 예제 내용을 첨부한다. Plugin2Module에 bootstrap에 Plugin2Component를 설정한다.

// plugin2.component.html
<div echarts [options]="options" class="demo-chart"></div>

// plugin2.component.ts 
options 내용 설정: https://xieziyu.github.io/ngx-echarts/#/usage/basic 참조

// plugin2.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NgxEchartsModule } from 'ngx-echarts';

import { Plugin2Component } from './plugin2/plugin2.component';

@NgModule({
  declarations: [Plugin2Component],
  imports: [
    CommonModule,
    NgxEchartsModule
  ],
  exports: [
    Plugin2Component
  ],
  bootstrap: [
    Plugin2Component
  ]
})
export class Plugin2Module {}

plugin2 컴포넌트를 사용하는 별도 애플리케이션을 생성해 보자.

 

 

별도 애플리케이션 생성하여 Plugin2 테스트 하기

Multi Application을 가정하여 Plugin2를 사용하는 App2 애플리케이션을 생성하여 Plugin2가 잘 나오는지 테스트 한다.

$ ng g application app2

app2 가 실행할 때 4300 port 를 사용토록 angular.json에 포트설정을 추가한다.

"serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "app2:build",
            "port": 4300
          },
 ... 중략 ...

 $ ng s app2 

apps/app2/src/app/app.module.ts 와 app.component.html을 수정한다. import 할 때"@jm/plugin2"로 사용함을 주의한다.

// app.component.html
<jm-plugin2></jm-plugin2>

// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { Plugin2Module } from '@jm/plugin2';

import { AppComponent } from './app.component';

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

"ng s app2" 실행하고 http://localhost:4300 으로 호출하면 샘플 echart가 보인다.

 

 

Plugin2 번들링 하기

app2 애플케이션에 plugin2 컴포넌트가 잘 나오는 것을 확인 하였다. plugin2 번들링을 위해 한가지 설정 옵션을 변경하여 다시 번들링한다. build시에 에러가 발생하면 ng-packagr v5.0.1 이상을 설치한다.

//libs/plugin2/src/tsconfig.lib.json 에서 skipTemplateCodegen을 false로 변경한다
  "angularCompilerOptions": {
    "annotateForClosureCompiler": true,
    "skipTemplateCodegen": false,

// ng-packagr 관련 업데이트 
$ npm update ng-packagr
$ npm i -D tsickle

//다시 번들링한다. 
$ ng build plugin2
Building Angular Package
Building entry point '@jm/plugin2'
Compiling TypeScript sources through ngc
Bundling to FESM2015
Bundling to FESM5
Bundling to UMD
No name was provided for external module 'ngx-echarts' in output.globals – guessing 'ngxEcharts'
Minifying UMD bundle
Copying declaration files
Writing package metadata
Removing scripts section in package.json as it's considered a potential security vulnerability.
Built @jm/plugin2
Built Angular Package!
 - from: /Users/dowonyun/prototyping/jamong/libs/plugin2

skipTemplateCodegen를 false로 하면 ngfactory파일까지 생성되고 해당 파일은 Angular메타정보를 해석해 놓은 파일로 동적으로 파일을 로딩할 때 사용할 것이다. 다음으로 bundle 된파일을 다시 하나의 파일로 합치기 위해 package.json에 script를 등록한다.

// package.json
"scripts": {
   ... 중략 ...
   "build:plugin2": "rollup dist/libs/plugin2/esm2015/lib/plugin2.module.ngfactory.js --file dist/apps/api/plugin2.js --format umd --name plugin2"
}

// bundling 
$ npm run build:plugin2

dist/apps/api/plugin2.js 파일이 생성된다. 또는 ROOT에 rollup.config.js 파일을 생성하여 관리할 수도 있다.

//rollup.config.js
export default [
  {
    input: 'dist/libs/plugin2/esm2015/lib/plugin2.module.ngfactory.js',
    output: [
      {
        name: 'plugin2',
        file: 'dist/apps/api/plugin2.js',
        format: 'umd'
      }
    ]
  }
];

//build 명령
$ rollup -c

두가지의 명령을 수행한다.

  • ng build plugin2
  • npm run build:plugin2  또는 rollup.config.js를 설정하였다면 rollup -c

 

Plugin2 파일 API 서비스 추가

plugin2 파일을 동적으로 로딩하기 위해 다음과 같이 역할을 추가한다.

  • Dev Server는 Platform 서버 역할
  • API Server는 별개 애플리케이션 서버로 보고 Plugin-2 파일을 서비스하는 역할

이제 API Server에서 plugin2.js 파일을 읽어서 파일 내용을 전달하는 API를 추가한다. NetsJS는 NodeJS위에 구동되는 서버프레임워크로 Angular와 유사한 Syntax를 통해 MVC 패턴으로 개발한다.

  • GET으로 plugin2를 호출하면 파일을 읽는다.
  • 파일 내용을 return하면 끝!
// apps/api/src/app/app.controller.ts

import { Controller, Get } from "@nestjs/common";

import { Message } from "@jm/api-interface";
import { AppService } from "./app.service";

const path = require('path');
const fs = require('fs-extra');

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get("hello")
  getData(): Message {
    return this.appService.getData();
  }

  @Get("plugin2")
  getPlugin2() {
    const fileName = path.join(process.cwd(), 'dist/apps/api/plugin2.js');
    const plugin2 = fs.readFileSync(fileName, 'utf8');
    return plugin2;
  }
}

테스트를 해보자. API Server기본 context는 api로 http://localhost:3333/api/plugin2 로 호출한다. 번들링 파일 내용이 응답됨을 확인했다.

// API Server 기동
$ ng s api

 

 

원격 Plugin2을 Lazy Loading 하기

plugin2.js 파일을 다운로드 받아 로딩하기 위해 apps/jamong/src/app/app.component.html과 .ts에 로딩 구문을 추가한다.

// app.component.html
<button (click)="loadPlugin()">Load plugin</button>
<lazy-af *ngIf="plugin1Path" [moduleName]="plugin1Path"></lazy-af>

<p></p>

<button (click)="loadPlugin2()">Load plugin2</button>
<ng-template #Plugin></ng-template>

app.component.ts

  • plugin2.js 파일을 요청하기 위해 HttpClient를 사용
  • Plugin2ModuleNgFactory안의 Angular MetaData를 통해 관련 컴포넌트들 JIT 컴파일 수행
  • @herodevs/dynamic-af 패키지이 DynamicAFService 서비스를 injector받는다.
  • 이때 plugin2.js 소스내의 require하는 모듈을 위해 import * as from ''; 하여 아래 소스처럼 설정한다.
import { Component, ViewChild, ViewContainerRef, NgModuleFactory, Injector } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { DynamicAFService } from '@herodevs/dynamic-af';

import * as common from '@angular/common';
import * as commonHttp from '@angular/common/http';
import * as core from '@angular/core';
import * as router from '@angular/router';
import * as rxjs from 'rxjs';
import * as rxjsOperators from 'rxjs/operators';
import * as ngxEcharts from 'ngx-echarts';

@Component({
  selector: 'jm-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {

  @ViewChild('Plugin', {read: ViewContainerRef}) pluginVcr: ViewContainerRef;
  plugin1Path: string;
  loadedPlugins: any = {};

  constructor(
    private http: HttpClient,
    private injector: Injector,
    private lazyService: DynamicAFService
  ) { }

  loadPlugin() {
    this.plugin1Path = 'apps/jamong/src/app/plugin1/plugin1.module#Plugin1Module';
  }

  loadPlugin2() {
    const moduleFactory = this.loadedPlugins['api/plugin2'];
    if (!moduleFactory) {
      this.loadRemoteComponent();
    } else {
      this.lazyService.createAndAttachModuleAsync(moduleFactory, this.injector, { vcr: this.pluginVcr });
    }
  }

  private loadRemoteComponent() {
    let moduleFactory: NgModuleFactory<any>;
    this.http.get('api/plugin2', { responseType: 'text' })
      .pipe(
        catchError(this.handleError)
      ).subscribe((compiledSource: any) => {
        const exports = {};
        const modules = {
          '@angular/core': core,
          '@angular/common': common,
          '@angular/common/http': commonHttp,
          '@angular/router': router,
          'rxjs': rxjs,
          'rxjs/operators': rxjsOperators,
          'ngx-echarts': ngxEcharts
        };
        const require: any = (module) => modules[module];
        // tslint:disable-next-line: no-eval
        eval(compiledSource);
        moduleFactory = exports['Plugin2ModuleNgFactory'];
        this.loadedPlugins['api/plugin2'] = moduleFactory;
        this.lazyService.createAndAttachModuleAsync(moduleFactory, this.injector, { vcr: this.pluginVcr});
      });
  }

  private handleError(error: HttpErrorResponse) {
    if (error.error instanceof ErrorEvent) {
      // A client-side or network error occurred. Handle it accordingly.
      console.error('An error occurred:', error.error.message);
    } else {
      // The backend returned an unsuccessful response code.
      // The response body may contain clues as to what went wrong,
      console.error(`Backend returned code ${error.status}, body was: ${error.error}`);
    }
    return throwError(error);
  }
}

Load plugin2를 호출하였을 때 원격 처리 결과

위의 모든 소스는 GitHub에...

 

 

<참조>

- ngx-echarts

 

ngx-echarts demo

 

xieziyu.github.io

- Another Angular Plugin Example

 

iwnow/angular-plugin-example

Angular plugin with AOT and separate build. Contribute to iwnow/angular-plugin-example development by creating an account on GitHub.

github.com

- Rollup Configuration

 

Bundling Your JavaScript Library with Rollup

A step-by-step tutorial on how to bundle your JavaScript library using Rollup. You’ll also learn how to publish those bundles to NPM.

bagja.net

 

posted by 윤영식
2018. 12. 13. 16:39 Angular/Architecture

Angular 프로젝트를 위한 구조 설계 요건에 대해 알아본다. 


  - 프레임워크 선택 고려 사항

  - 개발 생산성과 유지 보수성을 높일 수 있는 방안

  - 참조 문서와 소스 

    + Angular Enterprise Architecture pattern 문서

    + 해당 문서의 예제 소스




프레임워크 선택 고려 사항


프레임워크는 잘 정돈된 놀이터와 같아 기구를 타고 놀면 된다. 하지만 라이브러리의 경우는 툴박스와 같아서 박스안에는 망치, 드라이버, 못같은 것만 있다. 그럼 놀 것을 어떻게 만들지는 도구를 사용하는 사용자의 몫이다. 현재 UI 프레임워크/라이브러리는 Angular, React, Vue 등이고 풀스택으로는 Meteor를 선택해서 사용하고 있다. Angular 프레임워크와 React UI 라이브러리 또는 Meteor 선택시 고려사항을 개인의견으로 간략히 정리한다. 


Angular Framework

  - 대규모 협업 팀에 유리: 대략 10명이상으로 HTML Publisher와 Javascript 개발자가 분리되어 있을 경우

  - JQuery가 low level의 javascript로 개발해 오던 개발자들이 접근하기에 수월하다고 판단함

  - Typescript, RxJS 반드시 알아야 한다.


React Library

  - HTML Publisher와 Javascript 개발을 같이하는 스타트업 또는 프론앤드 개발자에게 유리

  - 원하는 것들을 선택해 적용할 수 있다. 


Meteor

  - FullStack 으로서 javascript가지고 전부 개발하고 싶을 경우

  - 아이디어를 빠르게 구현하고 검증 받고 싶을 경우

  - UI는 Angular, React, Vue 등으로 선택해 사용할 수 있으나, React가 적합하다 판단



그러나 어떤 것을 선택하든 공통적으로 고려해야 하는 사항으로 애플리케이션 아키텍쳐이다. 이에 대한 좋은 영상이 있어 보기를 권한다. 




애플리케이션 아키텍쳐



위의 영상을 정리해 보면 다음과 같다. 

  - Layered Architecture 구성

  - 역할 분담

    + Module: 업무 최소 단위, 하나를 제거해도 다른 것에 영향을 주지 않는 단위, Module은 Sandbox만 알뿐이다. 

    + Sandbox: Module은 Sandbox를 통해 Application Core나 Base Library를 접근한다.

         - 일관성을 유지시킴

         - 시큐리티 가드 역할

         - 공통 Interface 역할

    + Application Core: Module을 관리(Control) 한다. 

         - Module의 Life Cycle을 담당한다.

         - Module 간 통신을 담당한다 (inter-module communication)

         - Module을 직접 가져다 쓰지 않고, Sandbox의 공통 API를 통해 명령을 내리고, 듣는다. 

         - General error handling

         - Extension을 통해 Application core기능을 확장한다.  

            

   + Extension 확장

       - Error handling

       - Ajax communication: 공통 Format (request/response) 통신, 서버 오류 관리

       - New module capabilities

       - General Utils

       - What you want

    + Base Library

       - 개발자들이 직접 base library를 건드리게 하지 말자. 나중에 바뀔 수 있다. 

       - Browser normalization

       - General purpose utilities: Paser/serializer, Object / DOM manipulation, Ajax Communication

       - low-level extension 제공: application extension처럼 base library를 확장한 것들을 제공한다. 

         


정리

  - Base Library만이 사용중인 브라우저를 알고 있다. 아키텍쳐의 다른 layer에서 알 필요가 없다.

  - Application Core만이 사용중인 Base Library를 알고 있다. 아키텍쳐의 다른 layer에서 알 필요가 없다. 

  - Module은 Sandbox의 존재외에는 아무것도 모른다. 아키텍쳐의 나머지에 대해선 아무것도 모른다.


장점

  - 하나의 프레임워크상에서 서로 다른 멀티 애플리케이션을 만들 수 있다. 즉, 기존의 컴포넌트를 재사용함으로서 시간을 줄일 수 있다. 

  - 느슨한 연결(loose coupling)으로 인해 모듈 단위 테스트가 가능해짐.

  - 확장 가능한 자바스크립트 아키텍쳐는 하나의 블록을 교체한다고 해도 두려워 할 필요가 없는 상태가 되어야 한다. 즉, 이를 달성할 수 있다.




유지 보수성 높이기


유지보수가 좋은 코드는 다음의 특성을 갖는다. 

  - 직관적이다. Intuitive

  - 코드 이해가 쉽다 Understable

  - 적용하기 쉽다 Adaptable

  - 확장하기 쉽다 Extendable

  - 디버깅하기 쉽다 Debuggable


이를 위해 해야할 것들. 
  - Code Style, Code Convention 지키기
  - Naming 잘 하기: 이름에 대한 길이에 대해 걱정하지 말자.
  - Javascript에서 css 핸들링 하지 말자 
  - event object를 바로 다른 function의 argument로 넘기지 말고, 필요한 정보만을 준다. 
  - 사용전 object undefined 체크
  - string 값들은 별도의 Config Data 객체에 담아서 사용한다.

    

  - 빌드 자동화 하자

    


posted by 윤영식
2017. 11. 15. 16:31 Meteor/Angular + Meteor

Angular CLI와 Meteor를 분리하고, Meteor를 단순히 API 서버로 사용하면서 Angular CLI는 Webpack Dev Server를 통해 4200 port로 브라우져에서 접속하면 Meteor Client는  3000 port를 통해 Meteor Server와 DDP 통신을 한다. (이전 블로그 참조) 이번에는 외부 라이브러리로 Clarity CSS Framework을 추가해 본다. 





Clarity CSS Framework


Twitter Bootstrap 처럼 CSS Framework이지만, clarity-ui, clarity-icon, clarity-angular 3개의 파트로 구분된다. clarity-ui는 css만 존재하고, clarity-icon은 이름처럼 아이콘에 관련된 부분이며, clarity-angular는 Angular 컴포넌트를 제공한다. 


  - Clarity 홈페이지

  - Clarity 소스

  - Clarity Seed 소스


// 아이콘 관련

$ npm install clarity-icons --save

$ npm install @webcomponents/custom-elements@1.0.0 --save


// UI CSS

$ npm install clarity-ui --save


// Angular 컴포넌트 

$ npm install clarity-angular --save


Clarity-Angular는 ngModule에서 import를 하면되고 나머지는 아래 장의 설명처럼 ng eject 이전 상태라면 angular-cli.json 에 설정하고, ng eject가 되었다면 webpack.config.js에 설정한다. 

// 메인 모듈

import { ClarityModule } from 'clarity-angular';


@NgModule({

imports: [

BrowserModule,

ClarityModule.forRoot()

],

...

})

export class AppModule {} 





@angular/cli 설정을 webpack.config.js로 옮기기


Clarity Seed를 보면 @angular/cli 기반으로 생성되어 있고, angluar-cli.json 안에 설정이 다음과 같이 되어있다. 진행중인 프로젝트에 Clarity icons, ui에 대한 외부 경로 설정을 한다. 

"styles": [
"styles.css",
"../node_modules/clarity-icons/clarity-icons.min.css",
"../node_modules/clarity-ui/clarity-ui.min.css"
],
"scripts": [
"../node_modules/@webcomponents/custom-elements/custom-elements.min.js",
"../node_modules/clarity-icons/clarity-icons.min.js"
],


해당 설정을 이전강좌의 Angular CLI + Meteor 프로젝트의 angular-cli.json을 넣고 ng eject를 하게 되면 다음과 같은 내용이 webpack.config.js에 자동 설정된다. 

  - Module안에 "exclude"와 "include"의 "src/styles.css"이 있는 모든 곳에 설정.

  - Plugin안에 new ConcatPlugin내용 추가

"module": {

{

"exclude": [

path.join(process.cwd(), "src/styles.css"),

path.join(process.cwd(), "node_modules/clarity-icons/clarity-icons.min.css"),

path.join(process.cwd(), "node_modules/clarity-ui/clarity-ui.min.css")

]

...

},

{

"include": [

path.join(process.cwd(), "src/styles.css"),

path.join(process.cwd(), "node_modules/clarity-icons/clarity-icons.min.css"),

path.join(process.cwd(), "node_modules/clarity-ui/clarity-ui.min.css")

]

...

}

...

},

"plugins": [

new ConcatPlugin({

"uglify": false,

"sourceMap": true,

"name": "scripts",

"fileName": "[name].bundle.js",

"fileToConcat": [

"node_modules/@webcomponents/custsom-elements/custom-elements.min.js",

"node_modules/clarity-icons/clarity-icons.min.js"

]

})

]





Clarity 테스트


Clarity CSS Framework은 업무적으로 요하는 컴포넌트 요소를 잘 정리해 놓았다. Application Layout에 대한 내역을 app.component.html에 추가한다. 

<div class="main-container">
<div class="alert alert-app-level">
Alert
</div>
<header class="header header-6">
Angular Meteor
</header>
<nav class="subnav">
Navigation
</nav>
<div class="content-container">
<div class="content-area">
Main
</div>
<nav class="sidenav">
Side Menu
</nav>
</div>
</div>


결과 화면





Clarity Icon 선택적으로 적용하기


plugins부분에 clarity-icons/clarity-icons.min.js 은 전체 아이콘을 포함하고 있다. 전체을 적용하지 않고, Core와 일부분만 적용하고 싶을 경우 다음과 같이 한다. 


// webpack.config.js에서 clarity-icons.min.js 제거

"plugins": [

new ConcatPlugin({

"uglify": false,

"sourceMap": true,

"name": "scripts",

"fileName": "[name].bundle.js",

"fileToConcat": [

"node_modules/@webcomponents/custsom-elements/custom-elements.min.js"

]

})

]


src/main.ts안에 import

import 'clarity-icons';
import 'clarity-icons/shapes/essential-shapes';
import 'clarity-icons/shapes/technology-shapes';


app.component.html안에 clr-icon 적용 테스트. 자세한 사항은 문서를 참조한다. Clarity-icons은  svg icon으로 사용자 정의 아이콘을 추가 할 수도 있다.

<div class="content-area">
<p><clr-icon shape="user" size="24"></clr-icon> lives in the Core Shapes set.</p>
<p><clr-icon shape="pencil" size="24"></clr-icon> lives in the Essential Shapes set.</p>
<p><clr-icon shape="tablet" size="24"></clr-icon> lives in the Technology Shapes set.</p>
</div>


posted by 윤영식
2017. 11. 6. 15:57 Meteor/Angular + Meteor

angular-meteor.com의 Socially Merge를 따라하며 내용을 요약해 보았다. socially merge는 meteorionic의 조합을 webpack으로 묶어 프로젝트를 구성해보는 과정이다. 여기서 익힐 수 있는 것은 Angular 로 작성된 ionic을 프론트앤드로 사용하고 metetor를 백앤드로 구성하는 방법을 배울 수 있다.




Webpack 기반 프로젝트 초기화


meteor와 ionic는 서로의 CLI를 제공한다. ionic을 먼저 설정한다.

$ npm install -g ionic cordova


초기 프로젝트 구성을 ionic CLI를 통해 생성한다.

ionic start whatsapp blank --cordova --skip-link

...

? Install the free Ionic Pro SDK and connect your app? (Y/n) n


$ cd whatsapp

$ ionic serve


Ionic은 Angular를 사용하기 때문에 Typescript 최신버전을 설치한다.

$ npm install --save typescript


타입스크립트기반에서 외부라이브러리를 사용하기 위해서 src/declarations.d.ts 파일을 생성해 놓는다. d.ts작성법은 공식문서를 참조한다. 다음으로 Ionic 3가 Webpack을 기반으로 빌드되는 설정을 package.json에 추가한다.

//  package.json

"config": {

    "ionic_webpack": "./webpack.config.js"

}


Ionic은 webpack config에 대한 샘플 파일을 제공하고 이를 복사한다. webpack.config.js안에는 dev와 prod환경 기본값이 설정되어 있다.

$ cp node_modules/@ionic/app-scripts/config/webpack.config.js .




Ionic 에 Meteor 설정


프로젝트 폴더의 루트에 api 폴더를 만들고 이를 Meteor의 백앤드로 사용하는 설정을 webpack.config.js에 한다. meteor 관련된 것은 external로 취급하는 것 같다.

// dev, prod양쪽에 넣는다.

resolve: {

...

alias: {

'api': path.resolve(__dirname, 'api/server')

}

},


externals: [ resolveExternals ],


plugins: [ ..., new webpack.ProvidePlugin({ __extends: 'typescript-extends' }) ],


node: { ..., __dirname: true }


// 맨 마지막에 넣음 

function resolveExternals(context, request, callback) {

  return resolveMeteor(request, callback) ||

    callback();

}

 

function resolveMeteor(request, callback) {

  var match = request.match(/^meteor\/(.+)$/);

  var pack = match && match[1];

 

  if (pack) {

    callback(null, 'Package["' + pack + '"]');

    return true;

  }

}


다음으로 Meteor를 외부 의존하는 것으로 tsconfig.json에 Meteor관련 내용을 추가한다.

"compilerOptions": {

  "baseUrl": ".",

  "modules": "commonjs",

  "paths": { "api/*": ["./api/server/*"],

  ...,

  "skipLibCheck": true,

  "stripInternal": true,

  "noImplicitAny": false,

  "types": [

      "@types/meteor"

  ] 

},

"include": [ ..., "api/**/*.ts" ],

"exclude": [ ..., "api/node_modules", "api" ]


typescript-extends 패키지와 meteor 타입 패키지를 설치한다.

$ npm install --save-dev typescript-extends

$ npm install --save-dev @types/meteor


Ionic serve를 실행하고 브라우져의 console을 보면 다음 오류가 나온다. 이를 위해 src/app/app.component.ts에 cordova 설정을 한다.

// Chrome devtools 메세지

Native: tried calling StatusBar.styleDefault, but Cordova is not available. Make sure to include cordova.js or run in a device/simulator


// src/app/app.component.ts 설정




Meteor Server 생성 및 설정


Ionic의 webpack과 tsconfig안에 Meteor 서버 설정을 했고, Meteor CLI를 이용해 백앤드를 구성한다.

// 미티어 설치

$ curl https://install.meteor.com/ | sh


// api 폴더 미티어 초기화

$ meteor create api


api 폴더 밑으로 생성된 node_modules, client 폴더를 삭제하고, package.json, package-lock.json 파일도 삭제한다.

$ rm -rf api/node_modules

$ rm -rf api/client

$ rm api/package.json

$ rm api/package-lock.json


Meteor에서 사용하는 것을 Ionic 으로 심볼릭 링크를 건다.

$ cd api

api$ ln -s ../package.json

api$ ln -s ../pack-lock.json

api$ ln -s ../node_modules

api$ ln -s ../src/declarations.d.ts


Meteor 백앤드를 Typescript기반으로 개발하기 위해 다음 패키지를 설치한다.

api$ meteor add barbatus:typescript


// 별도 필요 패키지 설치 

$ npm install --save babel-runtime

$ npm install --save meteor-node-stubs

$ npm install --save meteor-rxjs


Typescript의 tsconfig.json 파일을 api 폴더안에 생성하고 다음 내역을 붙여넣는다.

{

  "compilerOptions": {

    "allowSyntheticDefaultImports": true,

    "declaration": false,

    "emitDecoratorMetadata": true,

    "experimentalDecorators": true,

    "lib": [

      "dom",

      "es2015"

    ],

    "module": "commonjs",

    "moduleResolution": "node",

    "sourceMap": true,

    "target": "es5",

    "skipLibCheck": true,

    "stripInternal": true,

    "noImplicitAny": false,

    "types": [

      "@types/meteor"

    ]

  },

  "exclude": [

    "node_modules"

  ],

  "compileOnSave": false,

  "atom": {

    "rewriteTsconfig": false

  }

}


Meteor의 server/main.js를 main.ts로 바꾼다. 위에서 설치한 meteor-rxjs는 클라이언트단의 Meteor를 RxJS Observable기반으로 사용할 수 있도록 한다.

// 예) Meteor 클라이언트단 Collection 구성

import { MongoObservable } from 'meteor-rxjs';


export const Chats = new MongoObservable.Collection('chats');




Meteor Client 준비


Meteor Server에 접속하기 위해 Meteor Client가 필요하기 때문에 하나의 bundler로 만들어 import해서 사용한다. 번들링도구를 설치한다.

$ sudo npm install -g meteor-client-bundler


// 필요 패키지를 설치

$ npm install --save-dev tmp


package.json 에 번들링 명령어를 설정하고, 번들링 명령을 수행한다.

// package.json

"scripts": {

...,

"meteor-client:bundle": "meteor-client bundle -s api"

}


// 번들링

$ npm run meteor-client:bundle


클라이언트 main.ts에 번들 내역을 import한다. 

// src/app/main.ts

import "meteor-client";




Ionic & Meteor 기동


Ionic 기동

$ ionic serve


Meteor 기동

$ cd api

api$ meteor


ionic HTTP는 8100이고, meteor client는 server에 websocket 3000 port로 접속을 한다.


posted by 윤영식
2017. 9. 4. 15:51 카테고리 없음

Angular 최신 버전이 v5 beta-6를 향해 가고 있다. 9월에는 v5 release가 될 것으로 보인다. Angular에서 툴 기능으로 @angular/cli를 제공하고 있는데 오늘은 이것의 내부구조에 대해 연구해 본다. 



@angular/cli 설치 및 프로젝트 생성

설치는 npm 또는 yarn을 이용한다.

$> npm i -g @angular/cli


or 


$> yarn add global @angular/cli


Angular 기반 프로젝트를 생성해 보자.

$> ng new jamong


실행해 보자

$> ng serve




폴더 구조

angular/cli는 Automation Tool 과 Module loader로 webpack을 사용하고 있다. 웹팩의 환경파일을 루트 폴더에 생성한다. 
$> ng eject
=====================================================
Ejection was successful.

To run your builds, you now need to do the following commands:
   - "npm run build" to build.
   - "npm test" to run unit tests.
   - "npm start" to serve the app using webpack-dev-server.
   - "npm run e2e" to run protractor.

Running the equivalent CLI commands will result in an error.

=====================================================
Some packages were added. Please run "npm install".


설명에 따라 추가 명령을 수행한다. 

$> npm run build && npm start


// package.json의 build 와 start 스크립트 설정내역

  "scripts": {

    "ng": "ng",

    "start": "webpack-dev-server --port=4200",

    "build": "webpack",

    "test": "karma start ./karma.conf.js",

    "lint": "ng lint",

    "e2e": "protractor ./protractor.conf.js",

    "pree2e": "webdriver-manager update --standalone false --gecko false --quiet"

  },



webpack 오류가 난다면 webpack이  global설치가 않되어 있기때문이다. webpack을 설치한다. 

$> yarn add webpack --dev

또는

$> yarn add global webpack 

또는 

$> npm install -g webpack





Webpack 환경파일

webpack의 기본은 초기 참조할 entry파일과 결과 번들파일을 지정하는 것이다. 

$> webpack <entry file path> <output bundle file path>


예) webpack ./entry.js bunlde.js


간단한 번들링은 CLI를 사용해도 무방하지만 복잡한 옵션이 적용되어야 한다면 환경파일을 사용한다. 예) webpack.config.js 아래 예에서 ouput의 filename에 [name]은 entry의 'app' 키값이다. 

module.exports = {

  context: __dirname + '/app',

  entry: {

    app: './app.js'

  },

  output: {

    path: __dirname + '/dist',

    filename: '[name].bundle.js'

  }

}


환경설정 자세한 옵션은 공식문서를 참조한다. 여기서 주의할 것은 entry는 String, Object, Array로 설정가능하다. 

- String: 하나만 설정

- Array: output 번들링 파일에 순서적으로 합쳐진다.

- Object: SPA처럼 index.html에 적용되는 것이 아니라, index1.html 또는 index2.html 등 output이 각각의 html에 포함될 때 사용한다. 





Webpack Loader

Webpack에는 자바스크립트가 아닌 확장형식의 파일을 자바스크립트에서 동작할 수 있도록 해주는 로더(Loader)가 있다. 즉, 모든(CSS, Images, HTML...)을 모듈로 취급하게 해주는 핵심역할을 로더가 수행한다. 말 그대로 다양한 파일 확장자의 Module Loader의 줄임말이라 보면된다. (전체 목록 참조) 만일 설정중에 module, rules를 사용하지않고 loaders를 쓰거나, options대신 query를 쓰면 webpack 1에 대한 설명이다.


- 스타일: style, css

- 변환: typescript, coffeescript, ES2015


환경파일안에 module 밑으로 설정한다. 


module.exports = {

  entry: ...

  output: ...

  module: {

    rules: [

      {

        test: /\.css$/,

        use: [ 'style-loader', 'css-loader']

      }

    ]

    ...

  }


}


특히 Angular 개발시에는 Typescript를 사용하므로 sourcemap을 남기려면 'source-map-loader'를 module 프로퍼티에 설정한다. (sourcemap에 대해 webpack3에서는 plugin으로 설정도 가능하다.) enforce 설정값을 "pre"로 하면 자바스크립트 변환전에 sourcemap 을 만든다.

{

        "enforce": "pre",

        "test": /\.js$/,

        "loader": "source-map-loader",

        "exclude": [

          /(\\|\/)node_modules(\\|\/)/

        ]

}


개발시에는 Node.js기반의 webpack-dev-server를 통해 개발 페이지를 테스트할 수 있다. 옵션은 --inline (전체 페이지 리로딩) --hot (변경 컴포넌트만 리로딩)한다. 

// 전체 및 부분 리로딩 옵션 설정

$> webpack-dev-server --inline --hot




Webpack Plugins

플러그인은 Output 번들의 Chunk 또는 Compilation 레벨 파일에 대한 추가 작업을 수행한다. 예로 ulgifyJSPlugin은 번들 파일 사이즈를 줄이고, 코드를 못 알아보게 만들어 준다. 또는 extract-text-webpack-plugin의 경우는 css-loader, style-loader의 결과를 하나의 별도 외부 파일로 만들어 준다. (플러그인 목록 참조)


@angular/cli에서  ng eject로 나온 webpack.config.js안의 plugins설정 내역

"plugins": [

    new NoEmitOnErrorsPlugin(),

    new GlobCopyWebpackPlugin({

      "patterns": [

        "assets",

        "favicon.ico"

      ],

      "globOptions": {

        "cwd": path.join(process.cwd(), "src"),

        "dot": true,

        "ignore": "**/.gitkeep"

      }

    }),

    new ProgressPlugin(),

    new CircularDependencyPlugin({

      "exclude": /(\\|\/)node_modules(\\|\/)/,

      "failOnError": false

    }),

    new NamedLazyChunksWebpackPlugin(),

    new HtmlWebpackPlugin({

      "template": "./src/index.html",

      "filename": "./index.html",

      "hash": false,

      "inject": true,

      "compile": true,

      "favicon": false,

      "minify": false,

      "cache": true,

      "showErrors": true,

      "chunks": "all",

      "excludeChunks": [],

      "title": "Webpack App",

      "xhtml": true,

      "chunksSortMode": function sort(left, right) {

        let leftIndex = entryPoints.indexOf(left.names[0]);

        let rightindex = entryPoints.indexOf(right.names[0]);

        if (leftIndex > rightindex) {

          return 1;

        } else if (leftIndex < rightindex) {

          return -1;

        } else {

          return 0;

        }

      }

    }),

    new BaseHrefWebpackPlugin({}),

    new CommonsChunkPlugin({

      "name": [

        "inline"

      ],

      "minChunks": null

    }),

    new CommonsChunkPlugin({

      "name": [

        "vendor"

      ],

      "minChunks": (module) => {

        return module.resource &&

          (module.resource.startsWith(nodeModules) ||

            module.resource.startsWith(genDirNodeModules) ||

            module.resource.startsWith(realNodeModules));

      },

      "chunks": [

        "main"

      ]

    }),

    new SourceMapDevToolPlugin({

      "filename": "[file].map[query]",

      "moduleFilenameTemplate": "[resource-path]",

      "fallbackModuleFilenameTemplate": "[resource-path]?[hash]",

      "sourceRoot": "webpack:///"

    }),

    new CommonsChunkPlugin({

      "name": [

        "main"

      ],

      "minChunks": 2,

      "async": "common"

    }),

    new NamedModulesPlugin({}),

    new AotPlugin({

      "mainPath": "main.ts",

      "replaceExport": false,

      "hostReplacementPaths": {

        "environments/environment.ts": "environments/environment.ts"

      },

      "exclude": [],

      "tsConfigPath": "src/tsconfig.app.json",

      "skipCodeGeneration": true

    })

]


JHipster로 자동생된 webpack.config.dev.js의 plugins 설정 내역

plugins: [

        new BrowserSyncPlugin({

            host: 'localhost',

            port: 9000,

            proxy: {

                target: 'http://localhost:9060',

                ws: true

            }

        }, {

            reload: false

        }),

        new webpack.NoEmitOnErrorsPlugin(),

        new webpack.NamedModulesPlugin(),

        new writeFilePlugin(),

        new webpack.WatchIgnorePlugin([

            utils.root('src/test'),

        ]),

        new WebpackNotifierPlugin({

            title: 'JHipster',

            contentImage: path.join(__dirname, 'logo-jhipster.png')

        })

 ]




Production vs Development

개발과 운영 시점에 webpack 환경을 달리 적용하기 위해 보통  webpack.config.dev.js 와 webpack.config.prod.js를  따로 만들고 package.json의 script에 적용해 사용한다. package.json에 적용하고 싶지 않다며 gulp를 사용해도 된다. 


@angular/cli의 script 내역

 "scripts": {

    "ng": "ng",

    "start": "webpack-dev-server --port=4200",

    "build": "webpack",

    "test": "karma start ./karma.conf.js",

    "lint": "ng lint",

    "e2e": "protractor ./protractor.conf.js",

    "pree2e": "webdriver-manager update --standalone false --gecko false --quiet"

}


JHipster script 내역

"scripts": {

    "lint": "tslint --type-check --project './tsconfig.json' -e 'node_modules/**'",

    "lint:fix": "yarn run lint -- --fix",

    "ngc": "ngc -p tsconfig-aot.json",

    "cleanup": "rimraf target/{aot,www}",

    "clean-www": "rimraf target//www/app/{src,target/}",

    "start": "yarn run webpack:dev",

    "serve": "yarn run start",

    "build": "yarn run webpack:prod",

    "test": "karma start src/test/javascript/karma.conf.js",

    "test:watch": "yarn test -- --watch",

    "webpack:dev": "yarn run webpack-dev-server -- --config webpack/webpack.dev.js --progress --inline --hot --profile --port=9060",

    "webpack:build:main": "yarn run webpack -- --config webpack/webpack.dev.js --progress --profile",

    "webpack:build": "yarn run cleanup && yarn run webpack:build:main",

    "webpack:prod:main": "yarn run webpack -- --config webpack/webpack.prod.js --progress --profile",

    "webpack:prod": "yarn run cleanup && yarn run webpack:prod:main && yarn run clean-www",

    "webpack:test": "yarn run test",

    "webpack-dev-server": "node --max_old_space_size=4096 node_modules/webpack-dev-server/bin/webpack-dev-server.js",

    "webpack": "node --max_old_space_size=4096 node_modules/webpack/bin/webpack.js",

    "e2e": "protractor src/test/javascript/protractor.conf.js",

    "postinstall": "webdriver-manager update && node node_modules/phantomjs-prebuilt/install.js"

}


to be continued...



<참고>

- Webpack core concept

- 네이버 webpack 소개

- Webpack의 혼란스러운 사항들

- Webpack3에서 주의할 점


posted by 윤영식
2017. 4. 23. 15:51 Angular/Concept

Angular CLI를 통해 프로젝트를 생성하고 Docker에 Nginx를 띄워서 간단히 연결해 보자. 




Angular CLI 기반 프로젝트 생성


NodeJS버전은 6최신을 사용한다. 

$ npm install -g @angular/cli 


설치가 되었다면 ng 명령을 통해 프로젝트를 생성한다. 

$ ng new angular-docker


프로젝트가 생성되었으면 프로젝트 폴더로 이동해서 소스를 빌드한다. 

$ cd angular-docker

$ ng build

Hash: cfba09939df99de45615

Time: 7424ms

chunk    {0} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 165 kB {4} [initial] [rendered]

chunk    {1} main.bundle.js, main.bundle.js.map (main) 3.61 kB {3} [initial] [rendered]

chunk    {2} styles.bundle.js, styles.bundle.js.map (styles) 9.77 kB {4} [initial] [rendered]

chunk    {3} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.09 MB [initial] [rendered]

chunk    {4} inline.bundle.js, inline.bundle.js.map (inline) 0 bytes [entry] [rendered]





Docker CE 기반 Nginx 관리


Docker를 잘 모른다면 Docker CE를 설치해 사용한다. 

  - download docker CE (Community Edition)

 - Mac 버전을 설치하면 Docker 가 기동이 된다.

 

     


다음으로 Nginx image 를 docker명령을 통해 내려받는다. 만일 사용하지 않는 Docker image들이 있다면 다 삭제를 한고 작업을 해보자.

// Delete all docker containers

$ docker rm $(docker ps -a -q)


// Delete all docker images

$ docker rmi $(docker images -q)


// Install docker image

$ docker pull nginx

Using default tag: latest


// 설치 내역 확인

$ docker images 

REPOSITORY        TAG                 IMAGE ID            CREATED             SIZE

nginx                  latest              5766334bdaa0   2 weeks ago         183 MB


Angular 빌드 폴더로 이동해서 Nginx를 수행한다. 

$ cd dist

$ docker run -d -p 8080:80 -v $(pwd):/usr/share/nginx/html nginx:latest

48db2f0626c5a10429e95cbbbaf3cc58769cda45a9c1e7084b4f3a260e576838


// 이미지 실행 확인

$ docker ps -a

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                           NAMES

48db2f0626c5        nginx:latest        "nginx -g 'daemon ..."   2 minutes ago       Up 2 minutes        443/tcp, 0.0.0.0:8080->80/tcp   loving_lumiere


수행되고 있는 이미지를 멈추고 제거해 보자. stop, rm 다음은 Container ID의 처음 두자를 입력한다. 

// 멈춤 

$ docker stop 48

48


// 제거

$ docker rm 48

48


// 확인

$ docker ps -a

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES


Kinetic 애플리케이션을 사용해서 CLI 명령과 동일하게 GUI 기반으로 Container를 관리할 수 있다.




Docker Compose 사용하기


컴포즈는 환경을 기반으로 다양한 도커 이미지들을 동시에 기동하고 연결하는 역할을 수행한다. Nginx, NodeJS, MongoDB등을 서로 다른 Docker image기반으로 기동하고 연결하는 환경설정을 docker_compose.yml에 한다. angular-seed 예는 다음과 같다. 

version: '2'


services:


  angular-seed:

    build:

      context: .

      dockerfile: ./.docker/angular-seed.development.dockerfile

    command: npm start

    container_name: angular-seed-start

    image: angular-seed

    networks:

      - dev-network

    ports:

      - '5555:5555'


networks:

  dev-network:

    driver: bridge


빌드하고 수행하기 

$ docker-compose build

$ docker-comopse up


ng-conf 2017 의 docker 이야기




<참조>



posted by 윤영식
2016. 10. 23. 22:59 Angular/Concept

Angular v2가 정식 릴리즈되었다. Angular v1 은 Two-way data-binding 이라는 독특한 특징으로 인해 많은 사용자 층을 확보했지만 장점 만큼이나 성능상의 단점도 존재했었다. 또한 처음엔 쉬운듯 하면서 좀 더 깊게 들어가볼려고 하면 학습곡선이 갑자기 껑충뛰기도 했다. 가장 많이 사용했던 Directive(지시자)가 대표적이다. 많은 개발자가 만들어 놓은 지시자를 쉽게 가져다 쓸 수는 있지만 직접 만들어 애플리케이션에 접목하려 할 때 첫 문턱을 만나게 된다. 그리고 jQuery사용에 익숙한 개발자에게 Angular v1 시점상의 차이로 Angular v1 방식의 개발패턴을 요구하기도 했다. 관성은 무섭다. 기존에 사용하던 방식을 버리고 Angular v1에 맞춰서 애플리케이션을 만들어 가기란 곤혹스럽다. Angular v2 또한 그런 인식의 전환을 요구할까? 그렇다 그리고 아니다. 






웹 애플리케이션 흐름

웹 애플리케이션 개발을 위해 우리가 사용하는 jQuery같은 라이브러리나 Angular, Backbone같은 프레임워크의 가장 1차적인 목적은 무엇일까? 나는 Data Projection이라 생각한다. 데이터를 화면에 출력하기 위해 DOM을 얼마나 쉽게 조작하고 상호 작용할 수 있느냐가 선택의 기준이라 생각한다. Data Projection을 일관되고 확장가능하고 배포가능하게 하는 방식으로 기술은 발전해 왔고, 현재는 화면에 대한 제어방식이 컴포넌트 기반 방식으로 발전해 오고 있다. 


Data Projection의 역사를 보면 초장기엔 Server Side Rendering 를 사용해 웹 애플리케이션을 개발했다. 예로 JSP, PHP, ASP 같이 서버 미들웨어서 데이터를 조회하고 HTML을 조작하여 결과 HTML을 브라우져에 전송하던 시대이다. 




1세대에는 AJAX가 나오고 다양한 라이브러리나 프레임워크가 나왔다. 이때는 데이터변경에 대한 DOM반영이 서버에서 클라이언트 개발자의 몫으로 넘어오게 되었다. 즉, 직접 DOM 을 얻어와서 특정 위치에 넣어 주어야 했고, DOM에서 발생하는 이벤트를 Listening해서 처리하고 DOM에 반영하는 모든 작업이 웹 개발자가 직접 코딩하던 단계였다. Java의 프레임워크 역사로 보면 Struts 로 비유할 수 있지 않을까 싶다. 




2세대로 넘어오게 되면 Model을 DOM 에 반영하는 방식은 자동화 된다. 여기에 대표적인 프레임워크가 Ember 와 Angular v1 이다. 이때 부터 Single Page Application (SPA) 개발이라는 용어가 나오게 된다. URI 변경에 대한 대응으로 Routing  개념이 나오고, Data Projection후 원하는 일부 DOM을 변경하는 역할이 프레임워크로 넘어갔고, 웹 개발자는 좀 더 애플리케이션 비즈니스 로직에 집중토록 만들었다. Java 프레임워크로 비유하자면 Struts와 Spring Framework 초기버전의 중간 지대 정도 쯤이라 생각한다.  이때부터 Frontend (프론트앤드)라는 직군이 웹 개발자와 분리되기 시작한 지점이라 생각한다. 이에 대한 자세한 설명은 태곤님이 작성한 "[번역] 프론트엔드 개발자는 왜 구하기 어렵나요?"를 참조하자. 2011년을 기점으로 2013년 웹 애플리케이션 프레임워크가 정착을 해가는 시기였고, 현재는 대부분의 스타트업이나 중견기업에서 2세대 웹 애플리케이션 프레임워크를 선택할 경우 프론트엔드 개발자와 백앤드 개발자를 구분하여 팀을 구성하고 있는 추세이다.





3세대는 2세대의 과도기를 거쳐 2세대의 장점을 흡수 하면서 성능상의 이슈를 해결하고, 점점 복잡해 지고있는 웹 애플리케이션을 보다 직관적이고 쉽게 개발할 수 있게 노력하고 있다. 대표적인 프레임워크로는 Facebook의 React와 Google의 Angular v2 (이하 Angular)이다. Angular는 Component기반 개발 방식으로 표준인 Web Components를 지원하며 Typescript를 기본 언어로 채택했다. Typescript는 Type 시스템을 제공하기 때문에 개발단계에서 버그의 가능성을 쉽게 찾을 수 있도록 도와준다. React와 Angular에 대한 장단점은 손창욱님의 "React보다 Angular v2에 더 주목해야 하는 이유"를 참조하자. Java의 Spring Framework이 성숙하면서 Annotation 같은 기능이 추가되듯, Angular v2 프레임워크는 Java의 Spring 프레임워크 최신버전과 비유할 수 있다. 



Angular v1에 대한 개발 및 컨설팅을 3년 가까이 하면서 올해 초 Angular v2를 공부하고 기존 v1 코드를 v2 코드로 전환하면서 코드 베이스는 50%가량 줄었고, 반응속도는 30%가량 개선되었다. 8명 프론트앤드 개발자와 컨버전을 진행하면서 이구동성으로 말하는 것은 "코드가 직관적으로 변했다. 코드량이 현저히 줄었다. Typescript의 타입체킹으로 인해 실수를 최소화 할 수 있었다" 이다. 



Angular v2 왜 배워야 하는가?

Angular를 왜 배워야 하는가? 답하자면 안배워도 된다. 단순 홈페이지나 업무 화면이라면 쉽고 더 빨리 만들 수 있는 워드프레스나 서비스를 이용하거나 DOM 핸들링 라이브러리나 플러그인을 사용해 개발하는 편이 낫다. 하지만 솔루션의 복잡한 요구사항을 지속적으로 반영해야 하고 DOM제어가 복잡해 질 가능성이 높다면 jQuery, React 같은 라이브러리 보다는 Angular 같은 프레임워크를 선택하는 것이 좋다. 그리고 최근에는 ES2015 표준이 확정되었고 최신 브라우져에 대부분 기능이 구현되고 있다. 2세대와 3세대 Data Projection의 가장 큰 개발 방식의 차이는 ES2015의 이해에서부터 시작한다.  즉, ES2015 문법을 잘 알고 사용하면 좀 더 쉽고 간단하게 코드 베이스를 유지하면서 오류를 최소화할 수 있다. 예로 -> 펑션은 this에 대한 오류를 방지하고, Set/Map등 Collection은 Java의 Collection과 유사한다. 



Angular v2 시작하면 초기에 배워야 하는 것들이 갑자기 늘어난다. 이것은 2세대와 3세대의 개발 패턴이 바뀌었음을 시사한다. ES2015 문법은 그대로 TypeScript에 녹아 있고, Type System과 Annotation 기능이 녹아 들어 더욱 편리한 개발을 가능토록 한다. 따라서 ES2015의 Syntax와 개념을 이해해야 한다. 그리고 Typescript를 다시 공부해야 한다. 또한 요즘 인기를 누리고 있는 Reactive Programming을 표방한 대표적인 라이브러리인 RxJS를 Angular가 근간으로 사용하고 있다. 따라서 RxJS 에 대한 개념과 사용법을 익혀야 한다. 그런후 Web Components 란 무엇인지 알아야 하고, Angular 프레임워크의 아키텍쳐를 구성하는 개념인 Change Detection 동작원리, Dependency Management, Modulization 을 알아야 하고, 다음으로 주변의 Tooling System으로 SystemJS (Webpack), Gulp 등을 알아야 한다. 


이렇게 열거해 보니 참으로 배울 것이 많다. 다시 말하지만 안 배워도 된다. 하지만 자신의 근육을 한단계 업그레이드 시키기 위해 고통스러운 인내의 시간은 필요하다. 배워야 하는 기준은 두가지 정도로 이야기 해본다. 


첫째, 서비스 버전업을 위해 요구사항이 계속 증가하고 있는가?

둘째, 더 적고 직관적인 코드 베이스를 유지하면서 성능을 높이고 싶은가?


 

프론트엔드 개발자 직군이 새롭게 자리잡게된  5년기간 동안 많은 부분이 기존의 백앤드 개발 패턴과 유사해 지고 있다. 모듈 의존성 관리, 빌드 시스템, 프레임워크의 발전은 Java개발자들이 초장기 프레임워크 없이 개발하다 Struts를 만났을 때 기쁨에서 Spring을 만나 자유를 얻었지만 여전히 배워야 할 것들은 더욱 증가했음을 알것이다. 그러나 어쩌겠는가 우리는 더 게을러 지고싶다는 욕구가 있고 프레임워크가 그것을 만족시켜줄것이라는 희망을 품고 있는 한 배움과 진보는 계속될 뿐이다. 



참조


posted by 윤영식
2016. 4. 16. 22:27 Angular/Concept

ExtJSAngular2의 기술적 차이점에 대한 질문을 받게되어 어떻게 대답을 하면 될까? 를 고민하다 정리를 해본다. ExtJS 는 일부 기능은 무료 오픈소스이지만 전체 기능을 사용하기 위해서는 유료이다. 현재 v6 까지 나와있다. 또한 최신버전은 Sencha라는 이름으로 통합되어 나오고, 현재 ExtJS v6 버전을 다운로드 받아 사용할 수도 있다. 따라서 ExtJS나 Sencha 에서 이야기하는 장점이라 말하는 부분을 토대로 Angular v2와 비교해 보기로 한다. 참조는 Top 8 Reasons Why Enterprises Prefer Sencha ExtJS over Angular 문서를 보고 ExtJS 쪽은 영문 요약을 간단히 했고, Angular 쪽은 영어로 작성해 본다.


This document is to write differences between ExtJS v6  framework and Angular v2 framework. I reference the ExtJS site (Top 8 Reasons Why Enterprises Prefer Sencha ExtJS over Angular) and I write my opinion about Angular.

*caution: AngularJS is Angular v1, Angular v2 or Angular is Angular v2. ExtJS is ExtJS v6.





Reason #1 Components, Components, Components


 - ExtJS

   + ExtJS contains components that are optimized for both desktop and mobile devices.

   + customize the theme. mobile apps can be themed to achieve an iOS, Android,  BlackBerry the look and feel.

   + Angular does not come with a component library.


 - Angular v2

  + Angular can easily make components that are usable anytime. If we want to use components i.e grids and charts, we can use the components that have already been made by many companies and open source developers.

  + Developer can build an application with components  and can also use the current web technology such as Web Components together.

  + Angular component architecture is a component-tree for improving performance to render web page views. This architecture is implemented in the ReactJS Framework from Facebook as well.

  + Angular can easily merge with the more popular CSS Framework such as Twitter Bootstrap, Material Design, Semantic UI and Foundation which can be customized to match with any company brand. One of these can be chosen for any brand style.

  + Angular is a platform for the web and mobile. Ionic v2 is a Hybrid mobile app framework. It is based on Angular v2 and has tools that can rapidly develop an app. So, the hybrid app can be developed using Angular in a friendly code style. The hybrid app gives customers a better UX(User eXperience) than mobile browsing.

  + Ionic v2 using Angular has lots of mobile components to support the look and feel for different mobile platforms such as iOS, Android and BlackBerry. We can also customize these styles.





Reason #2 ExtJS Has Ben Battle-Tested Since 2007


 - ExtJS

   + It is launched in 2007.

   + It has long-term success.

   + migration v5 to v6 smoothly


 - Angular v2

   + Angular v1 was launched on October 20, 2010 by Google.

   + Angular v1 also has had long-term success i.e NBC, Intel, ABC News and approximately 8,400 other sites out of 1 million tested in July 2015.

   + Angular v2 already has an upgrade tool to migrate v1 to v2.






Reason #3 A Robust Framework for Building Apps vs Building One's Own Framework


 - ExtJS

   + ExtJS has MVC(Model View Controller)/MVVM (Model View ViewModel) architecture.

   + ExtJS comes with an extensive component library.


 - Angular v2

   + Angular has MVC/MVVM as well.

   + Angular is easily customized for any application's architecture. It can be applied to the one-way data flow architecture like Flux from Facebook in order to easily manage any application's state.

   + Angular is an open source framework so, many developers and companies open their components like grids or charts that have been used within their applications. If there are  components with any issues, many contributors can solve the problems in the open system and components can easily be downloaded from consistency tools such as NPM (Node Package Manager) or Bower.

   + Angular v2 is a new platform for the modern web environment including Web Components, Reactive Programming and Immutable state to solve some issues which Angular v1 experienced. Applications can be developed to be more stable with better performance using these concepts.





Reason #4 Clearly-Defined Legacy Browser Compatibility By Default


 - ExtJS

   + support Internet Explorer (IE) 8


 - Angular v2

   + Angular can support IE 9 (link).

   + If IE 8 is to be supported, poly-fill can be used to support the lower version but MicroSoft has already stopped updating IE 8 securities. So, this version is not recommended because of the danger of hacking.





Reason #5 Integrated Tools Created With a Clear Vision and Purpose


 - ExtJS

   + Senchar has several tools including Senchar Cmd, IDE Plugins, and Senchar Inspector that developers can use to speed up their application development.


 - Angular v2

   + Angular uses the Gulp or Grunt tasker that can extend plugins which are required.

   + There are IDE Plugins (WebStorm, Atom, Sublime Text, MS Code) for Angular  and developers can check out an application's performance with Angular Inspector under the  chrome extension as well.

   + Angular uses the NPM (Node Package Manager) or Bower that all front-end developers use to manage the packages and components for any application.

   + Angular is oriented to use common tooling set in current front-end environments. So, the developer can use these tools in a friendlier way.





Reason #6 No Need to Learn TypeScript or other tools and component libraries with ExtJS6


 - ExtJS

   + ExtJS 6 is a JavaScript Framework.

   + Developer doesn't need to have experience with object-oriented programming (OOP)


 - Angular v2

   + Javascript before ECMAScript 5 doesn't support the OOP but ECMAScript 6 (ES2015) can support it and the developer can simply establish more complex applications with the OOP methodology.

   + TypeScript is a superset of ES2015 and has more features simply to code any sophisticated application such as Decorator. Typescript also includes a feature that transpiles code to ES5 or ES3 code, so it can be released into the production environment without worry.

   + TypeScript can support the Static Type Checking System during the development period. TypeScript can automatically suggest some problems in code so, the developer can recover the mistakes which occured in run-time.

   + If the developer has experience with Java or OOP, they can easily learn TypeScript. It is not a weakness, but rather a strength.





Reason #7 Excellent Design Tools


 - ExtJS

   + Sencha has an excellent set of design tools.

   + Sencha Architect is a perfect tool to create clickable prototypes.

   + ExtJS Stencils can be used to create wireframes and mockups that correctly reflect the look and feel of components


 - Angular v2

   + Angular doesn't have a design tool because it is optional. If it is needed, other professional design or mockup tools can be used .

   + Ionic v2 based on Angular v2 also has  a design tool such as Ionic Creator for mobile and has a testable native app in several mobile platforms (iOS, Android) such as Ionic Viewer.

   + The Angular team has been working closely together with the Ionic team.





Reason #8 Awesome Support and Training Options From the Creators of the Framework


 - ExtJS

   + Sencha has excellent support and training teams.

   + Sencha has a professional services team that can assist with a variety of enterprise development needs.


 - Angular v2

   + There are several professional Angular training companies such as Rangle.io and Egghead.io.

   + Global conferences are always held several times annually such as NG-Conf.

   + If developers want to learn Angular, they can easily get a huge range of materials like books, videos and online courses.

   + The Angular team at Google consists of 20 full-time developers and there are over 221 contributors for Angular v2.

   + It is not a level playing field when comparing ExtJS with Angular in my opinion.



ExtJS is a good framework and has a full set of tools for web development but it can restrict customization needs, because developers can just play around with ExtJS's environment and architecture. The current web trend - software and hardware - has been changing quickly so if some applications need to be adapted into the current environment, Angular v2 would be an excellent choice. Angular v2 can provide any company lots of opportunities to improve the UX and performance for applications but more time needs to be invested learning this framework. 


기회가 된다면 다음부터는 영어 블로그도 써볼 생각이다. 그리고 영어 Angular 관련 책을 출판해 보고 싶다. O'Reilly 또는 Packt Publishing 같은 곳과 연이 된다면... 그전에 열심히 영문 블로깅하면서 영작 실력을 키워야겠지만... 비오는 토요일 밤에...




Reference

  - Top 8 Reasons Why Enterprises Prefer Sencha Ext JS over Angular

posted by 윤영식
2016. 4. 13. 13:31 Electron & Ionic

Ionic 프레임워크는 하이브리드 웹앱을 만들기 위한 프레임워크이다. 웹앱이기에 웹을 위한 프레임워크로 Angular를 사용하고 있다. Ionic v1에서는 Angular v1Ionic v2에서는 Angular v2를 사용하고 있다. 







Ionic은 하이브리드 웹앱 프레임워크외에 다양한 서비스를 제공하고 있다. Ionic 플랫폼 상에서 화면을 디자인하고 배포하고 테스트할 수 있는 서비스들을 제공한다. Ionic.io 는 Ionic 프레임워크를 이용해 만든 앱을 배포 관리하는 곳이다. Ionic Creator는 화면을 디자인하는 서비스이며, Ionic View는 앱을 앱스토어에 배포하거나 로컬에 USB로 설치하지 않고 Ionic View앱을 설치하면 Ionic 프레임워크로 만든 모든 앱들을 iOS 또는 Android 상에서 바로 볼 수 있는 서비스이다. 


위의 그림 Ionic는 Cordova를 기반으로 하고 CLI(Command Line Interface)를 제공하며 CLI는 두가지를 통합하고 있다. 첫째는 Cordova의 plugin에 대한 install/uninstall을 위한 명령이고 두번째는 Gulp를 이용해서 웹파일(Sass, Html, Scripts)을 위한 명령 Task에 대해 Gulp의 환경파일인 gulpfile.js에 정의하고 있다. Vinyl은 Gulp에서 사용되는 모듈로 다양한 OS에 상관없이 File Stream을 지원하기 위한것으로 개념은 링크를 참조한다. Ionic은 iOS와 Android Native UI에 가깝게 Angular 기반으로 컴포넌트를 제공하고 있다. 즉, Angular의 Directive(지시자, 컴포넌트)를 이용해 화면을 만드는 방식이다. 또한 Cordova의 기능을 Angular의 서비스로 사용하기 위해 ngCordova도 제공하고 있다. 


Ionic을 사용하기 위해서는 다음과 같은 사전지식이 필요하다. 

  - Node.js 그리고 NPM 사용법

  - 단순 테스트 목적이 아니라면 반드시 Angular 프레임워크 사용경험이 필요하다  

  - XCode 또는 Android Studio에서 간혹 Cordova의 Plugin을 수정할 때도 있다. 

  - 기본 XCode, Android Studio 사용법은 알아두는게 좋다. 직접 툴에서 빌드할 경우가 많다. 


현재 Ionic v2는 Angular v2를 기반으로 하고 아직 beta 버전이다 (2016.4.12).  2016년 상반기에 Angular v2 정식버전이 나오면 비슷한 시기에 정식버전이 나오리라 기대해 본다. Angular v2가 정식버전하에 하반기부터 본격적으로 쓰일 것으로 보이기때문에 Ionic도 v2를 사용하고 준비하면 좋을 것같다. Ionic v2를 사용하기 위해서는 따라서 다음과 같은 기초 지식도 필요하다. 

  - ES2015 JavaScript 스팩 상의 문법 추가 사항을 숙지해야 한다. OOP 스타일의 Syntax로 바뀌었다고 보면 된다.  

  - Angular v2 가이드문서 (반드시 TypeScript 기반으로 참조)를 최소 한번쯤은 보도록 한다. 

  - TypeScript 기반 Cordova 서비스를 통해 Native 접근이 필요할 것이다.  


알아야 할 것은 많지만 일단 설치부터 실행까지 보도록 한다. Ionic v2 CLI 명령어Cordova 공식 CLI 명령어도 한번 훑어보는게 좋다. Cordova는 현재 6.* 버전이 최신이다.





1. 설치하기 


먼저 Node.js를 설치한다. NPM(Node Package Manager)를 통해 Cordova와 ionic 프레임워크를 설치하고 Gulp 수행의 기반을 제공한다. 

  - typescript 컴파일러 설치

  - typings 는 typescript의 definition 파일을 관리하는 메니저이다. typings 폴더에서 관리된다. 

  - cordova는 iOS와 Android Native 접근을 위한 Gateway library라고 보면 된다. 

> sudo npm install -g   typings  typescript  cordova ionic@beta



다음으로 ionic에서 제공하는 샘플 파일을 자동 다운로드 받아 설치한다. ionic v2 에 typescript 버전으로 설치하는 명령어이다. 자동으로 typings폴더에 typescript definition 파일 및 package.json에 정의된 모듈도 node_modules 밑으로 자동 설치된다.

프로젝트생성) ionic start <projectName> <templateType> --v2 --ts

예) ionic start myFirst blank --v2 --ts


blank 템플릿 타입을 주면 https://github.com/driftyco/ionic2-starter-blank/archive/typescript.zip 에서 typescript 버전을 자동 다운로드해 설치한다.

templateType에는 tutorial, tabs, sidemenu, blank가 있다. 설치를 하면 다음과 같은 안내글이 나온다. 


Make sure to cd into your new app directory:

  cd ionic2-tutorial-github


To run your app in the browser (great for initial development):

  ionic serve


To run on iOS:

  ionic run ios


To run on Android:

  ionic run android


To test your app on a device easily, try Ionic View:

  http://view.ionic.io


New! Add push notifications, update your app remotely, and package iOS and Android apps with the Ionic Platform!

  https://apps.ionic.io/signup


New to Ionic? Get started here: http://ionicframework.com/docs/v2/getting-started


설명대로 ionic2-tutorial-github 폴더로 이동후 ioinic serve 명령을 수행하면 Gulp의 serve 태스커가 수행된다. 수행 결과로 ionic $ 명령콘솔이 활성화 된다.

> ionic serve 

WARN: ionic.config.js has been deprecated, you can remove it.

Running live reload server: http://localhost:35729

Watching: www/**/*, !www/lib/**/*

√ Running dev server:  http://localhost:8100

Ionic server commands, enter:

  restart or r to restart the client app from the root

  goto or g and a url to have the app navigate to the given url

  consolelogs or c to enable/disable console log output

  serverlogs or s to enable/disable server log output

  quit or q to shutdown the server and exit


ionic $





2. ionic 폴더 구조


ionic의 폴더구조 및 환경설정 파일은 다음과 같다. 


 app

 개발자가 작성하는 모든 애플리케이션 코드가 위치한다. 

 hooks (cordova)

 Cordova 빌드과정의 일부로서 동작될 수 있는 스크립트를 포함하고 있다. 앱을 패키지 할때 필요하다면 언제든 커스터마이징 할 수 있다.  

 node_modules

 npm을 통해 설치된 모듈들이 있다.

 platforms (cordova)

 ionic platform 으로 ios, android를 설치하면 하위 폴더로 생기고, XCode 또는 Android Studio에서 import할 수 있다.

 plugins (cordova)

 ionic platform 선택시 Cordova의 플러그인이 설치되는 폴더이다. 

 resources 

 앱을 위한 icon과 splash image를 해상도가 틀린 모바일 기기별로 놓는 곳이다.  

 typings

 Typescript로 쓰여지지 않는 JS 라이브러리의 타입정의를 한 type definition 파일이 있다.  

 www (cordova)

 index.html를 포함한다. 이곳은 빌드될 때 사용되는 것으로 애플리케이션 코드가 위치하는 곳이 아님을 주의하자.  "ionic build" 를 하면 "cordova build"가 수행되어 www 해당 디렉토리에 app의 코드가 위치하고 다시 platforms/ios 또는 android의 www 폴더에 copy된다. 따라서 최종 사용되는 파일은 platforms/ios (또는 android) /www/* 에 위치한다. 

 config.xml (cordova)

 앱 패키지를 만들때 사용하기 위해 Cordova의 환경설정이 존재한다. 

 ionic.config.js

 not used로 앞으로 없어질 것이다. ionic.config.json 파일은 버전 정보만 전달

 package.json

 npm 으로 설치되는 모든 모듈에 대한 설정 

tsconfig.json / typings.json

 TypeScript 환경 설정 / type definition file 환경 설정


크게 "환경파일", "Cordova", "애플리케이션" 부분으로 나뉠 수 있다. 최초 템플릿이 생성된 이후 개발자는 "애플리케이션"폴더인 "app"를 사용하면 된다. blank타입으로 만들었을 때 platforms 폴더에는 ios 플랫폼이 기본 설치된다. 







3. TypeScript 기반 개발


최근(2016.4.8) Beta버전에 CLI 를 통해 TypeScript 기반의 Angular v2 페이지와 서비스를 만들 수 있는 명령어를 공개했다. 파일이름은 kebob-case로 my-page와 같이 되고, ES6/TypeScript기반의 class는 PascalCase로 MyData로 처럼 이름을 준다. 

> ionic generate ( 또는 축약해서 g ) <page 또는 provider> <Name>


예)

> ionic g page myPage

create app/pages/my-page/my-page.html (.js, .css)


> ionic g provider MyData

create app/providers/my-data/my-data.js 


생성된 my-page.ts 소스는 다음과 같다. @Page는 TypeScript에서 제공하는 Decorator를 이용해 Class Decorator를 Ionic을 위해 만든 것이다. 

  - TypeScript의 Decorator에 대해 자세히 알고 싶다면 링크를 참조한다. 

import {Page, NavController} from 'ionic-angular';


/*

  Generated class for the MyPagePage page.


  See http://ionicframework.com/docs/v2/components/#navigation for more info on

  Ionic pages and navigation.

*/

@Page({

  templateUrl: 'build/pages/my-page/my-page.html',

})

export class MyPagePage {

  constructor(public nav: NavController) {

    this.nav = nav;

  }

}


.ts가 .js코드로 컴파일된 내역을 보고 싶다면 app 폴더로 이동해서 TypeScript 컴파일 명령어인 "tsc"를 수행한다. @Page는 __decorate({ ... }) 안에서 "ionic-angular"의 decorators/page.js 의 Class Decorator가 수행되는 것이다. ionic_angular_1.Page( <config object> ); 결국, @Page는 구현한 decorator 펑션을 호출해 주는 역할을 수행할 뿐이다. TypeScript Decorator 공식문서를 통해 개념을 이해하자. @Page는 Angular v2에서 제공하는 데코레이터가 아니라 Ionic에서 제공하는 것이다. 내부 소스를 보면 @Page 클래스는 ion-page selector를 사용하고 결국 Angular v2 core에 있는 @Component를 호출하고 있을 뿐이다. 즉, 굳이 @Page를 쓰지 않아되 되는 Decorating이다. 

// app/pages/my-page/my-page.js TypeScript 컴파일된 소스


"use strict";

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {

    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;

    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);

    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;

    return c > 3 && r && Object.defineProperty(target, key, r), r;

};

var __metadata = (this && this.__metadata) || function (k, v) {

    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);

};

var ionic_angular_1 = require('ionic-angular');

/*

  Generated class for the MyPagePage page.


  See http://ionicframework.com/docs/v2/components/#navigation for more info on

  Ionic pages and navigation.

*/

// 자동으로 postfix로 Page가 붙는다. 

var MyPagePage = (function () {

    function MyPagePage(nav) {

        this.nav = nav;

    }

    MyPagePage = __decorate([

        ionic_angular_1.Page({

            templateUrl: 'build/pages/my-page/my-page.html',

        }), 

        __metadata('design:paramtypes', [ionic_angular_1.NavController])

    ], MyPagePage);

    return MyPagePage;

}());

exports.MyPagePage = MyPagePage;



// node_modules/ionic-angular/decorators/page.js 데코레이터 구현 소스


function Page(config) {

    return function (cls) {

        // @Page 의 기본 selector

        config.selector = 'ion-page';  

        config.directives = config.directives ? config.directives.concat(directives_1.IONIC_DIRECTIVES) : directives_1.IONIC_DIRECTIVES;

        config.host = config.host || {};

        config.host['[hidden]'] = '_hidden';

        config.host['[class.tab-subpage]'] = '_tabSubPage';

        var annotations = _reflect.getMetadata('annotations', cls) || [];

        // 기본 설정후 Angular v2 core의 @Component를 호출한다. 

        annotations.push(new core_1.Component(config)); 

        _reflect.defineMetadata('annotations', annotations, cls);

        return cls;

    };

}

exports.Page = Page;


provider로 만든 것은 ES2015의 Promise, RxJS, @Injectable이 포함되어 있다. Angular v2에서는 모든 Inject되는 Service Class에는 Class Decorator로 @Injectable를 설정토록 권고한다. 

// app/providers/my-data/my-data.ts 소스 


import {Injectable} from 'angular2/core';

import {Http} from 'angular2/http';

import 'rxjs/add/operator/map';


/*

  Generated class for the MyData provider.


  See https://angular.io/docs/ts/latest/guide/dependency-injection.html

  for more info on providers and Angular 2 DI.

*/

@Injectable()

export class MyData {

  data: any = null;


  constructor(public http: Http) {}


  load() {

    if (this.data) {

      // already loaded data

      return Promise.resolve(this.data);

    }


    // don't have the data yet

    return new Promise(resolve => {

      // We're using Angular Http provider to request the data,

      // then on the response it'll map the JSON data to a parsed JS object.

      // Next we process the data and resolve the promise with the new data.

      this.http.get('path/to/data.json')

        .map(res => res.json())

        .subscribe(data => {

          // we've got back the raw data, now generate the core schedule data

          // and save the data for later reference

          this.data = data;

          resolve(this.data);

        });

    });

  }

}





5. ionic 페이지 만들기 


일단, ionic generate를 통해 생성된 page, provider (서비스)는 참조만 한다. 다음과 같이 서비스를 파일을 만든다. Angular2 http 모듈이 RXJS를 사용하고 있고 return 오브젝트가 Promise가 아니라 Observable이다. RXJS는 Reactive Programming의 자바스크립트 구현체로 링크를 참조한다.

import { Injectable } from 'angular2/core';

import { Http, Headers } from 'angular2/http';


@Injectable()

export class GitHubService {

    constructor(private http: Http) {}


    getRepos(username) {

        let repos = this.http.get(`https://api.github.com/users/${username}/repos`);

        return repos;

    }

}


다음으로 /app/pages/home/home.ts를 수정한다. @Component 대신에 @Page를 사용하고 providers로 GitHubService를 정의한다. 

import {Page} from 'ionic-angular';

import {GitHubService} from '../../providers/github.service';


@Page({

  templateUrl: 'build/pages/home/home.html',

  providers: [GitHubService]

})

export class HomePage {

  public foundRepositories;

  public username;

  constructor(private github: GitHubService) {}

  getRepos() {

    this.github.getRepos(this.username)

      .subscribe(

          data => {

              this.foundRepositories = data.json();

          },

          err => console.error(err),

          () => console.log('getRepos completed')

      );

  }

}


home.html도 다음과 같이 재정의한다. 

<ion-navbar *navbar>

<ion-title>

Home

</ion-title>

</ion-navbar>


<ion-content class="home">

<ion-list inset>

<ion-item>

<ion-label>Username</ion-label>

<ion-input [(ngModel)]="username" type="text"></ion-input>

</ion-item>

</ion-list>

<div padding>

<button block (click)="getRepos()">Search</button>

</div>

<ion-card *ngFor="#repo of foundRepositories">

<ion-card-header>

{{ repo.name }}

</ion-card-header>

<ion-card-content>

{{ repo.git_url }}

</ion-card-content>

</ion-card>

</ion-content>


ionic CLI 명령을 통해 테스트를 해보자. serve 명령을 이용하면 로컬 서버를 띄워서 브라우져상에서 테스트를 해볼 수 있다. 옵션으로 --lab을 주면 ios, android 둘 다 테스트가 가능하다. 

> ionic serve --lab 



ionic의 ion-* 시작하는 Angular component를 사용하면 mobile 플랫폼에 따라 Look and feel이 자동으로 맞춰진다. 이를 원하지 않을 때는 최소한의 ion-* 태그만을 사용하면 된다. ion-navbar 와 ion-content 태그만을 사용하고 나머지는 서비스에 맞게 HTML을 작성해도 무방하다.





6. ionic Navigation 추가하기 


네비게이션을 위해 메인 컴포넌트인 app.ts를 살펴보면, ionic에서 제공하는 @App 데코레이터를 통해서 HomePage 를 최상위 페이지로 설정하고 있다.

import 'es6-shim';

import {App, Platform} from 'ionic-angular';

import {StatusBar} from 'ionic-native';

import {HomePage} from './pages/home/home';


@App({

  template: '<ion-nav [root]="rootPage"></ion-nav>',

  config: {} // http://ionicframework.com/docs/v2/api/config/Config/

})

export class MyApp {

  rootPage: any = HomePage;


  constructor(platform: Platform) {

    platform.ready().then(() => {

      // Cordova 준비

      StatusBar.styleDefault();

    });

  }

}


GitHub 저장소 목록에서 상세페이지 이동을 위한 detail 페이지를 만들고, 일단 home.html 과 .ts 파일을 수정한다.

// home.html 에서 click 이벤트를 추가한다. 

<ion-card *ngFor="#repo of foundRepositories" (click)="goDetail(repo)">


// home.ts 에 Navigation을 위한 controller와 추가 메소드를 정의한다. 

import {Page, NavController} from 'ionic-angular';

import {GitHubService} from '../../providers/github.service';

import {DetailPage} from '../detail/detail'


@Page({

  templateUrl: 'build/pages/home/home.html',

  providers: [GitHubService]

})

export class HomePage {

  public foundRepositories;

  public username;

  constructor(private github: GitHubService, private nav: NavController) {}

  getRepos() {

    this.github.getRepos(this.username)

      .subscribe(

          data => {

              this.foundRepositories = data.json();

          },

          err => console.error(err),

          () => console.log('getRepos completed')

      );

  }

  goDetail(repo) {

      // 페이지를 Stack에 추가하는 것이다. 

      this.nav.push(DetailPage, { repo: repo });

  }

}


home.html 상단의 ion-navbar 태그는 pagenavigation하면 <Back 버튼이 자동으로 생긴다. 즉, <Back 버튼을 클릭하는 순간 이동한 페이지를 pop 하는 것이 자동으로 이루어 지기때문에 명시적으로 해줄 필요가 없다. 다음으로 저장소의 README 파일 내역을 HTML 포멧으로 가져오는 메소드를 GitHubService에 추가한다. 

// github.service.ts


getDetail(repo) {

        let headers = new Headers();

        headers.append('Accept', 'application/vnd.github.VERSION.html');

        return this.http.get(`${repo.url}/readme`, {headers: headers});

}


상세내역을 detail.ts를 통해 상세정보를 가져오고 다시 detail.html에 뿌려준다. NavParams를 통해 전달받은 repo 객체의 url로 README 값을 받아온다. detail.html에서는 아래 소스처럼 {{ readme }} 표현식을 사용하면 HTML 태그가 text로 뿌려질 뿐이다. 따라서 innerHTML 속성을 이용한다. 

// detail.ts 


import {Page, NavController, NavParams} from 'ionic-angular';

import {GitHubService} from '../../providers/github.service';


@Page({

  templateUrl: 'build/pages/detail/detail.html',

  providers: [GitHubService]

})

export class DetailPage {

  public readme = '';

  public repo;

  constructor(private nav: NavController, private github: GitHubService, private navParams: NavParams) {

      this.repo = navParams.get('repo');

      this.github.getDetail(this.repo)

        .subscribe(

            data => this.readme = data.text(),

            err => {

                if(err.status == 404) {

                    this.readme = 'This repo does not have a README file';

                } else {

                    console.error(err);

                }

            },

            () => console.log('getDetail completed')

        );

  }

}



// detail.html 

<ion-navbar *navbar>

  <ion-title>{{ repo.name }}</ion-title>

</ion-navbar>


<ion-content padding class="detail">

  <div padding>{{ readme }}</div>

</ion-content>



detail.html을 text가 아니 DOM으로 넣기 위해 innerHTML속성으로 변경하고 최종 테스트 한다. 파일을 변경하면 ionic은 파일을 watch하고 있다가 자동으로 refresh 해준다. (ionic serve 경우)

<ion-content padding class="detail">

  <div padding [innerHTML]="readme"></div>

  <!-- <div padding>{{ readme }}</div> -->

</ion-content>


ios와 Android 플랫폼에 맞는 "< Back" 버튼이 생성된다. github 사용자 이름을 입력하고 목록이 나오면 목록중의 카드하나를 클릭하면 다음과 같이 Navigation되는 것을 볼 수 있다. ionic은 Tabs 형태 또는 Menus형태의 UI 컨테이너를 통해 Navigation을 Mobile UX에 맞게 변경할 수 있다. 





7. ionic 배포와 테스트 


ionic serve 명령으로 로컬 웹서버를 띄워 로컬 웹 브라우져를 통해 기능을 테스트 했다면 emulate 또는 기기에 App을 배포해서 테스트 한다. 브라우져에서 보는 것과 실 기기에서 테스트하는 것은 하늘과 땅 차이다. 희소식은 XCode 7 부터는 Developer Account(유료)없이도 USB를 통해 iPhone에 App을 배포 테스트할 수 있다. 

// 로컬에서 xcode emulate을 띄워준다. 

> ionic emulate ios


// 연결된 기기에서 수행할 수 있다

> ionic run ios 


MacBook 상에서 ionic run을 위해서는 "npm install -g ios-deploy" 사전에 설치가 필요하다. 



또는 ionic viewer를 모바일 기기에 설치하고 프로젝트를 ionic.io 서비스에 자신의 계정에 upload한 후 작동 여부를 테스트 할 수 있다. 하지만 작동중 디버깅에 대한 부분은 보다 많은 지면을 필요로 하기에 다음에 언급한다. upload시에 로그인 안되어 있으면 등록한 id/pwd를 물어본다. 

> ionic upload 

WARN: No 'upload:before' gulp task found!

If your app requires a build step, you may want to ensure it runs before upload.


Uploading app....

Saved app_id, writing to ionic.io.bundle.min.js...

Successfully uploaded (f3ded32c)


Share your beautiful app with someone:


$ ionic share EMAIL

Saved api_key, writing to ionic.io.bundle.min.js...


최근에는 Xamarin이나 React Native, NativeScript 같은 Native Code로 전환해 주는 프레임워크가 인기를 끌고 있다. 모바일만을 고려하지 않는다면 ionic은 웹표준 기술을 통해 빠른 기능개발과 Angular2기반의 프론트앤드의 서비스와 컴포넌트(만일 Web을 Angular2로 개발한다면)를 공유할 수 있다. 즉, Native Mobile UX/UI 특성과 빌드 환경의 편의성은 Ionic 프레임워크를 사용하면서 웹개발도 도모할 수 있는 Hybrid WebApp + Web 개발이 가능하다 판단된다. 


향후 angular2-seedNativescriptElectron을 접목한 angular2-seed-advanced가 나왔듯이, angular2-seed에 Ionic2를 접목한 seed를 만들 예정이다. 





참조


  - ionic v2 공식 문서

  - Vinyl 개념 이해하기 & Stream Handbook

  - Cordova 공식 홈페이지 문서

  - TypeScript Decorator 만들기

  - 블로깅 참조 

  - 다양한 모바일 기기 테스트 방법들

  - Ionic2 Conference App 소스

  - Ionic.io 의 다양한 서비스들: Push, Deploy, Analytics, User

  - Crosswalk



자료


  - Ionic Advantures

  - Ionic Collection

  - Ionic Resources

  - AppCamp

  - play.ionic.io: playground


     


posted by 윤영식
2016. 1. 15. 08:16 Angular/Concept

디자인 가이드는 초기 버전의 생각이므로 현재(2015.1.15) beta 버전의 구문 형식과 틀릴 수도 있다는 것을 숙지하고 어떤 사상을 가지고 있는지 알아보는 것에 집중하자. 물고기가 아니라 물고기 잡는 그물에 집중하자는 이야기이다. 




Expressions


Angular의 expression은 Angular1의 expression 과 유사하고 몇가지 더 추가되었다. 


  - Dirty Checking과 긴밀히 통합되어 expression의 prefix 또는 constant 부분에 대한 최적화를 수행한다. 

    좀 더 자세한 사항은 dirty checking 디자인 가이드를 참조한다. 

  - One time binding을 {{::foo}} 와 같이 최초 한번만 바인딩되고 이후 바뀌지 않도록 한다. Angular1에서 사용하던 방법




Directive API


Directive는 ES6 classes를 사용해 정의하고, Annotation을 사용해 meta data를 정의한다.



Class annotation


모든 Directive는 @DecoratorDirective, @TemplateDirective, @ComponentDirective 중 하나의 annotation을 갖는 ES6 class 이다. Annotations는 일반적인 properties를 갖는다. 


  - selector: directive가 적용될 element를 찾을 css selector이다. (string)

  - events: directive가 template안에서 on-.. attribute를 사용해 발생시키는 이벤트 목록이다. (list of string)

  - visibility: element의 subtree안의 directives들이 directive 인스턴스에 접근할 수 있도록 정의한다. ['local' | 'direct-children' | 'any-children']

  - microsyntax: 특별한 properties 구문 형식과 이 properties가 다른 properties와 어떻게 맵핑하는지 정의한다 (string)


아래 예를 보면 NgShowDirective 생성을 통해 element에 ng-show attribute를 사용할 수 있게 한다. 즉, annotation은 툴이 element안에 directive를 인스턴스화하는 과정없이 그리고 코드를 실행하지 않고도 directives를 찾게한다.

import {ng} from '...';


@ng.DecoratorDirective(selector: '[ng-show]')


class NgShowDirective {}



Constructor 에 Denpendency Injection 하기


모든 directives는 Dependency Injection을 통해 contructor의 parameters 값을 갖는다. 즉, DI를 통해 다른 오브젝트 인스턴스틀 갖질 수 있는 것이다. 아래 예를 보면 @Inject annotation을 사용해 DI하고 있다. 

import {ng} from '...';


@ng.DecoratorDirective(selector: '[ng-show]')


class NgShowDirective {

  @Inject(window.HTMLElement, ng.Http, ng.SomeOtherDirective)

  constructor(element, http, someOtherDirective) { ... }

}



Directive communication


http 갖은 것은 ui 오브젝트가 아니기 때문에 element의 subtree안의 모든 directives에 접근할 수 있도록 annotation에 visibility 을 사용할 수 있다. 예로 input element에서 user.value에 대해 NgInputDirective는 input 값의 변경을 Listening하고, NgModelDirective가 validation하며, NgFormDirective은 state를 유지하는 작업을 한다. 여기서 NgModelDirective는 <form> element에 있는 NgFormDirective에 접근할 필요가 있고, NgInputDirective는 같은 element에 있는 NgModelDirective에 접근할 필요가 있다. 이를 아래와 같이 visibility를 설정한다. 

<form>

  <input type="text" ng-model="user.value" required>

</form>


import {ng} from '...';


@ng.DecoratorDirective(

  selector: 'form',

  visibility: 'subtree'

)

class NgFormDirective {

  constructor() { … }

}


@ng.DecoratorDirective(

  selector: 'input[type=text]',

  visibility: 'local'

)

class NgModelDirective {

  @Inject(NgFormDirective)

  constructor(ngForm) { … }

}


@ng.DecoratorDirective(selector: 'input[type=text]',)

class NgInputDirective {

  @Inject(NgModelDirective)

  constructor(ngModel) { … }

}



Bindable properties


Directives의 모든 properties는 데이터 바인딩을 사용할 수 있고, 이를 위해 set(write)에 대해 @ng.PropertySet annotation을 통해 환경설정을 한다. minification 후에도 property 명을 보존하고 directive의 properties에 데이터 바인딩을 쉽게 만들기 위함이다. 


  - 구문: @ng.PropertySet({trigger: ['reference' | 'collection' | 'deferred'], domOnly: [false|true] })

  - trigger

     + reference: reference가 바뀌면 reference 안에서 전달 (default)

     + collection: collection의 entry가 바뀌면 collection 안에서 전달 

     + deferred: directive가 수행할 수 있는 컴파일된 expression을 전달

  - domOnly: DOM만 변경하고 watch하고 있는 다른 property는 제외함 (default: false)

  

예 

import {ng} from '...';


@ng.DecoratorDirective {

  selector: 'dialog'

}

class Dialog {

  constructor() {

    this._content = null;

  }

  get content(content) {

    return this._content;

  }

  @ng.PropertySet({domOnly: true})

  set content(content) {...}

}


<dialog content=”{{1+2}}”>



Properties를 위한 Microsyntax


directive가 여러 값을 다룰 필요가 있을 때 microsyntax로 설정을 한다. 예로 ng-repeat을 하면 track-by expression을 해야할 때. 구문은 다음과 같다. 


  - MICROSYNTAX=(VARIABLE|FIXED|OPTIONAL)+

  - VARIABLE=$\w+

  - FIXED=[^$\[\]]+

  - OPTIONAL=\[MICROSYNTAX\]


ng-repeat을 위한 microsyntax 예를 보면 <div>에 ng-repeat-item-name, ng-repeat-collection, ng-repeat-track-by를 사용한다. 

@TemplateDirective(

  selector: '[ng-repeat]'

  microsyntax: {

   'ng-repeat': '$item-name in $collection [track by $track-by]'

  })

class NgRepeat {

  @ng.PropertySet({trigger: 'reference'})

  set ngRepeatItemName() { … }


  @ng.PropertySet({trigger: 'collection'})

  set ngRepeatCollection() { … }


  @ng.PropertySet({trigger: 'deferred'})

  set ngRepeatTrackByFn;

}



<div ng-repeat="item in items track-by item.id">

<div ng-repeat-item-name="item" ng-repeat-collection="items" ng-repeat-track-by="item.id">



Lifecycle hooks 


모든 directives는 아래 특별한 함수를 구현할 수 있다. 이유는 templates이 initialization과 finalization을 알수 있도록 허용하기 위해 lifecycle hook을 둔다. 


  - attach: templates 안에 바인딩이 된 후 한번만 호출된다. 

  - detach: directive의 element가 destroy될 때 호출된다. 


Component directive는 특별히 아래 함수도 구현할 수 있다. 


  - templateLoaded: component의 template이 로딩되어 component element에 포함될 때 호출된다. 



Events


Directives는 HTMLElement.dispatchEvent를 사용해 사용자 정의 events를 발생시킬 수 있고, HTMLElement.addEventListener를 통해 이벤트를 Listening할 수 있다. 이벤트는 tree를 타고 bubble up 된다. 설정은 아래 예와 같이 class annotation 안에 하고, event가 발생하면 dirty checking 스케쥴이 돌고 template안에 on-... attributes 를 validation한다. 

 @ng.DecoratorDirective(

  selector: 'dialog',

  events: ['close']

)

class Dialog {

  @Inject(window.HTMLElement)

  constructor(element) { ... }

  close() {

    var evt = new Event('close');

    this.element.dispatchEvent(evt);

    if (!evt.defaultPrevented()) {

      // really close the dialog...

    }

  }

}




Directive 구현 예


Decorator: ng-show

import {ng} from '...';


@ng.DecoratorDirective(

  selector: '[ng-show]'  

)

class NgShow {

  @Inject(window.HTMLElement)

  constructor(element) {

    this.element = element;

  }


  set ngShow(value) {

    this.element.style.display = value ? 'block' : 'hidden';

  }

}


Template Directive: ng-if

template 태그의 content를 사용하는 간단한 template directive 이다. 

import {ng} from '...';


@ng.TemplateDirective(

  selector: '[ng-if]'  

)

class NgIf {

  @Inject(ng.ViewHole, ng.ViewFactory, Injector)

  constructor(viewHole, viewFactory, injector) {

    this.viewFactory = viewFactory;

    this.viewHole = viewHole;

    this.injector = injector;

    this.view = null; 

  }


  set ngIf(value) {

    if (this.view) {

      this.viewHole.remove(this.view);

    }

    if (value) {

      this.view = this.viewFactory(injector);

      this.viewHole.add(this.view);

    }

  }

}


Template Direcive: ng-include

import {ng} from '...';


@ng.TemplateDirective(

  selector: '[ng-include]'

)

class NgInclude {

  @Inject(ng.ViewHole, ng.Compiler, ng.Http, ng.Directives,

          Injector)

  constructor(viewHole, compiler, http, directives, injector) {

    this.viewHole = viewHole;

    this.compiler = compiler;

    this.directives = directives;

    this.injector = injector;

    this.view = null;

    this.$http = http;

  }


  set ngInclude(value) {

    var self = this;

    this.$http(value).always(removeView).then(addView);


    function removeView() {

      if (self.view) {

        self.viewHole.remove(self.view);

        self.view = null;

      }

    }

    function addView(templateString) {

      var viewFactory = 

            this.compiler(templateString, this.directives);

      this.view = viewFactory(injector);

      this.viewHole.add(this.view);

    }

  }

}


Templat directive: ng-repeat

template directive는 새로운 execution context를 제공할 수 있고, 아래 간단한 예에서 variable을 injector안에 있는 현재 execution context에서 검증한다. 

import {ng} from '...';


@ng.TemplateDirective(

  selector: '[ng-repeat]'

  microsyntax: {

   'ng-repeat': '$item-name in $collection [track by $track-by]'

  })

class NgRepeat {

  @Inject(ng.ViewHole, ng.ViewFactory, Injector injector)

  constructor(viewHole, viewFactory, injector) {

    this.viewHole = viewHole;

    this.viewFactory = viewFactory;

    this.injector = injector;

    this.views = [];

  }

  @ng.PropertySet({trigger: 'reference'})

  set ngRepeatItemName(value) { 

    this.itemName = value; 

    this.update(); 

  }

  @ng.PropertySet({trigger: 'collection'})

  set ngRepeatCollection(arrayChangeRecord:ArrayChangeRecord) { 

    this.arrayChangeRecord = arrayChangeRecord;

    this.update(); 

  }

  @ng.PropertySet({trigger: 'deferred'})

  set ngRepeatTrackByFn(value) { 

    this.trackByFn = value; 

    this.update();

  }

  update() {

    …

    // deleting a view    

    this.viewHole.remove(deletedView);

    …

    // creating a new view

    var childContext = {

         $index: index,

         $odd: !!index%2,

         $even: !index%2 

    };

    childContext[this.itemName] = this.collection[index];

    var view = this.viewFactory(injector, childContext);

    viewHole.append(view);

   }

   ...

  }

}


Component Directive: pane

title이 있는 pane 구현 예

import {ng} from '...';


@ng.ComponentDirective(

  select: 'pane',

  template: ng.inline('<div>{{title}}</div><content/>'),

  css: ng.url('pane.css'),

)

class sample.Pane {

  constructor() {}

  set title() { … }

}




다른 Web Component 프레임워크에 component를 publishing하거나 consuming하기 



Custom Elements로 X-Tags, Polymer를 사용하는 프레임워크 


Angular는 Custom element API가 유효한지 찾고 custom element로 컴포넌트를 자동 등록하고, component directive의 properties가 custom element의 인스턴스를 접근할 수 있게 한다. 


  - custom element로 Angular component를 export 할 경우 

    + custom element API가 유효하면 Javascript 애플리케이션에서 Angular component를 DOM element 접근방식으로 event의 listen과 properties를 접근가능케 만든다.

  - Angular 애플리케이션에서 custom element 사용할 경우 

    + Angular는 native DOM element를 사용하듯이 custom elements를 사용할 수 있고, event도 listen할 수 있다.



가이드-1 다시 보기



<참조> 


  - Angular Templating 디장인 가이드 

  - Creating, Triggering Events 모질라 문서 

posted by 윤영식
2016. 1. 12. 07:51 Angular/Prototyping

이번에는 앵귤러 컴포넌트가 어떻게 웹 컴포넌트가 될 수 있는지 보자. 본 내용은 앵귤러 2의 디자인 가이드 중 Web Components에 대한 내용을 바탕으로 한다. (디자인 문서는 초기 버전이고 최신 버전의 변경분 반영이 안될 수 있다. 말 그대로 초기 생각의 흔적으로만 보고, 정의한 것들이 확정되었다곤 생각하지 말자. 따라서 변경된 내역도 나중에 찾아 보아야 한다)



Angular Component as a Web Components 


Angular Component가 Web Components가 되기위한 기본은 events, properties와 자바스크립트나 타 프레임워크에서 접근가능하고 Web Components에 적용되는 methods(API)를 갖는 DOM element를 만드는 것이다. Template 디자인 가이드에 나온 아래 그림을 보면 Template을 갖는 Component Directive로 만들면 Web Components로 Export할 수 있고, Import할 수 있다. 





Publishing events, properties, methods


앵귤러로 컴포넌트를 만들어 보자.

@ComponentDirective({

  selector: 'x-zippy'

  template: ...

})

class ZippyComponent {

  constructor(events) {

    this.events;

  }


  @Property('title')

  get title() ...;

  set title(value) ...;


  @Property('isOpen')

  get isOpen() ...;

  set isOpen(value) {

    if (value != this.isOpen) this.toggle();

  }


  @Publish('open')

  open() {

    this.isOpen = truefalse;

    this.events.fire('open');

  }


  @Publish('close')

  close() {

    this.isOpen = falsetrue;

    this.events.fire('close');

  }


  @Publish('toggle')

  toggle() {

    value ? close() : open();

  }

}


@Proptery 와 @Publish 에노테이션을 통해 앵귤러가 앵귤러 프레임워크없이도 엘러먼트 자체에 접근할 수 있는 properties/methods를 노출하고 있다. 사용예는 다음과 같다.

<x-zippy title="Hi YoungSik">

   Some thing...

</x-zippy>


native 엘러먼트처럼 사용되어 지는 컴포넌트가 되기위해 다음과 같이 동작해야 한다. 

var tab = _lookup_custom_element;

var log = null;

tab.addEventListener('open', function() { log = 'open'; })

tab.addEventListener('close', function() { log = 'close'; })


tab.close();

expect(tab.isOpen).toEqual(false);

expect(log).toEqual('close');


tab.toggle();

expect(tab.isOpen).toEqual(true);

expect(log).toEqual('open');  


 

Custom Element 등록하기(Registering)


Angular Component가 Web Components가 되기 위한 마지막은 앵귤러의 스크프밖에서 인스턴스화 할 수 있어야 한다. 이를 위한 기본 생각은 다음과 같이 Angular가 customElementFactory를 registerElement API를 통해 등록하는 방식이다. 

var injector = angular.createInjector(setOfModules);

var customElementFactory = injector.get(CustomElementFactory);

var Greeter = customElementFactory(GreeterDirective);

document.registerElement('x-greeter', { prototype: Greeter });


var div = document.createElement('div');

div.innerHTML = '<greeter>'; // trigger internal DOM parser.

var greeter = div.firstChild; // retrieve the greeter


// Notice that we are now instance of Greeter

expect(greeter instanceOf Greeter).toBe(true);


// Notice that it can be used just like any other DOM Element

greeter.name = 'world';

greeter.greet();



이후 디자인 가이드 내용이 갑작이 끝나버린 관계로 다른 문서들을 뒤적여 보기로 한다.   To be continued...



<참조>

  - Web Components 디자인 가이드 


posted by 윤영식
2016. 1. 11. 07:58 Angular/Prototyping





앞으로의 Web App 개발은 Component기반 개발 방식으로 진행될 가능성이 높다. 아니 그렇게 될 것으로 보인다. 왜냐하면 표준 제정 기구와 기업이 원하고 그안에서 일하는 개발자들이 원한다. 여기서 기구는 W3C에서 스펙작업을 하고 있고, 기업은 구글형님이 열심히 삽질하며 진행을 하고 있다. 역시 삽질 내공은 구글이 참 잘 하는것 같다. 돈이 많아서 그런다기 보다는 미래의 먹거리를 위해 열심히 뛰는 양상이다. 


애플은 여기서 한발 물러나 있다. 그들의 주 수입은 하드웨어판매와 플랫폼에서 발생하는데, 여기서 말하는 플랫폼은 iOS 장터에서 거래되는 대부분의 수익이 Native App에서 발생한다. 따라서 굳이 Web에 열심히 힘쏫을 필요가 없다는 이야기다. 애플이 그냥 조금씩 서두리지 않고 곁눈질만 하는 놈이라면 구글은 광고로 돈을 벌고 그 광고는 웹에서 발생하니 웹의 외연 확장을 위해 Web Components는 중요한 기술로 생각한다. 그래서 열심히 3년전부터 구글 I/O를 통해 Polymer의 진행상황을 공유하고 있고, 꾸준히 삽질해 주면서 업계를 리딩할 것으로 보인다. 



"Web Components는 웹상(DOM/CSS 렌더링 엔진이 탑재된 모든 것)에서 Native Element와 같은 지위를 얻어 컴포넌트 기반 개발을 가능케 하는 핵심 스펙이다." 



라고 나는 생각한다. 기존 브라우저에서는 기본 정의된 Tag들만 해석을 할 수 있지만, 얼마든지 내가 만든 컴포넌트가 브라우저가 Native하게 해석을 해준다면... 그러니 Native 엘러먼트가 되면 Angular, Ember, React, Knockout 같은 프레임워크나 라이브러리 컴포넌트와 자연스럽게 붙여서 상호작용토록 사용할 수 있지 않겠는가? 앵귤러를 예로 들면 AngularJS v1.* 에서 Web Components와 엮어서 써볼려는 눈물겨운 노력도 있었지만 말 그대로 그냥 시도라고 보자. 근데 이상한 것은 Angular 도 구글! Web Components의 Polyfill 라이브러리인 Polymer도 구글! Future Web App Developments 방향이 Component based Development이니 Angular 프레임워크도 이 방향으로 새롭게 갱생할 필요를 느꼈을 것이다. 그래서 2년전부터 열심히 개발중인 Angular v2.* 에서 Web Components와 함께 쓸 수 있는 방향을 정의했다. 


무엇이라고 이야기하는지 보자.

allow Angular to consume any Web Components without resorting to some Angular unique APIs and or conventions. (Proposal for other frameworks require that component be aware of framework specific API )

  웹 컴포넌트가 네이티브로 인지 되면서 자연스럽게 Angular가 웹 컴포넌트를 소비할 수 있을 것이다. 이를 위해 Angular가 장치를 마련해 두려고 노력을 하고 있고, Misko가 컨퍼런스에서 이야기 하고 있다. 


- allow Angular components to be packaged as Web Components and be used in other frameworks or in vanilla JavaScript.

  앵귤러 컴포넌트가 웹 컴포넌트로도 패키지 된다. 


ng-conf 2015에서 미스코가 말한 Angular1에서 Angular2로 가면서 Syntax 표현이 바뀌게 된 이유와 Web Components를 어떻게 Angular2와 결합해 사용할 수 있는지 설명한다. 좀 박수를 쳐주고 싶다. 놀라울 따름... 잘 모르겠으면 아래의 디자인 가이드를 보자. 




Custom Element Interface


브라우저가 제공하는 엘러먼트와 Custom 엘러먼트는 구분을 하지 않고 모든 것을 DOM element로 보는 것이 웹 컴포넌트임을 전제로 한다. DOM element의 API 구성은 다음과 같다.


  - DOM events

  - DOM attributes 

  - Element properties 

  - Element methods 


DOM은 Domcument Object Model의 약어이고 이는 HTML/XML을 객체로 변화하고 객체 안에 어떤 API가 있어야 하는지 정의해 놓았다. 일단 모든 DOM의 최상위 인터페이스는 Node이고 하위로 크게 3가지인 Document, Element, Text가 존재한다. Custom element API는 events, attributes, properties, methods를 갖는다고 볼 수 있고 해당 엘러먼트에 대해서 두가지 관점으로 접근한다면 "사용하는 입장(Consumer)"과 "엘러먼트는 구현하는(implementer) 입장"이 있다. 먼저 엘러먼트 접근 사용하는 경우를 보자. 



Interface Consumer


접근해서 사용하는 입장의 4가지 경우. 여기서 native와 custom element간의 접근/사용에 대한 방법상의 차이는 전혀 존재하지 않는다.

var element = ... // element를 얻어온다


// DOM attributes

element.setAttribute('title', 'hi dowon');


// DOM event

element.addEventListener('change', ...);


// Element property 

element.value = 'youngsik';


// Element method 

element.focus();



Interface Implementer


Element를 구현하는 입장은 Custom Element를 만드는 것이고 Native element와 동일한 위치를 부여 받도록 Web Components 스펙을 제정한 것이다. 이때 구현을 위해 필요하는 영역으로 크게 4가지가 있다. 


  - Custom Element

     엘러먼트를 어떻게 만들지 API를 정의한다. 

     HTMLElement.prototype을 확장한다


  - HTML Templates

    엘러먼트의 내용을 Out of Box로 분리해서 만드는 방법을 정의한다. 

    <template id="my-element"> 태그안에 정의


  - HTML imports

    템플릿의 확장자는 .html이고 이를 import하는 방법을 정의한다. 비동기적 import. 

    <link rel="import" href="my-element.html"> 


  - Shadow DOM

    템플릿을 import한 후 사용하는 방법을 정의한다. Out of Box로 JS, CSS를 격리(Boundary)한다. 

    Document 노드 밑에 Root Node는 HTML 이듯이 Custom Element 밑의 Root 는 Shadow Root (Node)이다. 

    var shadow = node.createShadowRoot(); 처럼 Component안에서 Root Node 역할을 하는 Shadow Root Node를 생성한다. 




DOM Elements and Data-binding Frameworks


Custom Element와 앵귤러와 같은 프레임워크간에 데이터 바인딩을 위해 별도의 규칙을 강제하면 안된다. 이러한 문제를 프레임워크에서 해결을 해야한다. 데이터 바인딩에는 두가지 방향이 있다. 


  - 엘러먼트에 데이터 쓰기

     native 엘러먼트에 있는 API를 사용해 property 또는 attribute에 접근한다.  

  

  - 엘러먼트에서 데이터 읽기 

    항시 property를 읽어온다. attribute는 tag 에 표현되는 정적이 값으로 최초에 설정되고 그 이후 바뀌지 않고 tag가 DOM tree안의 Object 변경되어 property를 접근할 수 있다. 또한 attribute는 변경이 안되지만 property는 변경이 가능하다. 


앵귤러에서 이러한 property 변경을 감지하기 위해 Change Detection 이 필요하다. 일단 변경하는 방법은 엘러먼트의 속성(property)를 통해 아래와 같이 가능하다. 

// element 얻어오기 

var input = ...;


// 변경이 감지되었을 때 element의 property를 통해 변경을 수행

input.value = newValue;


그렇다면 변경의 감지는 어떻게 할까? 프레임워크도 DOM events를 이용해서 아래와 같이 감지하는 방법이 필요하다. 

var input = ...;


// 프레임워크가 input event를 등록한다. 

imput.addEventListener('input', () { 

   // 값을 읽어온다. 

   var newValue = input.value;


  // 변경 감지 시스템에 알려준다. 

  framework.notify(input.value);

});


그럼 Web Components 스펙으로 작성된 엘러먼트의 내부 상태 정보의 변경을 어떻게 Detection할까? 이에 대해 Web Components에는 스펙이 없다. 따라서 native element의 기존 이벤트를 적절히 사용해야 한다. 



Angular and Web Components


앵귤러가 Custom 엘러먼트를 어떻게 사용하는지 프롬프트 창에 텍스트를 입력하는 Web Components 예제를 통해 알아보자.


 

프롬프트 API 


  - Element properties 종류 

    + title: 다이얼로그 타이틀 텍스트

    + content: 사용자가 타이틀을 수정하면 반영될 property. 컴포넌트는 edited 이벤트 발생 

    + visible: 다이얼로그가 보이면 true, visible의 변경에 따라 open/closed 이벤트 발생


  - Element methods 종류 

    + open()

    + close()


  - DOM Attributes

    + accept-text: accept 버튼에 보여지는 텍스트

    + cancel-text: cancel 버튼에 보여지는 텍스트 


  - DOM Events 

    + accepted: accept 버튼을 클릭하면 발생하는 이벤트 

    + canceled: cancel 버튼을 클릭하면 발생하는 이벤트 

    + open: 다이얼로그 박스가 오픈하면 발생하는 이벤트

    + close: 다이얼로그 박스가 닫히면 발생하는 이벤트 

    + edited: 다이얼로그박스에 텍스트 입력할 때 발생하는 이벤트 


var Prompt = Object.create(HTMLElement.proptype, {

  // property 정의

  title: { get: function() {...}, set: function(value) {...}},

  content: { get: function() {...}, set: function(value) {...}},

  visible: { get: function() {...}, set: function(value) {...}},

  

  // attribute notification 정의 

  attributedChangedCallback(name, old, new) {...},


  // methods 정의

  open: function() {...},

  close: function() {...} 

});


document.registerElement('x-prompt', { prototype: Prompt });



Web Components 인스턴스 만들기


커스텀 엘러먼트로 사용하기 위해 registerElement를 해서 Web Component로 등록을 하면 이름을 통해 언제나 DOM Element를 인스턴화할 수 있다. 아래 보는 것처럼 앵귤러에서 사용하기 위해 앵귤러에서 추가 조치로 해야할 것이 없다. 

<div ng-repeat="item in items">

  <x-prompt></x-prompt>

</div>

 

 

이벤트 리스닝(Listening)


Custom Element가 자신의 open 이벤트를 발생시키면 Angular에서 리슨(Listen)할 수 있어야 한다. Angular 1에서는 ng-click같은 디렉티브가 설정되어 있어야 리슨을 할 수 있었다. 즉, 버전 1 로 하면 open 이벤트에 대한 디렉티브가 있어야 한다는 소리이다. 그래서 Angular 2는 디렉티브 없이 on-*을 붙이면 커스텀 이벤트도 리슨할 수 있게 제안하고 있다. (위에 ng-conf 2015에서 미스코가 후반부에 Web Components 사용예를 보면 (close)="doSomething()" 으로 처리한다. 예제)

<x-prompt on-close="doSomething()">


 

Attributes/Properties에 expression 바인딩하기 


앵귤러 데이터 바인딩은 expression이 변경되면 연결된 destination 값도 자동으로 바뀌게 한다. 앵귤러는 attribute 또는 property에 변경된 값이 쓰여지는 것을(be written) 알아야 한다. 실험적으로(heuristic) 아래와 이를 찾아낸다.

function updateValue(element, name, value) {

  // register에서 행위를 찾는다

  var useProperty = checkOverrides(element, name);

  if(useProperty == null) {

    // use heuristic

    useProperty = name in element;

  }

  if(useProperty) {

    element[name] = value;

  } else {

    element.setAttribute(name, value);

  }

}


위의 방법으로 다음을 해석해 보면

<x-prompt accept-text="I {{name}} accept" 

                   cancel-text="Cancel"

                   bind-title="contract.name">


  - cancel-text="Cancel"; 은 cancel-text Attribute 값으로 "Cancel" 문자를 updateValue(element, 'cancel-text', 'Cancel');로 호출한다. 

  - accept-text="I {{name}} accept"; 도 accept-text Attribute 값으로 expression을 문자로 변화하여 updateValue(element, 'accept-text', 'I ' + name + ' accept'); 로 호출한다. 

  - bind-title="contract.name"; 은 양방향 바인딩으로 엘러먼트의 title 프로퍼티와 contract.name이 맵핑어 expression이 바뀌면 title 프로퍼티도 업데이트 되어진다. 



Web Components 얻


최신 소스를 보면(ng-conf 2015의 youtube 예제) 다음과 같이 나온다. (앵귤러 디자인 가이드 문서를 보면 역시 글로 쓰여진 것의 업데이트하기는 거기나 여기나 비슷한가 보다. 써놓고 신경을 쓰지 않으면 업데이트 하기가 힘들어서 초기의 생각의 흔적만을 볼 수 있고, 변경된 최신 내역을 볼 수가 없다. 여기서도 마찬가지다)

<google-youtube chromeless="0"

   #player

   [videoid]="video.id"

   (googleyoutubestatechange)="setState($event.detail.data)">

</google-youtube>

<button *ng-if="!isPlaying()" class="md-button md-default-theme md-raised"

      (click)="player.play()">Play

</button>

<button *ng-if="isPlaying()" class="md-button md-default-theme md-raised"

      (click)="player.pause()">Pause

</button>


  - 일단 google-youtube Web Components에 #player 로컬 변수를 선언하고 Button 태그에서 사용을 하고 있다. 

  - (click)="player.play()" 또는 (click)="player.pause()"를 호출하고 있다. 

  - 디자인 문서에 따르면 on-click="player.play()" 그리고 ng-id="player", bind-videoid="video.id"와 같이 표현을 해야할 것이다. 어떻게 하든 많은논의를 통해 디자인에서 이야기 내용이 좀 더 발전적으로 변경된 모습을 볼 수 있다. 



엘러먼트의 property/attribute 발견하기(Detecting)


커스텀 엘러먼트는 DOM attributes와 Element property를 변경할 수 있다. DOM attributes는 DOM4의 DOM Mutation Observers를 이용해 찾을 수 있지만 Javascript나 DOM API를 이용해 Element property의 변경을 발견하는 것은 쉽지않다. 그래서 앵귤러는 실험적(heuristic)으로 컴포넌트의 이벤트를 찾아 리스닝(Listening)을 하고 변경이 발생하면 컴포넌트에 대해서만 dirty checking을 하여 변경을 반영함으로 부정적인 성능 저하를 제거한다. (즉, 해당 엘러먼트의 이벤트를 찾아 리스닝을 걸어 놓는 방법은 Native에서 많이 사용하는 방법이고 이를 그대로 활용한다. Angular 1에서는 dirty checking이 모든 $scope의 변경을 체크했다면 이제는 컴포넌트 변경안에서만 dirty checking이 일어아는 샘이다)



다음 장에서는 앵귤러 컴포넌트가 어떻게 웹 컴포넌트가 될 수 있는지 알아본다.



<참조> 

  - Angular v2.* 의 Web Components 관계 정립 문서  

  - Web Components 소개 

  - Angular + Web Components 사용 youtube 예제 소스 (ng-conf 2015)




posted by 윤영식
2015. 11. 14. 17:54 Angular/Concept

Angular2 구현 언어는 TypeScript이다. TypeScript를 배우기 위한 준비과정을 알아본다. 








Transpiler


  ES2015(EcmaScript 6) 스펙은 모던 브라우저에서 완벽히 구현되어 있지 않기 때문에 최소한 ES5기준에 맞춰서 사용을 해야 한다. TypeScript는 ES2015를 포함해 정적 타입 시스템과 ES7에서 나오는 데코레이터 @을 지원하는 슈퍼셋이다. 따라서 Babel로 ES2015를 ES5로 트랜스파일(Transpile) 하지 않고 TypeScript만의 트랜스파일러를 사용해야 한다.  


$ sudo npm install -g typescript 


TypeScript 파일의 확장자는 .ts파일로 한다. 그리고 자바스크립 파일로 트랜스파일하기 위해 tsc 명령을  사용한다. 결과물로 동일 명칭의 .js 파일이 생성된다.


$ tsc  <fileName>.ts 






Editor 


  Microsoft에서 mac에서도 사용할 수 있는 전용 Code Editor(Visual Studio Code)를 내놓았다. 설치 후 어느 위치에서든 code . 하면 현재 폴더의 파일 구조를 가지고 에디터가 열린다. 그리고 .ts 파일에 대한 스마트 위저드 헬퍼가 자동으로 뜨기 때문에 MS Visual Studio에서 코딩하는 착각을 불러 일으킨다. 필자는 최근 Sublime 3에서 Atom으로 코딩 툴을 옮겼는데 참 흥미롭게 사용중이다. 큰 파일의 경우 약간의 성능저하를 견딜 수 있다면 사용해 보길 권한다. 




Atom을 사용한다면 atom-typescript 패키지 설치를 통해 Visual Studio Code에서의 .ts 에 대한 스마트한 기능(Auto Complete, Type Info on hover...)을 그대로 사용할 수 있다. 설치후 F6를 클릭하면 tsc <fileName>.ts 명령을 수행하고 결과를 Notify UI 창으로 성공 실패여부를 알려준다. 


 





tsconfig.json


  tsconfig.json 파일은 TypeScript를 위한 프로젝트 단위 환경 파일이다. Exclude 하고 싶은 폴더나 파일, Compile 옵션, File 옵션등을 설정 할 수 있다. TypeScript의 컴파일러인 tsc 명령 수행시 참조한다. tsconfig.json의 전체 스키마를 참조한다.  또는 TypeScript Deep Dive 깃북의 설명을 참조한다.


{

    "exclude": [

        "node_modules",

        "bower_components"

    ],


    "compilerOptions": {

        "target": "es5",

        "sourceMap": true,

        "module": "commonjs",

        "declaration": false,

        "noImplicitAny": false,

        "removeComments": true,

        "noLib": false

    }

}






TypeScript 배우기 


TypeScript를 배우기 위해 ES2015의 문법을 먼저 익히는게 좋다. 

  - ES2015-features 사이트에서 새로운 기능들이 ES5 스펙과 어떤 차이가 있는지 눈여겨 보자. 

  - 동영상 강좌를 본다. 필자는 egghead.io의 동영상을 먼저 참조한다. (Pro는 유료이다)


공식홈페이지는 http://www.typescriptlang.org/ 이고, TypeScript 구현체 자체도 오픈소스로 깃헙에 공개되어 있다. 

  - 공식홈페이지에서 제공하는 TypeScript 스펙 배우기

  - TypeScript Deep Dive 깃북






TSD


TypeScript Definition Manager의 약어이다. 이미 나와있는 프레임워크(angular), 라이브러리(jquery) 같은 것을 사용하면서 애플리케이션을 TypeScript로 작성할 때 이들 구현체의 정의 내역을 미리 알고 코드에서 가이드를 주기위한 파일이다. 확장자는 angular.d.ts 또는 jquery.d.ts처럼 <name>.d.ts 가 붙는다. 예로 $('.awesome').show(); 라고 하면 타입스크립트는 $를 알지 못 한다. 먼저 declare var $:any; 라고 해주어야 사용할 수 있다.  


$ npm install -g tsd


tsd를 설치하고 tsd install <name> 으로 설치하면 수행위치에 typings 폴더 밑으로 파일을 다운로드한다. 


$ tsd init

// tsd.json 파일 

{

  "version": "v4",

  "repo": "borisyankov/DefinitelyTyped",

  "ref": "master",

  "path": "typings",

  "bundle": "typings/tsd.d.ts",

  "installed": {

    "jquery/jquery.d.ts": {

      "commit": "efd40e67ff323f7147651bdbef03c03ead7b1675"

    },

    "angularjs/angular.d.ts": {

      "commit": "efd40e67ff323f7147651bdbef03c03ead7b1675"

    }

  }

}


$ tsd install jquery

$ tsd install angular



이미 만들어져 있는 d.ts 파일을 이곳에서 http://definitelytyped.org/tsd/ 찾아 볼 수 있다. DefinitelyTyped 공식 홈페이지를 참조하자.






WorkFlow 


 Atom 에디터와 기본 툴을 설치했으니, 프로젝트를 위한 워크플로우를 만들어 보자.   


  - 프로젝트 폴더 만들기 

  - NodeJSGit 설치 

  - "npm install gulp -g" 설치 

  - npm 초기화 및 develop dependencies 설정 

$ npm init 


// package.json 첨부

"devDependencies": {

        "gulp": "^3.8.11",

        "gulp-debug": "^2.0.1",

        "gulp-inject": "^1.2.0",

        "gulp-sourcemaps": "^1.5.1",

        "gulp-tslint": "^1.4.4",

        "gulp-typescript": "^2.5.0",

        "gulp-rimraf": "^0.1.1",

        "del": *,

        "superstatic": *,

        "browser-sync": *

    }


// 의존 라이브러리 설치

$ npm install 


  - tsd 설치하고, init 초기화 명령으로 tsd.json 파일과 typings 폴더에 tsd.d.ts 파일을 자동 생성한다.

$ npm install -g tsd 


$ tsd init 



  - angular 의 TypeScript Definition 파일을 설치해 보자. 더 많은 라이브러리는 이곳에서 http://definitelytyped.org/tsd/ 에서 찾을 수 있다.

$ tsd install angular --save

 - angularjs / angular

   -> jquery > jquery


>> running install..

>> written 2 files:

    - angularjs/angular.d.ts

    - jquery/jquery.d.ts



$ tsd install jquery --save

 - jquery / jquery


>> running install..

>> written 1 file:

    - jquery/jquery.d.ts



  - gulpfile.config.js와 gulpfile.js를 https://github.com/DanWahlin/AngularIn20TypeScript 에서 복사해서 만든다. Gulp Task 설명


  - ts 파일 lint를 위해 tslint.json파일 만든다.

{

    "rules": {

        "class-name": true,

        "curly": true,

        "eofline": false,

        "forin": true,

        "indent": [true, 4],

        "label-position": true,

        "label-undefined": true,

        "max-line-length": [true, 140],

        "no-arg": true,

        "no-bitwise": true,

        "no-console": [true,

            "debug",

            "info",

            "time",

            "timeEnd",

            "trace"

        ],

        "no-construct": true,

        "no-debugger": true,

        "no-duplicate-key": true,

        "no-duplicate-variable": true,

        "no-empty": true,

        "no-eval": true,

        "no-imports": true,

        "no-string-literal": false,

        "no-trailing-comma": true,

        "no-trailing-whitespace": true,

        "no-unused-variable": false,

        "no-unreachable": true,

        "no-use-before-declare": true,

        "one-line": [true,

            "check-open-brace",

            "check-catch",

            "check-else",

            "check-whitespace"

        ],

        "quotemark": [true, "single"],

        "radix": true,

        "semicolon": true,

        "triple-equals": [true, "allow-null-check"],

        "variable-name": false,

        "whitespace": [true,

            "check-branch",

            "check-decl",

            "check-operator",

            "check-separator"

        ]

    }

}


  - Atom에서 atom-typescript를 설치해서 사용한다면 tsconfig.json도 만든다. 아래와 같이 설정값을 입력한다. 설정 값이 있어야 atom-typescript 기능을 사용할 수 있다. 보다 자세한 tsconfig.json 작성 문법을 참조한다.

{

    "compilerOptions": {

        "target": "es5",

        "module": "commonjs",

        "declaration": false,

        "noImplicitAny": false,

        "removeComments": true,

        "noLib": false

    },

    "filesGlob": [

        "./**/*.ts",

        "!./node_modules/**/*.ts"

    ],

    "files": [

        "./globals.ts",

        "./linter.ts",

        "./main/atom/atomUtils.ts",

        "./main/atom/autoCompleteProvider.ts",

        "./worker/messages.ts",

        "./worker/parent.ts"

    ]

}


  - gulp 명령을 수행하면 .js 파일과 .js.map 파일이 생성된다. 


  - TypeScript 소개 영상과 소스를 확인해 보자.






Start Kit 


  yeoman의 generator 처럼 TypeScript프로젝트 관련한 스타트 깃을 사용해 보자. 물론 angular2 기반으로 프로젝트를 가정한다. 

  - AngularClass에서 제공하는 angular2-webpack-starter 

  

   


  - AngularClass에서 제공하는 Angular2의 Server Rendering을 접목한 Universal Starter





참조 


  - TypeScript Workflow with Gulp

  - TypeScript in Awesome Angular2

 - Atom TypeScript 패키지 : atom-typescript



posted by 윤영식
2015. 7. 11. 19:45 Meteor

2013년 이맘때 미티어를 처음 접하고 클라이언트와 서버 사이에 데이터베이스가 있다는 것이 이해되지 않았다. 그 의문이 이번주 스마트링크의 스터디를 하며 조금씩 해소되는 느낌이다. 이제는 Modern Application Platform의 큰 흐름에서 Runtime이라는 주제를 놓고 볼 때 미티어는 단연 앞선 플랫폼이고 한 분야를 리딩할만한 플랫폼이라 여겨진다. 그속에 많은 사상과 기술이 녹아있다. 미티어를 한주 안에 파악해 보기 위해 다음의 과정을 밟아보길 권한다. 






Meteor 넌 뭐니?


  - 스터디에서 발표한 내용의 동영상이다. 

    + Realtime을 근간으로 Collaboration과 Share를 위해 잘 갖추어진 Modern Application Platform 이다. 

    + Reactive 영역을 프론트엔드에서 백앤드로 확장해 기술셋을 잘 구비해 놓았다.

    + 프론트앤드 리액트 템플릿엔진 : Blaze, AngularJS, ReactJS를 사용할 수 있다. 

    + 백앤드 리액트 기술 : Full Stack DB Driver = DDP + Client mini DataBase + LiveQuery 를 갖추었다. 

    
    + 동영상의 발표자료
    


  - 위의 내용을 이해하기 위하여 다음과 같은 자료를 보았다. 

    + DiscoverMeteor 강좌 : 반드시 전과장을 직접 따라서 코딩해 보면 좋다. 물론 한글로 번역이 되어 있다. 

       이번에 출간한 "실전 프로젝트로 배우는 AngularJS"의 상당 부분이 이와 유사하게 전개한다. 놀란 것은 Node.js에서 MongoDB 제어와 권한 체크를 위한 코드 작성이 너무나도 쉽게 해결된다는 것이다. 정말 말 그대로 서버는 날로 먹는 것이다. Isomorphic Programming의 정수이다. 

    + 박승현님 Meteor Dev Study : 페북 미티어 코리아 그룹을 이끄시는 박승현님의 미티어 강좌를 보고 친숙하게 이해할 수 있을 것이다. 





Meteor Environment


미티어는 Ionic과 같은 CLI 제공을 통해 실행과 빌드를 통합하고 있다. 따라서 의존성관리를 위한 npm, bower 또는 빌드를 위한 grunt, gulp 명령등을 알 필요가 없다. 단순히 meteor <command> <option> 을 통해 완료된다. 


  - Meteor Command Line Tool 명령들

  - 미티어의 패키지 저장소 : Atomsphere

  - 많이 사용하는 패키지 검색 : Fastosphere





Meteor가 AngularJS 또는 ReactJS를 만났을 때

  

                    



  Blaze를 사용하지 않고 AngularJS를 사용할 때의 방식은 확연히 차이가 보인다. 만일 JQuery 개발 방식에 익숙하다면 Blaze를 사용한다. AngularJS를 사용해 보았다면 당장 AngularJS+Meteor를 사용하길 권한다. 공식적으로 지원을 하고 예제 문서까지 친절하게 나와있다. ReactJS는 아직 Preview단계로 보여지지만 긱하게 새로운 것을 도전해 보고 싶다면 권한다. ReactJS의 Virtual DOM 개념도 관심의 대상이지만 Flux라는 개념이 참 맘에 든다. 따라서 MVC 패턴이 아닌 좀 더 견고한(페북이 말하는) 패턴이라는 Flux 패턴을 적용하고 싶다면 ReactJS를 사용해 보자. 나의 선택은 물론 ReactJS + Flux 패턴이다. 


  - Blaze, AngularJS, ReactJS 비교하며 따라하기 예제

  - AngularJS + Meteor 홈페이지 : 물론 미티어 홈페이지에 AngularJS 공식 문서도 있다.'

  - ng vegas에서 발표한 angular-meteor 개발자인 유리 골드스테인의 Angular/Angular2와 Meteor에 대한 소개

    


  - ReactJS + Meteor Preview 예제 페이지

  - 페북과 유사한 ReactJS + Meteor 샘플 예제 : 데모 사이트를 이동해 직접 사용해 보자. 

    + Data Fetching Component는 Flux의 Controller View 이다.

    + Mini-Mongo Store는 Flux의 Store 이다.

    + Action은 Flux의 Action Creator 이다.

    



Meteor는 Modern Application Platform 이면서 클라이언트의 리액트를 백앤드로 확장해 주는 Reactive Platform이라 말할 수 있다. 다시 들여다 본지 1주일밖에 되지 않기에 오류를 잡고 파보아야할 곳이 많이 존재한다. 앞으로 두달간 진행하는 파일럿 프로젝트에서 ReactJS + Meteor를 사용하면서 내공을 쌓아야 할 상황이다. 




Modern Application Platform 으로서 Meteor 

  

  "Realtime 애플리케이션을 위해 프론트/백앤드 개발을 동질로 리액트하게 할 수 있는 플랫폼을 제공해 보자" 이게 그들의 머릿속을 스친 생각이 아니었을까? 리액티브 프로그래밍을 이소모픽하게 제공한다면 View 와 Data에 대하 부분을 리액티브하게 처리할 수 있다면... 아마 미티어 개발자들은 신이 나지 않았을까 싶다. 그래서 그와 관련된 회사들을 M&A하고 개발자를 영입해서 오늘의 1.* 미티어라는 리액티브 플랫폼을 완성해 가고 있다고 본다. 미티어를 리얼타임 플랫폼을 하는 마이크로 서비스로 보고 외부 시스템과 연계는 몽고디비를 통해 한다면 다양한 연동이 가능할 것이라 생각한다. 


  이중 DDP를 통한 React Native로 DDP Client를 사용하는 방법이 있는가 하면 

  

  일라스틱서치(Elastic Search)와 연동하기도 하고 

  

  

 미티어를 통해 상상하던 모던 애플리케이션 서비스를 일관되고 쉽게 만들어 갈 수 있다라는게 나의 결론이다. 




<참조> 


  - JavaScript.isSexy에서 이야기하는 Meteor 배우는 방법

posted by 윤영식
2015. 7. 1. 09:17 React
AngularJS로 여러번의 프로젝트를 했었다. 그럴때 마다는 느끼는 보다낳은 컨트롤러의 구조는 무엇일까? 그리고 $scope를 어떻게 하면 잘 사용할 수 있을까이다. 어쩌면 $scope에 대해 고민하기 보다 다른 접근법을 시도해 보는 것은 어떨까? 그래서 ReactJS를 공부하기 시작했다. 1주일밖에 안된 입장에서 어떻게 접근하는 것이 좋을지 간략히 정리하고 스마트링커들과 토론한 내용을 올린다. 



AngularJS와 ReactJS 비교

  - AngularJS와 ReactJS간의 개발 방법론에 대한 스터디 자료 

    



  - MVC 패턴과 ReactJS의 차이점을 이야기한다. 

   + 해당 발표를 보면 위에서 내가 설명한 내용을 좀 더 이해하기 쉽지 않을까 싶다.

   


'React' 카테고리의 다른 글

[React] 다시 시작하기  (0) 2018.09.14
[React] Semantic-UI를 React 컴포넌트로 만들기 - 1  (0) 2015.08.23
[React] CSS Framework 선정하기  (0) 2015.08.15
[Flux] Flux 배우는 방법  (0) 2015.07.04
[React] 배우는 방법  (0) 2015.05.15
posted by 윤영식
2014. 4. 25. 00:15 NodeJS/Concept

Node.js를 제대로 배워보고 싶다면 이렇게 시작해보자


1. Node.js 사용 영역

  - fast, real-time 어플리케이션 개발

  - scalable 확장가능성이 높은 어플리케이션 개발

  - data-driven modern web 어플리케이션 개발

  * 클라우드 환경 이해



2. 잘 못 된 코스 

  - 온라인 및 동영상 강좌들부터 보지 말기

  - 서점에서 서평들 읽고 책 고르지 말기 



3. 제대로 된 코스

  - 봐야할 리소스 

    + 초보 : The Node Beginner Book 번역본

    + Felix's NodeJS Guide 번역본

   + Node.js 소개 자료 -여름으로 가는 문

   + Mastering NodeJS (비젼미디어)


  - JavaScript 를 배운다 : JavaScript Graden 번역본


  - 설치 : Professional Node.js 1장 보고 개발환경 셋업

  - 이해 : Beginner Book 보고서 간단히 프로그램들 돌려보기 

  - 모듈 : CommonJS에 대하여 이해하기

  - Professional Node.js 3장부터 쭉 읽자. 그러면 서버단 개발에 대해서 이해할 수 있다

  - 우리 목적은 modern web application개발이니 front-end의 backbone.js 를 배운다 



4. Node.js & Backbone.js를 익혔다면 

  - http://dailyjs.com/web-app.html 여기서 다양한 어플리케이션의 모험을 해보시라

  - 더 나아가서는 템플릿을 위한 Handlebars.js 와 MongoDB를 익힌다


  * 2013.2.20 일에 작성한 블로그라서 backbone.js가 나왔다. 이제는 angular.js로 옮겨가는 분위기

    3주를 목표로 잡고 시작하시라~~~ Good Luck!



<참조>

  - 원문 : http://javascriptissexy.com/learn-node-js-completely-and-with-confidence/

posted by 윤영식
prev 1 2 next