10000 使用angular schematics快速生成代码 · Issue #28 · jiayisheji/blog · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
使用angular schematics快速生成代码 #28
Open
@jiayisheji

Description

@jiayisheji

使用angular schematics快速生成代码

什么是Schematics?

Schematics是改变现存文件系统的生成器。有了Schematics我们可以:

  • 创建文件
  • 重构现存文件,或者
  • 到处移动文件

Schematics能做什么?

总体上,Schematics可以:

  • 为Angular工程添加库
  • 升级Angular工程中的库
  • 生成代码

在你自己的工程或者在你所在的组织中使用Schematics是具有无限可能的。下面一些例子展现了你或者你的组织或如何从创建一个schematics collection中获益:

  • 在应用中生成通用UI模板
  • 使用预先定义的模板或布局生成组织指定的组件
  • 强制使用组织内架构

Schematics现在是Angular生态圈的一部分,不仅限于Angular工程,你可以生成想要模板。

CLI集成?

是的,schematics与Angular CLI紧密集成。你可以在下列的CLI命令中使用schematics:

  • ng add
  • ng generate
  • ng update

什么是Collection?

Collection是一系列的schematic。我们会在工程中collection.json中为每个schematic定义元数据。

安装

首先,使用npm或者yarn安装schematics的CLI:

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

快速开始

这会创建名为 demo-schema 的文件夹,在其中已经创建了多个文件,如下所示。

schematics blank --name=demo-schema

我们使用 blank 为我们后继的工作打好基础。

image

collection.json

{
  "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
  "schematics": {
    "demo-schema": {
      "aliases": ["demo"],    //需要自己添加
      "factory": "./demo-schema/index.ts#demoSchema",
      "description": "A blank schematic.",
      "schema": "./demo-schema/schema.json"  //需要自己添加
    }
  }
}
  • $schema => 定义该 collection 架构的 url 地址.
  • schematics => 这是你的 schematics 定义.
    • demo-schema => 以后使用这个 schematics 的 cli 名称.
    • aliases => 别名.
    • factory => 定义代码.
    • description => 简单的说明.
    • schema => 你的 schema 的设置. 这个文件的内容应该如下所示。我们在其中定义了多个自定义的选项,在使用这个 Schematics 的时候,可以通过这些选项来设置生成的内容。

关于怎么创建schema.json,下面实战项目来说明。

入口函数

打开src/demo-schema/index.ts文件,看看内容:

import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';

// You don't have to export the function as default. You can also have more than one rule factory
// per file.
export function demoSchema(_options: any): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    return tree;
  };
}
  • 我们export了会作为"entry function"调用的demoSchema函数
  • 函数接受一个_options参数,它是命令行参数的键值对对象,和我们定义的schema.json有关
  • 这个函数是一个高阶函数,接受或者返回一个函数引用。此处,这个函数返回一个接受TreeSchmaticsContext对象的函数

什么是Tree?

Tree是变化的待命区域,包含源文件系统和一系列应用到其上面的变化。

我们能使用tree完成这些事情:

  • read(path: string): Buffer | null: 读取指定的路径
  • exists(path: string): boolean: 确定路径是否存在
  • create(path: string, content: Buffer | string): void: 在指定路径使用指定内容创建新文件
  • beginUpdate(path: string): UpdateRecorder: 为在指定路径的文件返回一个新的UpdateRecorder实例
  • commitUpdate(record: UpdateRecorder): void: 提交UpdateRecorder中的动作,简单理解就是更新指定文件内容(实战中会用到)

什么是Rule?

Rule是一个根据SchematicContext为一个Tree应用动作的函数,入口函数返回了一个Rule。

declare type Rule =  (tree: Tree, context: SchematicContext) => Tree | Observable<Tree> | Rule | void;

构建和执行

要运行我们的示例,首先需要构建它,然后使用schematics命令行工具,将schematic项目目录的路径作为集合。从我们项目的根:

npm run build
# ... 等待构建完成
schematics .:demo-schema --name=test --dry-run
# ...查看在控制台生成创建的文件日志

注意:使用--dry-run不会生成文件,在调试时候可以使用它,如果想要创建正在的文件,去掉即可。使用npm run build -- -w命令,修改index.ts文件以后自动构建。

如何使用在angular项目中

使用短连安装:

npm link demo-schema

使用ng generate运行:

ng generate demo-schema:demo-schema
  • 第一个demo-schema => 是package.jsonname
  • 第二个demo-schema => 是我们运行的schematics

实战项目

最新在公司项目需要把之前的项目的通用组件提取出来,做成一个单独的组件库并且带上demo。创建了一个新的工程。组件库使用ng-packagr,如果直接使用它去打包,会全部打包到一起,这样就会有问题,加载的时候特别大。ng-packagr提供的二次入口,可以解决这个问题,但是又会有新问题,必须要要和src同级目录。如果使用angular-cli默认去生成都会自动添加到src/lib里面,虽然可以修改path,但是问题是一个组件模块里面有一些特定的文件:

  • module
  • component(包含css,html,ts)
  • service
  • directive
  • class
  • types

大概就这些,如果这些用angular-cli,去生成,需要6次才能完成,也可以写一个shell,一次性完成。但是发现太麻烦,有些东西无法控制,如果需要定制,那就需要自己来写schematics

这里有2部分不一样的实战内容,一种是根据固定内容直接生成模板,一种是生成模板以后修改已有关联的文件。

为什么会有这2个,第一个是为了生成组件模块,第二个是为了生成演示组件模块。

创建一个schematics

schematics blank --name=tools

这时候也会创建src/tools,我们把它改成ui,把collection.json文件里面也修改了。

实战1

创建ui/schema.json文件:

{
  "$schema": "http://json-schema.org/schema",
  "id": "ui",
  "title": "UI Schema",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "生成一个组件模块",
      "$default": {
        "$source": "argv",
        "index": 0
      },
      "x-prompt": "组件使用什么名称?"
    },
    "path": {
      "type": "string",
      "default": "projects/ui",
      "description": "生成目标的文件夹路径"
    },
    "service": {
      "type": "boolean",
      "default": false,
      "description": "标志是否应该生成service"
    },
    "directive": {
      "type": "boolean",
      "default": false,
      "description": "标志是否应该生成directive"
    },
    "class": {
      "type": "boolean",
      "default": false,
      "description": "标志是否应该生成class"
    },
    "types": {
      "type": "boolean",
      "default": false,
      "description": "标志是否应该生成types/interfaces"
    }
  },
  "required": ["name"]
}

这里可以设置你的 schematics 的命令选项,类似于在使用 ng g c --name=user--name命令。

创建ui/schema.ts文件:

export interface SimpleOptions {
  name: string;
  path: string;
  service?: boolean;
  directive?: boolean;
  class?: boolean;
  types?: boolean;
}

修改ui/index.ts

import { strings } from '@angular-devkit/core';
import {
  apply,
  applyTemplates,
  branchAndMerge,
  chain,
  filter,
  mergeWith,
  move,
  noop,
  Rule,
  SchematicContext,
  Tree,
  url,
} from '@angular-devkit/schematics';
import { parseName } from '@schematics/angular/utility/parse-name';
import { SimpleOptions } from './schema';

export function simple(_options: SimpleOptions): Rule {
  return (tree: Tree, _context: SchematicContext) => {
     ...code
 }
}
  1. 先处理路径:
    if (!_options.path) {
      throw new Error('path不能为空');
    }
    // 处理路径
    const projectPath = `/${_options.path}/${_options.name}`;
    const parsedPath = parseName(projectPath, _options.name);
    _options.name = parsedPath.name;
    _options.path = parsedPath.path;
  1. 获取模板源:
    const templateSource = apply(url('./files'), [
      applyTemplates({
        ...strings,
        ..._options,
      }),
      move(_options.path),
    ]);

我们模块在files文件下,applyTemplates把我们的配置转换成模板可以使用的变量并生成文件,move把生成好的文件移动到目标路径下。

  1. 返回rule
    const rule = chain([branchAndMerge(chain([mergeWith(templateSource)]))]);

    return rule(tree, _context);

我们前面也也介绍了,入口函数总是要返回一个rulechain验证我们的配置规则。

整体看起来比较简单,这样就已经完成的整个的生成命令,下面就是关键模块定义:

  1. 模板放在files文件下
  2. .template后缀结尾
  3. 要替换变量使用__变量__方式
  4. 需要处理变量要以__变量@方法名__

举例:

__name@dasherize__.class.ts.template

将name变量驼峰式写法转换为连字符的写法。

模板里面如何使用,语法和EJS一样

标签含义:

  • <% '脚本' 标签,用于流程控制,无输出。
  • <%_ 删除其前面的空格符
  • <%= 输出数据到模板(输出是转义 HTML 标签)
  • <%- 输出非转义的数据到模板
  • <%# 注释标签,不执行、不输出内容
  • <%% 输出字符串 '<%'
  • %> 一般结束标签
  • -%> 删除紧随其后的换行符
  • _%> 将结束标签后面的空格符删除

语法示例:

<%# 变量 %>
<%= classify(name) %>

<%# 流程判断 %>
<% if (user) { %>
  <h2><%= user.name %></h2>
<% } %>

<%# 循环 %>
<ul>
  <% users.forEach(function(user){ %>
    <%- include('user/show', {user: user}); %>
  <% }); %>
</ul>

会大量使用变量和少量的流程判断,变量一般都会使用内置的模板方法来配合使用:

内置的模板变量方法:

  • classify:连字符写法转换为大驼峰式的写法
  • dasherize:驼峰式写法转换为连字符的写法

更多模板变量: node_modules\@angular-devkit\core\src\utils\strings.d.ts

内置的模板方法主要命名转换,如果不能满足你需求,可以自己定义:

const utils = {
...自定义方法
}

`applyTemplates({utils: utils})`

举个几个例子:

name@dasherize.module.ts.template

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { Sim<%= classify(name) %>Component } from './<%= dasherize(name) %>.component';

@NgModule({
  declarations: [Sim<%= classify(name) %>Component],
  imports: [CommonModule],
  exports: [Sim<%= classify(name) %>Component],
  providers: [],
})
export class Sim<%= classify(name) %>Module {}

name@dasherize.component.ts.template

import {
  ChangeDetectionStrategy,
  Component,
  ElementRef,
  HostBinding,
  HostListener,
  Input,
  OnDestroy,
  OnInit,
  ViewEncapsulation,
} from '@angular/core';

@Component({
  selector: 'sim-<%= dasherize(name) %>',
  templateUrl: './<%= dasherize(name) %>.component.html',
  styleUrls: ['./<%= dasherize(name) %>.component.scss'],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class Sim<%= classify(name) %>Component implements OnInit, OnDestroy {

  constructor(protected elementRef: ElementRef) {}

  public ngOnInit(): void {
  }

  public ngOnDestroy(): void {

  }
}

index.ts.template

export * from './<%= dasherize(name) %>.component';
export * from './<%= dasherize(name) %>.module';

我们可以构建试一下:

cd tools
npm run build
schematics .:ui --name=test --dry-run

image

  • index.ts 二次入口打包入口
  • package.json 二次入口打包必备配置
  • README.md 组件说明

基本已经完成我们想要的,还有几个文件生成是可选的,我们需要配置处理一下:

    const templateSource = apply(url('./files'), [
      _options.service ? noop() : filter(path => !path.endsWith('.service.ts.template')),
      _options.class ? noop() : filter(path => !path.endsWith('.class.ts.template')),
      _options.directive ? noop() : filter(path => !path.endsWith('.directive.ts.template')),
      _options.types ? noop() : filter(path => !path.endsWith('.type.ts.template')),
      applyTemplates({
        ...strings,
        ..._options,
      }),
      move(_options.path),
    ]);

如果是true,就忽略,如果是false,就排除这个后缀结尾文件。

schematics .:ui --name=test --dry-run --service 

image

注意:不需要写=true

npm link tools
 ng g tools:ui --name="test" --dry-run

image

 ng g tools:ui --name="test"

image

image

image

基本已经完成了,下面介绍一个进阶实战。

实战2

我们想要创建多个schematics,需要手动添加,我们需要文件:

src/demo/index.ts
src/demo/index_spec.ts
src/demo/schema.json
src/demo/schema.ts
src/demo/files

然后在src/collection.json里添加申明:

"schematics": {
  ...
    "demo": {
      "description": "A blank schematic.",
      "factory": "./demo/index#demo",
      "schema": "./demo/schema.json"
    }
}

配置schema.json:

{
  "$schema": "http://json-schema.org/schema",
  "id": "demo",
  "title": "simple demo Schema",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "生成一个组件模块",
      "$default": {
        "$source": "argv",
        "index": 0
      },
      "x-prompt": "组件使用什么名称?"
    },
    "path": {
      "type": "string",
      "default": "/projects/demo",
      "description": "生成目标的文件夹路径"
    }
  },
  "required": ["name"]
}

先保留这些Schema,后面来丰富。

我们把前面介绍的入口函数拷贝到src/demo/index.ts里,构建编译:

cd tools
npm run build
schematics .:demo --name=test --dry-run
# ...Nothing to be done.

这个实战和前面实战有些不一样,前面的只是一个替换生成,相当于一个入门级的,很容易学会,现在介绍一个高级点的,不光要替换生成,还要去改变已有文件的依赖关系。

使用angular-cli的时候,创建组件以后,会自动去关联的模块里面去申明,这个是怎么做到的?

我们就需要实现一个类似的功能,有一个功能需要去展示UI组件的demo,每次创建都是相当于有一套对应的模板,但是每次创建以后都是一个新的的页面,也需要一个路由规则需要添加,如果我们单纯创建一套demo组件的文件,还需要去手动添加路由,这样就比较麻烦,现在就需要自动完成这个功能。我们一起来实现它吧。

这里我们就用上beginUpdatecommitUpdate2个方法来实现

先介绍一下需要实现的功能:

我有三个文件夹:

guides 快速指南
experimental  实验功能
components 组件库
  • guides 没有子路由
  • experimental 有子路由没有菜单分组
  • components 有子路由有菜单分组

书写路由时候,每个ui组件,都是一个独立模块,使用懒加载模块方式,这样所有的懒加载路由都是平级的。

举个栗子:

const routes: Routes = [
  {
    path: '',
    component: ComponentsComponent,
    children: [
      {
        path: '',
        redirectTo: 'button',
        pathMatch: 'full',
      },
      {
        path: 'button',
        loadChildren: './button/button.module#ButtonModule',
      },
      {
        path: 'card',
        loadChildren: './card/card.module#CardModule',
      },
      {
        path: 'divider',
        loadChildren: './divider/divider.module#DividerModule',
      },
    ],
  },
];

其实angular也有自带添加路由依赖方法,但是只能添加一级路由,不能添加子路由,我们这个需求就是需要添加子路由。

  1. 验证路径
    if (!_options.path) {
      throw new Error('path不能为空');
    }
    // 处理路径
    const parsedPath = parseName(_options.path, _options.name);
    _options.name = parsedPath.name;
    _options.path = parsedPath.path;
  1. 生成模板
    const templateSource = apply(url('./files'), [
      applyTemplates({
        ...strings,
        ..._options,
      }),
      move(parsedPath.path),
    ]);

模板这块就不在说明了,和实战1是一样处理的,去files文件夹里面创建对应的模板即可。

  1. 返回rule
const rule = chain([addDeclarationToNgModule(_options), mergeWith(templateSource)]);
return rule(tree, _context);

其他没有什么好说明的,addDeclarationToNgModule是我们需要重点说明的,也是这个实战的核心。

function addDeclarationToNgModule(options: DemoOptions): Rule {
  return (host: Tree) => {
    // 路由模块路径
    const modulePath = `${options.path}/${options.module}/${options.module}-routing.module.ts`;
    // 懒加载模块名字
    const namePath = strings.dasherize(options.name);
    // 需要刷新AST,因为我们需要覆盖目标文件。
    const source = readIntoSourceFile(host, modulePath);
    // 获取更新文件
    const routesRecorder = host.beginUpdate(modulePath);
    // 获取变更信息
    const routesChanges = addRoutesToModule(source, modulePath, buildRoute(options, namePath)) as InsertChange;
    // 在多少行位置插入指定内容
    routesRecorder.insertLeft(routesChanges.pos, routesChanges.toAdd);
    // 更新文件
    host.commitUpdate(routesRecorder);
  };
}

这里有3个依赖方法:

  • readIntoSourceFile: 读取ts文件,使用ts.createSourceFile为我们解析文件源 AST
  • addRoutesToModule:获取变更信息重要方法
  • buildRoute:组装新的路由信息

说实话,我对AST这个玩意不熟,之前使用ng-packagr时候出现一个bug,通过源码拿到AST,修复这个bug,一直自用,不过后来ng-packagr已经通过其他方式修复了。

// 这个是一个工具方法
function readIntoSourceFile(host: Tree, modulePath: string): ts.SourceFile {
  // 先试着用Tree方法读文件
  const text = host.read(modulePath);
  if (text === null) {
    throw new SchematicsException(`File ${modulePath} does not exist.`);
  }
  const sourceText = text.toString('utf-8');

  return ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true);
}
// 我现在还用的angular7,懒加载路由还是老的写法,在等angular9更新。
function buildRoute(options: DemoOptions, modulePath: string) {
  const moduleName = `${strings.classify(options.name)}Module`;
  const loadChildren = normalize(`'./${modulePath}/${modulePath}.module#${moduleName}'`);
  return `{ path: '${modulePath}', loadChildren: ${loadChildren} }`;
}

addRoutesToModule内容太多,创建一个utils.ts文件来处理它。

大部分也是借鉴angular-cli的addRouteDeclarationToModule方法,改成我们想要。

export function addRoutesToModule(source: ts.SourceFile, fileToAdd: string, routeLiteral: string): Change {
  const routerModuleExpr = getRouterModuleDeclaration(source);
  if (!routerModuleExpr) {
    throw new Error(`Couldn't find a route declaration in ${fileToAdd}.`);
  }

  const scopeConfigMethodArgs = (routerModuleExpr as ts.CallExpression).arguments;
  if (!scopeConfigMethodArgs.length) {
    const { line } = source.getLineAndCharacterOfPosition(routerModuleExpr.getStart());
    throw new Error(`The router module method doesn't have arguments ` + `at line ${line} in ${fileToAdd}`);
  }

  let routesArr: ts.ArrayLiteralExpression | undefined;
  const routesArg = scopeConfigMethodArgs[0];

  // 检查路由声明数组是RouterModule的内联参数还是独立变量
  if (ts.isArrayLiteralExpression(routesArg)) {
    routesArr = routesArg;
  } else {
    const routesVarName = routesArg.getText();
    let routesVar;
    if (routesArg.kind === ts.SyntaxKind.Identifier) {
      routesVar = source.statements
        .filter((s: ts.Statement) => s.kind === ts.SyntaxKind.VariableStatement)
        .find((v: ts.VariableStatement) => {
          return v.declarationList.declarations[0].name.getText() === routesVarName;
        }) as ts.VariableStatement | undefined;
    }

    if (!routesVar) {
      const { line } = source.getLineAndCharacterOfPosition(routesArg.getStart());
      throw new Error(`No route declaration array was found that corresponds ` + `to router module at line ${line} in ${fileToAdd}`);
    }

    routesArr = findNodes(routesVar, ts.SyntaxKind.ArrayLiteralExpression, 1)[0] as ts.ArrayLiteralExpression;
  }

  const occurrencesCount = routesArr.elements.length;
  const text = routesArr.getFullText(source);

  let route: string = routeLiteral;
  let insertPos = routesArr.elements.pos;

  if (occurrencesCount > 0) {
    // 不一样的开始
    // 获取最后一个element
    const lastRouteLiteral = [...routesArr.elements].pop() as ts.Expression;
    // 从当前元素的属性里面获取`children`属性token信息
    const children = (ts.isObjectLiteralExpression(lastRouteLiteral) &&
      lastRouteLiteral.properties.find(n => {
        return ts.isPropertyAssignment(n) && ts.isIdentifier(n.name) && n.name.text === 'children';
      })) as ts.PropertyAssignment;
    if (!children) {
      throw new Error('"children" does not exist.');
    }
    // 处理路由字符串
    const indentation = text.match(/\r?\n(\r?)\s*/) || [];
    const routeText = `${indentation[0] || ' '}${routeLiteral}`;
    // 获取当前`children`结束位置
    insertPos = (children.initializer as ts.ArrayLiteralExpression).elements.end;
    // 拼接路由信息
    route = `${routeText},`;
    // 不一样的结束
  }

  return new InsertChange(fileToAdd, insertPos, route);
}

注意:这里有些代码相当于写死了,因为我本身都是固定的。

那些一样都不过多的解释,你需要知道最终拿到的是:const routes: Routes = []即可。

所以按angular自带的addRouteDeclarationToModule方法,操作总是routes.push(newRoute)这样的操作,而我们需要的操作是routes[0].children.push(newRoute),就需要自己弄了。

我们拿到lastRouteLiteral 注意:其实这个有个bug,如果我们路由里面改了,这个就挂了

NodeObject {
  pos: 193,
  end: 276,
  flags: 0,
  transformFlags: undefined,
  parent:
   NodeObject {
     pos: 191,
     end: 280,
     flags: 0,
     transformFlags: undefined,
     parent:
      NodeObject {
        pos: 174,
        end: 280,
        flags: 0,
        transformFlags: undefined,
        parent: [Object],
        kind: 235,
        name: [Object],
        type: [Object],
        initializer: [Circular],
        _children: [Array] },
     kind: 185,
     multiLine: true,
     elements: [ [Circular], pos: 193, end: 277, hasTrailingComma: true ],
     _children: [ [Object], [Object], [Object] ] },
  kind: 186,
  multiLine: true,
  properties:
   [ NodeObject {
       pos: 198,
       end: 212,
       flags: 0,
       transformFlags: undefined,
       parent: [Circular],
       kind: 273,
       decorators: undefined,
       modifiers: undefined,
       name: [Object],
       questionToken: undefined,
       exclamationToken: undefined,
       initializer: [Object],
       _children: [Array] },
     NodeObject {
       pos: 213,
       end: 251,
       flags: 0,
       transformFlags: undefined,
       parent: [Circular],
       kind: 273,
       decorators: undefined,
       modifiers: undefined,
       name: [Object],
       questionToken: undefined,
       exclamationToken: undefined,
       initializer: [Object],
       _children: [Array] },
     NodeObject {
       pos: 252,
       end: 270,
       flags: 0,
       transformFlags: undefined,
       parent: [Circular],
       kind: 273,
       decorators: undefined,
       modifiers: undefined,
       name: [Object],
       questionToken: undefined,
       exclamationToken: undefined,
       initializer: [Object],
       _children: [Array] },
     pos: 198,
     end: 271,
     hasTrailingComma: true ],
  _children:
   [ TokenObject { pos: 193, end: 198, flags: 0, parent: [Circular], kind: 17 },
     NodeObject {
       pos: 198,
       end: 271,
       flags: 0,
       transformFlags: undefined,
       parent: [Circular],
       kind: 304,
       _children: [Array] },
     TokenObject { pos: 271, end: 276, flags: 0, parent: [Circular], kind: 18 } ] }

这里拿的对应信息就是:

{
    path: '',
    component: ComponentsComponent,
    children: []
}

lastRouteLiteral.properties是一个数组,我们这个{}里面有几项,就会有几个数组。我们只关心children属性,就通过find查找目标,它有可能是undefined,需要处理一下。

我们来打印children:

我大家演示2个不一样的:

路由配置里children空的

NodeObject {
  pos: 252,
  end: 270,
  flags: 0,
  transformFlags: undefined,
  parent:
   NodeObject {
     pos: 193,
     end: 276,
     flags: 0,
     transformFlags: undefined,
     parent:
      NodeObject {
        pos: 191,
        end: 280,
        flags: 0,
        transformFlags: undefined,
        parent: [Object],
        kind: 185,
        multiLine: true,
        elements: [Array],
        _children: [Array] },
     kind: 186,
     multiLine: true,
     properties:
      [ [Object],
        [Object],
        [Circular],
        pos: 198,
        end: 271,
        hasTrailingComma: true ],
     _children: [ [Object], [Object], [Object] ] },
  kind: 273,
  decorators: undefined,
  modifiers: undefined,
  name:
   IdentifierObject {
     pos: 252,
     end: 266,
     flags: 0,
     parent: [Circular],
     escapedText: 'children' },
  questionToken: undefined,
  exclamationToken: undefined,
  initializer:
   NodeObject {
     pos: 267,
     end: 270,
     flags: 0,
     transformFlags: undefined,
     parent: [Circular],
     kind: 185,
     elements: [ pos: 269, end: 269 ],
     _children: [ [Object], [Object], [Object] ] },
  _children:
   [ IdentifierObject {
       pos: 252,
       end: 266,
       flags: 0,
       parent: [Circular],
       escapedText: 'children' },
     TokenObject { pos: 266, end: 267, flags: 0, parent: [Circular], kind: 56 },
     NodeObject {
       pos: 267,
       end: 270,
       flags: 0,
       transformFlags: undefined,
       parent: [Circular],
       kind: 185,
       elements: [Array],
       _children: [Array] } ] }

一个路由配置里children有的

NodeObject {
  pos: 239,
  end: 655,
  flags: 0,
  transformFlags: undefined,
  parent:
   NodeObject {
     pos: 185,
     end: 660,
     flags: 0,
     transformFlags: undefined,
     parent:
      NodeObject {
        pos: 183,
        end: 663,
        flags: 0,
        transformFlags: undefined,
        parent: [Object],
        kind: 185,
        multiLine: true,
        elements: [Array],
        _children: [Array] },
     kind: 186,
     multiLine: true,
     properties:
      [ [Object],
        [Object],
        [Circular],
        pos: 189,
        end: 656,
        hasTrailingComma: true ],
     _children: [ [Object], [Object], [Object] ] },
  kind: 273,
  decorators: undefined,
  modifiers: undefined,
  name:
   IdentifierObject {
     pos: 239,
     end: 252,
     flags: 0,
     parent: [Circular],
     escapedText: 'children' },
  questionToken: undefined,
  exclamationToken: undefined,
  initializer:
   NodeObject {
     pos: 253,
     end: 655,
     flags: 0,
     transformFlags: undefined,
     parent: [Circular],
     kind: 185,
     multiLine: true,
     elements:
      [ [Object],
        [Object],
        [Object],
        [Object],
        pos: 255,
        end: 649,
        hasTrailingComma: true ],
     _children: [ [Object], [Object], [Object] ] },
  _children:
   [ IdentifierObject {
       pos: 239,
       end: 252,
       flags: 0,
       parent: [Circular],
       escapedText: 'children' },
     TokenObject { pos: 252, end: 253, flags: 0, parent: [Circular], kind: 56 },
     NodeObject {
       pos: 253,
       end: 655,
       flags: 0,
       transformFlags: undefined,
       parent: [Circular],
       kind: 185,
       multiLine: true,
       elements: [Array],
       _children: [Array] } ] }

这里给大家科普几个数据就好了:

  • pos:起始位置
  • end: 结束位置
  • parent:父节点
  • initializer:自己
  • elements:子节点

主要看elements变化,空里面只有2个[pos, end],如果不是空里面就会有子节点,你现在是不是可以干点其他事情了。(ps:如果想批量更新之前内容是不是想想也容易了)

注意:如果你要调试,一定要用npm link安装,根目录使用ng g tools:demo --name=test

先试试默认的路由添加:

ng g tools:demo --name=test

image

image

ng g tools:demo --name=test  --module=experimental

image

image

大功告成,欢迎交流,发现更多好玩的东西。

Metadata

Metadata

Assignees

No one assigned

    Labels

    Angularangular相关实践

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0