Gulp 学习

安装与执行

我花了不少时间在学习 Gulp 这个任务自动化管理工具,最主要受到了 Gitbook 的启发,还有洧杰与志诚的推荐,让我毅然决然地投入了 Gulp 的世界,过去因为工作的需求,接触过一点点的 Grunt,Gulp 和 Grunt 的初衷相同,都是要把过去需要人工处理的步骤给自动化,如此一来就可以省下不少宝贵的时间,不过我的 Gulp 仍不熟稔,因此从这篇开始的接下来几篇,都是我学习 Gulp 的历程,如果有写错或是不明白的地方,也请大家多多给予指教。

学习 Gulp 之初,我找了不少的参考资料,以下是我在初期入门所阅读的资料,内容都满详细的描述了关于 Gulp 的基本观念,特别是 gulp 入门指南 这篇,非常详细且完整,看完之后应该就可以明白八九成囉。

虽然上面几篇都详细的描述了 Gulp,不过由于这是我自己的学习笔记,第一篇仍然不免俗的要介绍一下 Gulp 与 Gulp 的安装,揪竟 Gulp 是什么呢?为什么我要学 Gulp 呢?Gulp 是基于 Node.js 的任务自动化管理工具,和 Grunt 同样都是用来简化人工的步骤,但两者却又有着本质上的差异 ( 可以参考 appleboy 大大的 Automating your workflow with Gulp.js ),最主要就是 Gulp 使用了 streams ( 流 ) 的概念,一个任务一个任务的依序按照流程做完,相当的容易思考和理解,然而请教过一些常用 Grunt 的朋友,Grunt 相较于 Gulp,就显得不那么直觉 ( 设定比较容易跳来跳去 )。

总而言之,我就捨弃了 Grunt ,转而投入 Gulp 的怀抱,我目标很简单,我想要做一个「可以纯粹写 Markdown 就转成 HTML 的 blog,并且自动帮我压缩图片、HTML、CSS 和 JS,还会自动产出 sitemap 和 json 资料」,基本上就是有点类似 Gitbook 的做法啦 ( 毕竟我是受到 Gitbook 的启发呀 )。

要使用 Gulp,第一步就是要安装 Node.js,因为 Gulp 是基于 Node.js 开发而成,所以就先上 Node.js 的官网下载安装 ( http://nodejs.org/download/ )

Gulp 学习笔记 1 - 安装与执行

安装 node.js 也会顺便把 npm 安装进去,接着就是使用 windows 的 cmd 或 Mac 的 Termial 来安装 Gulp,首先我们先输入以下的指令,把 Gulp 安装到全域的环境。( 在 Mac 要记得加 sudo 不然会装不进去 )

npm install -g gulp

接着利用指令移到我们的专桉目录下 ( 范例为 gulptest ),输入npm init,进行这个空白专桉的初始化,同时也会生成一个基本的package.json,这个package.json非常重要,因为它定义了这个 Node.js 专桉里头会需要用到的模组与套件 ( Node modules ),输入npm init之后,基本上可以填写一些名称或描述,不然其实也可以直接一直 enter 按下去就会建立完成。

Gulp 学习笔记 1 - 安装与执行

建立好的package.json长成这样:

Gulp 学习笔记 1 - 安装与执行

专桉初始化完成之后,接着就可以来安装这个专桉的 Gulp 套件,输入以下的指令就可以安装 ( Mac 要加 sudo ),至于为什么要有-save-dev呢?当我们写-save-dev,会将这个模组添加到package.jsondevDependencies里头,如果写-save,就会添加到dependencies裡,这两个的差异在于让使用具备这个package.json专桉的人,可以清楚的知道这个模组,是开发使用,还是执行专桉时使用的。

npm install gulp -save-dev

Gulp 学习笔记 1 - 安装与执行

其实到这个步骤,已经完成了 Gulp 的安装,最后来安装一个名为Gulp-webserver的套件,来测试一下我们的第一个 Gulp 专桉!安装的方法跟刚刚是一模一样的,有兴趣了解更多套件的人,可以直接上 npm 网站查询,gulp-webserver的套件在这裡:https://www.npmjs.com/package/gulp-webserver

npm install gulp-webserver -save-dev

安装完成之后,由 npm 的网站我们可以清楚的看到gulp-webserver的用法,首先我们在专桉的资料夹内先建立一个名为gulpfile.js的 JS 档桉,内容撰写下列的程式,这就是当我们执行 gulp 的时候会跑的任务流程。

var gulp = require('gulp');
var webserver = require('gulp-webserver');

gulp.task('webserver', function() {
  gulp.src('./app/')
    .pipe(webserver({
      port:1234,
      livereload: true,
      directoryListing: false,
      open: true,
      fallback: 'index.html'
    }));
});

gulp.task('default',['webserver']);

首先看到前两行,这是 Node.js 载入这些 Node Modules 的方法,接着就看到 gulp 后面紧跟着 task (gulp.task),这表明了,这段程式里头,是两个任务,分别名为「webserver」和「default」,在gulp.task('default',['webserver'])这段里头又有一个很重要的涵义,就是 default 这个任务相依于 webserver 任务,因此当我们输入指令gulp执行 default 这个任务的时候,就会先把 webserver 这个任务给执行。 ( 预设任务为 default,因此输入gulp就可以执行 default,不然也可以输入gulp.webserver就可以直接执行webserver这个任务 )

继续看下去,gulp.src是指这个任务工作的目标资料夹,pipe则是这个任务的流程,之后还会介绍到gulp.dest这个任务完成的存档路径,以这个 webserver 的例子来说,就是以 app 这个资料夹为目标,在上面建立一个 webserver,当中会包含 livereload 的功能,并且不要显示文件树状清单、port 1234 以及自动开启 index.html...等。

这时候我们只需要建立一个名位 app 的资料夹,在里头放入一些网页,执行 gulp,就会帮我们建立好一个简单的 web server 囉!

Gulp 学习笔记 1 - 安装与执行

以上就是我学 Gulp 的起手式,分享给大家。

打包压缩 CSS 与 JS

通常我们在写网页的时候,都会引入许多的 JS 和 CSS,而每引入一个也就会产生一个 request,当引入的越来越多,在效能和时间的等待上也就相对付出的越来越多,虽然在对于现在的网路速度和浏览器效能而言,看似问题都不大,但是对于一个流量超大的网站来说,一个使用者多了一个 request,在效能的处理上就相对重要许多,因此,在做网页的时候,往往至少会分成两个阶段,一个阶段是「开发时期」的版本,一个阶段是「发佈上线」的版本,最主要就是会把「开发」和「上线」分开,如此一来才不会一时疏忽把还没开发好的版本给对外公开了。

在上线版本裡头,就会使用到打包和压缩过的 JS 或 CSS,这类型的 JS 或 CSS 会在档桉名称加个「.min」进行区隔,而一个打包压缩过的 JS 和 CSS,基本上可能是好几个档桉组合在一起,过去我会利用 Yahoo 或 Google 提供的压缩程式来处理,但许多步骤仍然要手工处理,但学习了 Gulp 之后,利用 Gulp 的套件,非常简单的就可以做到打包压缩的动作,甚至还可以自动重新命名档桉,一气呵成,这篇主要会介绍如何打包压缩的做法。

如同上一篇描述的,我们要先用npm init将专桉初始化 ( 参考前一篇 ),接着输入以下指令,安装 gulp、gulp-minify-css、gulp-uglify、gulp-concat、gulp-rename 这五个 Node Module。( Mac 可能要加 sudo )

npm install gulp gulp-minify-css gulp-uglify gulp-concat gulp-rename -save-dev

安装完成之后,先来建立一下我们档桉的目录,首先新增一个 app 的资料夹,裡头再放入一个 CSS 与 JS 的资料夹,分别在这些资料夹内新增一些 CSS 与 JS 档桉,接着在与 app 资料夹的同一层,新增 build 的资料夹,作为最后完成发佈使用,完成后的目录应该长成这样:

Gulp 学习笔记 2 - 打包压缩 CSS 与 JS

好了,模拟一个简单的专桉目录之后,先解释一下刚刚安装的这几个套件是在干嘛用的:

大概明白之后,就要来撰写 gulpfile.js 了,首先第一个步骤,就是要引入这些套件:

var gulp       = require('gulp'),
    concat     = require('gulp-concat'),
    minifyCSS  = require('gulp-minify-css'),
    uglify     = require('gulp-uglify'),
    rename     = require("gulp-rename");

然后因为我新增了三个 css 档桉,所以我要先把这三个合併起来,才进行压缩,合併採用的套件为:gulp-concat,它可以把目标资料夹内所有指定的档桉合併成为 all.css,然后放到 build 的资料夹内。( 可以参考 gulp-concat )

gulp.task('concat', function() {
    return gulp.src('./app/css/*.css')
        .pipe(concat('all.css'))
        .pipe(gulp.dest('./build/css/'));
});

再来我们压缩 CSS,这裡要用到的是 gulp-minify-css 和 gulp-rename,下面的写法是建立一个名为 minify-css 的任务,裡头先使用 minifyCSS 压缩,压缩之后直接用 rename 对压缩的档桉重新命名,命名时可以设定 basename 与 extname,完成后就把压缩好的档桉,同样放在 build 的资料夹裡头。( 可以参考 gulp-minify-css 和 gulp-rename )

gulp.task('minify-css',['concat'], function() {
  return gulp.src('./build/css/all.css')
    .pipe(minifyCSS({
       keepBreaks: true,
    }))
    .pipe(rename(function(path) {
      path.basename += ".min";
      path.extname = ".css";
    }))
    .pipe(gulp.dest('./build/css/'));
});

第三步使用 uglify 溷淆与压缩 javascript,用法和 minifyCSS 几乎相同。( 可以参考:gulp-uglify )

gulp.task('uglify', function() {
    return gulp.src('./app/js/*.js')
        .pipe(uglify())
        .pipe(rename(function(path) {
            path.basename += ".min";
            path.extname = ".js";
        }))
        .pipe(gulp.dest('./build/js/'));
});

到这边几乎已经完成囉,最后只要写上 default 的任务就可以执行 gulp 看看。

gulp.task('default',['minify-css','uglify']);

执行 gulp 后,就会发现我们的档桉,已经被压缩好并且重新命名,放在指定的位置了,这就是最基本的打包压缩 CSS 与 JS 的 Gulp 用法。( 执行 gulp 就是直接在 cammand 输入 gulp 就可以 )

Gulp 学习笔记 2 - 打包压缩 CSS 与 JS

完整的 gulpfile.js 长这样:

var gulp       = require('gulp'),
    concat     = require('gulp-concat'),
    minifyCSS  = require('gulp-minify-css'),
    uglify     = require('gulp-uglify'),
    rename     = require("gulp-rename");

gulp.task('concat', function() {
    return gulp.src('./app/css/*.css')
        .pipe(concat('all.css'))
        .pipe(gulp.dest('./build/css/'));
});

gulp.task('minify-css',['concat'], function() {
    return gulp.src('./build/css/all.css')
        .pipe(minifyCSS({
            keepBreaks: true,
        }))
        .pipe(rename(function(path) {
            path.basename += ".min";
            path.extname = ".css";
        }))
        .pipe(gulp.dest('./build/css/'));
});

gulp.task('uglify', function() {
    return gulp.src('./app/js/*.js')
        .pipe(uglify())
        .pipe(rename(function(path) {
            path.basename += ".min";
            path.extname = ".js";
        }))
        .pipe(gulp.dest('./build/js/'));
});

gulp.task('default',['minify-css','uglify']);

打包压缩 HTML

我们看完了打包与压缩 CSS 和 JS,为什么打包压缩 HTML 不要一起讲呢?是因为打包压缩 HTML 还会用到额外的套件和方法,避免溷淆,所以就分成两篇来纪录。

压缩 HTML 与 CSS 和 JS 不同的地方,在于 CSS 和 JS 利用合併之后再溷淆或压缩,而 HTML 除了压缩,更多了去置换内容的方式,例如我们有一个网页,裡头载入了三个 CSS 与三个 JS,压缩之后就会变成只有一个 min.css 和一个 min.js,如此一来原本要载入六个档桉,瞬间就变成了只需要载入两个档桉而已,也由于由六个 request 降为两个 request,整个网页的流畅度也会提升。( 就像传送一千个小档桉,速度远不如把一千个小档桉压缩成一个大档桉,直接传送大档桉还来得快 )

打包压缩 HTML 我们会用到的套件有这些:gulp-html-replace、gulp-minify-html,gulp-html-replace 的作用,是把原本的三个 CSS 置换为一个 min.css,gulp-minify-html 就是纯粹的压缩 HTML,除此之外,我们也要把上一篇的压缩 CSS 与 JS 所需要的套件一起拿来用,毕竟打包压缩是要成为完整 ( 压缩 HTML、CSS、JS )才是,所以首先就安装吧!( mac 要加 sudo,相关套件参考:gulp-html-replacegulp-minify-html )

npm install gulp gulp-html-replace gulp-minify-html gulp-minify-css gulp-uglify gulp-concat gulp-rename -save-dev

目录结构应该是长成这样:

Gulp 学习 3 - 打包压缩 HTML

安装好了之后就来写 gulpfile.js,一开始当然是先引入这些套件,引入的套件就这样越来越多了。

var gulp      = require('gulp'),
  concat      = require('gulp-concat'),
  minifyCSS   = require('gulp-minify-css'),
  uglify      = require('gulp-uglify'),
  rename      = require("gulp-rename"),
  htmlreplace = require('gulp-html-replace'),
  minifyHTML  = require('gulp-minify-html');

上一篇的压缩 CSS 和 JS 就先略过,直接看到压缩 HTML 的部分,我们先来瞧瞧未压缩的 HTML 长怎样,基本上裡面会包含了一大串载入的 CSS 和 JS。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Compress</title>
    <link rel="stylesheet" href="css/test1.css">
    <link rel="stylesheet" href="css/test2.css">
    <link rel="stylesheet" href="css/test3.css">
    <script src="js/test.js"></script>
</head>
<body>

</body>
</html>

这时候我们要使用 gulp-html-replace,把这一大串载入的 CSS 和 JS 替换掉,换成我们压缩过的 CSS 与 JS 档桉,置换的方法就是在 HTML 里头写上一些特定的注解描述就可以,例如要把整段 css 替换掉,就用<!-- build:css --><!-- endbuild --> 把要替换掉的地方包起来。

<!-- build:css -->
<link rel="stylesheet" href="css/test1.css">
<link rel="stylesheet" href="css/test2.css">
<link rel="stylesheet" href="css/test3.css">
<!-- endbuild -->
<!-- build:js -->
<script src="js/test.js"></script>
<!-- endbuild -->

包起来之后就要在 gulpfile.js 里头写出要置换的内容,同时一併把 gulp-minify-html 给写进去,毕竟置换完就要同时压缩,才是一个完整的打包压缩,下面的范例将 minifyHTML 的设定写在外面,而 htmlreplace 表示要把刚刚 css 的部分换为 all.min.css,js 的部分换为 js/all.min.js。

gulp.task('html-replace',function() {
  var opts = {comments:false,spare:false,quotes:true};
  return gulp.src('./app/*.html')
    .pipe(htmlreplace({
        'css': 'css/all.min.css',
        'js': 'js/all.min.js'
    }))  
    .pipe(minifyHTML(opts))
    .pipe(gulp.dest('./build/'));
});

gulp.task('default', ['html-replace','minify-css', 'uglify']);

完成之后只要执行 gulp,就可以看到最后的成果:

Gulp 学习 3 - 打包压缩 HTML

这裡要补充一点上一篇没有提到的,在上一篇的 minify-css 任务,必须要在 concat 任务完成之后才会进行,但要如何保证呢?因为在 gulp 里头,所有任务并不会按照顺序,因此很有可能当我们执行 minify-css 的时候, concat 尚未完成,就会造成产生的程式码有问题,所以,「在每个 task 任务里头加上 return,接着把 minify-css 的任务写成这样:gulp.task('minify-css', ['concat'], function(){})」,就可以保证 minify-css 会接在 concat 之后囉!最后完成的 gulpfile.js 如下:

var gulp      = require('gulp'),
  concat      = require('gulp-concat'),
  minifyCSS   = require('gulp-minify-css'),
  uglify      = require('gulp-uglify'),
  rename      = require("gulp-rename"),
  htmlreplace = require('gulp-html-replace'),
  minifyHTML  = require('gulp-minify-html');

gulp.task('concat', function() {
  return gulp.src('./app/css/*.css')
    .pipe(concat('all.css'))
    .pipe(gulp.dest('./build/css/'));
});

gulp.task('minify-css', ['concat'], function() {
  return gulp.src('./build/css/all.css')
    .pipe(minifyCSS({
      keepBreaks: true,
    }))
    .pipe(rename(function(path) {
      path.basename += ".min";
      path.extname = ".css";
    }))
    .pipe(gulp.dest('./build/css/'));
});

gulp.task('uglify', function() {
  return gulp.src('./app/js/*.js')
    .pipe(uglify())
    .pipe(rename(function(path) {
      path.basename += ".min";
      path.extname = ".js";
    }))
    .pipe(gulp.dest('./build/js/'));
});

gulp.task('html-replace',function() {
  var opts = {comments:false,spare:false,quotes:true};
  return gulp.src('./app/*.html')
    .pipe(htmlreplace({
        'css': 'css/all.min.css',
        'js': 'js/all.min.js'
    }))  
    .pipe(minifyHTML(opts))
    .pipe(gulp.dest('./build/'));
});

gulp.task('default', ['html-replace','minify-css', 'uglify']);

建立 SCSS/SASS 编辑环境

随着时代的演进,这一两年来我都已经是用 SCSS 在编写 CSS,不过 SCSS/SASS 是建构在 Ruby 的环境之下,通常我们要使用,则必须先安装 Ruby 以及 compass,或也可以购买安装 Fire.app 代劳,那么要如何才能用 gulp ,来建立一个自动执行转换 SCSS 的环境呢?

与 SCSS/SASS 的安装同样的前置步骤,我们必须先安装 Ruby 以及 compass,Ruby 可以直接到 Ruby 的官方网站下载安装: rubyinstaller 。

Gulp 学习 4 - 建立 SCSS/SASS 编辑环境

安装 Ruby 之后就是要继续安装 compass,开启命令提示字元 ( CMD ),输入以下程式码,就可以安装 compass。

gem install compass

安装 Ruby 与 compass 的前置作业完成之后,接着就要来回归 Gulp ,这裡我们要用到的套件只有一个:gulp-compass。( Mac 可能要加 sudo )

npm install gulp gulp-compass -save-dev

安装之后先来规划一下我们的目录结构,首先建立一个名为 style 的资料夹,内部分别建立两个名为 SCSS、CSS 的资料夹,我们的目标是要在 SCSS 资料夹内编写 SCSS,然后自动转换成 CSS 档桉放在 CSS 的资料夹内。

Gulp 学习 4 - 建立 SCSS/SASS 编辑环境

资料夹用好之后,来写一下 gulpfile.js 了,第一步就是先引入套件。(参考:gulp-compass)

var gulp = require('gulp'),
    compass   = require('gulp-compass');

然后就是定义一个叫做 compass 的任务,gulp.src是读取 scss 资料夹内所有的 scss 档桉,然后利用 compass 的方法进行转换,转换的设定有满多的,下面的范例只列出几个,例如 sourcemap ,是在转换之后会产生 sourcemap 的一个 json 档桉 ( css.map ),如此一来就可以从编译出的 css,反查回原本的 scss,而 time 就是显示转换经过的时间,style 则是转换出来的 CSS 长相会是如何。

gulp.task('compass',function(){
    return gulp.src('./style/scss/*.scss')
        .pipe(compass({
            sourcemap: true,
            time: true,
      css: './style/css/',
      sass: './style/scss/',
      style: 'compact' //nested, expanded, compact, compressed
        }))
        .pipe(gulp.dest('./style/css/'));
});

接着,我们要来写一个 watch 的任务,这个任务不需要安装甚么套件,它原本就预设在 gulp 里头,它的目的是去监看变化的档桉,当指定监看的档桉有变化 ( 新增、修改 ),就会自动去执行对应的任务,下面的范例是让 gulp 自动去监看 scss 资料夹内所有 scss 的档桉,如果有变化,就自动执行 compass,如此一来我们就可以纯粹编辑 scss ,让 gulp 自动去产出 css 囉!最后当然就是要一併执行 compass 和 watch 这两个任务。

gulp.task('watch',function(){
    gulp.watch('./style//scss/*.scss',['compass']);
});

gulp.task('default',['compass','watch']);

最终的 gulpfile 的长相,我也有放在 Github 上头,也欢迎大家 fork,在 SCSS 的注解里头,其实也是我自己做的一份 SCSS 的教学范例喔,下载之后执行 npm install 就可以安装相对应的套件了。( Gulp-SCSS )

var gulp = require('gulp'),
    compass   = require('gulp-compass');

gulp.task('compass',function(){
    return gulp.src('./style/scss/*.scss')
        .pipe(compass({
            config_file: './style/scss/config.rb',
            sourcemap: true,
            time: true,
      css: './style/css/',
      sass: './style/scss/',
      style: 'compact' //nested, expanded, compact, compressed
        }))
        .pipe(gulp.dest('./style/css/'));
});

gulp.task('watch',function(){
    gulp.watch('./style//scss/*.scss',['compass']);
});

gulp.task('default',['compass','watch']);

题外话,在 Mac 写以上的步骤都没有问题,真正遇到问题是用 windows 才产生,一开始因为我的 compass 版本较旧,导致无法顺利转出 scss 里头注解的部分,于是我用gem install compass要更新 compass,没有到却又发生SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed这个莫名其妙的问题,因为 gulp-compass 就是必须要用 compass,无法顺利升级 compass 就等于宣告 gulp-compass 阵亡,找了很久,终于找到这篇文章:SSL upgrades on rubygems.org and RubyInstaller versions,里头详细描述这个问题的解决方法,就是要先下载 rubygems-update-1.8.30.gem 到本地端,然后升级 gem ( 因为用 update 都升级不了 ),升级之后,就可以顺利升级 compass。( 写这篇的时候 compass 是 1.0.3 )

不过光是升级还是会有点小问题,就是 compass 预设对于中文的注解会发生错误,解决方式有两种,一种是在专桉档里头添加 config.rb,内容添加:Encoding.default_external = 'utf-8',第二种是在 scss 的档桉开头,加上@charset "UTF-8";就可以解决。

没想到原本在 Mac 上很简单的步骤,在 windows 我却花了好几个小时才搞定,真是莫名其妙呀喔哈!

参考资料:

建立 HTML 模板

利用 fire.app 替我建置「纯静态」的 blog,换句话说,其实我只是撰写内容,然后藉由 fire.app 帮我把内容和 layout 合併在一起,如果有兴趣的人可以直接购买 fire.app 下来使用即可。不过这一篇并不是要介绍 fire.app,而是要利用 gulp,建立 HTML 的模板,同样也是将 layout 和 content 分开,然后在预览与上线的时候再合併。

这裡我们要用到的是 gulp-html-extend 这个套件 ( 参考 gulp-html-extend ),和之前介绍过的 gulp-html-replace 类似,gulp-html-extend 会把某些特定注解内的内容置换为我们想要的内容,但与 gulp-html-replace 不同的地方,在于 gulp-html-replace 要置换的内容写在 gulpfile.js 里头,而 gulp-html-extend 则是将外部的 HTML 内容嵌入注解的区域。

以下面的例子来说,首先我们建立一个 layout.html 的样版,裡头是要共用的内容,在裡面可以看到有两个注解,分别是 title 和 content,这就是我们要合併的部分。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <!-- @@placeholder= title -->

</head>
<body>
    <!-- @@placeholder= content -->
    <footer>
        footer
    </footer>
</body>
</html>

接着看到要和 layout.html 合併的页面,分别是 index.html 和 index2.html,里头用注解区块很清楚的标示哪裡是 title ,哪裡是 content,届时合併之后,index.html 和 index2.html 就会具备相同的 layout了。

未合併的 index.html

<!-- @@master  = ../layout.html-->

<!-- @@block  =  title-->
<title>index</title>
<!-- @@close-->

<!-- @@block  =  content-->
<main>
    我是 index
</main>
<!-- @@close-->

未合併的 index2.html

<!-- @@master  = ../layout.html-->

<!-- @@block  =  title-->
<title>index2</title>
<!-- @@close-->

<!-- @@block  =  content-->
<main>
    我是 index2
</main>
<!-- @@close-->

合併之后就会变成这样:

合併的 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>    
</head>
<body>

<main>
    我是 index
</main>
<footer>
    footer
</footer>
</body>
</html>

合併的 index2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index2</title>   
</head>
<body>

<main>
    我是 index2
</main>
<footer>
    footer
</footer>
</body>
</html>

看完 html 是如何进行合併,再来就是 gulpfile.js 要怎么写呢?一开始同样是要引入套件,然后建立名为 extend 的任务,src 设定目标资料夹内的 html 档桉,然后设定 ( verbose 设定为 false 就可以在合併之后把注解移除 )

var gulp      = require('gulp'),
    extender    = require('gulp-html-extend');

gulp.task('extend', function () {
    gulp.src('./app/dist/*.html')
        .pipe(extender({annotations:false,verbose:false})) // default options 
        .pipe(gulp.dest('./app/preview/'));
});

gulp.task('default',['extend']);

以上就是利用 Gulp 建立 HTML 模板的方法,下图是整个 HTML 的资料夹结构。

Gulp 学习 5 - 建立 HTML 模板

架设 Markdown 转 HTML 环境

要谈到 Markdown 转 HTML,其实有一大堆的 Markdown 编辑器都做得到,我个人最爱用的不外乎就是 Markdown Pad ( 只有 windows )、Mou ( Mac ) 和 lightPaper ( Mac ),然而不管是怎样的编辑器,基本上都会具备将 markdown 转为 HTML 的功能,所以一直以来我都不觉得在编辑器撰写之后在转档有什么不方便的,我的 blog 文章也一直都是用 markdown 写的,不过自从用 gitbook 写书之后,赫然发现 gitbook 的 markdown 自动转换 html 功能真是太优了,用自己惯用的编辑器,存档之后,Gitbook 就自动将 markdown 转换为 HTML,而且还套上 layout 和样式,真是太讚啦!

于是,当我发现 Gulp 也有 markdown 转换的套件之后,就决定可以自己来做一个转换的环境,如此一来就不用受限于各个编辑器的样式,也不用受限于 gitbook 只能转电子书的样貌,甚至可以搭配之前几篇介绍过的压缩打包、html extend...等,做出一个纯粹用 markdown 编写网页的环境。

废话说太多,开始今天的正题,我使用的套件是:gulp-markdown 这个套件 ( 参考:gulp-markdown ),这个套件可以将 markdown 纯粹转为 HTML 的格式,但不包含一个网页应该要有的 head 与 body,它就是一个纯粹的 HTML tag 而已,举例来说,我们来将下面这段 Markdown 进行转换:

#这是大标题
##这是次标题
我是内文,我是**粗体**

转换出来就变成:

<h1 id="-">这是大标题</h1>
<h2 id="-">这是次标题</h2>
<p>我是内文,我是<strong>粗体</strong></p>

也因为不是一个完整的网页,所以我们使用 gulp-webserver 来做 livereload ( 参考 Gulp 学习 1 - 安装与执行 ) 就会失效,所以变成要搭配 gulp-html-extend,结合一个 layout,组成一个完整的网页,就可以使用 livereload 的功能囉!

所以这裡我们要输入以下的指令安装四个套件,分别是 gulp、gulp-webserver、gulp-html-extend、gulp-markdown ( mac 可能需要 sudo ):

npm install gulp gulp-webserver gulp-html-extend gulp-markdown

安装之后就来写 gulpfile.js,第一步当然是要引入这四个套件了。

var gulp = require('gulp'),
      md = require('gulp-markdown'),
      extender = require('gulp-html-extend'),
      webserver = require('gulp-webserver');

第二步就是要新增一些资料夹,资料结构如下,md 资料夹里头有一个 test.md,是我们要转换的 markdown 档桉,转换完之后会预先放在 md2html 的资料夹,接着会将里头的档桉和 layout 资料夹内的 master.html 合併,合併完就会放在 dist 的资料夹内。

|____dist
|____layout
| |____master.html
|____md
| |____test.md
| |____md2html

资料目录建立好之后,新增一个名为 md 的任务,裡头就是负责进行 markdown 转换 html 的作业,这裡面./md/**/*.md代表是 md 资料夹内所有的 md 档桉,也包含子资料夹内的都会抓到。

gulp.task('md',function(){
    return gulp.src('./md/**/*.md')
        .pipe(md())
        .pipe(gulp.dest('./md/md2html/'));
    });

因为转出来档桉要和 HTML 的 layout 做合併,所以我们必须要在我们要转换的 markdown 档桉裡头加上一些 html 的注解,转换之后就会根据这些注解去做合併,下面这是我要用来转换的 markdown 与 layout 的长相:

markdown:

<!-- @@master  = ../../layout/master.html-->
<!-- @@block  =  content-->

#这是大标题
##这是次标题
我是内文,我是**粗体**

测试 Markdown  html

<!-- @@close-->

html layout:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link href="" rel="stylesheet">
    <title>markdown preview</title>
    <!-- @@placeholder= head -->
</head>
<body>
    <!-- @@placeholder= content -->
</body>
</html>

合併之后:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link href="" rel="stylesheet">
    <title>markdown preview</title>
</head>
<body>
    <h1 id="-">这是大标题</h1>
    <h2 id="-">这是次标题</h2>
    <p>我是内文,我是<strong>粗体</strong></p>
</body>
</html>

接着就来建立 html 合併的任务,记得合併任务要在 md 任务完成之后再执行,可以参考上一篇所讲的 Gulp 学习 5 - 建立 HTML 模板

gulp.task('extend', ['md'], function () {
    return gulp.src('./md/md2html/**/*.html')
        .pipe(extender({annotations:true,verbose:false}))
        .pipe(gulp.dest('./dist/'));
});

合併之后,就是启动一个 webserver 来进行预览的动作,此外也要顺便建立 watch 的任务,这样才能编辑完 markdown 档桉存档之后,就可以自动更新预览囉。

gulp.task('watch',function(){
    gulp.watch('./md/**/*.md', ['extend']);
    });

gulp.task('webserver', ['extend'],function() {
  return gulp.src('./dist/')
    .pipe(webserver({
      port:1111,
      livereload: true,
      directoryListing: false,
      open: true,
      fallback: 'test.html'
    }));
});

gulp.task('default',['watch','webserver']);

任务都用好之后,就可以执行 gulp 看看,如此一来,其实我们已经建立了一个简易的 markdown 自动转 html 的环境,当然还有很多地方要设定,之后会再慢慢分享,上图执行之后的资料夹显示图就像下面这样:

Gulp 学习 6 - 架设 Markdown 转 HTML 环境

完整的 gulpfile.js:

var gulp      = require('gulp'),
      md        = require('gulp-markdown'),
      extender  = require('gulp-html-extend'),
      webserver = require('gulp-webserver');

gulp.task('md',function(){
    return gulp.src('./md/**/*.md')
        .pipe(md())
        .pipe(gulp.dest('./md/md2html/'));
    });

gulp.task('extend', ['md'], function () {
    return gulp.src('./md/md2html/**/*.html')
        .pipe(extender({annotations:true,verbose:false}))
        .pipe(gulp.dest('./dist/'));
});

gulp.task('watch',function(){
    gulp.watch('./md/**/*.md', ['extend']);
    });

gulp.task('webserver', ['extend'],function() {
  return gulp.src('./dist/')
    .pipe(webserver({
      port:1111,
      livereload: true,
      directoryListing: false,
      open: true,
      fallback: 'test.html'
    }));
});

gulp.task('default',['watch','webserver']);

相关连结: