使用 Node.js 写一个代码生成器

背景第一次接触代码生成器用的是动软代码生成器,数据库设计好之后,一键生成后端curd代码。之后也用过CodeSmith,T4。目前市面上也有很多优秀的代码生成器,而且大部分都提供可视化界面操作。自己写一个的原因是因为要集成到自己写的一个小工具中,而且使用Node.js这种动态脚本语言进行编写更加灵活。原理代码生成器的原理就是:数据 模板=>文件。数据一般为数据库的表字段结构。模板的语法与使用...

使用 Node.js 写一个代码生成器

背景

第一次接触代码生成器用的是动软代码生成器,数据库设计好之后,一键生成后端 curd代码。之后也用过 CodeSmith , T4。目前市面上也有很多优秀的代码生成器,而且大部分都提供可视化界面操作。

自己写一个的原因是因为要集成到自己写的一个小工具中,而且使用 Node.js 这种动态脚本语言进行编写更加灵活。

原理

代码生成器的原理就是:数据模板 => 文件

数据一般为数据库的表字段结构。

模板的语法与使用的模板引擎有关。

使用模板引擎将数据模板进行编译,编译后的内容输出到文件中就得到了一份代码文件。

功能

因为这个代码生成器是要集成到一个小工具 lazy-mock 内,这个工具的主要功能是启动一个 mock server 服务,包含curd功能,并且支持数据的持久化,文件变化的时候自动重启服务以最新的代码提供 api mock 服务。

代码生成器的功能就是根据配置的数据和模板,编译后将内容输出到指定的目录文件中。因为添加了新的文件,mock server 服务会自动重启。

还要支持模板的定制与开发,以及使用 CLI 安装模板。

可以开发前端项目的模板,直接将编译后的内容输出到前端项目的相关目录下,webpack 的热更新功能也会起作用。

模板引擎

模板引擎使用的是 nunjucks。

lazy-mock 使用的构建工具是 gulp,使用 gulp-nodemon 实现 mock-server 服务的自动重启。所以这里使用 gulp-nunjucks-render 配合 gulp 的构建流程。

代码生成

编写一个 gulp task :

const rename = require('gulp-rename')const nunjucksRender = require('gulp-nunjucks-render')const codeGenerate = require('./templates/generate')const ServerFullPath = require('./package.json').ServerFullPath; //mock -server项目的绝对路径const FrontendFullPath = require('./package.json').FrontendFullPath; //前端项目的绝对路径const nunjucksRenderConfig = {  path: 'templates/server',  envOptions: { tags: {blockStart: '<%',blockEnd: '%>',variableStart: '<$',variableEnd: '$>',commentStart: '<#',commentEnd: '#>' },  },  ext: '.js',  //以上是 nunjucks 的配置  ServerFullPath,  FrontendFullPath}gulp.task('code', function () {  require('events').EventEmitter.defaultMaxListeners = 0  return codeGenerate(gulp, nunjucksRender, rename, nunjucksRenderConfig)});

代码具体结构细节可以打开 lazy-mock 进行参照

为了支持模板的开发,以及更灵活的配置,我将代码生成的逻辑全都放在模板目录中。

templates 是存放模板以及数据配置的目录。结构如下:

只生成 lazy-mock 代码的模板中 :

generate.js的内容如下:

const path = require('path')const CodeGenerateConfig = require('./config').default;const Model = CodeGenerateConfig.model;module.exports = function generate(gulp, nunjucksRender, rename, nunjucksRenderConfig) { nunjucksRenderConfig.data = {  model: CodeGenerateConfig.model,  config: CodeGenerateConfig.config } const ServerProjectRootPath = nunjucksRenderConfig.ServerFullPath; //server const serverTemplatePath = 'templates/server/' gulp.src(`${serverTemplatePath}controller.njk`)  .pipe(nunjucksRender(nunjucksRenderConfig))  .pipe(rename(Model.name'.js'))  .pipe(gulp.dest(ServerProjectRootPathCodeGenerateConfig.config.ControllerRelativePath)); gulp.src(`${serverTemplatePath}service.njk`)  .pipe(nunjucksRender(nunjucksRenderConfig))  .pipe(rename(Model.name'Service.js'))  .pipe(gulp.dest(ServerProjectRootPathCodeGenerateConfig.config.ServiceRelativePath)); gulp.src(`${serverTemplatePath}model.njk`)  .pipe(nunjucksRender(nunjucksRenderConfig))  .pipe(rename(Model.name'Model.js'))  .pipe(gulp.dest(ServerProjectRootPathCodeGenerateConfig.config.ModelRelativePath)); gulp.src(`${serverTemplatePath}db.njk`)  .pipe(nunjucksRender(nunjucksRenderConfig))  .pipe(rename(Model.name'_db.json'))  .pipe(gulp.dest(ServerProjectRootPathCodeGenerateConfig.config.DBRelativePath)); return gulp.src(`${serverTemplatePath}route.njk`)  .pipe(nunjucksRender(nunjucksRenderConfig))  .pipe(rename(Model.name'Route.js'))  .pipe(gulp.dest(ServerProjectRootPathCodeGenerateConfig.config.RouteRelativePath));}

类似:

gulp.src(`${serverTemplatePath}controller.njk`)  .pipe(nunjucksRender(nunjucksRenderConfig))  .pipe(rename(Model.name'.js'))  .pipe(gulp.dest(ServerProjectRootPathCodeGenerateConfig.config.ControllerRelativePath));

表示使用 controller.njk 作为模板,nunjucksRenderConfig作为数据(模板内可以获取到 nunjucksRenderConfig 属性 data 上的数据)。编译后进行文件重命名,并保存到指定目录下。

model.js 的内容如下:

var shortid = require('shortid')var Mock = require('mockjs')var Random = Mock.Random//必须包含字段idexport default { name: “book“, Name: “Book“, properties: [  {key: “id“,title: “id“  },  {key: “name“,title: “书名“  },  {key: “author“,title: “作者“  },  {key: “press“,title: “出版社“  } ], buildMockData: function () {//不需要生成设为false  let data = []  for (let i = 0; i < 100; i  ) {data.push({ id: shortid.generate(), name: Random.cword(5, 7), author: Random.cname(), press: Random.cword(5, 7)})  }  return data }}

模板中使用最多的就是这个数据,也是生成新代码需要配置的地方,比如这里配置的是 book ,生成的就是关于 book 的curd 的 mock 服务。要生成别的,修改后执行生成命令即可。

buildMockData 函数的作用是生成 mock 服务需要的随机数据,在 db.njk 模板中会使用:

{  “<$ model.name $>“:<% if model.buildMockData %><$ model.buildMockData()|dump|safe $><% else %>[]<% endif %>}

这也是 nunjucks 如何在模板中执行函数

config.js 的内容如下:

export default { //server RouteRelativePath: '/src/routes/', ControllerRelativePath: '/src/controllers/', ServiceRelativePath: '/src/services/', ModelRelativePath: '/src/models/', DBRelativePath: '/src/db/'}

配置相应的模板编译后保存的位置。

config/index.js 的内容如下:

import model from './model';import config from './config';export default { model, config}

针对 lazy-mock 的代码生成的功能就已经完成了,要实现模板的定制直接修改模板文件即可,比如要修改 mock server 服务 api 的接口定义,直接修改 route.njk 文件:

import KoaRouter from 'koa-router'import controllers from '../controllers/index.js'import PermissionCheck from '../middleware/PermissionCheck'const router = new KoaRouter()router .get('/<$ model.name $>/paged', controllers.<$model.name $>.get<$ model.Name $>PagedList) .get('/<$ model.name $>/:id', controllers.<$ model.name $>.get<$ model.Name $>) .del('/<$ model.name $>/del', controllers.<$ model.name $>.del<$ model.Name $>) .del('/<$ model.name $>/batchdel', controllers.<$ model.name $>.del<$ model.Name $>s) .post('/<$ model.name $>/save', controllers.<$ model.name $>.save<$ model.Name $>)module.exports = router

模板开发与安装

不同的项目,代码结构是不一样的,每次直接修改模板文件会很麻烦。

需要提供这样的功能:针对不同的项目开发一套独立的模板,支持模板的安装。

代码生成的相关逻辑都在模板目录的文件中,模板开发没有什么规则限制,只要保证目录名为 templatesgenerate.js中导出generate函数即可。

模板的安装原理就是将模板目录中的文件全部覆盖掉即可。不过具体的安装分为本地安装与在线安装。

之前已经说了,这个代码生成器是集成在 lazy-mock 中的,我的做法是在初始化一个新 lazy-mock 项目的时候,指定使用相应的模板进行初始化,也就是安装相应的模板。

使用 Node.js 写了一个 CLI 工具 lazy-mock-cli,已发到 npm ,其功能包含下载指定的远程模板来初始化新的 lazy-mock 项目。代码参考( copy )了 vue-cli2。代码不难,说下某些关键点。

安装 CLI 工具:

npm install lazy-mock -g

使用模板初始化项目:

lazy-mock init d2-admin-pm my-project

d2-admin-pm 是我为一个前端项目已经写好的一个模板。

源文地址:https://www.guoxiongfei.cn/cntech/16689.html