Когда вы создаёте Angular-библиотеку, её можно предоставить и упаковать вместе со schematics, которые интегрируют её с Angular CLI.
С вашими schematics пользователи могут использовать ng add для установки начальной версии библиотеки,
ng generate для создания артефактов, определённых в библиотеке, и ng update для адаптации проекта к новой версии библиотеки, вводящей breaking changes.
Все три типа schematics могут быть частью коллекции, которую вы упаковываете с библиотекой.
Создание коллекции schematics
Чтобы начать коллекцию, нужно создать файлы schematic. Следующие шаги показывают, как добавить начальную поддержку без изменения каких-либо файлов проекта.
В корневой папке библиотеки создайте папку
schematics.В папке
schematics/создайте папкуng-addдля первого schematic.На корневом уровне папки
schematicsсоздайте файлcollection.json.Отредактируйте файл
collection.json, чтобы определить начальную схему для коллекции.projects/my-lib/schematics/collection.json (Schematics Collection)
{ "$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описывает именованные schematics, входящие в эту коллекцию. - Первая запись — для schematic с именем
ng-add. Она содержит описание и указывает на фабричную функцию, вызываемую при выполнении schematic.
- Путь
В файле
package.jsonпроекта библиотеки добавьте запись «schematics» с путём к файлу схемы. Angular CLI использует эту запись, чтобы находить именованные schematics в коллекции при запуске команд.
projects/my-lib/package.json (Schematics Collection Reference)
{
"name": "my-lib",
"version": "0.0.1",
"schematics": "./schematics/collection.json",
}
Начальная схема, которую вы создали, сообщает CLI, где найти schematic, поддерживающий команду ng add.
Теперь можно создать этот schematic.
Предоставление поддержки установки
Schematic для команды ng add может улучшить начальный процесс установки для пользователей.
Следующие шаги определяют этот тип schematic.
Перейдите в папку
<lib-root>/schematics/ng-add.Создайте файл
schema.jsonдля определения опций, которые принимает schematic.projects/my-lib/schematics/ng-add/schema.json (ng-add Schema)
{ "$schema": "http://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.tsдля определения интерфейса опций, определённых в файлеschema.json.projects/my-lib/schematics/ng-add/schema.ts (ng-add Schema Interface)
export interface Schema { // Name of the project. project: string; }Создайте основной файл
index.tsи добавьте исходный код фабричной функции schematic.projects/my-lib/schematics/ng-add/index.ts (ng-add Rule Factory)
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 принимает callback, который должен вернуть блок кода. Можно писать любой код внутри строки, помеченной функцией code, а любые внешние символы нужно оборачивать функцией external, чтобы гарантировать генерацию соответствующих import-операторов.
Определение типа зависимости
Используйте опцию save у ng-add, чтобы настроить, должна ли библиотека добавляться в dependencies, devDependencies или вообще не сохраняться в файле конфигурации package.json проекта.
projects/my-lib/package.json (ng-add Reference)
"ng-add": {
"save": "devDependencies"
},
Возможные значения:
| Значения | Подробности |
|---|---|
false |
Не добавлять пакет в package.json |
true |
Добавить пакет в dependencies |
"dependencies" |
Добавить пакет в dependencies |
"devDependencies" |
Добавить пакет в devDependencies |
Сборка schematics
Чтобы объединить schematics вместе с библиотекой, нужно настроить библиотеку на отдельную сборку schematics, затем добавить их в бандл. Schematics нужно собирать после сборки библиотеки, чтобы они попали в правильный каталог.
- Библиотеке нужен пользовательский файл конфигурации TypeScript с инструкциями, как скомпилировать schematics в дистрибутивную библиотеку
- Чтобы добавить schematics в бандл библиотеки, добавьте скрипты в файл
package.jsonбиблиотеки
Предположим, в Angular workspace есть проект библиотеки my-lib.
Чтобы сообщить библиотеке, как собирать schematics, добавьте файл tsconfig.schematics.json рядом со сгенерированным файлом tsconfig.lib.json, который настраивает сборку библиотеки.
Отредактируйте файл
tsconfig.schematics.json, добавив следующее содержимое.projects/my-lib/tsconfig.schematics.json (TypeScript Config)
{ "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, "strictNullChecks": true, "target": "es6", "types": [ "jasmine", "node" ] }, "include": [ "schematics/**/*" ], "exclude": [ "schematics/*/files/**/*" ] }Опции Подробности rootDirУказывает, что папка schematicsсодержит входные файлы для компиляции.outDirСоответствует выходной папке библиотеки. По умолчанию это папка dist/my-libв корне workspace.Чтобы исходные файлы schematics компилировались в бандл библиотеки, добавьте следующие скрипты в файл
package.jsonв корневой папке проекта библиотеки (projects/my-lib).projects/my-lib/package.json (Build Scripts)
{ "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компилирует schematic с помощью пользовательского файлаtsconfig.schematics.json - Скрипт
postbuildкопирует файлы schematic после завершения скриптаbuild - Оба скрипта
buildиpostbuildтребуют зависимостиcopyfilesиtypescript. Чтобы установить зависимости, перейдите по пути, определённому вdevDependencies, и выполнитеnpm installперед запуском скриптов.
- Скрипт
Предоставление поддержки генерации
В коллекцию можно добавить именованный schematic, позволяющий пользователям использовать команду ng generate для создания артефакта, определённого в библиотеке.
Предположим, библиотека определяет сервис my-service, требующий некоторой настройки.
Вы хотите, чтобы пользователи могли генерировать его следующей командой CLI.
ng generate my-lib:my-service
Для начала создайте новую подпапку my-service в папке schematics.
Настройка нового schematic
При добавлении schematic в коллекцию нужно указать на него в схеме коллекции и предоставить файлы конфигурации для определения опций, которые пользователь может передать команде.
Отредактируйте файл
schematics/collection.json, чтобы указать на новую подпапку schematic, и включите указатель на файл схемы, задающий входы для нового schematic.projects/my-lib/schematics/collection.json (Schematics Collection)
{ "$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и определите доступные опции для schematic.projects/my-lib/schematics/my-service/schema.json (Schematic JSON Schema)
{ "$schema": "http://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: Объект, определяющий доступные опции для schematic.
Каждая опция связывает ключ с типом, описанием и необязательным алиасом. Тип определяет форму ожидаемого значения, а описание отображается, когда пользователь запрашивает справку по использованию schematic.
См. схему workspace для дополнительных кастомизаций опций schematic.
Создайте файл
schema.tsи определите интерфейс, хранящий значения опций, определённых в файлеschema.json.projects/my-lib/schematics/my-service/schema.ts (Schematic Interface)
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 Переопределяет путь, предоставленный schematic. Значение пути по умолчанию основано на текущем рабочем каталоге. project Указывает конкретный проект для запуска schematic. В schematic можно предоставить значение по умолчанию, если опция не предоставлена пользователем.
Добавление файлов шаблонов
Чтобы добавлять артефакты в проект, schematic нужны собственные файлы шаблонов. Шаблоны schematic поддерживают специальный синтаксис для выполнения кода и подстановки переменных.
Создайте папку
files/внутри папкиschematics/my-service/.Создайте файл с именем
__name@dasherize__.service.ts.template, определяющий шаблон для генерации файлов. Этот шаблон сгенерирует сервис, в который уже внедрёнHttpClientAngular в свойствоhttp.import { Service } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Service() export class <%= classify(name) %>Service { private http = inject(HttpClient); }- Методы
classifyиdasherize— утилитарные функции, которые schematic использует для преобразования исходного шаблона и имени файла. nameпредоставляется как свойство из фабричной функции. Это то жеname, которое вы определили в схеме.
- Методы
Добавление фабричной функции
Теперь, когда инфраструктура на месте, можно определить основную функцию, выполняющую нужные модификации в проекте пользователя.
Фреймворк Schematics предоставляет систему шаблонов файлов, поддерживающую шаблоны и путей, и содержимого.
Система работает с плейсхолдерами, определёнными внутри файлов или путей, загруженных во входной Tree.
Она заполняет их значениями, переданными в Rule.
Подробности об этих структурах данных и синтаксисе см. в Schematics README.
Создайте основной файл
index.tsи добавьте исходный код фабричной функции schematic.Сначала импортируйте определения schematics, которые понадобятся. Фреймворк Schematics предлагает много утилитарных функций для создания и использования rules при запуске schematic.
Импортируйте определённый интерфейс схемы, предоставляющий информацию о типах для опций schematic.
projects/my-lib/schematics/my-service/index.ts (Schema Import)
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';Чтобы построить generation schematic, начните с пустой фабрики rule.
projects/my-lib/schematics/my-service/index.ts (Initial Rule)
export function myService(options: MyServiceSchema): Rule { return (tree: Tree) => tree; }
Эта фабрика rule возвращает tree без изменений.
Опции — это значения опций, переданные через команду ng generate.
Определение generation rule
Теперь у вас есть фреймворк для создания кода, который фактически модифицирует приложение пользователя, чтобы настроить его для сервиса, определённого в библиотеке.
Angular workspace, куда пользователь установил библиотеку, содержит несколько проектов (приложения и библиотеки). Пользователь может указать проект в командной строке или оставить значение по умолчанию. В любом случае код должен идентифицировать конкретный проект, к которому применяется этот schematic, чтобы можно было получить информацию из конфигурации проекта.
Делайте это с помощью объекта Tree, передаваемого в фабричную функцию.
Методы Tree дают доступ к полному дереву файлов в workspace, позволяя читать и писать файлы во время выполнения schematic.
Получение конфигурации проекта
Чтобы определить целевой проект, используйте метод
workspaces.readWorkspaceдля чтения содержимого файла конфигурации workspaceangular.json. Для использованияworkspaces.readWorkspaceнужно создатьworkspaces.WorkspaceHostизTree. Добавьте следующий код в фабричную функцию.projects/my-lib/schematics/my-service/index.ts (Schema Import)
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 (Project)
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определяет, куда перемещаются файлы шаблонов schematic после применения schematic.Опция
pathв схеме schematic по умолчанию подставляется текущим рабочим каталогом. Еслиpathне определён, используйтеsourceRootиз конфигурации проекта вместе сprojectType.projects/my-lib/schematics/my-service/index.ts (Project Info)
if (options.path === undefined) { options.path = `${project.sourceRoot}/${projectType}`; }
Определение rule
Rule может использовать внешние файлы шаблонов, трансформировать их и возвращать другой объект Rule с трансформированным шаблоном.
Используйте шаблоны для генерации любых пользовательских файлов, необходимых для schematic.
Добавьте следующий код в фабричную функцию.
projects/my-lib/schematics/my-service/index.ts (Template transform)
const templateSource = apply(url('./files'), [ applyTemplates({ classify: strings.classify, dasherize: strings.dasherize, name: options.name, }), move(normalize(options.path as string)), ]);Методы Подробности apply()Применяет несколько rules к источнику и возвращает трансформированный источник. Принимает 2 аргумента: источник и массив rules. url()Читает исходные файлы из файловой системы относительно schematic. applyTemplates()Принимает аргумент методов и свойств, которые вы хотите сделать доступными шаблону schematic и именам файлов schematic. Возвращает Rule. Здесь определяются методыclassify()иdasherize()и свойствоname.classify()Принимает значение и возвращает его в title case. Например, если предоставлено имя my service, возвращаетсяMyService.dasherize()Принимает значение и возвращает его в dashed и lowercase. Например, если предоставлено имя MyService, возвращается my-service.move()Перемещает предоставленные исходные файлы в назначение при применении schematic. Наконец, фабрика rule должна вернуть rule.
projects/my-lib/schematics/my-service/index.ts (Chain Rule)
return chain([mergeWith(templateSource)]);Метод
chain()позволяет объединить несколько rules в одно rule, чтобы выполнять несколько операций в одном schematic. Здесь вы только объединяете rules шаблонов с любым кодом, выполняемым schematic.
См. полный пример следующей функции rule schematic.
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)]);
};
}
Дополнительную информацию о rules и утилитарных методах см. в Provided Rules.
Запуск schematic библиотеки
После сборки библиотеки и schematics можно установить коллекцию schematics для запуска против проекта. Следующие шаги показывают, как сгенерировать сервис с помощью schematic, созданного ранее.
Сборка библиотеки и schematics
Из корня workspace выполните команду ng build для библиотеки.
ng build my-lib
Затем перейдите в каталог библиотеки, чтобы собрать schematic
cd projects/my-lib
npm run build
Связывание библиотеки
Библиотека и schematics упакованы и размещены в папке dist/my-lib в корне workspace.
Для запуска schematic нужно связать библиотеку в папку node_modules.
Из корня workspace выполните команду npm link с путём к дистрибутивной библиотеке.
npm link dist/my-lib
Запуск schematic
Теперь, когда библиотека установлена, запустите schematic командой ng generate.
ng generate my-lib:my-service --name my-data
В консоли видно, что schematic был выполнен и файл my-data.service.ts создан в папке приложения.
CREATE src/app/my-data.service.ts (208 bytes)