还记得上一篇雪狼和大家聊的「Vibe Coding」时代吗?那是一个代码能够读懂你「心流」,自动遵循团队规范的时代。而其中最核心的「秘密武器」,就是咱们今天的主角 —— Angular Schematics。

是不是觉得 Schematics 像一支能自动生成代码、强制团队规范、甚至还能平滑迁移代码的「魔法棒」?没错!但光看别人耍「魔法」哪够过瘾?今天,雪狼就要手把手教你「炼制」这支属于你自己的「魔法棒」!咱们不光要「知其然」,更要「知其所以然」,把它的「内功心法」彻底学到手。

但授人以鱼不如授人以渔。今天,我们将深入 Schematics 的核心,学习如何亲手「锻造」这支「魔法棒」,打造属于我们自己的自动化代码生成工具。

搭建你的「魔法工坊」:Schematics 工作区初探#

要「炼制」魔法棒,总得有个像样的「魔法工坊」不是?别担心,雪狼带你一步步搭建起来。这里就是我们开发和测试 Schematics 的秘密基地。

  1. 请出「引路人」:安装 Schematics CLI

    
    npm install -g @angular-devkit/schematics-cli

    这位「引路人」会帮我们管理和测试我们即将创造的「魔法」。

  2. 创建你的第一个「魔法阵」:Schematics 项目

    
    schematics blank my-schematics-collection
    
    cd my-schematics-collection

    执行这两行「咒语」,一个名为 my-schematics-collection 的新文件夹就会凭空出现。这,就是你「魔法阵」的雏形。它的核心结构是这样的:

    
    my-schematics-collection/
    
    ├── src/
    
    │   ├── my-schematic/        # 你的第一个「魔法」包(Schematic)
    
    │   │   ├── files/           # 「魔法材料」存放地(模板文件)
    
    │   │   │   └── __name__.ts.template
    
    │   │   ├── index.ts         # 「魔法核心」所在(Schematic 的核心逻辑)
    
    │   │   └── schema.json      # 「魔法参数」定义(定义 Schematic 的选项)
    
    │   └── collection.json      # 「魔法索引」目录(定义 Schematic 的入口和配置)
    
    ├── package.json
    
    ├── tsconfig.json
    
    └── ...

    看到没?每个文件都有它独特的「魔法用途」,就像魔法书里的咒语和材料,缺一不可。

揭秘「魔法咒语」:index.ts里的「乾坤」与「变化」#

如果说 collection.json 是魔法书的目录,那 index.ts 就是真正的「魔法咒语」所在。它定义了 Schematic 的核心逻辑,所有的「变化」都将在这里发生。

一个 Schematic 的核心,其实就是一个接收 options(也就是我们施法时的「命令行参数」)并返回一个 Rule 的函数。而这个 Rule 呢,它又是一个接收 TreeSchematicContext,最终返回一个(经过「魔法」转换后的)Tree 的函数。听起来有点绕?没关系,雪狼帮你拆解:

  • Tree (虚拟文件系统):你可以把它想象成你项目的「平行宇宙」或者说「沙盘」。所有的「魔法」操作(比如创建、修改、删除文件)都不会直接影响你真实的项目文件,而是先在这个「沙盘」上进行推演。只有当你对结果满意,并最终「提交」时,这些改变才会真正映射到你的项目中。这保证了我们施法时的「原子性」和「可逆性」,即便施法失败也能「时光回溯」,不至于「炸毁」项目。

  • SchematicContext (上下文):这就像是「魔法工坊」里的「施法助手」,它提供了一系列辅助功能,比如记录施法日志、调度其他「魔法」任务等等,确保「魔法」施展得有条不紊。

  • Rule (转换规则):这便是我们编写的具体的「魔法法则」了,它清晰地定义了如何修改 Tree,如何让「沙盘」上的文件发生我们预期的「变化」。

掌握了这三位「魔法三巨头」,我们再来看看施法过程中常用的「核心工具函数」,它们就像魔法师手中的不同工具,帮助我们精准施法:

  • apply():组合一系列规则来转换模板文件。

  • url('./files'):指定模板文件的目录。

  • template({...}):替换模板文件中的占位符(如 __name__)。

  • move('./src/app'):将生成的文件移动到目标路径。

  • chain([]):将多个规则串联起来,按顺序执行。

  • mergeWith():将由模板生成的文件合并到虚拟文件系统 Tree 中。

  • strings@angular-devkit/core 提供的字符串工具,如 classify(PascalCase)、dasherize(kebab-case)等,用于生成符合命名规范的文件名和类名。

施展「无中生有之术」:亲手「召唤」一个自定义组件#

理论知识讲得再多,不如上手实操一番!现在,雪狼就带你施展「无中生有之术」,亲手「召唤」一个名为 feature-component 的 Schematic。这个「魔法」能够为你生成一个独立的(standalone)组件,并且还带上你自定义的「印记」。

第一步:绘制「魔法符文」:定义 Schematic 选项 (src/my-schematic/schema.json)#

在施展任何「魔法」之前,我们都需要先定义好它的「作用范围」和「核心参数」。这就是 schema.json 的职责。它就像一张「魔法符文」,告诉 Schematic 它可以接收哪些参数,这些参数又代表什么意义。

{
  "$schema": "http://json-schema.org/schema",
  "id": "FeatureComponent",
  "title": "Feature Component Options Schema",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "The name of the feature component.",
      "$default": {
        "$source": "argv",
        "index": 0
      },
      "x-prompt": "What name would you like to use for the feature component?"
    },
    "path": {
      "type": "string",
      "description": "The path to create the component.",
      "default": "src/app",
      "format": "path"
    }
  },
  "required": [
    "name"
  ]
}

这里我们定义了两个核心参数:name 代表组件的「法号」,path 则指定了它将「降临」何处。

第二步:撰写「魔法卷轴」:编写模板文件 (src/my-schematic/files/__name__/__name__.component.ts.template)#

有了「魔法符文」,接下来就是准备「魔法卷轴」了。这些模板文件,就是我们未来要生成的代码的「蓝图」。注意其中的命名约定和占位符,它们是实现「千变万化」的关键。

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
  standalone: true,
  selector: 'app-<%= dasherize(name) %>',
  templateUrl: './<%= dasherize(name) %>.component.html',
  styleUrl: './<%= dasherize(name) %>.component.scss',
  imports: [CommonModule],
})
export class <%= classify(name) %>Component {
  title = 'Hello from my custom <%= name %> feature!';
}

这里的文件名和内容都包含了特殊的占位符(如 __name__<%= %>),它们会在施法时被我们传入的参数动态替换。

第三步:谱写「核心咒语」:编写核心逻辑 (src/my-schematic/index.ts)#

这是我们「魔法」的灵魂所在!index.ts 文件中定义的函数,就是我们施展「无中生有之术」的核心「咒语」。它将读取你的「魔法符文」(schema.json),结合你的「魔法卷轴」(模板文件),最终在你的项目「沙盘」(Tree)上实现代码的「召唤」与「转化」。

import { Rule, SchematicContext, Tree, apply, url, template, mergeWith, move, chain } from '@angular-devkit/schematics';
import { strings } from '@angular-devkit/core'; // 引入字符串工具
interface Options {
  name: string;
  path?: string;
}
export function featureComponent(options: Options): Rule {
  return (tree: Tree, context: SchematicContext) => {
    // 确保 options.path 被正确设置
    const path = options.path || 'src/app';
    const templateSource = apply(
      url('./files'), // 指定模板文件所在的目录
      [
        template({
          ...options,
          ...strings, // 注入字符串处理工具 (dasherize, classify 等)
        }),
        // 根据 name 选项动态创建子目录
        move(`${path}/${strings.dasherize(options.name)}`), 
      ]
    );
    // 将生成的文件合并到虚拟文件系统 Tree 中
    return chain([
      mergeWith(templateSource),
      // 你可以在这里添加更多规则,比如自动将组件添加到路由配置中
      // addRouteToModule(options) // 假设有这样一个规则
    ])(tree, context); // 返回一个 Rule 函数
  };
}

这串「咒语」详细描述了如何根据我们定义的选项,去查找模板文件,替换其中的占位符,并最终将生成的文件放置到指定的位置。

第四步:刻录「魔法阵图」:在 collection.json 中注册 Schematic#

「魔法」完成了,但还需要一个「魔法阵图」来激活它!collection.json 就是这个「阵图」,它负责将我们刚刚「炼制」好的 Schematic 注册进来,让它能够被识别和调用。

src/collection.json 中,我们需要将 feature-component 这个 Schematic 注册进来。

{
  "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
  "schematics": {
    "feature-component": {
      "description": "Generates a standalone feature component.",
      "factory": "./src/my-schematic/index#featureComponent",
      "schema": "./src/my-schematic/schema.json"
    }
  }
}

通过这个「阵图」,我们告诉系统,有一个名为 feature-component 的「魔法」已经准备就绪,它的「咒语」在哪个文件,它的「符文」又在哪里。

挥舞你的「魔法棒」:让它为你所用!#

「魔法阵图」刻录完毕,「核心咒语」也已谱写,现在,是时候拿起你的「魔法棒」,让它为你所用了!

  1. 激活你的「魔法」(编译 Schematic)

    
    npm run build

    这就像给你的「魔法」注入能量,将你的 TypeScript 「咒语」转化成 JavaScript 「魔力」,存储在 dist 目录下的「魔力之源」中。

  2. 「魔法」预演(本地测试 Schematic)

    
    schematics .:feature-component --name=my-user-profile --path=src/app/features

    在真正的项目中使用前,雪狼建议你先进行「魔法预演」。使用 -d--dry-run 参数,它能在不实际修改任何文件的情况下,让你「看到」你的「魔法」将要产生怎样的效果。这就像在「沙盘」上预演一场战役,确保万无一失!

  3. 在 Angular 项目中「施法」

    当你的「魔法棒」经过测试,确认无误后,就可以在你的 Angular 项目中真正「施法」了。你可以通过 npm link 将你的 Schematic 集合临时链接到本地项目,然后:

    
    ng generate my-schematics-collection:feature-component --name=my-user-profile

    看!你的「魔法棒」开始工作了!如果你的「魔法」足够强大,并且希望与团队共享,那就把它发布到 npm 上吧。这样,其他「魔法师」也能轻松安装并使用你的「大作」了。

结语#

各位「架构魔法师」们,到这里,雪狼相信你已经感受到了 Angular Schematics 的强大魔力。它远不止是 Angular CLI 的幕后英雄,更是你手中掌控项目架构、提升团队效率的「魔法棒」。它把那些重复且容易出错的「体力活」,彻底转化为一套自动化、可控的「魔法流程」。

通过亲手学习和「炼制」自定义 Schematics,你不再仅仅是被动地遵循框架的规则,而是真正晋升为一名能够定义规则、塑造项目的「架构魔法师」。告别那些无休止的重复劳动,拥抱智能的自动化,让你的代码库在每一次「魔法生成」中,都散发出规范与秩序的独特魅力。

正如《淮南子·俶真训》所言:「运用之妙,存乎一心。」(意思是:事物的精妙运用,全在于思考者的智慧与灵活。在技术领域,这强调了工具固然重要,但更在于人如何巧妙地驾驭和创新,以实现卓越的价值。)

我是雪狼,希望这根「魔法棒」能助你在前端开发的江湖中,所向披靡!期待与你下次再见!