Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

使用angular schematics快速生成代码 #28

Open
jiayisheji opened this issue Dec 11, 2019 · 0 comments
Open

使用angular schematics快速生成代码 #28

jiayisheji opened this issue Dec 11, 2019 · 0 comments
Labels
Angular angular相关实践

Comments

@jiayisheji
Copy link
Owner

jiayisheji commented Dec 11, 2019

使用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

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

@jiayisheji jiayisheji added the Angular angular相关实践 label Apr 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Angular angular相关实践
Projects
None yet
Development

No branches or pull requests

1 participant