Markdown轉(zhuǎn)HTML之Node篇

[toc]

前言

之前用Python寫過(guò)類似的工具吉挣,更能上來(lái)說(shuō)一般般著洼。而且實(shí)用性不是很強(qiáng)。http://blog.csdn.net/marksinoberg/article/details/51863506

然后好巧不巧又看到了一個(gè)Node上的相關(guān)模塊,看起來(lái)渲染效果比我那個(gè)好多了。聯(lián)想到hexo這款靜態(tài)博客生成工具寞忿,就思量著也大致的做一下。

環(huán)境及編碼

接下來(lái)簡(jiǎn)單的模擬一下相關(guān)的實(shí)現(xiàn)顶岸。代碼比較凌亂腔彰,思維比較混亂,還望海涵(雖然主要是作為我自己的筆記辖佣,但如果對(duì)你有些許幫助霹抛,那就不枉我碼了這么多字咯)。

搭建環(huán)境

所依賴的第三方模塊有如下幾個(gè):

  • express: 開啟本地服務(wù)卷谈, 預(yù)覽生成效果杯拐。
  • Markdown-it: 渲染md文件為HTML內(nèi)容。
  • rd: 一個(gè)讀取文件夾內(nèi)容的好幫手世蔗。
  • commander: 制作命令行工具的一大利器端逼。

下面簡(jiǎn)要對(duì)這幾個(gè)模塊進(jìn)行闡述,以及相關(guān)的使用技巧凸郑。

express

express不僅作為對(duì)connect的高層封裝裳食,更包含了一些額外的處理。所以我們可以方便的進(jìn)行路由控制芙沥,這對(duì)于本次的工具而言,是個(gè)不錯(cuò)的選擇。

// 初始化服務(wù)器
    var app = express();
    var router = express.Router();
    app.use('/assets', server_static(path.resolve(dir, 'assets')));
    app.use(router);

    // 渲染文章
    router.get('/posts/*', function(req, res, next){
        var name = stripExtname(req.params[0]);
        // 渲染req.params[0]對(duì)應(yīng)的文章而昨,然后展示給前臺(tái)
        res.end(html);
        });

        // res.end(req.params[0]);
    });

    // 渲染列表
    router.get('/', function(req, res, next){
        // 讀取源文件目錄救氯, 渲染出列表內(nèi)容。
        res.end('list of articles.');
    });

    app.listen(8080);

markdown-it

相對(duì)于Python中的那些第三方庫(kù)歌憨,我倒是覺(jué)得Node中與其也沒(méi)甚么兩樣着憨。使用起來(lái)同樣很簡(jiǎn)單。

第一步务嫡,引入依賴

let markdowner = require('markdown-it');

第二步甲抖, 配置構(gòu)造器

var md = new markdowner({
    html: true,
    prefix: 'code-',
});

第三步, 調(diào)用渲染方法心铃,獲取渲染后內(nèi)容

var html = md.render(sourcedata||'');

如此准谚,便是markdown-it的基礎(chǔ)內(nèi)容了,待會(huì)將在代碼中更加詳細(xì)的運(yùn)用去扣。

commander

用過(guò)Python的argparser的估計(jì)都知道柱衔,很方便的一個(gè)處理命令行參數(shù)的第三方庫(kù)。不過(guò)Node中的commander用起來(lái)更方便愉棱。

下面簡(jiǎn)單的介紹一下使用流程唆铐。詳細(xì)內(nèi)容還是看人家的官網(wǎng)吧,如下:
https://www.npmjs.com/package/commander

第一步奔滑, 安裝模塊

npm install commander --save

第二步艾岂,編碼

/**
 * 一個(gè)命令行工具庫(kù)。
 */

let commander = require('commander');

// help 命令
commander.command('help')
         .description('顯示工具如何使用的幫助信息')
         .action(function(){
             commander.outputHelp();
         });

// create 命令
commander.command('create [dirname]')
         .description('創(chuàng)建一個(gè)空的博客')
         .action(function(dirname){
            console.log(dirname+' 創(chuàng)建完成朋其。')
         });

// preview 命令
commander.command('preview [dirname]')
         .description('預(yù)覽獲取到的Markdown文件夾內(nèi)容')
         .action(function(dirname){
            console.log(' preview of %s', dirname);
         });
        //  .action(require('./cmd_preview'));

// build 命令
commander.command('build [dirname]')
         .description('根據(jù)給定的文件夾路徑生成HTML內(nèi)容.')
         .option('-o OR --output <dirname>', '導(dǎo)出生成的HTML存放的路徑')
         .action(function(dirname){
            console.log('build based on %s', dirname);
         });
        // .action(require('./cmd_build'));

// 解析相關(guān)命令
commander.parse(process.argv);

第三步王浴, 查看效果


**commander**運(yùn)行效果

如果想更加方便一點(diǎn),直接使用命令來(lái)操作令宿。使用

npm link

當(dāng)然了叼耙,還需要設(shè)置一下對(duì)應(yīng)的package.json文件內(nèi)容。這里不過(guò)多敘述了粒没。

rd

我這里的需求是讀取_posts文件夾下的Markdown源文件筛婉,所以只需要readFile方法即可。
具體代碼如下:

rd.readFile(sourcedir, function(err, files){
        if(err){
            console.log('讀取文件夾內(nèi)容失旕伞爽撒!');
            return;
        }
        // 遍歷文件夾列表,對(duì)每一個(gè)文件執(zhí)行渲染操作响蓉。
        files.forEach(function(file){
            // 做自己的邏輯處理即可硕勿。
        });
    });

這個(gè)模塊比較簡(jiǎn)單,有興趣的可以參考下面的作者鏈接枫甲。
https://github.com/leizongmin/node-rd

核心編碼

下面正式開始今天的主題源武,做一個(gè)帶預(yù)覽功能的Markdown文件轉(zhuǎn)HTML頁(yè)面的工具扼褪。

cmd_preview模塊

/**
 * 關(guān)于預(yù)覽實(shí)現(xiàn)相關(guān)的代碼。
 */

let express = require('express');
let path = require('path');
let markdowner = require('markdown-it');
let fs = require('fs');
let rd = require('rd');

var md = new markdowner({
    html: true,
    langPrefix: 'code-',
});




module.exports = function(dir) {
    dir = dir || '.';

    // 初始化服務(wù)器
    var app = express();
    var router = express.Router();
    app.use(router);

    // 渲染文章
    router.get('/posts/*', function(req, res, next){
        var name = stripExtname(req.params[0]);
        var file = path.resolve(dir, '_posts', name+'.md');
        console.log('---dir--', dir);
        console.log('---name--', name);
        console.log('---file--', file);

        fs.readFile(file, function(err, content){
            if(err){
                console.log('讀取文件失斄黄堋话浇!');
                res.end(JSON.stringify(err)+"\n");
                return next(err);
            }
            res.writeHead(200, {"Content-Type": "text/html;charset=UTF-8"});
            var html = markdownTOHTML(content.toString());
            console.log('讀取文件成功, 解析后的內(nèi)容為:\n', html);
            res.end(html);
        });

        // res.end(req.params[0]);
    });

    // 渲染列表
    router.get('/', function(req, res, next){
        var sourcefolder = path.resolve(dir, '_posts');
        rd.readFile(sourcefolder, function(err, files){
            if(err){
                console.log('讀取文件夾內(nèi)文件失斈志俊幔崖!');
                return next(err);
            }
            res.writeHead(200, {"Content-Type": "text/html;charset=UTF-8"});
            var html = "<html><h1>Markdown 轉(zhuǎn) HTML 實(shí)時(shí)預(yù)覽</h1><hr><br />";
            files.forEach(function(filepath){
                html += "<a href='/posts/"+ get_file_name(filepath) +".md' target='_blank'>"+get_file_name(filepath)+"</a><br /><br />";
            });
            html += "</html>";
            res.end(html);
        }); 
        
        
        // res.end('list of articles.');
    });

    app.listen(8080);
};


function stripExtname(name) {
    var i = 0-path.extname(name).length;
    if(i==0) i=name.length;
    return name.slice(0, i);
}

function get_file_name(fullname){
    var ls = fullname.toString().split('\\');
    var filename =  ls[ls.length-1].split('.');
    // console.log('ls--', ls);
    // console.log('filename--', filename);
    return filename[0];
}

function markdownTOHTML(content) {
    return md.render(content||'');
}

cmd_build模塊

/**
 * 實(shí)現(xiàn)Markdown文件到HTML文件的轉(zhuǎn)換。
 */

let markdowner = require('markdown-it');
let rd = require('rd');
let path = require('path');
let fs = require('fs');

var md = new markdowner({
    html: true,
    langPrefix: 'code-',
})


module.exports = function(dir) {
    dir = dir || '.';
    console.log('當(dāng)前文件路徑為:', dir);

    // 讀取出給定目錄下的所有的文件
    // 將所有Markdown文件依次轉(zhuǎn)成HTML頁(yè)面渣淤,并進(jìn)行保存操作赏寇。
    get_files_by_dir(dir);
}

function get_files_by_dir(dir) {
    // 計(jì)算出源文件的路徑
    var sourcedir = path.resolve(dir, '_posts');
    var publicdir = path.resolve(dir, 'public');
    rd.readFile(sourcedir, function(err, files){
        if(err){
            console.log('讀取文件夾內(nèi)容失敗价认!');
            return;
        }
        // 遍歷文件夾列表嗅定,對(duì)每一個(gè)文件執(zhí)行渲染操作。
        files.forEach(function(file){
            var html = md2html(file);
            var filename = get_filename_by_path(file);
            var output = path.resolve(publicdir, filename+'.html');
            console.log('保存路徑為:', output);
            save_html_content(html, output);
            console.log('%s.html 生成成功刻伊!', filename);
        });
    });
}

function md2html(filepath){
    var content = fs.readFileSync(filepath);
    var html = md.render(content.toString()||'');
    return "<html><head><meta charset='UTF-8'><title>"+get_filename_by_path(filepath)+"</title></head>"+html+"</html>";
}

function save_html_content(content, outpath){
    fs.writeFile(outpath, content, function(err){
        if(err){
            console.log('save_html_content: 保存文件內(nèi)容失斅督洹!');
            return;
        }
        console.log('save_html_content: %s 保存成功捶箱!', outpath);
    });
}

function get_filename_by_path(filepath){
    var paths = filepath.toString().split('\\');
    return paths[paths.length-1].split('.')[0];
}

打造命令行工具

還是用剛才的hello.js智什,現(xiàn)在稍微修改一下action里面的內(nèi)容,對(duì)應(yīng)我們剛才做的那兩個(gè)小模塊丁屎,做下修改即可荠锭。完整代碼如下:

/**
 * 一個(gè)命令行工具庫(kù)。
 */

let commander = require('commander');

// help 命令
commander.command('help')
         .description('顯示工具如何使用的幫助信息')
         .action(function(){
             commander.outputHelp();
         });

// create 命令
commander.command('create [dirname]')
         .description('創(chuàng)建一個(gè)空的博客')
         .action(function(dirname){
            console.log(dirname+' 創(chuàng)建完成晨川。')
         });

// preview 命令
commander.command('preview [dirname]')
         .description('預(yù)覽獲取到的Markdown文件夾內(nèi)容')
        //  .action(function(dirname){
        //     console.log(' preview of %s', dirname);
        //  });
         .action(require('./cmd_preview'));

// build 命令
commander.command('build [dirname]')
         .description('根據(jù)給定的文件夾路徑生成HTML內(nèi)容.')
         .option('-o OR --output <dirname>', '導(dǎo)出生成的HTML存放的路徑')
        //  .action(function(dirname){
        //     console.log('build based on %s', dirname);
        //  });
        .action(require('./cmd_build'));

// 解析相關(guān)命令
commander.parse(process.argv);

寫點(diǎn)xx.md

巧婦難為無(wú)米之炊证九, 現(xiàn)在先在hello.js的同級(jí)目錄下建個(gè)文件夾_posts,里面寫點(diǎn)xx.md文件共虑,然后建一個(gè)public文件夾保存生成的HTML文件愧怜。比如我的目錄結(jié)構(gòu)是這樣的。

E:\Code\Nodejs\learn\libs-learn\commander-related>tree /f .
卷 文檔 的文件夾 PATH 列表
卷序列號(hào)為 0000-4823
E:\CODE\NODEJS\LEARN\LIBS-LEARN\COMMANDER-RELATED
│  cmd_build.js
│  cmd_preview.js
│  hello.js
│
├─public
│
└─_posts
        helloworld.md
        second.md

演示

首先是預(yù)覽實(shí)現(xiàn)妈拌,在命令行里輸入以下命令:

node hello.js preview . # .代表當(dāng)前目錄

結(jié)果如下:


**preview**效果圖

然后是針對(duì)每一篇文章生成效果的演示拥坛。

每一篇文章的預(yù)覽效果

最后就是看下build功能的實(shí)現(xiàn)。

現(xiàn)在在命令行里面輸入以下命令:

node hello.js build . # .代表當(dāng)前目錄

然后看下效果尘分。


**build** 功能實(shí)現(xiàn)

總結(jié)

大致來(lái)說(shuō)功能就算是完成了猜惋,但是目前這樣子是沒(méi)法直接應(yīng)用的。很多東西都需要潤(rùn)色培愁,比如:

  • 模板化HTML頁(yè)面處理著摔。
  • 命令行選項(xiàng)的link實(shí)現(xiàn)。
  • 通過(guò)監(jiān)測(cè)文件內(nèi)容變化實(shí)現(xiàn)實(shí)時(shí)預(yù)覽定续。

就先到這里吧谍咆,今天又發(fā)現(xiàn)了兩個(gè)不錯(cuò)的網(wǎng)址禾锤,然后還是先去學(xué)一波,補(bǔ)充補(bǔ)充知識(shí)吧卧波。很多時(shí)候时肿,不是能力不夠庇茫,而是見(jiàn)識(shí)不足港粱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市旦签,隨后出現(xiàn)的幾起案子查坪,更是在濱河造成了極大的恐慌,老刑警劉巖宁炫,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件偿曙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡羔巢,警方通過(guò)查閱死者的電腦和手機(jī)望忆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)竿秆,“玉大人启摄,你說(shuō)我怎么就攤上這事∮母郑” “怎么了歉备?”我有些...
    開封第一講書人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)匪燕。 經(jīng)常有香客問(wèn)我蕾羊,道長(zhǎng),這世上最難降的妖魔是什么帽驯? 我笑而不...
    開封第一講書人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任龟再,我火速辦了婚禮,結(jié)果婚禮上尼变,老公的妹妹穿的比我還像新娘利凑。我一直安慰自己,他們只是感情好享甸,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開白布截碴。 她就那樣靜靜地躺著,像睡著了一般蛉威。 火紅的嫁衣襯著肌膚如雪日丹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,785評(píng)論 1 314
  • 那天蚯嫌,我揣著相機(jī)與錄音哲虾,去河邊找鬼丙躏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛束凑,可吹牛的內(nèi)容都是我干的晒旅。 我是一名探鬼主播,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼汪诉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼废恋!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起扒寄,我...
    開封第一講書人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鱼鼓,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后该编,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體迄本,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年课竣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嘉赎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡于樟,死狀恐怖公条,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情隔披,我是刑警寧澤赃份,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站奢米,受9級(jí)特大地震影響抓韩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鬓长,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一谒拴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涉波,春花似錦英上、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至窗声,卻和暖如春相恃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背笨觅。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工拦耐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留耕腾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓杀糯,卻偏偏與公主長(zhǎng)得像扫俺,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子固翰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

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