npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jj-viewer/skinnable

v0.0.27

Published

This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.0.

Downloads

17

Readme

@jj-viewer/skinnable

This library was generated with Angular CLI version 9.1.0.

Features

  • Angular 프로젝트에서 다음 사항을 컴파일 이후에도 동적으로 로드하여 설정할 수 있도록 한다.

    • HTML (template)
    • Css (style)
    • Json (Component의 속성 설정)
  • 컴포넌트간 API를 호출할 수 있는 Mediator를 제공한다.

(주의) Jit 컴파일 일때만 정상 동작합니다.

  • AOT 컴파일을 지원되지 않습니다. (--aot=false)
  • <router-outlet></router-outlet> 구문은 테스트되지 않았습니다.

Installation

npm install @jj-viewer/skinnable --save

Typescript 환경 설정

tsconfig.json

{
  "compilerOptions": {
    ...
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "useDefineForClassFields": true, // 생성자에서 초기화하지 않은 member도 Object.keys 목록에 추가함
    }
  },
  
  "angularCompilerOptions": {
    ...
    "enableIvy": false
  },
  ...
}

proxy 설정

개발환경에서 런타임에 사용할 bundle 폴더에 접근하기 위해 proxy 서버를 설정합니다.
bundle 폴더에는 skin(template, css) 파일 및 이미지등의 asset 파일을 넣어둡니다.

workspace (root) 위치가 다음 주소로 서비스된다고 가정할때

http://localhost/dev/workspace
  1. proxy.conf.json 파일 생성

    {
      "/bundle/*": {
        "target": "http://localhost/dev/workspace",
        "secure": false,
        "logLevel": "debug"
      }
    }
  2. angular.json 파일 수정

    "serve": {
      "options": {
        "proxyConfig": "proxy.conf.json",
      },
    }

    또는 실행시 옵션 설정

    ng serve --proxy-config=proxy.conf.json
  3. ng serve 실행 http://localhost/dev/workspace/bundle 서비스를 http://localhost:4200/bundle로 접근 가능해짐.

Usage

testApp 프로젝트에서 보다 자세한 예시를 참고하실 수 있습니다.

동적으로 Skin (template, css) 이 설정되는 과정

  1. 설정 파일을 정의합니다. (bundle 폴더)
  2. SkinApplicationComponent에서 설정 파일이 로드됩니다. (json 파일)
  3. 설정 파일 내용이 각 SkinComponent에 동적으로 적용됩니다. (template, css)

AppModule 설정

@NgModule({

    // 중요: Skinnable Component는 AppModule에 export 되어 있어야 함
    // AppModule에 export 되어있는 module은 추가할 필요 없음
    providers: [
        {provide: SKIN_INJECT_MODULES, useValue: [AppModule]}
    ],

    schemas: [
        NO_ERRORS_SCHEMA,
        CUSTOM_ELEMENTS_SCHEMA
    ],

    imports: [
        ...imports,
        SkinnableModule
    ],

    exports: [
        ...exports
    ],
    ...
})
export class AppModule {}

SKIN_INJECT_MODULES에 값 지정

컴파일된 이후에 사용되는 (다른 모듈에서 정의된) 컴포넌트를 사용하려면

  1. 컴포넌트는 AppModule에서 export 되어 있어야 하고,
  2. exporte된 컴포넌트를 런타임에 사용하는 SkinnableModuleimport 되어야 하므로,
  3. 필요한 module을 배열로 지정하는 것 보다는 모든 기능(요소)을 포함하는 AppModule을 대표로 import 시킵니다..
  4. AppModule 대신 필요한 모듈만 import 시켜도 됩니다.
    {provide: SKIN_INJECT_MODULES, useValue: [...modules]}

(주의) AOT 컴파일을 지원되지 않습니다. (--aot=false) SkinnableModule에 런타임에 import되는 Module을 동적으로 인식할 수없습니다.

Component에 적용

SkinComponent를 상속 받아 사용합니다.

import {
    SkinApplicationComponent,
    SkinComponent,
    SKINNABLE_TEMPLATE,
    SkinnableParameter,
    skinnableParameterProvider
} from '@jj-viewer/skinnable';

@Component({
    selector: 'jj-sample',
    providers: [skinnableParameterProvider],

    // 설정파일 또는 Attribute에 설정된 경로의 템플릿이 동적 로딩됨
    template: SKINNABLE_TEMPLATE

    // 아래 설정도 여전히 사용 가능함 (이곳 템플릿은 컴파일 됨)
    // templateUrl: 'sample.html',
    // styleUrls: ['sample.css'],
})
export class SampleComponent extends SkinComponent {
    constructor(protected param: SkinnableParameter, protected viewContainerRef: ViewContainerRef) {
        super(param, viewContainerRef);
    }
}

Application에 적용

SkinApplicationComponent를 상속 받아 사용합니다.

import {
    SkinApplicationComponent,
    SKINNABLE_TEMPLATE,
    SkinnableParameter,
    skinnableParameterProvider
} from '@jj-viewer/skinnable';

@Component({
    selector: 'jj-application',
    providers: [skinnableParameterProvider],
    template: SKINNABLE_TEMPLATE
})
export class ApplicationComponent extends SkinApplicationComponent {
    constructor(protected param: SkinnableParameter, protected viewContainerRef: ViewContainerRef) {
        super(param, viewContainerRef);
    }
}

컴포넌트간 통신

testApp 프로젝트에서 SampleComponent에서 발송한 이벤트를 DefineComponent에서 받아 처리하는 예제 입니다.

컴포넌트간 서로 통신을 하려면 반드시 id가 (attribute or class value) 설정되어 있어야 합니다.

  • SampleComponent에서 이벤트 발송
// SampleComponent
bookLoadedEvent = new EventEmitter<any>();

// @오버라이딩
// 모든 컴포넌트 설정이 완료된 applicationCompleteEvent 발생 후 emit 한다.
// 오버라이딩해서 사용하도록 applicationCompleteEvent 청취가 구현되어 있다.
protected onApplicationComplete() {

    setTimeout(() => {
        // 예 : bookLoaded 생성 완료를 알림
        this.bookLoadedEvent.emit(this.id);
    }, 1000);

}
  • template에 id 설정
<jj-sample id="3.app_skin_sample" ...>
  • DefineComponent에서 이벤트 청취
// @오버라이딩
// 모든 컴포넌트 설정이 완료된 applicationCompleteEvent 발생 후 청취 한다.
protected onApplicationComplete() {

    // Mediator를 통해 컴포넌트 instance에 접근
    const book = this.param.mediatorService.get('3.app_skin_sample') as SampleComponent;

    // 테스트 : bookLoaded 생성 완료 청취
    const subscription = book.bookLoadedEvent.subscribe((id: string) => {
        console.error('bookLoadedEvent : ', book.nodeName, id);
        subscription.unsubscribe();

        // book API 사용
        // book.goPage(10);
    });

}

설정 파일 정의

컴포넌트에 설정값을 전달하는 방법은 다음과 같습니다.
다음 순서대로 (우선순위) 설정값이 적용됩니다.

  1. Attribute 설정값이 있으면 우선적으로 적용됩니다.
  2. 설정 파일에서 [태그(selector)] 항목에서 정의된 설정값
  3. 설정 파일에서 [global] 항목에서 정의된 설정값

설정 파일에 대해 설명합니다.

{
  "global": {
    /*
    컴포넌트들의 공통 속성중 기본값으로 설정할 값을 전역 설정합니다.
    이곳에서는 properties, skins 외 다른 속성은 정의할 수 없습니다.
    새로운 속성을 추가하려면 properties 항목을 이용합니다.
    */
    
    // 이곳에 속성을 추가해도 컴포넌트에 전달되지 않습니다.
    "can't add propertise": "전달되지 않는 속성값",

    "properties": {
      "can add propertise": "추가할 수 있는 속성 영역",
      // 예) a, b 속성을 모든 컴포넌트에 전달 
      "a": 1, "b": 2
    },

    "skins": {
      "useLoader": true,
      "urlTemplate": "",
      "urlCss": ""
    }
  },

  // 'jj-application' selector를 가진 컴포넌트에 전달되는 속성 설정
  "jj-application": {
    "properties": {
      // 예) jj-application 컴포넌트에 global 설정값까지 함께 전달됨 
      // "a": 1, "b": 10, "c": 20
      "b": 10, "c": 20
    },
    "skins": {
      "useLoader": false,
      "urlTemplate": "bundle/skins/application.html",
      "urlCss": "bundle/skins/application.css"
    }
  },

  // 'jj-sample' selector를 가진 컴포넌트에 전달되는 속성 설정
  "jj-sample": {
    // attribute에 값이 지정된 경우 값 지정 생략해도 됨.
    "skins": {}
  }
  ...
}

Skin 파일 지정 방법

  • url-configuration에 지정된 설정파일에 정의
  • attribute에서 직접 지정하는 방법

Template (HTML) 작성 방법

attribute에 다음 속성을 활용합니다.

  • url-configuration
    • 설정파일 경로를 지정 합니다.
    • SkinApplicationComponent에만 설정할 수 있습니다.
  • url-template
    • 동적 로드되는 템플릿(HTML) 파일 경로입니다.
    • 파일 내부에 @import url() 구문 사용할 수 없습니다.
  • url-css
    • 동적 로드되는 CSS 파일 경로입니다.
  • use-loader
    • 파일 로드 방식을 설정
    • (false) angular 로더를 사용하여 파일 내용을 로드
    • (true) 새로 작성된 http 로더로 파일 내용을 로드
    • true 이면 같은 url의 파일은 요청을 한번만 호출함 (cache 사용과는 다름)

Attribute 속성으로 사용

<!--문자열은 "'"로 감싸야 함.-->
<jj-book [use-loader]="false"
         [url-template]="'bundle/skins/book.component.html'"
         [url-css]="'bundle/skins/book.component.css'">

Attribute 값으로 사용

<!--type을 유지하려면 속성 바인딩 구문 사용해야 함.-->
<!--그렇지 않으면 아래 use-loader 속성은 문자열 "false"로 인식됨.-->
<jj-sample use-loader="false"
         url-template="bundle/skins/book.component.html"
         url-css="bundle/skins/book.component.css">

Attribute 바인딩 사용

<!--interpoliate 구문 사용-->
<jj-application url-configuration="{{urlConfiguration}}">

경로 설정

Template, Css에 사용되는 URL은 publishing 후 index.html을 기준으로한 상대경로로 작성해야 합니다.

  • Template 내부에서 경로 지정

    <jj-sample urlTemplate="./bundle/skins/sample.html"
               urlCss="./bundle/skins/sample.css">
    </jj-sample>
  • Css 내부에서 경로 지정

    @import url("./bundle/skins/test_import.css");
    /* styleUrl로 지정된 경우 파일 내부에 @import url() 구문 사용 불가함. */

디렉티브로 기능을 구현하지 않은 이유

  • Directive Attribute으로 로드 기능을 구현하는 경우 클래스 상속을 활용할 수없다.
  • 기능이 필요한 모든 노드에 직접 디렉티브를 명시해 주어야 한다.
    (설정값까지 attribute에 작성하면 작업이 너무 번거로워 진다.)