玩轉編程語言:構建自定義代碼生成器

在真實的軟件開發過程當中,不管使用何種編程開發語言,都不可避免的會遇到代碼重複的問題。如何處理重複的問題,能夠選擇情懷(手動再敲一遍),也能夠選擇 Copy-to-Copy ,或者選擇代碼生成器。正如在以前的文章 個人寫做工具鏈 中,我介紹過一種 Blog 生成器 hexo ,能夠將 Markdown 格式的內容自動生成方便發佈的 HTML 格式。本文將還原 hexo 的運行原理,爲解決相似問題提供一些參考思路。css

這裏輸入引用文本示例:經過 Markdown 文件聲明模板(源代碼),經過腳本生成 HTML 文件(目標代碼),並預覽代碼生成效果。html

Step 1: 準備環境 (dependencies)

開發語言 Node.js, 一個可以運行 JavaScript 的開放源代碼、跨平臺運行環境。node

  • npm init — 初始化 root 目錄
  • npm i -s live-server — 該模塊支持本示例生成靜態 HTML 站點,提供熱部署能力
  • npm i -s nodemon — 該模塊支持當文件變化自動執行重構任務
  • npm i -s concurrently — 該模塊支持支持併發執行任務、腳本(scripts/tasks)
  • npm i -s markdown-it — 該模塊提供 Markdown 文件解析器
mkdir project-generator
mkdir project-generator/pages
mkdir project-generator/pages_meta
mkdir project-generator/js
mkdir project-generator/css
mkdir project-generator/images
mkdir project-generator/build_scripts
mkdir project-generator/build

cd project-generator
npm init 
npm i -s concurrently
npm i -s fs
npm i -s fs-extra
npm i -s markdown-it
npm i -s live-server 
npm i -s nodemon

Step 2: 準備元數據

例如:pages/index.mdgit

# Home page

Hello world!

[Link to another page](./other.html)

例如:pages_meta/index.json 用於存儲一些須要的元數據(參數、固定內容等),JSON 文件格式方便後面調用。github

{
  "lang": "en",
  "title": "Index",
  "stylesheets": ["./css/style.css"],
  "scripts": ["./js/main.js"],
  "charset": "utf-8",
  "description": "This is a page",
  "keywords": "page, sample",
  "author": "None",
  "favicon": "./images/favicon.png",
  "viewport": "width=device-width, initial-scale=1",
  "extra": []
}

Step 3: 編寫模板和構建腳本(template & build Script)

代碼生成器中須要定製開發的部分包括 builder.jspages_template.js。build.js 至關於 main 函數,控制入口和流程,加載資源數據、調用 generator 任務,與 Makefile 和 Ant.xml 很是相似。pages_template.js 依賴的組件是 markdown-it ,負責將 Markdown 源文件轉換輸出成 HTML 文件。builder.js 將 pages_template.js 視爲一個模塊引用:pageTemplate.generatePage(pageContent, metaData)) 所以能夠根據須要定製多個不一樣的 XXX_template.js 或者在每一個 template.js 中定義其它函數。npm

builder.js編程

var pageTemplate = require('./page_template');

// All paths are relative to package.json.
var pagesPath = './pages';
var pagesMetaPath = './pages_meta';
var copyFolders = ['./images', './css', './js'];
var outputPath = './build';

// Clean
console.log('Cleaning previous build...');
for (var file of fs.readdirSync(outputPath)){
    fs.removeSync(path.join(outputPath, file));
}

//Loading
console.log('Loading pages metadata...');
  for(var pageMeta of fs.readdirSync(pagesMetaPath)){
    pagesMeta[pageMeta] = fs.readFileSync(path.join(pagesMetaPath,pageMeta),'utf8');
  }
}

// Generate
console.log('Generating pages...');
for(var page of Object.entries(pages)) {
    var pageName = page[0].slice(0, page[0].lastIndexOf('.'));
    var metaData = pagesMeta.hasOwnProperty(pageName+'.json')
      ? JSON.parse(pagesMeta[pageName+'.json'])
      : {};
    metaData.title = metaData.title || pageName;
    var pageContent = page[1];
    fs.writeFileSync(
      path.join(outputPath,pageName+'.html'),
      pageTemplate.generatePage(pageContent, metaData));
  }
}

// Copy
console.log('Copying folders...');
  for(var copyFolder of copyFolders){
    fs.copySync(copyFolder, path.join(outputPath,copyFolder));
  }

pages_template.jsjson

var md = require('markdown-it')();

module.exports = {
  generatePage: function(pageContent,pageMeta){
    return`<!DOCTYPE html>
<html lang="${pageMeta.lang || this.defaultMeta.lang}">
  <head>
    <title>${pageMeta.title || this.defaultMeta.title}</title>
    <meta charset="${pageMeta.charset || this.defaultMeta.charset}">
    <meta name="description" content="${pageMeta.description || this.defaultMeta.description}">
    <meta name="keywords" content="${pageMeta.keywords || this.defaultMeta.keywords}">
    <meta name="author" content="${pageMeta.author || this.defaultMeta.author}">
</head>
<body>
  ${md.render(pageContent)}
</body>
</html>
    `;
  }
}

Step 4: 優化任務腳本

在 Step 1 步驟中,npm init 建立了一個文件:package.json,咱們能夠定義其中的 「scripts」 , 執行 npm run start 將默認在 1080 端口開啓 Web 服務。bash

{
  "name": "coffee",
  "version": "1.0.0",
  "description": "beyond my coffee",
  "main": "index.js",
  "scripts": {  
    "build-pages": "node ./build_scripts/builder.js",
    "start": "concurrently --kill-others \"nodemon -e js,json,css,md -i build -x \\\"npm run build-pages\\\"\" \"live-server ./build\""
  },
  "author": "@RiboseYim"
}
$ npm run build-pages

> coffee@1.0.0 build-pages /generator-code
> node ./build_scripts/builder.js

Cleaning previous build...
Loading pages...
Loading pages metadata...
Generating pages...
Copying folders...
Done!

$ npm run start

> coffee@1.0.0 start /Users/yanrui/project/generator-code
> concurrently --kill-others "nodemon -e js,json,css,md -i build -x \"npm run build-pages\"" "live-server ./build"

[0] [nodemon] 1.14.1
[0] [nodemon] to restart at any time, enter `rs`
[0] [nodemon] watching: *.*
[0] [nodemon] starting `npm run build-pages`
[1] Serving "./build" at http://127.0.0.1:8080
[1] Ready for changes
[1] GET /js/main.js 404 42.133 ms - 23
[1] GET /js/main.js 404 12.204 ms - 23
[0]
[0] > coffee@1.0.0 build-pages /Users/yanrui/project/generator-code
[0] > node ./build_scripts/builder.js
[0]
[0] Cleaning previous build...
[0] Loading pages...
[0] Loading pages metadata...
[0] Generating pages...
[0] Copying folders...
[0] Done!
[0] [nodemon] clean exit - waiting for changes before restart
[1] Change detected build/index.html
[1] Change detected build/images

擴展閱讀:開發語言&代碼工程

更多精彩內容掃碼關注公衆號:RiboseYim's Blog 微信公衆號微信

相關文章
相關標籤/搜索