您好,登录后才能下订单哦!
# 如何在本地开发Angular Schematics
## 目录
- [1. 理解Angular Schematics](#1-理解angular-schematics)
- [1.1 什么是Schematics](#11-什么是schematics)
- [1.2 Schematics的核心概念](#12-schematics的核心概念)
- [1.3 典型应用场景](#13-典型应用场景)
- [2. 环境准备](#2-环境准备)
- [2.1 Node.js与npm版本要求](#21-nodejs与npm版本要求)
- [2.2 全局安装必要工具](#22-全局安装必要工具)
- [2.3 开发工具推荐](#23-开发工具推荐)
- [3. 创建第一个Schematic项目](#3-创建第一个schematic项目)
- [3.1 初始化项目结构](#31-初始化项目结构)
- [3.2 核心文件解析](#32-核心文件解析)
- [3.3 编译与测试配置](#33-编译与测试配置)
- [4. Schematic开发基础](#4-schematic开发基础)
- [4.1 Tree结构详解](#41-tree结构详解)
- [4.2 Rule与Action](#42-rule与action)
- [4.3 模板文件处理](#43-模板文件处理)
- [5. 高级开发技巧](#5-高级开发技巧)
- [5.1 用户交互处理](#51-用户交互处理)
- [5.2 动态文件生成](#52-动态文件生成)
- [5.3 外部依赖集成](#53-外部依赖集成)
- [6. 调试与测试](#6-调试与测试)
- [6.1 本地调试方法](#61-本地调试方法)
- [6.2 单元测试编写](#62-单元测试编写)
- [6.3 集成测试策略](#63-集成测试策略)
- [7. 发布与分发](#7-发布与分发)
- [7.1 打包优化](#71-打包优化)
- [7.2 发布到npm](#72-发布到npm)
- [7.3 版本管理](#73-版本管理)
- [8. 实战案例](#8-实战案例)
- [8.1 组件生成器](#81-组件生成器)
- [8.2 项目脚手架](#82-项目脚手架)
- [8.3 代码转换器](#83-代码转换器)
- [9. 最佳实践](#9-最佳实践)
- [9.1 性能优化](#91-性能优化)
- [9.2 错误处理](#92-错误处理)
- [9.3 文档编写](#93-文档编写)
- [10. 常见问题解决](#10-常见问题解决)
- [结语](#结语)
## 1. 理解Angular Schematics
### 1.1 什么是Schematics
Angular Schematics是Angular团队开发的强大代码生成工具,它通过定义可重用的代码生成逻辑,帮助开发者自动化重复性开发任务。本质上,它是一个基于模板的代码生成器,但功能远不止于此。
Schematics的核心优势在于:
- **标准化代码结构**:确保团队遵循一致的代码规范
- **提升开发效率**:自动化重复的初始化工作
- **降低错误率**:减少手动操作带来的失误
- **支持复杂转换**:可执行代码重构和迁移操作
### 1.2 Schematics的核心概念
理解这些关键概念对开发至关重要:
1. **Tree**:虚拟文件系统表示,包含源文件结构和要应用的变更
2. **Rule**:接收Tree并返回新Tree的函数,代表一个转换操作
3. **Action**:具体的文件操作类型,如Create、Delete、Rename等
4. **Template**:带有动态插值的文件模板
5. **Collection**:一组相关schematics的集合
### 1.3 典型应用场景
Schematics在Angular生态中有多种应用:
- **项目初始化**:`ng new`命令背后的实现
- **生成组件/服务**:`ng generate`的各种生成器
- **代码迁移**:Angular版本升级时的自动代码转换
- **自定义模板**:企业特定的代码规范和模板
- **开发工作流**:集成CI/CD流程中的自动化步骤
## 2. 环境准备
### 2.1 Node.js与npm版本要求
开发Schematics需要满足以下环境要求:
- Node.js: ^14.15.0 || ^16.10.0 || >=18.0.0
- npm: >=7.0.0 或 yarn: >=1.13.0
- Angular CLI: >=15.0.0
推荐使用nvm管理Node版本:
```bash
nvm install 18
nvm use 18
安装开发所需的全局依赖:
npm install -g @angular-devkit/schematics-cli @angular/cli
验证安装:
schematics --version
ng version
提高开发效率的工具配置:
VS Code扩展:
调试配置:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Schematic",
"program": "${workspaceFolder}/node_modules/.bin/schematics",
"args": [".:my-schematic", "--debug", "false"],
"cwd": "${workspaceFolder}"
}
]
}
创建新的schematics项目:
schematics blank --name=my-schematics
cd my-schematics
npm install
项目结构说明:
my-schematics/
├── src/
│ ├── collection.json # Schematic集合声明
│ ├── my-schematic/ # 默认schematic
│ │ ├── files/ # 模板文件目录
│ │ ├── index.ts # 主逻辑文件
│ │ └── schema.json # 参数配置
│ └── my-schematic_spec.ts # 测试文件
├── .npmignore
├── package.json
└── tsconfig.json
collection.json - Schematic集合定义:
{
"$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
"schematics": {
"my-schematic": {
"description": "A blank schematic.",
"factory": "./my-schematic/index#mySchematic"
}
}
}
schema.json - 参数配置:
{
"$schema": "http://json-schema.org/schema",
"id": "MySchematicSchema",
"title": "My Schematic Options",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the component",
"$default": {
"$source": "argv",
"index": 0
}
}
},
"required": ["name"]
}
修改tsconfig.json添加开发配置:
{
"compilerOptions": {
"baseUrl": ".",
"lib": ["es2018", "dom"],
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"target": "es6",
"types": ["jasmine", "node"]
},
"include": ["src/**/*"],
"exclude": ["src/**/*.spec.ts"]
}
添加构建脚本到package.json:
{
"scripts": {
"build": "tsc -p tsconfig.json",
"test": "npm run build && jasmine src/**/*_spec.js",
"link": "npm run build && cd dist && npm link"
}
}
Tree是Schematics的核心抽象,表示虚拟文件系统:
import { Tree } from '@angular-devkit/schematics';
function mySchematic(options: any): Rule {
return (tree: Tree) => {
// 检查文件是否存在
if (tree.exists('/README.md')) {
// 读取文件内容
const content = tree.read('/README.md')!.toString();
// 修改内容
const newContent = content + '\nModified by schematic';
// 写回文件
tree.overwrite('/README.md', newContent);
}
// 创建新文件
tree.create('/hello.txt', 'Hello World!');
return tree;
};
}
Rule是转换Tree的函数,可以组合多个操作:
import { Rule, apply, mergeWith, template, url } from '@angular-devkit/schematics';
function generateComponent(options: any): Rule {
return () => {
// 1. 加载模板源
const templateSource = url('./files');
// 2. 应用模板参数
const parameterizedSource = apply(templateSource, [
template({
...options,
classify: strings.classify,
dasherize: strings.dasherize
})
]);
// 3. 合并到目标Tree
return mergeWith(parameterizedSource);
};
}
模板文件使用特殊语法进行插值:
示例模板文件 (files/__name@dasherize__.component.ts
):
import { Component } from '@angular/core';
@Component({
selector: 'app-<%= dasherize(name) %>',
templateUrl: './<%= dasherize(name) %>.component.html',
styleUrls: ['./<%= dasherize(name) %>.component.css']
})
export class <%= classify(name) %>Component {
constructor() {}
}
常用模板工具函数:
- classify
: 转换为大驼峰 (my-component → MyComponent)
- dasherize
: 转换为短横线连接 (MyComponent → my-component)
- camelize
: 转换为小驼峰 (my-component → myComponent)
使用@angular-devkit/core
处理用户输入:
import { strings } from '@angular-devkit/core';
import { prompt } from 'inquirer';
async function interactiveSchematic(options: any): Promise<Rule> {
const answers = await prompt([
{
type: 'input',
name: 'style',
message: 'Which stylesheet format would you like to use?',
choices: ['css', 'scss', 'sass', 'less'],
default: 'css'
}
]);
return mergeWith(
apply(url('./files'), [
template({
...options,
...answers,
classify: strings.classify,
dasherize: strings.dasherize
})
])
);
}
根据条件动态生成不同文件:
function dynamicFiles(options: any): Rule {
return (tree: Tree) => {
const templatePath = options.withTests ?
'./files-with-tests' :
'./files-basic';
return mergeWith(
apply(url(templatePath), [
template({
...options,
classify: strings.classify,
dasherize: strings.dasherize
})
])
);
};
}
在Schematic中使用外部npm包:
npm install lodash --save
import * as _ from 'lodash';
function useExternalLib(options: any): Rule {
return (tree: Tree) => {
const names = _.split(options.name, '-');
const upperNames = _.map(names, _.upperFirst);
tree.create(
'/generated.txt',
`Processed name: ${upperNames.join(' ')}`
);
return tree;
};
}
npm run build
cd dist
npm link
npm link my-schematics
ng generate my-schematics:my-schematic --name=test
使用Jasmine测试Schematics:
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
import { join } from 'path';
describe('My Schematic', () => {
const collectionPath = join(__dirname, '../collection.json');
const runner = new SchematicTestRunner('schematics', collectionPath);
it('should create expected files', async () => {
const tree = await runner.runSchematicAsync(
'my-schematic',
{ name: 'test' },
Tree.empty()
).toPromise();
expect(tree.files).toEqual([
'/test.component.ts',
'/test.component.html',
'/test.component.css'
]);
});
});
测试完整的Angular项目集成:
import { UnitTestTree } from '@angular-devkit/schematics/testing';
import { getWorkspace } from '@schematics/angular/utility/workspace';
describe('Angular Workspace Test', () => {
let runner: SchematicTestRunner;
let appTree: UnitTestTree;
beforeEach(async () => {
runner = new SchematicTestRunner('schematics', collectionPath);
appTree = await runner.runExternalSchematicAsync(
'@schematics/angular',
'workspace',
{ name: 'workspace', version: '15.0.0' }
).toPromise();
appTree = await runner.runExternalSchematicAsync(
'@schematics/angular',
'application',
{ name: 'my-app' },
appTree
).toPromise();
});
it('should work in Angular workspace', async () => {
const tree = await runner.runSchematicAsync(
'my-schematic',
{ name: 'test', project: 'my-app' },
appTree
).toPromise();
const workspace = await getWorkspace(tree);
const project = workspace.projects.get('my-app');
// 验证项目配置
});
});
配置生产构建:
{
"scripts": {
"build:prod": "tsc -p tsconfig.json && npm run copy-schemas",
"copy-schemas": "cpx \"src/**/*.json\" dist"
}
}
npm login
npm version patch
# 或 minor/major
npm publish --access public
遵循语义化版本控制: - MAJOR: 不兼容的API更改 - MINOR: 向后兼容的功能新增 - PATCH: 向后兼容的问题修复
使用npm标签管理版本:
npm dist-tag add my-schematics@1.0.0 next
创建带storybook的Angular组件:
export function storybookComponent(options: any): Rule {
return async (tree: Tree) => {
const angularComponent = await externalSchematic(
'@schematics/angular',
'component',
{
...options,
skipTests: true
}
).toPromise() as Rule;
const storySource = apply(url('./files/story'), [
template({
...options,
classify: strings.classify,
dasherize: strings.dasherize
}),
move(`/src/app/${dasherize(options.name)}`)
]);
return chain([
angularComponent,
mergeWith(storySource)
]);
};
}
自定义企业级项目模板:
export function enterpriseProject(options: any): Rule {
return chain([
// 1. 创建标准Angular项目
externalSchematic('@schematics/angular', 'ng-new', {
name: options.name,
version: '15.0.0',
routing: true,
style: 'scss'
}),
// 2. 添加企业模块
(_tree: Tree, context: SchematicContext) => {
return mergeWith(
apply(url('./files/enterprise'), [
template({
...options,
classify: strings.classify
}),
move(`/${options.name}`)
])
)(context);
},
// 3. 安装依赖
(_tree: Tree, context: SchematicContext) => {
const dependencies = [
'ngx-translate-core',
'rxjs-tslint-rules',
'prettier'
];
context.addTask(new NodePackageInstallTask({
packageName: dependencies.join(' ')
}));
return tree;
}
]);
}
Angular版本迁移工具:
”`typescript export function migrateToV15(): Rule { return (tree: Tree) => { // 1. 更新package.json if (tree.exists(‘/package.json’)) { const content = JSON.parse(tree.read(‘/package.json’)!.toString());
content.dependencies = {
...content.dependencies,
'@angular/core': '^15.0.0',
'@angular/cli': '^15.0.0'
};
tree.overwrite('/package.json', JSON.stringify(content, null, 2));
}
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。