玩轉(zhuǎn)編程語(yǔ)言:構(gòu)建自定義代碼生成器

在真實(shí)的軟件開(kāi)發(fā)過(guò)程中,無(wú)論使用何種編程開(kāi)發(fā)語(yǔ)言,都不可避免的會(huì)遇到代碼重復(fù)的問(wèn)題举庶。如何處理重復(fù)的問(wèn)題,可以選擇情懷(手動(dòng)再敲一遍)揩抡,也可以選擇 Copy-to-Copy 户侥,或者選擇代碼生成器。正如在之前的文章 我的寫(xiě)作工具鏈 中捅膘,我介紹過(guò)一種 Blog 生成器 hexo 添祸,可以將 Markdown 格式的內(nèi)容自動(dòng)生成方便發(fā)布的 HTML 格式。本文將還原 hexo 的運(yùn)行原理寻仗,為解決類(lèi)似問(wèn)題提供一些參考思路刃泌。

示例:通過(guò) Markdown 文件聲明模板(源代碼),通過(guò)腳本生成 HTML 文件(目標(biāo)代碼)署尤,并預(yù)覽代碼生成效果耙替。

Step 1: 準(zhǔn)備環(huán)境 (dependencies)

開(kāi)發(fā)語(yǔ)言 Node.js, 一個(gè)能夠運(yùn)行 JavaScript 的開(kāi)放源代碼、跨平臺(tái)運(yùn)行環(huán)境曹体。

  • npm init?—?初始化 root 目錄
  • npm i -s live-server?—?該模塊支持本示例生成靜態(tài) HTML 站點(diǎn)俗扇,提供熱部署能力
  • npm i -s nodemon?—?該模塊支持當(dāng)文件變化自動(dòng)執(zhí)行重構(gòu)任務(wù)
  • npm i -s concurrently?—?該模塊支持支持并發(fā)執(zhí)行任務(wù)、腳本(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

[圖片上傳失敗...(image-7d14f7-1514002237128)]

Step 2: 準(zhǔn)備元數(shù)據(jù)

例如:pages/index.md

# Home page

Hello world!

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

例如:pages_meta/index.json 用于存儲(chǔ)一些需要的元數(shù)據(jù)(參數(shù)箕别、固定內(nèi)容等)铜幽,JSON 文件格式方便后面調(diào)用。

{
  "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: 編寫(xiě)模板和構(gòu)建腳本(template & build Script)

代碼生成器中需要定制開(kāi)發(fā)的部分包括 builder.jspages_template.js串稀。build.js 相當(dāng)于 main 函數(shù)除抛,控制入口和流程,加載資源數(shù)據(jù)母截、調(diào)用 generator 任務(wù)到忽,與 Makefile 和 Ant.xml 非常類(lèi)似。pages_template.js 依賴(lài)的組件是 markdown-it 清寇,負(fù)責(zé)將 Markdown 源文件轉(zhuǎn)換輸出成 HTML 文件喘漏。builder.js 將 pages_template.js 視為一個(gè)模塊引用:pageTemplate.generatePage(pageContent, metaData)) 因此可以根據(jù)需要定制多個(gè)不同的 XXX_template.js 或者在每個(gè) template.js 中定義其它函數(shù)。

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.js

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: 優(yōu)化任務(wù)腳本

在 Step 1 步驟中华烟,npm init 創(chuàng)建了一個(gè)文件:package.json翩迈,我們可以定義其中的 “scripts” , 執(zhí)行 npm run start 將默認(rèn)在 1080 端口開(kāi)啟 Web 服務(wù)。

[圖片上傳失敗...(image-6b1322-1514002237128)]

{
  "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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盔夜,一起剝皮案震驚了整個(gè)濱河市负饲,隨后出現(xiàn)的幾起案子搅方,更是在濱河造成了極大的恐慌,老刑警劉巖绽族,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異衩藤,居然都是意外死亡吧慢,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)赏表,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)检诗,“玉大人,你說(shuō)我怎么就攤上這事瓢剿》昊牛” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵间狂,是天一觀的道長(zhǎng)攻泼。 經(jīng)常有香客問(wèn)我,道長(zhǎng)鉴象,這世上最難降的妖魔是什么忙菠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮纺弊,結(jié)果婚禮上牛欢,老公的妹妹穿的比我還像新娘。我一直安慰自己淆游,他們只是感情好傍睹,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著犹菱,像睡著了一般拾稳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上已亥,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天熊赖,我揣著相機(jī)與錄音,去河邊找鬼虑椎。 笑死震鹉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的捆姜。 我是一名探鬼主播传趾,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼泥技!你這毒婦竟也來(lái)了浆兰?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎簸呈,沒(méi)想到半個(gè)月后榕订,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜕便,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年劫恒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片轿腺。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡两嘴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出族壳,到底是詐尸還是另有隱情憔辫,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布仿荆,位于F島的核電站贰您,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏拢操。R本人自食惡果不足惜枉圃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望庐冯。 院中可真熱鬧孽亲,春花似錦、人聲如沸展父。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)栖茉。三九已至篮绿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吕漂,已是汗流浹背亲配。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留惶凝,地道東北人吼虎。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像苍鲜,于是被迫代替她去往敵國(guó)和親思灰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,527評(píng)論 25 707
  • 什么是 NPM npm之于Node混滔,就像pip之于Python,gem之于Ruby,composer之于PHP洒疚。 ...
    ihoey閱讀 6,248評(píng)論 2 36
  • 無(wú)意中看到zhangwnag大佬分享的webpack教程感覺(jué)受益匪淺歹颓,特此分享以備自己日后查看,也希望更多的人看到...
    小小字符閱讀 8,140評(píng)論 7 35
  • 高中兩年的陪伴油湖,你的細(xì)心呵護(hù)巍扛,讓我產(chǎn)生了一種錯(cuò)覺(jué),我以為乏德,你是愛(ài)我的电湘。 高中二年級(jí)的時(shí)候,你跟隨大家出去...
    安雨薇閱讀 356評(píng)論 0 0
  • 周六鹅经,我和彭澤在公司值班。百無(wú)聊賴(lài)之際怎诫,他開(kāi)啟打印機(jī)瘾晃,A4紙緩緩出來(lái)的沙沙聲打破了辦公室的寧?kù)o。我問(wèn)他在打印什么幻妓?...
    余想閱讀 345評(píng)論 3 3