Angular에서 Route없이 모듈 동적 로딩하기

in #angular7 years ago (edited)

P5270018.jpg

블로터닷넷 기사에 따르면 2017년 대한민국 1분기 평균 모바일 인터넷 속도는 28Mbps입니다. 이 속도로 계산된 [email protected]의 다운로드 시간은 8.5ms(아래 계산식 참고)입니다. 여기에 LTE망 평균 응답속도 30ms, 다운받은 스크립트를 파싱하고 실행하는 데 까지 15ms(iPhone 5S 기준)를 더하면 대략 53.5ms입니다. 시간이 지난 자료를 토대로 계산한 결과이지만. 이마저도 평균이거나 좋은 조건일때 기준이고. 보통 이런 파일을 하나만 다운로드하는것이 아니기 때문에 실제 서비스를 사용할 때는 더 느릴 것입니다.

30KB after gzipped.
28Mbps = 3500KBps
1s : 3500KB = x : 30KB
x = 0.008571429



서비스 초기 진입 시간의 중요성은 규모가 커질수록 중요해집니다. Google은 10개의 검색결과를 400ms에 노출하던 것에서. 30개의 검색결과를 900ms에 노출하도록 변경한 후 20%의 트래픽과 광고 수익 감소를 겪었다고 합니다. Google Maps는 리소스 용량을 30KB 줄인 것으로 첫주 10%의 트래픽 증가를. 이후 3주 내에 추가로 25%의 트래픽이 증가했다고 합니다. Amazon.com은 로드 타임이 100ms증가할때마다 매출액이 1%씩 감소했다고 합니다. 본문 링크

위의 내용을 볼 때 30ms도 결코 작다고 할 수 없습니다. 서비스 초기 진입 시간을 줄일 수 있는 방법은 많지만 이 글에서 소개할 동적 모듈 로딩은 가장 간편하면서도 큰 효과를 볼 수 있는 기법입니다. Angular는 출시될때부터 이 기능을 가지고 있었습니다. 동적으로 받을 리소스들을 모아 별도의 모듈로 만들고 아래 코드와 같이 라우터에 등록하면 됩니다.

const routes: Routes = [{
  path: 'customers',
  loadChildren: 'app/customers/customers.module#CustomersModule'
}];



사용자가 /customers에 접속하면 자동으로 app-customers-customers.module.js파일을 동적으로 다운로드받고 파일 내 CustomerModule을 사용할 수 있게 됩니다. jQuery등의 라이브러리를 해당 페이지에서만 사용하는 것이 확실하다면. 이 분리된 모듈에서 사용하는것으로 간단히 기법을 적용할 수 있습니다. 이렇게 분리된 모듈 파일의 용량 만큼 서비스 초기 진입에 걸리는 시간을 줄일 수 있습니다.

하지만 어떤 모듈은 해당 컴포넌트가 사용되는 시점에 받는 것이 더 자연스럽고 편리한 경우가 있습니다. 위지윅 에디터를 예로 들 수 있습니다. 동적 모듈 로딩 없이 개발된 A페이지에 위지윅 에디터를 추가한다고 가정해 봅시다. 라우터에 기존에 등록된 컴포넌트를 모듈로 변경하고 위와 같이 수정해야 합니다. 또 이렇게 모듈을 동적으로 받는다고 해도. 만약 위지윅 에디터 자체를 특정 버튼으로 노출하도록 기획한 경우 실제 위지윅 에디터를 사용하지 않아도 페이지 진입 만으로 리소스를 받게 됩니다.

위지윅 에디터 컴포넌트를 실제로 사용하는 시점에 관련 모듈을 다운로드받도록 하고 싶으면 NgModuleFactoryLoader를 사용하면 됩니다. 이 모듈을 사용하기 위해서는 먼저 루트 모듈에 해당 모듈들을 추가해야 합니다.

@NgModule({
  ...
  providers: [{provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader}]
})
export class AppModule {}



그리고 필요한 리소스들을 동적으로 로드하는 별도의 모듈을 만듭니다.

@NgModule()
export class EditorModule {
  constructor(@Inject(DOCUMENT) private document: Document) {}

  async load() {
    const style = document.createElement('style');
    style.textContent = require('../../assets/css/editor.css').default;
    this.document.head.appendChild(style);

    return require('../../assets/js/editor.js');
  }
}

동적으로 다운로드할 모듈을 직접 사용하지 않아도 될 경우 코드를 생성자에 구현하면 됩니다. 예제 코드에서는 editor.js파일이 내보내는 함수를 직접 사용해야 하기 때문에 해당 모듈을 반환하는 별도의 메서드 load에 구현합니다. 모듈을 구현했으면 angular.json 파일에 해당 모듈을 등록합니다.

{
  "projects": {
    "my-app": {
      "architect": {
        "build": {
          "lazyModules": [
            "src/app/modules/editor.module#EditorModule"
          ]
        }
      }
    }
  }
}



lazyModules의 배열에 해당 모듈 파일의 경로와 모듈의 이름을 #으로 구분해 기재합니다. 이후 이 프로젝트가 빌드될 때 editor.module.ts, editor.css, editor.js파일들은 src-app-modules-editor.module.js로 묶여 별도의 파일로 분리됩니다. 이제 아래의 코드로 위지윅 에디터의 생성자를 동적으로 얻어올 수 있습니다.

@Component({...})
export class AppComponent {
  title = 'app';
  @ViewChild('textarea') el: ElementRef;

  constructor(private injector: Injector, private loader: NgModuleFactoryLoader) {}

  onClickLoadEditor() {
    this.loader.load('src/app/modules/editor.module#EditorModule')
        .then(factory => factory.create(this.injector).instance.load())
        .then(({Editor}) => new Editor(this.el.nativeElement));
  }
}



사실 Angular@v5 이하에서는 webpack이 기본적으로 사용했던 System.import파서 플러그인으로 .ts파일 뿐만 아닌 여러 파일을 동적으로 간단히 분리할 수 있었습니다. 하지만 [email protected] 부터 webpack이 System.import파서 플러그인을 사용하지 않게 되었고. Angular@v6 버전 이후부터는 해당 코드가 발견되면 컴파일 시점에 경고를 출력합니다. 그렇기 때문에 System.import를 직접적으로 사용할 수 없고 본문과 같이 NgModuleFactoryLoader를 사용해야 합니다.

재미있는 것은 NgModuleFactoryLoader의 구현체인 SystemJsNgModuleLoader내부에서는 System.import를 아직 사용하고 있다는 것입니다. 공식 github에서는 이 코드를 typescript의 import문을 사용하는 것으로 수정하려는 PR이 있습니다. 이 PR이 머지된다고 하더라도. 위 방법을 이용하는 데 문제는 없습니다.

Sort:  

LTE가 꽤나 빠르다고는 해도 반응시간이 무시 못할 수준이군요.. 좋은 정보 감사합니다.

#kr 태그를 달지 않으시면 한국어 사용자에게 글이 잘 노출되지 않습니다. 스팀잇에 익숙해지실 때까지 #kr-newbie 태그를 사용하시는 것을 추천드립니다. #kr 커뮤니티에서 사용하는 태그 목록은 @myfan 님의 태그 정리글에서 확인하실 수 있습니다.
이지스팀잇 가이드북 을 보시면 앞으로 스팀잇 활동하시는데 도움이 되실겁니다.

와 좋은 팁 고맙습니다. 사실 맨땅에 헤딩하는 기분으로 글을 쓰고 태그를 달고있긴한데 이런 방법이 있었군요! 알려주신 링크도 정독하겠습니다. 고마워요!

스팀잇이 많이 불친절하죠 ㅠㅠ 즐거운 스팀잇 활동 되시길!

Congratulations @mnkim! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 1 year!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!