国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

使用 Node.js 寫一個(gè)代碼生成器

韓冰 / 1747人閱讀

摘要:目前市面上也有很多優(yōu)秀的代碼生成器,而且大部分都提供可視化界面操作。原理代碼生成器的原理就是數(shù)據(jù)模板文件。但是如果寫一個(gè)正兒八經(jīng)的代碼生成器,那肯定是需要根據(jù)已經(jīng)設(shè)計(jì)好的數(shù)據(jù)庫表來生成代碼的。

背景

第一次接觸代碼生成器用的是動(dòng)軟代碼生成器,數(shù)據(jù)庫設(shè)計(jì)好之后,一鍵生成后端 curd代碼。之后也用過 CodeSmith , T4。目前市面上也有很多優(yōu)秀的代碼生成器,而且大部分都提供可視化界面操作。

自己寫一個(gè)的原因是因?yàn)橐傻阶约簩懙囊粋€(gè)小工具中,而且使用 Node.js 這種動(dòng)態(tài)腳本語言進(jìn)行編寫更加靈活。

原理

代碼生成器的原理就是:數(shù)據(jù) + 模板 => 文件。

數(shù)據(jù)一般為數(shù)據(jù)庫的表字段結(jié)構(gòu)。

模板的語法與使用的模板引擎有關(guān)。

使用模板引擎將數(shù)據(jù)模板進(jìn)行編譯,編譯后的內(nèi)容輸出到文件中就得到了一份代碼文件。

功能

因?yàn)檫@個(gè)代碼生成器是要集成到一個(gè)小工具 lazy-mock 內(nèi),這個(gè)工具的主要功能是啟動(dòng)一個(gè) mock server 服務(wù),包含curd功能,并且支持?jǐn)?shù)據(jù)的持久化,文件變化的時(shí)候自動(dòng)重啟服務(wù)以最新的代碼提供 api mock 服務(wù)。

代碼生成器的功能就是根據(jù)配置的數(shù)據(jù)和模板,編譯后將內(nèi)容輸出到指定的目錄文件中。因?yàn)樘砑恿诵碌奈募?,mock server 服務(wù)會(huì)自動(dòng)重啟。

還要支持模板的定制與開發(fā),以及使用 CLI 安裝模板。

可以開發(fā)前端項(xiàng)目的模板,直接將編譯后的內(nèi)容輸出到前端項(xiàng)目的相關(guān)目錄下,webpack 的熱更新功能也會(huì)起作用。

模板引擎

模板引擎使用的是 nunjucks。

lazy-mock 使用的構(gòu)建工具是 gulp,使用 gulp-nodemon 實(shí)現(xiàn) mock-server 服務(wù)的自動(dòng)重啟。所以這里使用 gulp-nunjucks-render 配合 gulp 的構(gòu)建流程。

代碼生成

編寫一個(gè) 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項(xiàng)目的絕對(duì)路徑
const FrontendFullPath = require("./package.json").FrontendFullPath; //前端項(xiàng)目的絕對(duì)路徑
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)
});

代碼具體結(jié)構(gòu)細(xì)節(jié)可以打開 lazy-mock 進(jìn)行參照

為了支持模板的開發(fā),以及更靈活的配置,我將代碼生成的邏輯全都放在模板目錄中。

templates 是存放模板以及數(shù)據(jù)配置的目錄。結(jié)構(gòu)如下:

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

generate.js的內(nèi)容如下:

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作為數(shù)據(jù)(模板內(nèi)可以獲取到 nunjucksRenderConfig 屬性 data 上的數(shù)據(jù))。編譯后進(jìn)行文件重命名,并保存到指定目錄下。

model.js 的內(nèi)容如下:

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 () {//不需要生成設(shè)為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
    }
}

模板中使用最多的就是這個(gè)數(shù)據(jù),也是生成新代碼需要配置的地方,比如這里配置的是 book ,生成的就是關(guān)于 book 的curd 的 mock 服務(wù)。要生成別的,修改后執(zhí)行生成命令即可。

buildMockData 函數(shù)的作用是生成 mock 服務(wù)需要的隨機(jī)數(shù)據(jù),在 db.njk 模板中會(huì)使用:

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

這也是 nunjucks 如何在模板中執(zhí)行函數(shù)

config.js 的內(nèi)容如下:

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

配置相應(yīng)的模板編譯后保存的位置。

config/index.js 的內(nèi)容如下:

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

針對(duì) lazy-mock 的代碼生成的功能就已經(jīng)完成了,要實(shí)現(xiàn)模板的定制直接修改模板文件即可,比如要修改 mock server 服務(wù) 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
模板開發(fā)與安裝

不同的項(xiàng)目,代碼結(jié)構(gòu)是不一樣的,每次直接修改模板文件會(huì)很麻煩。

需要提供這樣的功能:針對(duì)不同的項(xiàng)目開發(fā)一套獨(dú)立的模板,支持模板的安裝。

代碼生成的相關(guān)邏輯都在模板目錄的文件中,模板開發(fā)沒有什么規(guī)則限制,只要保證目錄名為 templates,generate.js中導(dǎo)出generate函數(shù)即可。

模板的安裝原理就是將模板目錄中的文件全部覆蓋掉即可。不過具體的安裝分為本地安裝與在線安裝。

之前已經(jīng)說了,這個(gè)代碼生成器是集成在 lazy-mock 中的,我的做法是在初始化一個(gè)新 lazy-mock 項(xiàng)目的時(shí)候,指定使用相應(yīng)的模板進(jìn)行初始化,也就是安裝相應(yīng)的模板。

使用 Node.js 寫了一個(gè) CLI 工具 lazy-mock-cli,已發(fā)到 npm ,其功能包含下載指定的遠(yuǎn)程模板來初始化新的 lazy-mock 項(xiàng)目。代碼參考( copy )了 vue-cli2。代碼不難,說下某些關(guān)鍵點(diǎn)。

安裝 CLI 工具:

npm install lazy-mock -g

使用模板初始化項(xiàng)目:

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

d2-admin-pm 是我為一個(gè)前端項(xiàng)目已經(jīng)寫好的一個(gè)模板。

init 命令調(diào)用的是 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(" [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
//項(xiàng)目名稱
const rawName = program.args[1]
//在當(dāng)前文件下創(chuàng)建
const inPlace = !rawName || rawName === "."
//項(xiàng)目名稱
const name = inPlace ");"../", process.cwd()) : rawName
//創(chuàng)建項(xiàng)目完整目標(biāo)位置
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
}

//判斷是否當(dāng)前目錄下初始化或者覆蓋已有目錄
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()
}

判斷了是使用本地緩存的模板還是拉取最新的模板,拉取線上模板時(shí)是從官方倉庫拉取還是從別的倉庫拉取。

一些小問題

目前代碼生成的相關(guān)數(shù)據(jù)并不是來源于數(shù)據(jù)庫,而是在 model.js 中簡單配置的,原因是我認(rèn)為一個(gè) mock server 不需要數(shù)據(jù)庫,lazy-mock 確實(shí)如此。

但是如果寫一個(gè)正兒八經(jīng)的代碼生成器,那肯定是需要根據(jù)已經(jīng)設(shè)計(jì)好的數(shù)據(jù)庫表來生成代碼的。那么就需要連接數(shù)據(jù)庫,讀取數(shù)據(jù)表的字段信息,比如字段名稱,字段類型,字段描述等。而不同關(guān)系型數(shù)據(jù)庫,讀取表字段信息的 sql 是不一樣的,所以還要寫一堆balabala的判斷??梢允褂矛F(xiàn)成的工具 sequelize-auto , 把它讀取的 model 數(shù)據(jù)轉(zhuǎn)成我們需要的格式即可。

生成前端項(xiàng)目代碼的時(shí)候,會(huì)遇到這種情況:

某個(gè)目錄結(jié)構(gòu)是這樣的:

index.js 的內(nèi)容:

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")
}

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

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

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

但是生成的內(nèi)容怎么加到index.js中呢?

第一種方法:復(fù)制粘貼

第二種方法:

這部分的模板為 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

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

第三種方法:

開發(fā)模板的時(shí)候,做特殊處理,讀取原有 index.js 的內(nèi)容,按行進(jìn)行分割,在數(shù)組的最后一個(gè)元素之前插入新生成的內(nèi)容,注意逗號(hào)的處理,將新數(shù)組內(nèi)容重新寫入 index.js 中,注意換行。

打個(gè)廣告

如果你想要快速的創(chuàng)建一個(gè) mock-server,同時(shí)還支持?jǐn)?shù)據(jù)的持久化,又不需要安裝數(shù)據(jù)庫,還支持代碼生成器的模板開發(fā),歡迎試試 lazy-mock 。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/7316.html

相關(guān)文章

  • 不會(huì)shell的程序員照樣是好前端——用Node.JS實(shí)現(xiàn)git hooks

    摘要:當(dāng)退出的錯(cuò)誤碼不為的時(shí)候,表示失敗,操作終止,否則操作繼續(xù)。執(zhí)行命令進(jìn)行測試,如果測試全部通過的話,退出,錯(cuò)誤碼為,否則錯(cuò)誤碼為,同樣退出。這樣雖然沒有解決不會(huì)隨著倉庫移動(dòng)的問題,但也提供了一種在項(xiàng)目組里通用一套的方案。 git hooks想必很多攻城獅都不陌生,官方對(duì)于hooks有詳細(xì)的文檔,也有站內(nèi)網(wǎng)友的文章Git Hooks (1):介紹,GIt Hooks (2):腳本分類,說...

    BWrong 評(píng)論0 收藏0
  • JavaScript深入淺出第4課:V8引擎是如何工作的?

    摘要:摘要性能彪悍的引擎。深入淺出系列深入淺出第課箭頭函數(shù)中的究竟是什么鬼深入淺出第課函數(shù)是一等公民是什么意思呢深入淺出第課什么是垃圾回收算法深入淺出第課是如何工作的最近,生態(tài)系統(tǒng)又多了個(gè)非常硬核的項(xiàng)目。 摘要: 性能彪悍的V8引擎。 《JavaScript深入淺出》系列: JavaScript深入淺出第1課:箭頭函數(shù)中的this究竟是什么鬼? JavaScript深入淺出第2課:函數(shù)是一...

    hsluoyz 評(píng)論0 收藏0
  • vscode調(diào)試node.js c++擴(kuò)展

    摘要:類似的,如果我們想要調(diào)試擴(kuò)展,我們也需要擴(kuò)展源文件的調(diào)試符號(hào)信息。配置安裝插件這里我們將用來調(diào)試擴(kuò)展。配置輸入配置一個(gè)任務(wù),該任務(wù)會(huì)執(zhí)行,生成帶調(diào)試信息的擴(kuò)展文件。 Debugging NodeJS C++ addons using VS Code 之前筆者寫了一篇 用NAN寫一個(gè)nodejs的c++擴(kuò)展, 實(shí)際開發(fā)過程中,肯定是有單步調(diào)試的需求。這里簡單介紹用如何用vscode調(diào)試...

    王偉廷 評(píng)論0 收藏0
  • 從 JavaScript 到 TypeScript - 模塊化和構(gòu)建

    摘要:不過,相對(duì)于靜態(tài)類型檢查帶來的好處,這些代價(jià)是值得的。當(dāng)然少不了的模塊化標(biāo)準(zhǔn),雖然到目前為止和大部分瀏覽器都還不支持它。本身支持兩種模塊化方式,一種是對(duì)的模塊的微小擴(kuò)展,另一種是在發(fā)布之前本身模仿的命名空間。有一種情況例外。 TypeScript 帶來的最大好處就是靜態(tài)類型檢查,所以在從 JavaScript 轉(zhuǎn)向 TypeScript 之前,一定要認(rèn)識(shí)到添加類型定義會(huì)帶來額外的工作量...

    Jonathan Shieber 評(píng)論0 收藏0
  • 聊一聊前端自動(dòng)化測試

    摘要:在真正寫了一段時(shí)間的基礎(chǔ)組件和基礎(chǔ)工具后,才發(fā)現(xiàn)自動(dòng)化測試有很多好處。有了自動(dòng)化測試,開發(fā)者會(huì)更加信任自己的代碼。由于維護(hù)測試用例也是一大筆開銷畢竟沒有多少測試會(huì)專門幫前端寫業(yè)務(wù)測試用例,而前端使用的流程自動(dòng)化工具更是沒有測試參與了。 本文轉(zhuǎn)載自 天貓前端博客,更多精彩文章請(qǐng)進(jìn)入天貓前端博客查看 前言 為何要測試 以前不喜歡寫測試,主要是覺得編寫和維護(hù)測試用例非常的浪費(fèi)時(shí)間。在真正寫了...

    wthee 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<