1. 程式人生 > >Markdown轉HTML之Node篇

Markdown轉HTML之Node篇

前言

然後好巧不巧又看到了一個Node上的相關模組,看起來渲染效果比我那個好多了。聯想到hexo這款靜態部落格生成工具,就思量著也大致的做一下。

環境及編碼

接下來簡單的模擬一下相關的實現。程式碼比較凌亂,思維比較混亂,還望海涵(雖然主要是作為我自己的筆記,但如果對你有些許幫助,那就不枉我碼了這麼多字咯)。

搭建環境

所依賴的第三方模組有如下幾個:

  • express: 開啟本地服務, 預覽生成效果。
  • Markdown-it: 渲染md檔案為HTML內容。
  • rd: 一個讀取資料夾內容的好幫手。
  • commander: 製作命令列工具的一大利器。

下面簡要對這幾個模組進行闡述,以及相關的使用技巧。

express

express不僅作為對connect的高層封裝,更包含了一些額外的處理。所以我們可以方便的進行路由控制,這對於本次的工具而言,是個不錯的選擇。

// 初始化伺服器
    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]對應的文章,然後展示給前臺 res.end(html); }); // res.end(req.params[0]); }); // 渲染列表 router.get('/', function(req, res, next){ // 讀取原始檔目錄, 渲染出列表內容。 res.end('list of articles.'); }); app.listen(8080
);

markdown-it

相對於Python中的那些第三方庫,我倒是覺得Node中與其也沒甚麼兩樣。使用起來同樣很簡單。

第一步,引入依賴

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

第二步, 配置構造器

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

第三步, 呼叫渲染方法,獲取渲染後內容

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

如此,便是markdown-it的基礎內容了,待會將在程式碼中更加詳細的運用。

commander

用過Python的argparser的估計都知道,很方便的一個處理命令列引數的第三方庫。不過Node中的commander用起來更方便。

第一步, 安裝模組

npm install commander --save

第二步,編碼

/**
 * 一個命令列工具庫。
 */

let commander = require('commander');

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

// create 命令
commander.command('create [dirname]')
         .description('建立一個空的部落格')
         .action(function(dirname){
            console.log(dirname+' 建立完成。')
         });

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

// build 命令
commander.command('build [dirname]')
         .description('根據給定的資料夾路徑生成HTML內容.')
         .option('-o OR --output <dirname>', '匯出生成的HTML存放的路徑')
         .action(function(dirname){
            console.log('build based on %s', dirname);
         });
        // .action(require('./cmd_build'));

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

第三步, 檢視效果
**commander**執行效果

如果想更加方便一點,直接使用命令來操作。使用

npm link

當然了,還需要設定一下對應的package.json檔案內容。這裡不過多敘述了。

rd

我這裡的需求是讀取_posts資料夾下的Markdown原始檔,所以只需要readFile方法即可。
具體程式碼如下:

rd.readFile(sourcedir, function(err, files){
        if(err){
            console.log('讀取資料夾內容失敗!');
            return;
        }
        // 遍歷資料夾列表,對每一個檔案執行渲染操作。
        files.forEach(function(file){
            // 做自己的邏輯處理即可。
        });
    });

核心編碼

下面正式開始今天的主題,做一個帶預覽功能的Markdown檔案轉HTML頁面的工具。

cmd_preview模組

/**
 * 關於預覽實現相關的程式碼。
 */

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 || '.';

    // 初始化伺服器
    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', 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('讀取資料夾內檔案失敗!');
                return next(err);
            }
            res.writeHead(200, {"Content-Type": "text/html;charset=UTF-8"});
            var html = "<html><h1>Markdown 轉 HTML 實時預覽</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模組

/**
 * 實現Markdown檔案到HTML檔案的轉換。
 */

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('當前檔案路徑為:', dir);

    // 讀取出給定目錄下的所有的檔案
    // 將所有Markdown檔案依次轉成HTML頁面,並進行儲存操作。
    get_files_by_dir(dir);
}

function get_files_by_dir(dir) {
    // 計算出原始檔的路徑
    var sourcedir = path.resolve(dir, '_posts');
    var publicdir = path.resolve(dir, 'public');
    rd.readFile(sourcedir, function(err, files){
        if(err){
            console.log('讀取資料夾內容失敗!');
            return;
        }
        // 遍歷資料夾列表,對每一個檔案執行渲染操作。
        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: 儲存檔案內容失敗!');
            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,現在稍微修改一下action裡面的內容,對應我們剛才做的那兩個小模組,做下修改即可。完整程式碼如下:

/**
 * 一個命令列工具庫。
 */

let commander = require('commander');

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

// create 命令
commander.command('create [dirname]')
         .description('建立一個空的部落格')
         .action(function(dirname){
            console.log(dirname+' 建立完成。')
         });

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

// build 命令
commander.command('build [dirname]')
         .description('根據給定的資料夾路徑生成HTML內容.')
         .option('-o OR --output <dirname>', '匯出生成的HTML存放的路徑')
        //  .action(function(dirname){
        //     console.log('build based on %s', dirname);
        //  });
        .action(require('./cmd_build'));

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

寫點xx.md

巧婦難為無米之炊, 現在先在hello.js的同級目錄下建個資料夾_posts,裡面寫點xx.md檔案,然後建一個public資料夾儲存生成的HTML檔案。比如我的目錄結構是這樣的。

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

演示

首先是預覽實現,在命令列裡輸入以下命令:

node hello.js preview . # .代表當前目錄

結果如下:
**preview**效果圖

然後是針對每一篇文章生成效果的演示。

每一篇文章的預覽效果

最後就是看下build功能的實現。

現在在命令列裡面輸入以下命令:

node hello.js build . # .代表當前目錄

然後看下效果。
**build** 功能實現

總結

大致來說功能就算是完成了,但是目前這樣子是沒法直接應用的。很多東西都需要潤色,比如:

  • 模板化HTML頁面處理。
  • 命令列選項的link實現。
  • 通過監測檔案內容變化實現實時預覽

就先到這裡吧,今天又發現了兩個不錯的網址,然後還是先去學一波,補充補充知識吧。很多時候,不是能力不夠,而是見識不足