kuitos / kuitos.github.io

📝Kuitos's Blog https://github.com/kuitos/kuitos.github.io/issues
https://kuitos.github.io/
MIT License
1.13k stars 81 forks source link

前端工程之构建工具-gulp实战 #11

Open kuitos opened 9 years ago

kuitos commented 9 years ago

前端工程之构建工具-gulp实战

原文写于 2014-10-24

对前端构建自动化有所了解的同学应该都听过大名鼎鼎的grunt,但是grunt作为前端自动化构建工具的领头羊现在越来越受到诟病。大而全已经不适应这个时代了,这个时代需要的是小而美的工具及插件。什么都想干往往意味着什么都干不好,YUI的死掉至少说明了近年前端的发展趋势。grunt的不足及弊端大家可以参考这篇文章。后起之秀gulp大有取代grunt的趋势(gulp的github的stars目前已经超过grunt了),本文主要介绍如何通过gulp实现自动化压缩、合并以及文件的修改。 首先来说说我们想要实现的功能吧

来看看我们怎样通过gulp实现我们的前端工程自动化的吧 首先是一个源码层级的index.html,差不多长这样

<!--
 Created by kui.liu on 2014/05/29 14:34.
-->
<!DOCTYPE html>
<html ng-app="invoiceApp">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

    一堆css文件。。。。
    <!-- build:css css/app.css -->
    <link href="/src/css/bootstrap-tooltips.css" rel="stylesheet">
    <link href="/src/js/lib/jquery-ui/development-bundle/themes/base/jquery.ui.theme.css" rel="stylesheet">
    <link href="/src/js/lib/jquery-ui/development-bundle/themes/base/jquery.ui.core.css" rel="stylesheet">
    <link href="/src/js/lib/jquery-ui/development-bundle/themes/base/jquery.ui.datepicker.css" rel="stylesheet">
    <link href="/src/js/lib/jQuery-Timepicker-Addon/dist/jquery-ui-timepicker-addon.min.css" rel="stylesheet">
    <link href="/src/css/float-tips.css" rel="stylesheet"/>
    <link href="/src/css/ccms.css" rel="stylesheet">
    <link href="/src/css/app.css" rel="stylesheet">
    <link href="/src/js/lib/ccms_pop/css/style.css" rel="stylesheet">
    <!-- endbuild -->

    <style>
        [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
            display: none !important;
        }
    </style>
    <title>账户管理</title>
    <script>
        //  设置资源文件目录,必需!!
        window.ResourceDir = "/src/";
    </script>
</head>
<body ng-cloak class="ng-cloak" ng-controller="AppCtrl"> 
    此处省略若干结构。。。
    一堆js文件
    <!-- build:js js/app.js -->
    <script src="/src/js/lib/jquery/jquery-1.11.1.js"></script>
    <script src="/src/js/lib/jquery-ui/development-bundle/ui/jquery.ui.core.js"></script>
    <script src="/src/js/lib/jquery-ui/development-bundle/ui/jquery.ui.datepicker.js"></script>
    <script src="/src/js/lib/jQuery-Timepicker-Addon/dist/jquery-ui-timepicker-addon.min.js"></script>
    <script src="/src/js/lib/jQuery-Timepicker-Addon/dist/i18n/jquery-ui-timepicker-zh-CN.js"></script>
    <script src="/src/js/lib/bootstrap/bootstrap-tooltips.js"></script>
    <script src="/src/js/lib/ccms_pop/js/yunat_pop.js"></script>
    <script src="/src/js/float-tips.js"></script>
    <script src="/src/js/lib/angular/angular.js"></script>
    <script src="/src/js/lib/angular/angular-locale_zh-cn.js"></script>
    ...........
    <!-- endbuild -->
</body>
</html>

然后是我们开发时的目录结构
image2014-10-26 15:30:4.png
然后我使用gulp定义了一系列构建任务后,执行一下命令 gulp build (假设你也写了个名为build的任务)
我们的工程变成这样了
首先是工程目录
image2014-10-26 15:32:40.png
然后是/dist/index.html

<!--
 Created by kui.liu on 2014/05/29 14:34.
-->
<!DOCTYPE html>
<html ng-app="invoiceApp">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

    <link rel="stylesheet" href="/dist/css/app-cb402b9f.css"/>

    <style>
        [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
            display: none !important;
        }
    </style>
    <title>账户管理</title>
    <script>
        //  设置资源文件目录,必需!!
        window.ResourceDir = "/dist/";
    </script>
</head>
<body ng-cloak class="ng-cloak" ng-controller="AppCtrl">
    ........
    <script src="/dist/js/app-3db90639.js"></script>
</body>
</html>

可以看到,/dist目录如期而至,而且长得跟我们预想的一样,然后是/dist/index.html,所有的css和js引用均指向一个压缩合并后的文件,眼尖的同学会发现,后面跟的那一串奇怪的数字是啥,如 app-3db90639.js。中划线后面那串数字是我们通过gulp计算了合并文件的MD5之后算出的hash值,也就是说,只要你的源码有改动,那么这个hash值便会自动追加到合并文件后面,而不需要你每次手动修改版本号那种古老的做法,一切都是gulp帮我们做好,我们需要做的就是敲一行命令就好了。赞一个!

下面具体介绍一下gulp怎么玩才能达到上文的效果

  1. 首先你要有个nodejs环境。nodeJs
  2. 安装gulp、bower(假设你有用到)等node组件

    node install gulp -g
    node install gulp --save-dev
  3. 引入gulp合并、压缩等插件(package.json文件)

    "devDependencies": {
       "gulp"            : ">=3.8.8",
       "del"             : ">=0.1.3",
       "gulp-minify-css" : ">=0.3.10",
       "gulp-minify-html": ">=0.1.6",
       "gulp-replace"    : ">=0.4.0",
       "gulp-rev"        : ">=1.1.0",
       "gulp-uglify"     : ">=1.0.1",
       "gulp-usemin"     : ">=0.3.8",
       "run-sequence"    : ">=1.0.1"
    }
  4. 构建gulp任务流,就像这样

    /**
    * @author kui.liu
    * @since 2014/09/29 上午11:45
    */
    "use strict";
    var webRoot = "src/main/webapp/",
       gulp = require('gulp'),
       del = require('del'),
       runSequence = require('run-sequence'),
       minifyCss = require('gulp-minify-css'),
       minifyHtml = require('gulp-minify-html'),
       uglify = require('gulp-uglify'),
       rev = require('gulp-rev'),
       replace = require('gulp-replace'),
       usemin = require('gulp-usemin');
    /*------------------------- 清空dist目录 -----------------------------*/
    gulp.task('clean', function (cb) {
       del(webRoot + "dist/*", cb);
    });
    /*------------------------- 拷贝资源文件 -----------------------------*/
    gulp.task('copy-tpl', function () {
       return gulp.src(webRoot + 'src/tpls/**/*.html')
           //        .pipe(minifyHtml({empty: true, quotes: true}))
           .pipe(gulp.dest(webRoot + 'dist/tpls'));
    });
    gulp.task('copy-font', function () {
       return gulp.src(webRoot + 'src/fonts/*')
           .pipe(gulp.dest(webRoot + 'dist/fonts/'));
    });
    gulp.task('copy-image', function () {
       return gulp.src(webRoot + 'src/images/**/*')
           .pipe(gulp.dest(webRoot + 'dist/images'));
    });
    gulp.task('copy-jqueryui-image', function () {
       return gulp.src(webRoot + 'src/js/lib/jquery-ui/development-bundle/themes/base/images/*')
           .pipe(gulp.dest(webRoot + 'dist/css/images'));
    });
    
    /*------------------------- 对首页引用的css、js作合并压缩,并根据文件md5值自动更新 -----------------------------*/
    /*------------------------- 首页html需要加入build标识,具体参照gulp-usemin插件文档 -----------------------------*/
    gulp.task('usemin', function () {
       return gulp.src(webRoot + 'src/index.html')
           .pipe(replace(/\/src\/(js|css)/g, '$1'))
           .pipe(usemin({
               css: [minifyCss({keepSpecialComments: 0}), rev()],
               //            html: [minifyHtml({empty: true, quotes: true})],
               js : [uglify(), rev()]
           }))
           .pipe(replace(/(js\/|css\/)/g, "/dist/$1"))
           .pipe(replace(/\/src\//g, "/dist/"))
           .pipe(gulp.dest(webRoot + 'dist/'));
    });
    // 定义构建任务队列
    gulp.task('build', function (cb) {
       runSequence('clean', ['copy-tpl', 'copy-font', 'copy-image', 'copy-ccmsPop-image', 'copy-jqueryui-image', 'usemin'], cb);
    });

    gulp采用管道流机制定义任务,任务的定义跟使用变得更易用和清晰。要运行单个定义的任务很简单,比如上面定义的clean任务:

    gulp clean

    一般情况下我们不会只执行一个构建任务,如果要一个个任务去敲命令显然是效率低下的,这里引入runSequence插件,从而自定义任务队列,比如上面我们定义build任务用来执行所有的合并、压缩的任务,执行方式跟其他任务一样:

    gulp build

    有的时候我们还用了bower等其他工具,不仅仅是gulp。那么构建工程的时候需要bower install,然后gulp build。懒人总想找到更简单的方式,我们在package.json中加入这个

    "scripts"        : {
       "start": "npm install & bower install --allow-root & gulp build"
    }

    这样我们构建项目只需要一个命令就OK

    npm start

    命令跑完之后一切就都变成了你想要的模样。就是这么简单。so easy!麻麻再也不用担心我每次发布都要小心翼翼了。

ps:由于我们node配置文件是放在项目根路径下的,而maven只打包webapp下的资源,所以如果你不想每次发布前还要手动跑node命令然后提交编译目录,配合bamboo可以在maven打包前加上脚本命令,如图
image2014-10-27 10:7:27.png

更多详细代码请移步:source code