2018年8月10日 星期五

[Gulp] Create a Markdown server


 gulp    gulp-markdown   gulp-html-extend    ASP.NET Core


Introduction


A Markdown server is a web server which can convert markdown files into HTML.
We are going to use the power of gulp-markdown and gulp-html-extend to have the live-converting ability in an ASP.NET Core website.



Environment


.NET Core 2.1.300
gulp 3.9.1
gulp-markdown 3.0.0
gulp-html-extend 1.1.6
gulp-watch 5.0.1
marked 0.4.0




Implement



Before we build the website, lets take a quick look at the gulp packages.

gulp-markdown

Convert Markdown to HTML with marked, which is a markdown parser and compiler.

Basic usage is as following, results in converting the content of README.md as HTML and copy to dist/README.md

const gulp = require('gulp');
const markdown = require('gulp-markdown');

gulp.task('default', () =>
    gulp.src('README.md')
        .pipe(markdown())
.pipe(gulp.dest('dist'));
);


We can pass a marked option to gulp-markdown, for example,

const marked = require('marked');

// Set options
marked.setOptions({
    renderer: new marked.Renderer(),
pedantic: false,
    gfm: true,
    tables: true,
    breaks: false,
    sanitize: false,
    smartLists: true,
    smartypants: false,
    xhtml: false
});

gulp.task('md', function () {
    return gulp.src('README.md')
        .pipe(markdown(marked))
        .pipe(gulp.dest('dist'));
})

See more options here.


gulp-html-extend

However, the HTML after converted doesn’t have default HTML tags such as <html>, <head>, <body>.
We do need a default HTML template that can be put the converted-markdown content inside. Thaz why we need gulp-html-extend.

The usage is similar with the template tag in Django.

Create a template page like this,

master.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" >
    <title><!-- @@var title --></title>
    <!-- @@placeholder=head -->
</head>
<body>
    <!-- @@placeholder=body -->
</body>
</html>


@@var [=] <variableName> preservers the position for a value for the variable.

@@placeholder [=] <blockName> preservers a block to put the real content inside.


my-content.html

Now we can make a content html as following,

<!-- @@master= ./master.html {"title":"Demo"}-->

<!-- @@block=head-->
<meta name="author" content="JB" />
<!-- @@close-->

<!-- @@block=body-->
<h2>gulp-html-extend demo<h2>
<!-- @@close-->



@@master [=] path [jsonString] defines the relative path of the master page and set the value for variables by JSON.

Between @@block [=] blockName and @@close is the content for the block in master page.


gulp-html-extend

gulp.task('extend', function () {
    return gulp.src('content.html')
        .pipe(extender({
            annotations: true,
            verbose: false
        }))
.pipe(gulp.dest('dist'))
});

Which will result in:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <title>Demo</title>
    <!-- start head -->
    <meta name="author" content="JB" />
    <!-- end head -->
</head>

<body>
    <!-- start body -->
    <h2>gulp-html-extend demo</h2>
    <!-- end body -->
</body>

</html>


Create website and install packages

Use the following donet CLI commands to create a website,

$ dotnet new mvc --name my-mdserver.web
$ dotnet new sln --name my-mdserver
$ dotnet sln my-mdserver.sln  add my-mdserver-web/my-mdserver-web.csproj
$ dotnet restore
$ dotnet build
$ npm init



Install the npm packages and create a gulp file

$ npm install gulp --save-dev
$ npm install marked --save-dev
$ npm install gulp-markdown --save-dev
$ npm install gulp-html-extend --save-dev
$ npm install gulp-watch --save-dev
$ cd my-mdserver.web
$ echo nul>gulpfile.js




gulpfile.js

requires

// Include gulp
const gulp = require('gulp');
const rename = require('gulp-rename');
const watch = require('gulp-watch');
const extender = require('gulp-html-extend')
const markdown = require('gulp-markdown');
const marked = require('marked');

var mdFilePath = "./wwwroot/markdown/";




Task for gulp-markdown

// Set options
marked.setOptions({
    renderer: new marked.Renderer(),
    highlight: function (code) {
        return require('highlight.js').highlightAuto(code).value;
    },
    pedantic: false,
    gfm: true,
    tables: true,
    breaks: false,
    sanitize: false,
    smartLists: true,
    smartypants: false,
    xhtml: false
});

//Convert MD to HTML
gulp.task('md', function () {
    return gulp.src(mdFilePath + '**/*.md')
        .pipe(markdown(marked))
        .pipe(rename({
            extname: ".html"
        }))
        .pipe(gulp.dest(function (file) {
            return file.base;
        }));
})


The destination directory is same as the original Markdown file, but the converted file’s extension name will be .html.


Task for gulp-html-extend

This is the main callback task for watching changes of any Markdown file.
Therefore, we put the md task as a dependency of this task, extend.

//Merge the master and extend pages
gulp.task('extend', ['md'], function () {
    return gulp.src(mdFilePath + '**/*.html')
        .pipe(extender({
            annotations: true,
            verbose: false
        }))
        .pipe(gulp.dest(function (file) {
            return file.base;
        }));
});



Task for watching Markdown files

In the last, we use gulp-watch to watch the *.md files.
Notice that the watch function in gulp.js doesn’t support watching new/deleted files in run-time. So we use gulp-watch instead.


gulp.task('watch', function () {
    // gulp.watch(mdFilePath + '**/*.md', ['extend']); //Not support new/delete files
    return watch(mdFilePath + '**/*.md', function(){
        gulp.start("extend");
    });
});

//Default tasks
gulp.task('default', ["watch"]);



Start the service

$ dotnet build
$ gulp



Demo






fs.watch roblem

While I ran the gulp-watch in a virtual machine, sometimes the watch listener caused the following error,

[09:55:30] Error: watch …\wwwroot\markdown\新增資料夾 (2) EBUSY
    at _errnoException (util.js:1003:13)
    at FSWatcher.start (fs.js:1397:19)
    at Object.fs.watch (fs.js:1423:11)
    at createFsWatchInstance




This is because that the maximum number for listening document is not the same in every environment or OS. So when you are using VM, follow the post in Stackoverflow to increase this max number, I shorten the answer as following,  

Cmd in Windows:
$ echo fs.inotify.max_user_watches=524288

Unix shell:
$ echo fs.inotify.max_user_watches=582222 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p


For information, see this post:  gulp watch error – ENOSPC





Reference



沒有留言:

張貼留言