亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

使用Node.js寫一個代碼生成器的方法步驟

2024-05-06 16:50:41
字體:
來源:轉載
供稿:網友

 背景

第一次接觸代碼生成器用的是動軟代碼生成器,數據庫設計好之后,一鍵生成后端 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 是存放模板以及數據配置的目錄。結構如下:

Node.js,代碼生成器

只生成 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(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));}

類似:

gulp.src(`${serverTemplatePath}controller.njk`)    .pipe(nunjucksRender(nunjucksRenderConfig))    .pipe(rename(Model.name + '.js'))    .pipe(gulp.dest(ServerProjectRootPath + CodeGenerateConfig.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

模板開發與安裝

不同的項目,代碼結構是不一樣的,每次直接修改模板文件會很麻煩。

需要提供這樣的功能:針對不同的項目開發一套獨立的模板,支持模板的安裝。

代碼生成的相關邏輯都在模板目錄的文件中,模板開發沒有什么規則限制,只要保證目錄名為 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 nodeconst download = require('download-git-repo')const program = require('commander')const ora = require('ora')const exists = require('fs').existsSyncconst rm = require('rimraf').syncconst 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.isLocalPathconst getTemplatePath = localPath.getTemplatePathprogram.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('/') > -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 確實如此。

但是如果寫一個正兒八經的代碼生成器,那肯定是需要根據已經設計好的數據庫表來生成代碼的。那么就需要連接數據庫,讀取數據表的字段信息,比如字段名稱,字段類型,字段描述等。而不同關系型數據庫,讀取表字段信息的 sql 是不一樣的,所以還要寫一堆balabala的判斷。可以使用現成的工具 sequelize-auto , 把它讀取的 model 數據轉成我們需要的格式即可。

生成前端項目代碼的時候,會遇到這種情況:

某個目錄結構是這樣的:

Node.js,代碼生成器

index.js 的內容:

import layoutHeaderAside from '@/layout/header-aside'export default {  "layoutHeaderAside": layoutHeaderAside,  "menu": () => import(/* webpackChunkName: "menu" */'@/pages/sys/menu'),  "route": () => import(/* webpackChunkName: "route" */'@/pages/sys/route'),  "role": () => import(/* webpackChunkName: "role" */'@/pages/sys/role'),  "user": () => import(/* webpackChunkName: "user" */'@/pages/sys/user'),  "interface": () => import(/* webpackChunkName: "interface" */'@/pages/sys/interface')}

如果添加一個 book 就需要在這里加上 "book": () => import(/* webpackChunkName: "book" */'@/pages/sys/book')

這一行內容也是可以通過配置模板來生成的,比如模板內容為:

"<$ model.name $>": () => import(/* webpackChunkName: "<$ model.name $>" */'@/pages<$ model.module $><$ model.name $>')

但是生成的內容怎么加到 index.js 中呢?

第一種方法:復制粘貼

第二種方法:

這部分的模板為 routerMapComponent.njk 

export default {  "<$ model.name $>": () => import(/* webpackChunkName: "<$ model.name $>" */'@/pages<$ model.module $><$ model.name $>')}

編譯后文件保存到 routerMapComponents 目錄下,比如 book.js

修改 index.js :

const files = require.context('./', true, //.js$/);import layoutHeaderAside from '@/layout/header-aside'let componentMaps = {  "layoutHeaderAside": layoutHeaderAside,  "menu": () => import(/* webpackChunkName: "menu" */'@/pages/sys/menu'),  "route": () => import(/* webpackChunkName: "route" */'@/pages/sys/route'),  "role": () => import(/* webpackChunkName: "role" */'@/pages/sys/role'),  "user": () => import(/* webpackChunkName: "user" */'@/pages/sys/user'),  "interface": () => import(/* webpackChunkName: "interface" */'@/pages/sys/interface'),}files.keys().forEach((key) => {  if (key === './index.js') return  Object.assign(componentMaps, files(key).default)})export default componentMaps

使用了 require.context

我目前也是使用了這種方法

第三種方法:

開發模板的時候,做特殊處理,讀取原有 index.js 的內容,按行進行分割,在數組的最后一個元素之前插入新生成的內容,注意逗號的處理,將新數組內容重新寫入 index.js 中,注意換行。

打個廣告

如果你想要快速的創建一個 mock-server,同時還支持數據的持久化,又不需要安裝數據庫,還支持代碼生成器的模板開發,歡迎試試lazy-mock 。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品亚洲美女av网站| 国产91精品高潮白浆喷水| 亚洲国产精品久久久久秋霞蜜臀| 久久人人爽人人| 国产免费成人av| 国产福利精品视频| 97精品久久久| 精品国产精品自拍| 欧美国产日本在线| 国产伊人精品在线| 色婷婷综合久久久久| 一区二区日韩精品| 欧美综合一区第一页| 欧洲美女免费图片一区| 日韩欧美在线免费观看| 国产啪精品视频网站| 成人免费看黄网站| 国产精品视频免费观看www| 97视频在线观看免费高清完整版在线观看| 欧美日韩亚洲一区二| 97精品视频在线播放| 日韩精品中文字幕有码专区| 亚洲精品suv精品一区二区| 中文字幕久精品免费视频| 亚洲国产小视频| 91香蕉嫩草影院入口| 91亚洲国产精品| 中文字幕亚洲综合久久| 亚洲国产欧美自拍| 欧美怡春院一区二区三区| 国产免费一区二区三区香蕉精| 欧美亚洲成人免费| 欧美午夜女人视频在线| 性欧美亚洲xxxx乳在线观看| 欧美成人午夜剧场免费观看| 国产91精品最新在线播放| 国产亚洲欧美视频| 日韩的一区二区| 国产高清在线不卡| 日韩av网址在线观看| 18性欧美xxxⅹ性满足| 国产在线一区二区三区| 国产一区二区av| 亚洲曰本av电影| 国产精品视频26uuu| 日韩av网站电影| 成人激情视频免费在线| 久久国产精品亚洲| 最近中文字幕2019免费| 亚洲精品理论电影| 日韩欧美在线看| 揄拍成人国产精品视频| 亚洲奶大毛多的老太婆| 亚洲第一精品久久忘忧草社区| 久久天天躁狠狠躁夜夜躁2014| 国产精品中文字幕久久久| 国产午夜精品免费一区二区三区| 亚洲香蕉伊综合在人在线视看| 久久99亚洲热视| 色婷婷亚洲mv天堂mv在影片| 韩日精品中文字幕| 91精品国产综合久久久久久久久| 国产激情综合五月久久| 91在线观看免费网站| 精品色蜜蜜精品视频在线观看| 国产精品一区av| 欧美极品少妇xxxxⅹ裸体艺术| 91免费视频网站| 2024亚洲男人天堂| 国产亚洲人成网站在线观看| 国产精品小说在线| 国产精品久久中文| 欧美性猛交视频| 中文字幕亚洲精品| 一本色道久久88综合日韩精品| 日韩小视频在线观看| 北条麻妃一区二区三区中文字幕| 欧美一级电影在线| 欧美精品videossex88| 亚洲福利在线视频| 亚洲黄色在线观看| 国产精品丝袜高跟| 国产亚洲激情视频在线| 日本欧美一级片| 91久久久久久国产精品| 最近的2019中文字幕免费一页| 国产精品久久久久久久久久久新郎| 亚洲乱码一区二区| 日韩高清av一区二区三区| 日本19禁啪啪免费观看www| 人九九综合九九宗合| 国产在线视频2019最新视频| 国产精品自拍网| 在线视频国产日韩| 2020欧美日韩在线视频| 一区二区三区高清国产| 综合网中文字幕| 精品久久久一区二区| 97久久精品人搡人人玩| 北条麻妃99精品青青久久| 国产亚洲成av人片在线观看桃| 成年无码av片在线| 91av国产在线| 亚洲欧美综合精品久久成人| 成人女保姆的销魂服务| 美女999久久久精品视频| 久久久久中文字幕2018| 91免费人成网站在线观看18| 欧美一区二区三区精品电影| 亚洲精品久久久久中文字幕二区| 中文字幕日韩欧美在线| 人体精品一二三区| 性色av一区二区三区红粉影视| 亚洲小视频在线| 奇米4444一区二区三区| 日韩精品在线看| 欧美成人免费观看| 久久久av免费| 亚洲综合中文字幕68页| 精品爽片免费看久久| 中文字幕免费精品一区高清| 欧美一级淫片videoshd| 91精品国产色综合久久不卡98| 久久精品人人做人人爽| 岛国av一区二区在线在线观看| 欧美性xxxx18| 国内免费久久久久久久久久久| 国产99久久精品一区二区| 国产午夜精品全部视频在线播放| 欧美肥老太性生活视频| 2019中文字幕免费视频| 国产精品激情av在线播放| 国产区精品在线观看| 日韩av电影在线网| 亚洲国产精品中文| 国产精品视频免费在线观看| 国产97在线亚洲| 日韩一区二区三区xxxx| 国产精品成人免费视频| 国产精品永久免费视频| 国内精品美女av在线播放| 欧美日韩性视频在线| 欧美性猛交丰臀xxxxx网站| 91精品国产91久久久久久不卡| 欧美最顶级的aⅴ艳星| 亚洲美女视频网站| 日韩视频免费中文字幕| 亚洲999一在线观看www| 久久亚洲精品网站| 这里只有精品久久| 久久99久国产精品黄毛片入口| 性欧美亚洲xxxx乳在线观看| 懂色av中文一区二区三区天美| 国产婷婷97碰碰久久人人蜜臀| 美女久久久久久久久久久| 亚洲91精品在线观看| 亚洲免费高清视频| 亚洲一级黄色av| 欧美老少做受xxxx高潮| 久久夜色精品国产亚洲aⅴ| 一本色道久久88精品综合| 欧美大人香蕉在线| 亚洲免费av电影| 亚洲国产精品99久久|