1.0.0 • Published 2 years ago

ecodecc v1.0.0

Weekly downloads
-
License
ISC
Repository
-
Last release
2 years ago

开始

简介

Ecode 是一个代码生成器,用于生成业务代码,让用户只专注于业务逻辑本身,而不用理会关于依赖引用和一些细枝末节的语法处理,最终得到一个完整且干净的应用。

Ecode主要利用TS面向对象的语法特性作为编译器前端,在此基础上,你可以使用for循环、模块导入导出等手段实现代码的复用。

该方案等侵入型非常低,它的最小编译单位是组件,因此可以在不改变项目架构的情况下使用。

目前Ecode仅支持Angular工程代码的生成。

快速上手

安装

npm i -g ecodecc
# or
yarn add ecodecc --global
# or
pnpm add -g ecodecc

配置文件

配置文件目前仅支持默认读取,无法指定,因此命名必须为ecode.config.js。编译运行会以配置文件为参照路径,因此需要在配置文件中指定入口和出口(日后支持更多配置),目前配置文件支持的选项不多,仅支持入口和出口:

// ecode.config.js

module.exports = {
  entry: './views',
  output: './src/app/pages',
};

入口一般指一个文件夹,这个文件夹中的所有文件,最终都会按照一定的依赖规则,编译至output所指定的文件夹中。

书写第一个模块

// views/first.ts
import { Module, Component } from 'ecode/dist/render'

export default new Module({
  id: 'hello',
	children: [
    new Component({
      id: 'member',
      children: []
    })
  ]
})

编译

编译命令是 ecode go [path], path是可选参数,指向 ecode.config.js ,默认指向命令行的执行目录。

import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'

import { FirstRoutingModule } from './first-routing.module'
import { MemberComponent } from './member.component'

@NgModule({
  imports: [FirstRoutingModule, CommonModule],
  declarations: [MemberComponent],
  exports: [],
  providers: []
})
export class FirstModule {}

封装一个简单的组件

这是一个真实的、用于业务上的组件类,它仅仅是对于antd的分割线组件进行简单的二次封装,根据antd的官方文档可知,要使用divider组件,必须引用NzDividerModule,并注册到我们工程的相关module中。

分割线的使用很直观,也不存在嵌套的关系,因此,children只需要传一个空数组即可。在Render函数中也仅仅需要返回几个最简单的属性,其中imports属性会用于构建依赖关系。

import { Render } from 'ecode/dist/render';

export class Line extends Render {
  constructor() {
    super({ children: [] });
  }
  render() {
    return {
      type: 'element',
      imports: [
        {
          target: [{ name: 'NzDividerModule', type: 'module' }],
          from: 'ng-zorro-antd/divider',
        },
      ],
      template: `<nz-divider></nz-divider>`,
      data: [],
      methods: [],
    };
  }
}

生成后的文件

生成的文件中包含Angular模块最基础的几个部分(上述例子中未必会完整体现)包含:

  1. 引用;如NgModuleCommonModule等Angular Module必须依赖的模块。
  2. 注册;该模块下辖的Component、Service等部件,并合理地注册。

使用

一个完整组件创建和使用的过程如下:

  • 新建文件,引入Render基类。
  • 封装出一个元素类。
  • 在类的constructor函数中接收你希望传入的几类API,有几个API是约定俗成的,如id、children等。将这些必要的数据传入super函数中,其他API可以由用户自定义其行为,例如放置在模板中或将其渲染成数据。

基础概念

为了增强业务表达能力,需要介绍一下几个基本概念。

组件的实例化

组件需要实例化后才能够使用,每个组件都是一个类,每个类会带有自己的方法,这些方法主要是为了针对性地减少信息量。

在封装一个类时,需要让它继承Render类,Render类中已经包含了几种常见的基本逻辑,例如自动维护事件和回调函数之间的联系。

在使用业务中使用这个组件时,尽可能通过实例方法或静态方法来使用。

为什么除了实例方法外还需要静态方法?

因为在实际业务中,有可能会出现组件逻辑相互引用的场景,如果组件未能合理地初始化,使用指令时会报错,因此为了让前端语法得以运行,有需要的话,需要有静态方法作为弥补。

指令

指令是操作组件的方式,一般建议设计得比较贴合人类的直观感受:

  • 如我们通过指令唤起一个弹框:Modal.wakeUp()

  • 如我们通过指令清空一个表单:Form.reset()

  • 如我们通过指令发起一个请求:httpS.send('foo')

指令中会包含完整的逻辑,如非空判断、错误判断等。

Callback数据结构

Callback数据结构专注于描述业务逻辑;

在形式上,它是一个数组,一般而言是一维的,根据需要也可以是多维的;

Callback中的每一个元素,要么是指令(这些指令在编译时会运行并返回字符串),要么是字符串,当然,字符串一般仅用于弥补指令能力的不足,不是推荐的做法。

由于指令生成的代码会可能有重复的部分,因而在部分场景下,可能需要将其封闭在一个作用域内,这时可以使用中括号[]将一部分逻辑约束在一个block内。

引入

ecode既包含一个编译器,也包含多个导出的API。这些API的使用方式是基本是一致的,都是通过实例化后传入父元素的children字段中,只是每个元素由于复杂度和职能不同,在实例化时传入的参数不一致,幸好TS能够给予比较详细的类型提示,因此不需要死记硬背。

ecode仅提供了几个重要且基本的类,内部封装了自身所需要的逻辑关系,但仅仅是这样还不足以应付业务,因此在业务中需要基于Render类自行封装。接下来一一介绍:

Module类

建议作为最小生成单位来编译,即在一个文件中导出一个Module实例。

在实例化时,它需要设置两个属性

  • id
  • children;它直属的children成员类型需要是ModuleComponent

Component 类

尽管建议将Module作为最小的编译单位,但Component也可以作为最小的编译单位,即一个文件中导出一个Component实例,之所以不推荐是因为如此一来,就需要用户手动处理与Component相关的依赖关系了。举个例子:一个UI组件foo需要依赖一个fooModule的模块,用户需要手动将fooModule注册到父Module文件中,生成后的组件才能够正常地使用。

在实例化时,它需要设置多个属性:

  • id
  • imports
  • children
  • init

Render 类

我们一般不会直接使用render类,但会使用继承Render类来使用,当然,需要保持必要的一致性:

首先,由于它继承自Render类,因而它需要有一个constructor函数并包含一个super方法,向super传入正确的属性。

其次,需要包含一个render方法,该方法返回一个对象,包含以下几个属性:

  • type;用于声明当前的组件类型
  • imports;是个对象数组,含有target、from
  • template; 用于
  • data;
  • methods;

导出

嵌套

几个简单的例子

Canvas

Line

Button

自定义组件

Service

# import 

@Component({
  selector: '# name',
  template: `# template`
})
export class #nameComponent {
  # data
  # create
  constructor(# inject) {
    # data
    # create
  }
  ngOnInit() {
    # init
  }
  #methods
}