使用Node.js写一个代码生成器的方法步骤(2)

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

model.js 的内容如下:

var shortid = require('shortid') var Mock = require('mockjs') var Random = Mock.Random //必须包含字段id export 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

模板开发与安装

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

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

代码生成的相关逻辑都在模板目录的文件中,模板开发没有什么规则限制,只要保证目录名为 templates , generate.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 是我为一个 前端项目 已经写好的一个模板。

init 命令调用的是 lazy-mock-init.js 中的逻辑:

#!/usr/bin/env node const download = require('download-git-repo') const program = require('commander') const ora = require('ora') const exists = require('fs').existsSync const rm = require('rimraf').sync const path = require('path') const chalk = require('chalk') const inquirer = require('inquirer') const home = require('user-home') const fse = require('fs-extra') const tildify = require('tildify') const cliSpinners = require('cli-spinners'); const logger = require('../lib/logger') const localPath = require('../lib/local-path') const isLocalPath = localPath.isLocalPath const getTemplatePath = localPath.getTemplatePath program.usage('<template-name> [project-name]') .option('-c, --clone', 'use git clone') .option('--offline', 'use cached template') program.on('--help', () => { console.log(' Examples:') console.log() console.log(chalk.gray(' # create a new project with an official template')) console.log(' $ lazy-mock init d2-admin-pm my-project') console.log() console.log(chalk.gray(' # create a new project straight from a github template')) console.log(' $ vue init username/repo my-project') console.log() }) function help() { program.parse(process.argv) if (program.args.length < 1) return program.help() } help() //模板 let template = program.args[0] //判断是否使用官方模板 const hasSlash = template.indexOf('https://www.jb51.net/') > -1 //项目名称 const rawName = program.args[1] //在当前文件下创建 const inPlace = !rawName || rawName === '.' //项目名称 const name = inPlace ? path.relative('../', process.cwd()) : rawName //创建项目完整目标位置 const to = path.resolve(rawName || '.') const clone = program.clone || false //缓存位置 const serverTmp = path.join(home, '.lazy-mock', 'sever') const tmp = path.join(home, '.lazy-mock', 'templates', template.replace(/[\/:]/g, '-')) if (program.offline) { console.log(`> Use cached template at ${chalk.yellow(tildify(tmp))}`) template = tmp } //判断是否当前目录下初始化或者覆盖已有目录 if (inPlace || exists(to)) { inquirer.prompt([{ type: 'confirm', message: inPlace ? 'Generate project in current directory?' : 'Target directory exists. Continue?', name: 'ok' }]).then(answers => { if (answers.ok) { run() } }).catch(logger.fatal) } else { run() } function run() { //使用本地缓存 if (isLocalPath(template)) { const templatePath = getTemplatePath(template) if (exists(templatePath)) { generate(name, templatePath, to, err => { if (err) logger.fatal(err) console.log() logger.success('Generated "%s"', name) }) } else { logger.fatal('Local template "%s" not found.', template) } } else { if (!hasSlash) { //使用官方模板 const officialTemplate = 'lazy-mock-templates/' + template downloadAndGenerate(officialTemplate) } else { downloadAndGenerate(template) } } } function downloadAndGenerate(template) { downloadServer(() => { downloadTemplate(template) }) } function downloadServer(done) { const spinner = ora('downloading server') spinner.spinner = cliSpinners.bouncingBall spinner.start() if (exists(serverTmp)) rm(serverTmp) download('wjkang/lazy-mock', serverTmp, { clone }, err => { spinner.stop() if (err) logger.fatal('Failed to download server ' + template + ': ' + err.message.trim()) done() }) } function downloadTemplate(template) { const spinner = ora('downloading template') spinner.spinner = cliSpinners.bouncingBall spinner.start() if (exists(tmp)) rm(tmp) download(template, tmp, { clone }, err => { spinner.stop() if (err) logger.fatal('Failed to download template ' + template + ': ' + err.message.trim()) generate(name, tmp, to, err => { if (err) logger.fatal(err) console.log() logger.success('Generated "%s"', name) }) }) } function generate(name, src, dest, done) { try { fse.removeSync(path.join(serverTmp, 'templates')) const packageObj = fse.readJsonSync(path.join(serverTmp, 'package.json')) packageObj.name = name packageObj.author = "" packageObj.description = "" packageObj.ServerFullPath = path.join(dest) packageObj.FrontendFullPath = path.join(dest, "front-page") fse.writeJsonSync(path.join(serverTmp, 'package.json'), packageObj, { spaces: 2 }) fse.copySync(serverTmp, dest) fse.copySync(path.join(src, 'templates'), path.join(dest, 'templates')) } catch (err) { done(err) return } done() }

判断了是使用本地缓存的模板还是拉取最新的模板,拉取线上模板时是从官方仓库拉取还是从别的仓库拉取。

一些小问题

目前代码生成的相关数据并不是来源于数据库,而是在 model.js 中简单配置的,原因是我认为一个 mock server 不需要数据库,lazy-mock 确实如此。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/c3d899a8158ed11710615a849e6fac5b.html