background
The first contact with the code generator was the dynamic soft code generator. After the database was designed, the back-end CURD code was generated with one click. And then CodeSmith, T4. There are a lot of good code generators out there, and most of them provide visual interface manipulation.
The reason for writing your own is that it’s integrated into a widget you wrote yourself, and it’s more flexible to write using node.js, a dynamic scripting language.
The principle of
The code generator principle is: data + template => file.
Data is generally a database table field structure.
The syntax of a template is dependent on the template engine used.
Data and templates are compiled using a template engine, and the compiled content is output to a file to get a code file.
function
Because the code generator is integrated into a small tool called lazy-Mock, the main function of this tool is to start a mock Server service, including curd, and support data persistence. The service is automatically restarted when the file changes to provide API mock service with the latest code.
The function of a code generator is to compile and output the content to a specified directory file based on the configured data and templates. The Mock Server service restarts automatically because a new file is added.
It also supports template customization and development, as well as using the CLI to install templates.
You can develop templates for the front-end project and directly export the compiled content to the relevant directory of the front-end project. The hot update feature of WebPack also comes into play.
A template engine
The template engine uses Nunjucks.
Lazy-mock uses gulp as the construction tool. Gulp-nodemon is used to automatically restart the mock-server service. So gulp-nunjucks-render is used in conjunction with the gulP build process.
Code generation
Write a gulp task:
const rename = require('gulp-rename')
const nunjucksRender = require('gulp-nunjucks-render')
const codeGenerate = require('./templates/generate')
const ServerFullPath = require('./package.json').ServerFullPath; // The absolute path of the mock-server project
const FrontendFullPath = require('./package.json').FrontendFullPath; // The absolute path of the front-end project
const nunjucksRenderConfig = {
path: 'templates/server'.envOptions: {
tags: {
blockStart: '< %'.blockEnd: '% >'.variableStart: '< $'.variableEnd: '$>'.commentStart: '< #'.commentEnd: '# >'}},ext: '.js'.// This is the nunjucks configuration
ServerFullPath,
FrontendFullPath
}
gulp.task('code'.function () {
require('events').EventEmitter.defaultMaxListeners = 0
return codeGenerate(gulp, nunjucksRender, rename, nunjucksRenderConfig)
});
Copy the code
Lazy-mock can be turned on for code structure details
To support template development and more flexible configuration, I put all the logic for code generation in the template directory.
Templates is the directory where templates and data configuration are stored. The structure is as follows:
In a template that generates only lazy-mock code:
Generate.js reads as follows:
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(ServerProjectRootPath + CodeGenerateConfig.config.ControllerRelativePath));
gulp.src(`${serverTemplatePath}service.njk`)
.pipe(nunjucksRender(nunjucksRenderConfig))
.pipe(rename(Model.name + 'Service.js'))
.pipe(gulp.dest(ServerProjectRootPath + CodeGenerateConfig.config.ServiceRelativePath));
gulp.src(`${serverTemplatePath}model.njk`)
.pipe(nunjucksRender(nunjucksRenderConfig))
.pipe(rename(Model.name + 'Model.js'))
.pipe(gulp.dest(ServerProjectRootPath + CodeGenerateConfig.config.ModelRelativePath));
gulp.src(`${serverTemplatePath}db.njk`)
.pipe(nunjucksRender(nunjucksRenderConfig))
.pipe(rename(Model.name + '_db.json'))
.pipe(gulp.dest(ServerProjectRootPath + CodeGenerateConfig.config.DBRelativePath));
return gulp.src(`${serverTemplatePath}route.njk`)
.pipe(nunjucksRender(nunjucksRenderConfig))
.pipe(rename(Model.name + 'Route.js'))
.pipe(gulp.dest(ServerProjectRootPath + CodeGenerateConfig.config.RouteRelativePath));
}
Copy the code
Similar to:
gulp.src(`${serverTemplatePath}controller.njk`)
.pipe(nunjucksRender(nunjucksRenderConfig))
.pipe(rename(Model.name + '.js'))
.pipe(gulp.dest(ServerProjectRootPath + CodeGenerateConfig.config.ControllerRelativePath));
Copy the code
Controller. NJK as template and nunjucksRenderConfig as data (nunjucksRenderConfig property data is available within the template) After the file is compiled, rename the file and save the file to a specified directory.
Model.js contains the following contents:
var shortid = require('shortid')
var Mock = require('mockjs')
var Random = Mock.Random
// Must contain the field ID
export default {
name: "book".Name: "Book".properties: [{key: "id".title: "id"
},
{
key: "name".title: "Title"
},
{
key: "author".title: "The author"
},
{
key: "press".title: "Publishing house"}].buildMockData: function () {// Do not set the generation to 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
}
}
Copy the code
This is the data that is most used in the template, and it is where you configure new code, such as book, to generate the mock service on book’s curd. To generate something else, modify it and run the build command.
The buildMockData function generates random data for the mock service and is used in the db.njk template:
{
"<$ model.name $>": < %if model.buildMockData %><$ model.buildMockData()|dump|safe $><% else %>[]<% endif %>
}
Copy the code
That’s how Nunjucks executes functions in templates, right
The contents of config.js are as follows:
export default {
//server
RouteRelativePath: '/src/routes/'.ControllerRelativePath: '/src/controllers/'.ServiceRelativePath: '/src/services/'.ModelRelativePath: '/src/models/'.DBRelativePath: '/src/db/'
}
Copy the code
Configure the location where the template is saved after compilation.
The contents of config/index.js are as follows:
import model from './model';
import config from './config';
export default {
model,
config
}
Copy the code
To customize the template, modify the template file directly. For example, to modify the interface definition of the Mock Server API, modify the route. NJK file directly:
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
Copy the code
Template development and installation
The structure of the code varies from project to project, and it can be cumbersome to modify the template file directly each time.
You need to provide the ability to develop a separate set of templates for different projects and support the installation of templates.
The logic for code generation is in the templates directory. There are no rules for template development, just make sure that the directory is named templates and generate is exported from generate.js.
The installation principle of a template is to overwrite all files in the template directory. However, specific installation is divided into local installation and online installation.
As mentioned earlier, the code generator is integrated into lazy-mock. What I do is when I initialize a new lazy-mock project, I specify that the appropriate template is used for initialization, that is, the appropriate template is installed.
A CLI tool, lazy-mock-cli, was written using Node.js and sent to NPM. The function includes downloading the specified remote template to initialize the new lazy-mock project. Code reference (copy) vuE-cli2. The code isn’t that hard, but some key points.
Installing the CLI tool:
npm install lazy-mock -g
Copy the code
Initialize the project with a template:
lazy-mock init d2-admin-pm my-project
Copy the code
D2-admin-pm is a template I have written for a front-end project.
The init command calls the logic in 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()
/ / template
let template = program.args[0]
// Determine whether to use an official template
const hasSlash = template.indexOf('/') > - 1
// Project name
const rawName = program.args[1]
// Create it under the current file
constinPlace = ! rawName || rawName ==='. '
// Project name
const name = inPlace ? path.relative('.. / ', process.cwd()) : rawName
// Create a complete target location for the project
const to = path.resolve(rawName || '. ')
const clone = program.clone || false
// Cache location
const serverTmp = path.join(home, '.lazy-mock'.'sever')
const tmp = path.join(home, '.lazy-mock'.'templates', template.replace(/[\/:]/g.The '-'))
if (program.offline) {
console.log(`> Use cached template at ${chalk.yellow(tildify(tmp))}`)
template = tmp
}
// Determine whether to initialize or overwrite existing directories under the current directory
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() {
// Use local cache
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) {// Use the official template
const officialTemplate = 'lazy-mock-templates/' + template
downloadAndGenerate(officialTemplate)
} else {
downloadAndGenerate(template)
}
}
}
function downloadAndGenerate(template) {
downloadServer((a)= > {
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()
}
Copy the code
Determines whether to use the locally cached template or pull the latest template, and whether to pull the online template from the official repository or another repository.
A few minor problems
The data generated by the code is not currently from a database, but is simply configured in model.js because I don’t think a mock server needs a database, and lazy-mock does.
But if you’re writing a serious code generator, you’ll need to generate code from a database table that you’ve already designed. Then you need to connect to the database, read the data table of the field information, such as field name, field type, field description, etc. Different relational databases use different SQL to read table field information, so there are a lot of Balabala judgments to write. We can use an off-the-shelf tool, Sequelize-Auto, to convert the Model data it reads into the format we want.
When generating front-end project code, you encounter this situation:
A directory structure looks like this:
Contents of index.js:
import layoutHeaderAside from '@/layout/header-aside'
export default {
"layoutHeaderAside": layoutHeaderAside,
"menu": (a)= > import(/* webpackChunkName: "menu" */'@/pages/sys/menu'),
"route": (a)= > import(/* webpackChunkName: "route" */'@/pages/sys/route'),
"role": (a)= > import(/* webpackChunkName: "role" */'@/pages/sys/role'),
"user": (a)= > import(/* webpackChunkName: "user" */'@/pages/sys/user'),
"interface": (a)= > import(/* webpackChunkName: "interface" */'@/pages/sys/interface')}Copy the code
() => import(/* webpackChunkName: “book” */’@/pages/sys/book’)
This line can also be generated by configuring the template, for example:
"<$ model.name $>": () => import(/* webpackChunkName: "<$ model.name $>"* /'@/pages<$ model.module $><$ model.name $>')
Copy the code
But how does the generated content add to index.js?
The first method: copy and paste
The second method:
The template for this section is RouterMapComponent.njk:
export default {
"<$ model.name $>": () => import(/* webpackChunkName: "<$ model.name $>"* /'@/pages<$ model.module $><$ model.name $>')}Copy the code
The compiled file is saved to a routerMapComponents directory, such as book.js
Modified index. Js:
const files = require.context('/'.true, /\.js$/);
import layoutHeaderAside from '@/layout/header-aside'
let componentMaps = {
"layoutHeaderAside": layoutHeaderAside,
"menu": (a)= > import(/* webpackChunkName: "menu" */'@/pages/sys/menu'),
"route": (a)= > import(/* webpackChunkName: "route" */'@/pages/sys/route'),
"role": (a)= > import(/* webpackChunkName: "role" */'@/pages/sys/role'),
"user": (a)= > import(/* webpackChunkName: "user" */'@/pages/sys/user'),
"interface": (a)= > import(/* webpackChunkName: "interface" */'@/pages/sys/interface'),
}
files.keys().forEach((key) = > {
if (key === './index.js') return
Object.assign(componentMaps, files(key).default)
})
export default componentMaps
Copy the code
Using the require. The context
I’m currently using this method as well
The third method:
When developing the template, do the special processing: read the contents of the original index.js, split it by line, insert the newly generated contents before the last element of the array, pay attention to the comma processing, write the contents of the new array into the index.js, pay attention to the newline.
Make an advertisement
If you want to quickly create a mock-server that allows persistence of data, doesn’t require a database installation, and supports template development for code generators, try lazy-mock.