آنلاین

شماتیک‌ها برای کتابخانه‌ها

هنگام ایجاد یک کتابخانه Angular، می‌توانید شماتیک‌هایی را همراه آن ارائه و بسته‌بندی کنید که با Angular CLI یکپارچه می‌شوند. با این شماتیک‌ها، کاربران می‌توانند برای نصب نسخه اولیه کتابخانه از ng add، برای ایجاد مصنوعات تعریف‌شده در کتابخانه از ng generate و برای تطبیق پروژه با نسخه جدیدی از کتابخانه که تغییرات ناسازگار دارد از ng update استفاده کنند.

هر سه نوع شماتیک می‌توانند بخشی از یک collection باشند که همراه کتابخانه بسته‌بندی می‌کنید.

ایجاد یک collection شماتیک

برای شروع یک collection، باید فایل‌های شماتیک را ایجاد کنید. مراحل زیر نحوه افزودن پشتیبانی اولیه را بدون تغییر هیچ‌یک از فایل‌های پروژه نشان می‌دهند.

  1. در پوشه ریشه کتابخانه، پوشه‌ای به نام schematics ایجاد کنید.
  2. در پوشه schematics/، برای نخستین شماتیک خود پوشه‌ای به نام ng-add بسازید.
  3. در سطح ریشه پوشه schematics، فایل collection.json را ایجاد کنید.
  4. فایل collection.json را ویرایش و schema اولیه collection را تعریف کنید.
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"
    }
  }
}

این ورودی شامل توضیحات است و به تابع factory اشاره می‌کند که هنگام اجرای شماتیک فراخوانی می‌شود.

  • مسیر $schema نسبت به schema مربوط به collection در Angular Devkit تعیین می‌شود.
  • شیء schematics شماتیک‌های نام‌گذاری‌شده‌ای را توصیف می‌کند که بخشی از این collection هستند.
  • نخستین ورودی مربوط به شماتیکی با نام ng-add است.

Angular CLI هنگام اجرای فرمان‌ها از این ورودی برای یافتن شماتیک‌های نام‌گذاری‌شده در collection استفاده می‌کند.

  1. در فایل package.json پروژه کتابخانه، یک ورودی «schematics» با مسیر فایل schema خود اضافه کنید.
projects/my-lib/package.json (Schematics Collection Reference)
// #docplaster
// #docregion collection
{
  "name": "my-lib",
  "version": "0.0.1",
// #enddocregion collection
  "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"
  },
// #docregion collection
  "schematics": "./schematics/collection.json",
// #enddocregion collection
// #docregion ng-add
  "ng-add": {
    "save": "devDependencies"
  },
// #enddocregion ng-add
  "devDependencies": {
    "copyfiles": "file:../../node_modules/copyfiles",
    "typescript": "file:../../node_modules/typescript"
  }
// #docregion collection
}

schema اولیه‌ای که ایجاد کرده‌اید به CLI می‌گوید شماتیک پشتیبان فرمان ng add را کجا پیدا کند. اکنون آماده ایجاد آن شماتیک هستید.

فراهم کردن پشتیبانی نصب

یک شماتیک برای فرمان ng add می‌تواند فرایند نصب اولیه را برای کاربران بهبود دهد. مراحل زیر این نوع شماتیک را تعریف می‌کنند.

  1. به پوشه <lib-root>/schematics/ng-add بروید.
  2. یک فایل schema.json برای تعریف گزینه‌های قابل پذیرش شماتیک ایجاد کنید.
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"
      }
    }
  }
}
  1. فایل schema.ts را برای تعریف interface گزینه‌های موجود در فایل schema.json ایجاد کنید.
projects/my-lib/schematics/ng-add/schema.ts (ng-add Schema Interface)
export interface Schema {
  // Name of the project.
  project: string;
}
  1. فایل اصلی یعنی index.ts را ایجاد و کد منبع تابع factory شماتیک را اضافه کنید.
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 به ریشه application یک گام فراتر می‌رود. تابع addRootImport یک callback دریافت می‌کند که باید یک بلوک کد برگرداند. می‌توانید هر کدی را داخل رشته‌ای بنویسید که با تابع code برچسب خورده است؛ هر نماد خارجی نیز باید با تابع external پوشانده شود تا import statementهای مناسب تولید شوند.

تعریف نوع dependency

از گزینه save مربوط به ng-add استفاده کنید تا مشخص شود کتابخانه به dependencies یا devDependencies افزوده شود، یا اصلاً در فایل پیکربندی package.json پروژه ذخیره نشود.

projects/my-lib/package.json (ng-add Reference)
// #docplaster
// #docregion collection
{
  "name": "my-lib",
  "version": "0.0.1",
// #enddocregion collection
  "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"
  },
// #docregion collection
  "schematics": "./schematics/collection.json",
// #enddocregion collection
// #docregion ng-add
  "ng-add": {
    "save": "devDependencies"
  },
// #enddocregion ng-add
  "devDependencies": {
    "copyfiles": "file:../../node_modules/copyfiles",
    "typescript": "file:../../node_modules/typescript"
  }
// #docregion collection
}

مقادیر ممکن عبارت‌اند از:

مقدارهاجزئیات
falsepackage را به package.json اضافه نمی‌کند
truepackage را به dependencies اضافه می‌کند
"dependencies"package را به dependencies اضافه می‌کند
"devDependencies"package را به devDependencies اضافه می‌کند

ساخت شماتیک‌ها

برای بسته‌بندی شماتیک‌ها همراه کتابخانه، باید کتابخانه را طوری پیکربندی کنید که شماتیک‌ها را جداگانه بسازد و سپس آن‌ها را به bundle اضافه کند. شماتیک‌ها را باید پس از ساخت کتابخانه بسازید تا در دایرکتوری درست قرار گیرند.

  • کتابخانه به یک فایل پیکربندی سفارشی TypeScript نیاز دارد که دستورالعمل‌های کامپایل شماتیک‌ها در کتابخانه توزیع‌شده را مشخص کند
  • برای افزودن شماتیک‌ها به bundle کتابخانه، scriptهایی را به فایل package.json کتابخانه اضافه کنید

فرض کنید در workspace انگولار خود پروژه کتابخانه‌ای به نام my-lib دارید. برای مشخص کردن نحوه ساخت شماتیک‌ها، در کنار فایل تولیدشده tsconfig.lib.json که build کتابخانه را پیکربندی می‌کند، یک فایل tsconfig.schematics.json اضافه کنید.

  1. فایل 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 شما است.
  1. برای اطمینان از کامپایل فایل‌های منبع شماتیک در bundle کتابخانه، scriptهای زیر را به فایل package.json در پوشه ریشه پروژه کتابخانه \(projects/my-lib\) اضافه کنید.
projects/my-lib/package.json (Build Scripts)
// #docplaster
// #docregion collection
{
  "name": "my-lib",
  "version": "0.0.1",
// #enddocregion collection
  "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"
  },
// #docregion collection
  "schematics": "./schematics/collection.json",
// #enddocregion collection
// #docregion ng-add
  "ng-add": {
    "save": "devDependencies"
  },
// #enddocregion ng-add
  "devDependencies": {
    "copyfiles": "file:../../node_modules/copyfiles",
    "typescript": "file:../../node_modules/typescript"
  }
// #docregion collection
}

برای نصب dependencyها، به مسیر تعریف‌شده در devDependencies بروید و پیش از اجرای scriptها، npm install را اجرا کنید.

  • script مربوط به build شماتیک را با استفاده از فایل سفارشی tsconfig.schematics.json کامپایل می‌کند
  • script مربوط به postbuild پس از تکمیل script مربوط به build فایل‌های شماتیک را کپی می‌کند
  • هر دو script مربوط به build و postbuild به dependencyهای copyfiles و typescript نیاز دارند.

فراهم کردن پشتیبانی تولید

می‌توانید یک شماتیک نام‌گذاری‌شده به collection خود اضافه کنید تا کاربران بتوانند با فرمان ng generate یک artifact تعریف‌شده در کتابخانه را ایجاد کنند.

فرض می‌کنیم کتابخانه شما serviceای به نام my-service تعریف می‌کند که به مقداری راه‌اندازی نیاز دارد. می‌خواهید کاربران بتوانند آن را با فرمان CLI زیر تولید کنند.

shell
ng generate my-lib:my-service

برای شروع، در پوشه schematics زیرپوشه جدیدی با نام my-service ایجاد کنید.

پیکربندی شماتیک جدید

هنگام افزودن یک شماتیک به collection باید در schema مربوط به collection به آن اشاره کنید و فایل‌های پیکربندی لازم را برای تعریف گزینه‌های قابل ارسال کاربر به فرمان فراهم کنید.

  1. فایل schematics/collection.json را ویرایش کنید تا به زیرپوشه شماتیک جدید و فایل schema مشخص‌کننده ورودی‌های آن اشاره کند.
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"
    }
  }
}
  1. به پوشه <lib-root>/schematics/my-service بروید.
  2. فایل schema.json را ایجاد و گزینه‌های در دسترس شماتیک را تعریف کنید.
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: شناسه‌ای یکتا برای schema در collection.
  • title: توضیحی خوانا برای schema.
  • type: توصیف‌کننده نوع ارائه‌شده توسط propertyها.
  • properties: شیئی که گزینه‌های در دسترس شماتیک را تعریف می‌کند.

هر گزینه، یک کلید را به نوع، توضیحات و alias اختیاری مرتبط می‌کند. نوع، شکل مقدار مورد انتظار را تعریف می‌کند و وقتی کاربر راهنمای استفاده از شماتیک را درخواست کند، توضیحات نمایش داده می‌شوند.

برای سفارشی‌سازی بیشتر گزینه‌های شماتیک، schema مربوط به workspace را ببینید.

  1. فایل schema.ts را ایجاد و interfaceای تعریف کنید که مقادیر گزینه‌های موجود در فایل 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نامی که می‌خواهید برای service ایجادشده ارائه کنید.
pathمسیر ارائه‌شده به شماتیک را بازنویسی می‌کند. مقدار پیش‌فرض مسیر بر اساس دایرکتوری کاری فعلی است.
projectپروژه مشخصی را برای اجرای شماتیک فراهم می‌کند. اگر کاربر گزینه را ارائه نکند، می‌توانید در شماتیک یک مقدار پیش‌فرض تعیین کنید.

افزودن فایل‌های template

برای افزودن artifactها به پروژه، شماتیک به فایل‌های template مخصوص خود نیاز دارد. templateهای شماتیک از syntax ویژه‌ای برای اجرای کد و جایگزینی متغیرها پشتیبانی می‌کنند.

این template یک service ایجاد می‌کند که HttpClient انگولار از قبل در property به نام http آن inject شده است.

  1. داخل پوشه schematics/my-service/ یک پوشه files/ ایجاد کنید.
  2. فایلی با نام _name@dasherize_.service.ts.template ایجاد کنید که template مورد استفاده برای تولید فایل‌ها را تعریف کند.

```ts {header: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); }

```

این همان name تعریف‌شده در schema است.

  • متدهای classify و dasherize توابع کمکی‌ای هستند که شماتیک برای تبدیل template منبع و نام فایل به کار می‌برد.
  • name به‌عنوان یک property از تابع factory شما ارائه می‌شود.

افزودن تابع factory

اکنون که زیرساخت آماده است، می‌توانید تابع اصلی انجام‌دهنده تغییرات مورد نیاز در پروژه کاربر را تعریف کنید.

فریم‌ورک Schematics یک سیستم templateسازی فایل فراهم می‌کند که هم از templateهای مسیر و هم محتوا پشتیبانی می‌کند. این سیستم بر placeholderهای تعریف‌شده داخل فایل‌ها یا مسیرهای بارگذاری‌شده در Tree عمل می‌کند. سپس با مقادیر ارسال‌شده به Rule، آن‌ها را پر می‌کند.

برای جزئیات این ساختارهای داده و syntax، README مربوط به Schematics را ببینید.

فریم‌ورک Schematics توابع کمکی بسیاری برای ایجاد و استفاده از ruleها هنگام اجرای شماتیک ارائه می‌کند.

  1. فایل اصلی index.ts را ایجاد و کد منبع تابع factory شماتیک را اضافه کنید.
  2. ابتدا تعریف‌های شماتیک مورد نیاز را import کنید.
projects/my-lib/schematics/my-service/index.ts (Imports)
// #docplaster
// #docregion schematics-imports, schema-imports, workspace
import {
  Rule,
  Tree,
  SchematicsException,
  apply,
  url,
  applyTemplates,
  move,
  chain,
  mergeWith,
} from '@angular-devkit/schematics';

import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';
// #enddocregion schematics-imports

import {Schema as MyServiceSchema} from './schema';
// #enddocregion schema-imports

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);

    // #enddocregion workspace

    // #docregion project-info
    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';
    // #enddocregion project-info

    // #docregion path
    if (options.path === undefined) {
      options.path = `${project.sourceRoot}/${projectType}`;
    }
    // #enddocregion path

    // #docregion template
    const templateSource = apply(url('./files'), [
      applyTemplates({
        classify: strings.classify,
        dasherize: strings.dasherize,
        name: options.name,
      }),
      move(normalize(options.path as string)),
    ]);
    // #enddocregion template

    // #docregion chain
    return chain([mergeWith(templateSource)]);
    // #enddocregion chain
    // #docregion workspace
  };
}
  1. interface تعریف‌شده schema را که اطلاعات نوع گزینه‌های شماتیک را فراهم می‌کند import کنید.
projects/my-lib/schematics/my-service/index.ts (Schema Import)
// #docplaster
// #docregion schematics-imports, schema-imports, workspace
import {
  Rule,
  Tree,
  SchematicsException,
  apply,
  url,
  applyTemplates,
  move,
  chain,
  mergeWith,
} from '@angular-devkit/schematics';

import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';
// #enddocregion schematics-imports

import {Schema as MyServiceSchema} from './schema';
// #enddocregion schema-imports

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);

    // #enddocregion workspace

    // #docregion project-info
    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';
    // #enddocregion project-info

    // #docregion path
    if (options.path === undefined) {
      options.path = `${project.sourceRoot}/${projectType}`;
    }
    // #enddocregion path

    // #docregion template
    const templateSource = apply(url('./files'), [
      applyTemplates({
        classify: strings.classify,
        dasherize: strings.dasherize,
        name: options.name,
      }),
      move(normalize(options.path as string)),
    ]);
    // #enddocregion template

    // #docregion chain
    return chain([mergeWith(templateSource)]);
    // #enddocregion chain
    // #docregion workspace
  };
}
  1. برای ساخت شماتیک تولید، با یک rule factory خالی شروع کنید.
projects/my-lib/schematics/my-service/index.ts (Initial Rule)
import {Rule, Tree} from '@angular-devkit/schematics';

import {Schema as MyServiceSchema} from './schema';

// #docregion factory
export function myService(options: MyServiceSchema): Rule {
  return (tree: Tree) => tree;
}
// #enddocregion factory

این rule factory، tree را بدون تغییر برمی‌گرداند. گزینه‌ها همان مقادیری هستند که از فرمان ng generate ارسال شده‌اند.

تعریف rule تولید

اکنون چارچوب لازم برای ایجاد کدی را دارید که application کاربر را واقعاً تغییر می‌دهد تا برای service تعریف‌شده در کتابخانه آماده شود.

workspace انگولاری که کاربر کتابخانه شما را در آن نصب کرده شامل چندین پروژه \(application و کتابخانه\) است. کاربر می‌تواند پروژه را در command line مشخص کند یا اجازه دهد مقدار پیش‌فرض استفاده شود. در هر دو حالت، کد شما باید پروژه مشخصی را که شماتیک بر آن اعمال می‌شود شناسایی کند تا بتوانید اطلاعات پیکربندی پروژه را دریافت کنید.

این کار را با شیء Tree ارسال‌شده به تابع factory انجام دهید. متدهای Tree به شما امکان دسترسی به کل file tree در workspace را می‌دهند تا هنگام اجرای شماتیک فایل‌ها را بخوانید و بنویسید.

دریافت پیکربندی پروژه

برای استفاده از workspaces.readWorkspace باید یک workspaces.WorkspaceHost از Tree ایجاد کنید. کد زیر را به تابع factory اضافه کنید.

  1. برای تعیین پروژه مقصد، از متد workspaces.readWorkspace برای خواندن محتوای فایل پیکربندی workspace یعنی angular.json استفاده کنید.
projects/my-lib/schematics/my-service/index.ts (Schema Import)
// #docplaster
// #docregion schematics-imports, schema-imports, workspace
import {
  Rule,
  Tree,
  SchematicsException,
  apply,
  url,
  applyTemplates,
  move,
  chain,
  mergeWith,
} from '@angular-devkit/schematics';

import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';
// #enddocregion schematics-imports

import {Schema as MyServiceSchema} from './schema';
// #enddocregion schema-imports

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);

    // #enddocregion workspace

    // #docregion project-info
    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';
    // #enddocregion project-info

    // #docregion path
    if (options.path === undefined) {
      options.path = `${project.sourceRoot}/${projectType}`;
    }
    // #enddocregion path

    // #docregion template
    const templateSource = apply(url('./files'), [
      applyTemplates({
        classify: strings.classify,
        dasherize: strings.dasherize,
        name: options.name,
      }),
      move(normalize(options.path as string)),
    ]);
    // #enddocregion template

    // #docregion chain
    return chain([mergeWith(templateSource)]);
    // #enddocregion chain
    // #docregion workspace
  };
}

حتماً وجود context را بررسی کنید و خطای مناسب را صادر کنید.

  1. اکنون که نام پروژه را دارید، از آن برای دریافت اطلاعات پیکربندی مخصوص پروژه استفاده کنید.
projects/my-lib/schematics/my-service/index.ts (Project)
// #docplaster
// #docregion schematics-imports, schema-imports, workspace
import {
  Rule,
  Tree,
  SchematicsException,
  apply,
  url,
  applyTemplates,
  move,
  chain,
  mergeWith,
} from '@angular-devkit/schematics';

import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';
// #enddocregion schematics-imports

import {Schema as MyServiceSchema} from './schema';
// #enddocregion schema-imports

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);

    // #enddocregion workspace

    // #docregion project-info
    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';
    // #enddocregion project-info

    // #docregion path
    if (options.path === undefined) {
      options.path = `${project.sourceRoot}/${projectType}`;
    }
    // #enddocregion path

    // #docregion template
    const templateSource = apply(url('./files'), [
      applyTemplates({
        classify: strings.classify,
        dasherize: strings.dasherize,
        name: options.name,
      }),
      move(normalize(options.path as string)),
    ]);
    // #enddocregion template

    // #docregion chain
    return chain([mergeWith(templateSource)]);
    // #enddocregion chain
    // #docregion workspace
  };
}

شیء workspace.projects تمام اطلاعات پیکربندی مخصوص پروژه را در خود دارد.

  1. مقدار options.path تعیین می‌کند فایل‌های template شماتیک پس از اعمال شماتیک به کجا منتقل شوند.

گزینه path در schema شماتیک به‌طور پیش‌فرض با دایرکتوری کاری فعلی جایگزین می‌شود. اگر path تعریف نشده باشد، از sourceRoot موجود در پیکربندی پروژه همراه با projectType استفاده کنید.

projects/my-lib/schematics/my-service/index.ts (Project Info)
// #docplaster
// #docregion schematics-imports, schema-imports, workspace
import {
  Rule,
  Tree,
  SchematicsException,
  apply,
  url,
  applyTemplates,
  move,
  chain,
  mergeWith,
} from '@angular-devkit/schematics';

import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';
// #enddocregion schematics-imports

import {Schema as MyServiceSchema} from './schema';
// #enddocregion schema-imports

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);

    // #enddocregion workspace

    // #docregion project-info
    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';
    // #enddocregion project-info

    // #docregion path
    if (options.path === undefined) {
      options.path = `${project.sourceRoot}/${projectType}`;
    }
    // #enddocregion path

    // #docregion template
    const templateSource = apply(url('./files'), [
      applyTemplates({
        classify: strings.classify,
        dasherize: strings.dasherize,
        name: options.name,
      }),
      move(normalize(options.path as string)),
    ]);
    // #enddocregion template

    // #docregion chain
    return chain([mergeWith(templateSource)]);
    // #enddocregion chain
    // #docregion workspace
  };
}

تعریف rule

یک Rule می‌تواند از فایل‌های template خارجی استفاده کند، آن‌ها را تبدیل کند و شیء Rule دیگری را با template تبدیل‌شده برگرداند. از templateسازی برای تولید هر فایل سفارشی مورد نیاز شماتیک استفاده کنید.

  1. کد زیر را به تابع factory اضافه کنید.
projects/my-lib/schematics/my-service/index.ts (Template transform)
// #docplaster
// #docregion schematics-imports, schema-imports, workspace
import {
  Rule,
  Tree,
  SchematicsException,
  apply,
  url,
  applyTemplates,
  move,
  chain,
  mergeWith,
} from '@angular-devkit/schematics';

import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';
// #enddocregion schematics-imports

import {Schema as MyServiceSchema} from './schema';
// #enddocregion schema-imports

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);

    // #enddocregion workspace

    // #docregion project-info
    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';
    // #enddocregion project-info

    // #docregion path
    if (options.path === undefined) {
      options.path = `${project.sourceRoot}/${projectType}`;
    }
    // #enddocregion path

    // #docregion template
    const templateSource = apply(url('./files'), [
      applyTemplates({
        classify: strings.classify,
        dasherize: strings.dasherize,
        name: options.name,
      }),
      move(normalize(options.path as string)),
    ]);
    // #enddocregion template

    // #docregion chain
    return chain([mergeWith(templateSource)]);
    // #enddocregion chain
    // #docregion workspace
  };
}
متدهاجزئیات
apply()چندین rule را روی یک source اعمال می‌کند و source تبدیل‌شده را برمی‌گرداند. این متد دو آرگومان می‌گیرد: یک source و آرایه‌ای از ruleها.
url()فایل‌های source را نسبت به شماتیک از فایل‌سیستم می‌خواند.
applyTemplates()آرگومانی از متدها و propertyهایی را می‌گیرد که می‌خواهید در دسترس template شماتیک و نام فایل‌های شماتیک قرار گیرند و یک Rule برمی‌گرداند. متدهای classify() و dasherize() و property به نام name را اینجا تعریف می‌کنید.
classify()مقداری را می‌گیرد و آن را به حالت title case برمی‌گرداند. برای نمونه، اگر نام ارائه‌شده my service باشد، خروجی MyService است.
dasherize()مقداری را می‌گیرد و آن را به حالت حروف کوچک و خط‌تیره‌دار برمی‌گرداند. برای نمونه، اگر نام ارائه‌شده MyService باشد، خروجی my-service است.
move()هنگام اعمال شماتیک، فایل‌های source ارائه‌شده را به مقصد منتقل می‌کند.
  1. در پایان، rule factory باید یک rule برگرداند.
projects/my-lib/schematics/my-service/index.ts (Chain Rule)
// #docplaster
// #docregion schematics-imports, schema-imports, workspace
import {
  Rule,
  Tree,
  SchematicsException,
  apply,
  url,
  applyTemplates,
  move,
  chain,
  mergeWith,
} from '@angular-devkit/schematics';

import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';
// #enddocregion schematics-imports

import {Schema as MyServiceSchema} from './schema';
// #enddocregion schema-imports

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);

    // #enddocregion workspace

    // #docregion project-info
    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';
    // #enddocregion project-info

    // #docregion path
    if (options.path === undefined) {
      options.path = `${project.sourceRoot}/${projectType}`;
    }
    // #enddocregion path

    // #docregion template
    const templateSource = apply(url('./files'), [
      applyTemplates({
        classify: strings.classify,
        dasherize: strings.dasherize,
        name: options.name,
      }),
      move(normalize(options.path as string)),
    ]);
    // #enddocregion template

    // #docregion chain
    return chain([mergeWith(templateSource)]);
    // #enddocregion chain
    // #docregion workspace
  };
}

متد chain() اجازه می‌دهد چندین rule را در یک rule واحد ترکیب کنید تا بتوانید چندین عملیات را در یک شماتیک انجام دهید. در اینجا فقط ruleهای template را با هر کدی که شماتیک اجرا می‌کند ادغام می‌کنید.

نمونه کامل تابع rule شماتیک زیر را ببینید.

projects/my-lib/schematics/my-service/index.ts
// #docplaster
// #docregion schematics-imports, schema-imports, workspace
import {
  Rule,
  Tree,
  SchematicsException,
  apply,
  url,
  applyTemplates,
  move,
  chain,
  mergeWith,
} from '@angular-devkit/schematics';

import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';
// #enddocregion schematics-imports

import {Schema as MyServiceSchema} from './schema';
// #enddocregion schema-imports

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);

    // #enddocregion workspace

    // #docregion project-info
    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';
    // #enddocregion project-info

    // #docregion path
    if (options.path === undefined) {
      options.path = `${project.sourceRoot}/${projectType}`;
    }
    // #enddocregion path

    // #docregion template
    const templateSource = apply(url('./files'), [
      applyTemplates({
        classify: strings.classify,
        dasherize: strings.dasherize,
        name: options.name,
      }),
      move(normalize(options.path as string)),
    ]);
    // #enddocregion template

    // #docregion chain
    return chain([mergeWith(templateSource)]);
    // #enddocregion chain
    // #docregion workspace
  };
}

برای اطلاعات بیشتر درباره ruleها و متدهای کمکی، Ruleهای ارائه‌شده را ببینید.

اجرای شماتیک کتابخانه

پس از ساخت کتابخانه و شماتیک‌ها می‌توانید collection شماتیک را نصب و آن را روی پروژه اجرا کنید. مراحل زیر نحوه تولید یک service با شماتیکی را نشان می‌دهند که پیش‌تر ایجاد کردید.

ساخت کتابخانه و شماتیک‌ها

از ریشه workspace، فرمان ng build را برای کتابخانه اجرا کنید.

shell
ng build my-lib

سپس برای ساخت شماتیک وارد دایرکتوری کتابخانه شوید.

shell
cd projects/my-lib
npm run build

لینک کردن کتابخانه

کتابخانه و شماتیک‌های شما بسته‌بندی می‌شوند و در پوشه dist/my-lib در ریشه workspace قرار می‌گیرند. برای اجرای شماتیک باید کتابخانه را به پوشه node_modules لینک کنید. از ریشه workspace، فرمان npm link را با مسیر کتابخانه قابل توزیع اجرا کنید.

shell
npm link dist/my-lib

اجرای شماتیک

اکنون که کتابخانه نصب شده است، شماتیک را با فرمان ng generate اجرا کنید.

shell
ng generate my-lib:my-service --name my-data

در console می‌بینید که شماتیک اجرا و فایل my-data.service.ts در پوشه application ایجاد شده است.

shell
CREATE src/app/my-data.service.ts (208 bytes)