Angular 라이브러리를 만들 때 Angular CLI와 통합하는 스키매틱을 함께 패키지로 제공할 수 있습니다.
스키매틱을 제공하면 사용자는 ng add로 라이브러리의 초기 버전을 설치하고, ng generate로 라이브러리에 정의된 구성 요소를 생성하고, ng update로 호환성이 깨지는 변경을 포함한 새 라이브러리 버전에 맞춰 프로젝트를 수정할 수 있습니다.
이 세 가지 스키매틱 유형을 모두 컬렉션에 포함해서 라이브러리와 함께 배포할 수 있습니다.
스키매틱 컬렉션 생성하기
컬렉션을 시작하려면 스키매틱 파일을 생성해야 합니다. 다음 단계에서는 프로젝트 파일을 수정하는 동작 없이 초기 지원 구조를 추가합니다.
라이브러리의 최상위 폴더에
schematics폴더를 만듭니다.schematics/폴더에 첫 번째 스키매틱을 위한ng-add폴더를 만듭니다.schematics폴더의 최상위에collection.json파일을 만듭니다.collection.json파일을 편집해서 컬렉션의 초기 스키마를 정의합니다.projects/my-lib/schematics/collection.json (스키매틱 컬렉션)
{ "$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "ng-add": { "description": "Add my library to the project.", "factory": "./ng-add/index#ngAdd", "schema": "./ng-add/schema.json" } } }$schema경로는 Angular Devkit 컬렉션 스키마를 가리키는 상대 경로입니다.schematics객체는 이 컬렉션에 포함된 이름 있는 스키매틱을 설명합니다.- 첫 번째 항목은
ng-add라는 스키매틱입니다. 설명을 포함하며 스키매틱 실행 시 호출할 팩토리 함수를 가리킵니다.
라이브러리 프로젝트의
package.json파일에 스키마 파일 경로를 담은 "schematics" 항목을 추가합니다. Angular CLI는 명령어를 실행할 때 이 항목을 사용해서 컬렉션에서 해당 이름의 스키매틱을 찾습니다.
projects/my-lib/package.json (스키매틱 컬렉션 참조)
{
"name": "my-lib",
"version": "0.0.1",
"schematics": "./schematics/collection.json",
}
생성한 초기 스키마는 ng add 명령어를 지원하는 스키매틱의 위치를 CLI에 알려줍니다.
이제 해당 스키매틱을 만들 준비가 되었습니다.
설치 기능 지원하기
ng add 명령어용 스키매틱을 제공하면 사용자의 초기 설치 과정을 개선할 수 있습니다.
다음 단계에 따라 이 스키매틱을 정의합니다.
<lib-root>/schematics/ng-add폴더로 이동합니다.스키매틱이 받는 옵션을 정의하는
schema.json파일을 만듭니다.projects/my-lib/schematics/ng-add/schema.json (ng-add 스키마)
{ "$schema": "https://json-schema.org/schema", "$id": "SchematicsMyLibNgAdd", "title": "MyLib ng add Schema", "type": "object", "properties": { "project": { "type": "string", "description": "Name of the project.", "$default": { "$source": "projectName" } } } }schema.json파일의 옵션에 대한 인터페이스를 정의하는schema.ts파일을 만듭니다.projects/my-lib/schematics/ng-add/schema.ts (ng-add 스키마 인터페이스)
export interface Schema { // Name of the project. project: string; }주요 파일인
index.ts를 만들고 스키매틱 팩토리 함수의 소스 코드를 추가합니다.projects/my-lib/schematics/ng-add/index.ts (ng-add 규칙 팩토리)
import {Rule} from '@angular-devkit/schematics'; import {addRootImport} from '@schematics/angular/utility'; import {Schema} from './schema'; export function ngAdd(options: Schema): Rule { // Add an import `MyLibModule` from `my-lib` to the root of the user's project. return addRootImport( options.project, ({code, external}) => code`${external('MyLibModule', 'my-lib')}`, ); }
Angular CLI는 라이브러리의 최신 버전을 자동으로 설치합니다.
이 예제에서는 여기서 더 나아가 애플리케이션의 최상위에 MyLibModule을 추가합니다.
addRootImport 함수는 코드 블록을 반환하는 콜백을 받습니다.
code 함수로 태그한 문자열 안에는 어떤 코드든 작성할 수 있으며, 외부 심볼은 적절한 import 구문이 생성되도록 반드시 external 함수로 감싸야 합니다.
의존성 유형 정의하기
ng-add의 save 옵션으로 프로젝트의 package.json 설정 파일에서 라이브러리를 dependencies나 devDependencies에 추가할지, 아예 저장하지 않을지 지정할 수 있습니다.
projects/my-lib/package.json (ng-add 참조)
"ng-add": {
"save": "devDependencies"
},
사용할 수 있는 값은 다음과 같습니다:
| 값 | 설명 |
|---|---|
false |
패키지를 package.json에 추가하지 않습니다. |
true |
패키지를 dependencies에 추가합니다. |
"dependencies" |
패키지를 dependencies에 추가합니다. |
"devDependencies" |
패키지를 devDependencies에 추가합니다. |
스키매틱 빌드하기
라이브러리와 함께 스키매틱을 묶으려면 스키매틱을 별도로 빌드한 뒤 번들에 추가하도록 라이브러리를 설정해야 합니다. 올바른 디렉터리에 배치되도록 라이브러리를 빌드한 후에 스키매틱을 빌드해야 합니다.
- 라이브러리에는 스키매틱을 컴파일해서 배포 라이브러리에 포함하는 방법을 지정한 커스텀 TypeScript 설정 파일이 필요합니다.
- 스키매틱을 라이브러리 번들에 추가하려면 라이브러리의
package.json파일에 스크립트를 추가합니다.
Angular 워크스페이스에 my-lib라는 라이브러리 프로젝트가 있다고 가정해 봅시다.
스키매틱 빌드 방법을 지정하려면 라이브러리 빌드를 설정하는 기존 tsconfig.lib.json 파일 옆에 tsconfig.schematics.json 파일을 추가하세요.
tsconfig.schematics.json파일에 다음 내용을 추가합니다.projects/my-lib/tsconfig.schematics.json (TypeScript 설정)
{ "compilerOptions": { "baseUrl": ".", "lib": ["es2018", "dom"], "declaration": true, "module": "commonjs", "moduleResolution": "node", "noEmitOnError": true, "noFallthroughCasesInSwitch": true, "noImplicitAny": true, "noImplicitThis": true, "noUnusedParameters": true, "noUnusedLocals": true, "rootDir": "schematics", "outDir": "../../dist/my-lib/schematics", "skipDefaultLibCheck": true, "skipLibCheck": true, "sourceMap": true, "target": "es6", "types": ["jasmine", "node"] }, "include": ["schematics/**/*"], "exclude": ["schematics/*/files/**/*"] }옵션 설명 rootDir컴파일할 입력 파일이 schematics폴더에 있음을 지정합니다.outDir라이브러리의 출력 폴더에 대응합니다. 기본값은 워크스페이스 최상위의 dist/my-lib폴더입니다.스키매틱 소스 파일이 컴파일되어 라이브러리 번들에 포함되도록 라이브러리 프로젝트의 최상위 폴더(
projects/my-lib)에 있는package.json파일에 다음 스크립트를 추가합니다.projects/my-lib/package.json (빌드 스크립트)
{ "name": "my-lib", "version": "0.0.1", "scripts": { "build": "tsc -p tsconfig.schematics.json", "postbuild": "copyfiles schematics/*/schema.json schematics/*/files/** schematics/collection.json ../../dist/my-lib/" }, "peerDependencies": { "@angular/common": "^22.0.0", "@angular/core": "^22.0.0" }, "schematics": "./schematics/collection.json", "ng-add": { "save": "devDependencies" }, "devDependencies": { "copyfiles": "file:../../node_modules/copyfiles", "typescript": "file:../../node_modules/typescript" } }build스크립트는 커스텀tsconfig.schematics.json파일로 스키매틱을 컴파일합니다.postbuild스크립트는build스크립트가 완료된 후 스키매틱 파일을 복사합니다.build와postbuild스크립트에는copyfiles와typescript의존성이 필요합니다. 의존성을 설치하려면devDependencies가 정의된 경로로 이동해서 스크립트를 실행하기 전에npm install을 실행하세요.
생성 기능 지원하기
컬렉션에 이름이 있는 스키매틱을 추가하면 사용자가 ng generate 명령어로 라이브러리에 정의된 구성 요소를 생성할 수 있습니다.
라이브러리에 설정 작업이 필요한 my-service라는 서비스가 정의되어 있다고 가정해 봅시다.
사용자가 다음 CLI 명령어로 이 서비스를 생성할 수 있도록 만들려고 합니다.
ng generate my-lib:my-service
먼저 schematics 폴더에 my-service 하위 폴더를 만드세요.
새 스키매틱 설정하기
컬렉션에 스키매틱을 추가할 때는 컬렉션 스키마에서 해당 스키매틱을 가리키도록 하고, 사용자가 명령어에 전달할 수 있는 옵션을 정의하는 설정 파일을 제공해야 합니다.
schematics/collection.json파일을 수정해서 새 스키매틱 하위 폴더를 가리키도록 하고, 새 스키매틱의 입력을 지정하는 스키마 파일 참조도 추가합니다.projects/my-lib/schematics/collection.json (스키매틱 컬렉션)
{ "$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "ng-add": { "description": "Add my library to the project.", "factory": "./ng-add/index#ngAdd", "schema": "./ng-add/schema.json" }, "my-service": { "description": "Generate a service in the project.", "factory": "./my-service/index#myService", "schema": "./my-service/schema.json" } } }<lib-root>/schematics/my-service폴더로 이동합니다.schema.json파일을 만들고 스키매틱에서 사용할 수 있는 옵션을 정의합니다.projects/my-lib/schematics/my-service/schema.json (스키매틱 JSON 스키마)
{ "$schema": "https://json-schema.org/schema", "$id": "SchematicsMyService", "title": "My Service Schema", "type": "object", "properties": { "name": { "description": "The name of the service.", "type": "string" }, "path": { "type": "string", "format": "path", "description": "The path to create the service.", "visible": false, "$default": { "$source": "workingDirectory" } }, "project": { "type": "string", "description": "The name of the project.", "$default": { "$source": "projectName" } } }, "required": ["name"] }- id: 컬렉션 안에서 스키마를 식별하는 고유 ID입니다.
- title: 사람이 읽을 수 있는 스키마 설명입니다.
- type: 프로퍼티가 제공하는 타입을 설명합니다.
- properties: 스키매틱에서 사용할 수 있는 옵션을 정의하는 객체입니다.
각 옵션은 키에 타입, 설명, 선택적인 별칭을 연결합니다. 타입은 기대하는 값의 형태를 정의하고, 설명은 사용자가 스키매틱 사용법에 대한 도움말을 요청할 때 표시됩니다.
스키매틱 옵션을 추가로 구성하는 방법은 워크스페이스 스키마를 참고하세요.
schema.ts파일을 만들고schema.json에 정의된 옵션 값을 저장하는 인터페이스를 정의합니다.projects/my-lib/schematics/my-service/schema.ts (스키매틱 인터페이스)
export interface Schema { // The name of the service. name: string; // The path to create the service. path?: string; // The name of the project. project?: string; }옵션 설명 name 생성할 서비스에 지정할 이름입니다. path 스키매틱에 제공되는 경로를 덮어씁니다. 기본 경로 값은 현재 작업 디렉터리를 기준으로 합니다. project 스키매틱을 실행할 특정 프로젝트를 지정합니다. 사용자가 옵션을 지정하지 않으면 스키매틱에서 기본값을 제공할 수 있습니다.
템플릿 파일 추가하기
프로젝트에 구성 요소를 추가하려면 스키매틱에 자체 템플릿 파일이 필요합니다. 스키매틱 템플릿은 코드 실행과 변수 치환을 위한 특별한 문법을 지원합니다.
schematics/my-service/폴더 안에files/폴더를 만듭니다.__name@dasherize__.service.ts.template파일을 만들고 파일 생성에 사용할 템플릿을 정의합니다. 이 템플릿은 Angular의HttpClient가http프로퍼티에 이미 주입된 서비스를 생성합니다.projects/my-lib/schematics/my-service/files/__name@dasherize__.service.ts.template (Schematic Template)
import { Service } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Service() export class <%= classify(name) %>Service { private http = inject(HttpClient); }classify와dasherize메서드는 스키매틱에서 소스 템플릿과 파일명을 변환할 때 사용하는 유틸리티 함수입니다.name은 팩토리 함수에서 프로퍼티로 제공합니다. 스키마에서 정의한name과 같은 값입니다.
팩토리 함수 추가하기
기반 구조를 마련했으므로 이제 사용자 프로젝트에 필요한 변경을 수행하는 주요 함수를 정의할 수 있습니다.
스키매틱 프레임워크는 경로와 콘텐츠 템플릿을 모두 지원하는 파일 템플릿 시스템을 제공합니다.
이 시스템은 입력 Tree에 로드된 파일이나 경로 내부의 플레이스홀더를 처리합니다.
Rule에 전달한 값으로 플레이스홀더를 채웁니다.
이 자료 구조와 문법에 대한 자세한 내용은 Schematics README를 참고하세요.
주요 파일인
index.ts를 만들고 스키매틱 팩토리 함수의 소스 코드를 추가합니다.먼저 필요한 스키매틱 정의를 가져옵니다. 스키매틱 프레임워크는 스키매틱 실행 과정에서 규칙을 만들고 사용하는 여러 유틸리티 함수를 제공합니다.
스키매틱 옵션의 타입 정보를 제공하는 스키마 인터페이스를 가져옵니다.
projects/my-lib/schematics/my-service/index.ts (스키마 가져오기)
import { Rule, Tree, SchematicsException, apply, url, applyTemplates, move, chain, mergeWith, } from '@angular-devkit/schematics'; import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core'; import {Schema as MyServiceSchema} from './schema';빈 규칙 팩토리에서 시작해서 생성 스키매틱을 구현합니다.
projects/my-lib/schematics/my-service/index.ts (초기 규칙)
export function myService(options: MyServiceSchema): Rule { return (tree: Tree) => tree; }
이 규칙 팩토리는 트리를 수정하지 않고 그대로 반환합니다.
옵션에는 ng generate 명령어로 전달된 옵션 값이 들어 있습니다.
생성 규칙 정의하기
이제 사용자 애플리케이션을 실제로 수정해서 라이브러리에 정의된 서비스를 설정하는 코드를 작성할 기반이 마련되었습니다.
사용자가 라이브러리를 설치한 Angular 워크스페이스에는 애플리케이션과 라이브러리로 이루어진 여러 프로젝트가 있습니다. 사용자는 명령줄에서 프로젝트를 지정하거나 기본값을 사용할 수 있습니다. 어느 경우든 프로젝트 설정에서 정보를 가져올 수 있도록 스키매틱이 적용되는 특정 프로젝트를 코드에서 식별해야 합니다.
팩토리 함수에 전달되는 Tree 객체로 이 작업을 수행합니다.
Tree의 메서드로 워크스페이스의 전체 파일 트리에 접근할 수 있으므로 스키매틱 실행 중에 파일을 읽고 쓸 수 있습니다.
프로젝트 설정 가져오기
대상 프로젝트를 확인하려면
workspaces.readWorkspace메서드로 워크스페이스 설정 파일angular.json의 내용을 읽습니다.workspaces.readWorkspace를 사용하려면Tree를 바탕으로workspaces.WorkspaceHost를 생성해야 합니다. 팩토리 함수에 다음 코드를 추가하세요.projects/my-lib/schematics/my-service/index.ts (스키마 가져오기)
import { Rule, Tree, SchematicsException, apply, url, applyTemplates, move, chain, mergeWith, } from '@angular-devkit/schematics'; import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core'; import {Schema as MyServiceSchema} from './schema'; function createHost(tree: Tree): workspaces.WorkspaceHost { return { async readFile(path: string): Promise<string> { const data = tree.read(path); if (!data) { throw new SchematicsException('File not found.'); } return virtualFs.fileBufferToString(data); }, async writeFile(path: string, data: string): Promise<void> { return tree.overwrite(path, data); }, async isDirectory(path: string): Promise<boolean> { return !tree.exists(path) && tree.getDir(path).subfiles.length > 0; }, async isFile(path: string): Promise<boolean> { return tree.exists(path); }, }; } export function myService(options: MyServiceSchema): Rule { return async (tree: Tree) => { const host = createHost(tree); const {workspace} = await workspaces.readWorkspace('/', host); }; }컨텍스트가 존재하는지 반드시 확인하고 적절한 오류를 발생시키세요.
프로젝트 이름을 확인했으므로 이를 사용해서 프로젝트별 설정 정보를 가져옵니다.
projects/my-lib/schematics/my-service/index.ts (프로젝트)
const project = options.project != null ? workspace.projects.get(options.project) : null; if (!project) { throw new SchematicsException(`Invalid project name: ${options.project}`); } const projectType = project.extensions.projectType === 'application' ? 'app' : 'lib';workspace.projects객체에는 모든 프로젝트별 설정 정보가 들어 있습니다.options.path는 스키매틱 적용 후 템플릿 파일을 이동할 위치를 결정합니다.스키매틱 스키마의
path옵션은 기본적으로 현재 작업 디렉터리로 대체됩니다.path가 정의되지 않았다면 프로젝트 설정의sourceRoot와projectType을 사용하세요.projects/my-lib/schematics/my-service/index.ts (프로젝트 정보)
if (options.path === undefined) { options.path = `${project.sourceRoot}/${projectType}`; }
규칙 정의하기
Rule은 외부 템플릿 파일을 가져와 변환한 뒤 변환된 템플릿을 사용하는 다른 Rule 객체를 반환할 수 있습니다.
템플릿을 활용해서 스키매틱에 필요한 커스텀 파일을 생성하세요.
팩토리 함수에 다음 코드를 추가합니다.
projects/my-lib/schematics/my-service/index.ts (템플릿 변환)
const templateSource = apply(url('./files'), [ applyTemplates({ classify: strings.classify, dasherize: strings.dasherize, name: options.name, }), move(normalize(options.path as string)), ]);메서드 설명 apply()소스에 여러 규칙을 적용하고 변환된 소스를 반환합니다. 소스와 규칙 배열의 두 인자를 받습니다. url()스키매틱 기준 상대 경로로 파일 시스템에서 소스 파일을 읽습니다. applyTemplates()스키매틱 템플릿과 파일명에서 사용할 메서드와 프로퍼티를 인자로 받고 Rule을 반환합니다. 여기에서classify(),dasherize()메서드와name프로퍼티를 지정합니다.classify()값을 받아 각 단어의 첫 글자를 대문자로 연결한 형태로 반환합니다. 예를 들어 my service를 전달하면MyService를 반환합니다.dasherize()값을 받아 소문자와 하이픈으로 구성된 형태로 반환합니다. 예를 들어 MyService를 전달하면 my-service를 반환합니다.move()스키매틱을 적용할 때 제공된 소스 파일을 대상 위치로 이동합니다. 마지막으로 규칙 팩토리는 규칙을 반환해야 합니다.
projects/my-lib/schematics/my-service/index.ts (규칙 연결)
return chain([mergeWith(templateSource)]);chain()메서드로 여러 규칙을 하나로 묶으면 스키매틱 하나에서 여러 작업을 수행할 수 있습니다. 여기서는 템플릿 규칙을 스키매틱에서 실행하는 코드와 병합하기만 합니다.
다음 스키매틱 규칙 함수의 전체 예제를 확인하세요.
projects/my-lib/schematics/my-service/index.ts
import {
Rule,
Tree,
SchematicsException,
apply,
url,
applyTemplates,
move,
chain,
mergeWith,
} from '@angular-devkit/schematics';
import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';
import {Schema as MyServiceSchema} from './schema';
function createHost(tree: Tree): workspaces.WorkspaceHost {
return {
async readFile(path: string): Promise<string> {
const data = tree.read(path);
if (!data) {
throw new SchematicsException('File not found.');
}
return virtualFs.fileBufferToString(data);
},
async writeFile(path: string, data: string): Promise<void> {
return tree.overwrite(path, data);
},
async isDirectory(path: string): Promise<boolean> {
return !tree.exists(path) && tree.getDir(path).subfiles.length > 0;
},
async isFile(path: string): Promise<boolean> {
return tree.exists(path);
},
};
}
export function myService(options: MyServiceSchema): Rule {
return async (tree: Tree) => {
const host = createHost(tree);
const {workspace} = await workspaces.readWorkspace('/', host);
const project = options.project != null ? workspace.projects.get(options.project) : null;
if (!project) {
throw new SchematicsException(`Invalid project name: ${options.project}`);
}
const projectType = project.extensions.projectType === 'application' ? 'app' : 'lib';
if (options.path === undefined) {
options.path = `${project.sourceRoot}/${projectType}`;
}
const templateSource = apply(url('./files'), [
applyTemplates({
classify: strings.classify,
dasherize: strings.dasherize,
name: options.name,
}),
move(normalize(options.path as string)),
]);
return chain([mergeWith(templateSource)]);
};
}
규칙과 유틸리티 메서드에 대한 자세한 내용은 제공되는 규칙을 참고하세요.
라이브러리 스키매틱 실행하기
라이브러리와 스키매틱을 빌드한 뒤 스키매틱 컬렉션을 설치해서 프로젝트에 실행할 수 있습니다. 다음 단계에서는 앞에서 만든 스키매틱으로 서비스를 생성합니다.
라이브러리와 스키매틱 빌드하기
워크스페이스의 최상위에서 라이브러리를 대상으로 ng build 명령어를 실행합니다.
ng build my-lib
그런 다음 라이브러리 디렉터리로 이동해서 스키매틱을 빌드합니다.
cd projects/my-lib
npm run build
라이브러리 연결하기
라이브러리와 스키매틱은 패키지로 묶여 워크스페이스 최상위의 dist/my-lib 폴더에 배치됩니다.
스키매틱을 실행하려면 라이브러리를 node_modules 폴더에 연결해야 합니다.
워크스페이스 최상위에서 배포 가능한 라이브러리의 경로를 지정해서 npm link 명령어를 실행하세요.
npm link dist/my-lib
스키매틱 실행하기
라이브러리 설치를 마쳤으므로 ng generate 명령어로 스키매틱을 실행합니다.
ng generate my-lib:my-service --name my-data
콘솔에서 스키매틱이 실행되고 애플리케이션 폴더에 my-data.service.ts 파일이 생성된 것을 확인할 수 있습니다.
CREATE src/app/my-data.service.ts (208 bytes)