在真實(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.js 和 pages_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