zoukankan      html  css  js  c++  java
  • 用gulp替代fekit构建前端项目

    https://segmentfault.com/a/1190000003060016

    离开qunar有一个多月了,在离开的时候就决定不再用fekit。做出这个决定并不是因为fekit不好,恰恰相反,fekit帮我们做了很多事情,还屏蔽了许多细节,让开发人员能够专注于开发过程。不过随着fekit的升级,也出现了一些问题,同时fekit和公司业务及发布流程有一定耦合,所以觉得采用开源的构建方案。

    在使用gulp的过程中,基本也是依据使用fekit的思路来逐步完善构建过程的,所以还是要感谢fekit。现在进入正题,
    看看利用gulp如何实现本地服务、mock数据、css预处理、js模块化、打包压缩、版本号替换等功能的。

    1. 本地服务

    依赖模块:gulp-webserver

    使用:

    gulp.task('webserver', function() {
        gulp.src('./app')
            .pipe(webserver({
                livereload: true,
                directoryListing: {
                    enable:true,
                    path: 'app'
                },
                host: '172.16.100.27',
                port: 8000
            }));
    });
    

    注意:

    1. app是你的项目目录,比如我的目录结果如下

      -app
          |--- src
          |--- images
          |--- mock
          |--- prd
          |--- index.html
      
      -gulpfile.js
    2. 开启directoryListing的enbale,访问根目录(172.16.100.27:8000)时才能显示目录或文件列表。

    3. host可以是ip也可以是域名,不过在虚拟机里面调试时,用域名访问似乎有点问题,没有深究;如果要在手机上调试,同样建议用ip。

    2. mock 数据

    依赖模块:gulp-webserver

    使用:请参考之前的博文 gulp构建之mock data(模拟数据、转发请求)

    3. css 预处理编译

    依赖模块:gulp-sass gulp-minify-css gulp-autoprefixer

    使用:我这里使用的是sass

    var sass = require('gulp-sass'),
        minifyCSS = require('gulp-minify-css'),
        autoprefix = require('gulp-autoprefixer');
    
    var cssFiles = [
        'app/src/styles/page/index.scss',
        'app/src/styles/page/touch.scss'
    ];
    
    /* 编译压缩sass文件 */
    gulp.task('sass', function() {
        gulp.src(cssFiles)
            .pipe(sass().on('error', sass.logError))
            .pipe(autoprefix())
            .pipe(minifyCSS())
            .pipe(gulp.dest(paths.build.styles));
    });
    

    注意:

    1. 使用sass模块时,要加上.on('error', sass.logError)),这样sass编译出错时会打log,而不是终止运行。

    2. autoprefix,这是一个好东西,也是我不用fekit的一个原因(它还没集成autoprefix)

    4. js 模块化开发

    依赖模块:gulp-webpack gulp-uglify vinyl-named imports-loader

    说明:fekit采用的CommonJs的模块化方式,此外还有requireJS这种AMD的模块化方式。而webpack则是理想中的打包工具,它同时支持CommonJs和AMD,并拥有大量的加载器,包括上面说都的sass编译、前端模板编译等加载器,而且还有webpack-dev-server,已经强大到几乎不需要gulp了。我对webpack还不太熟,并且需要用到gulp的一些其他功能,所以暂时考虑把webpack作为gulp的一个模块来使用。

    使用:

    var named = require('vinyl-named');
    var webpack = require('gulp-webpack');
    
    var jsFiles = [
        'app/src/scripts/page/index.js',
        'app/src/scripts/page/touch.js'
    ];
    
    // webpack打包压缩js
    gulp.task('packjs', function() {
        return gulp.src(jsFiles)
            .pipe(named())
            .pipe(webpack({
                output: {
                    filename: '[name].js'
                },
                module: {
                    loaders: [{
                        test: /.html$/,
                        loader: 'mustache'
                    }, {
                        test: /.js$/,
                        loader: "imports?define=>false"
                    }]
                },
                resolve: {
                    alias: {
                        jquery: 'app/src/scripts/lib/jquery-1.11.3.min.js'
                    }
                },
                devtool: "#eval-source-map"
            }))
            .pipe(uglify().on('error', function(e) {
                console.log('x07',e.lineNumber, e.message);
                return this.end()}
            ))
            .pipe(gulp.dest(paths.build.scripts));
    });
    

    注意:

    1. 对于有多个入口文件(entry point)的情况,需要使用vinyl-named这个模块,这样就能实现以下打包需求

      src/scripts/index.js -> prd/scripts/index.js
      src/scripts/touch.js -> prd/scripts/touch.js
      在output里面可以设置打包后的文件名,如 "[name].min.js"
      
    2. 我在项目中使用的是commonjs的模块化方式,但大多数插件(如jquery或zepto插件)都是支持两种模块化方式,并且是先判断 define 再判断 module.export,所以我们需要“屏蔽”amd的模块加载方式。

      首先安装`imports-loader`模块,然后做如下配置:
      module: {
          loaders: [{
              test: /.js$/,
              loader: "imports?define=>false"
          }]
      }
      
      这样我们就能放心地使用各种插件了。
      
    3. 我的项目中使用了HoganJs作为前端模块,在webpack中只要找到相应的加载器就行。

      首先安装[mustache-loader](https://github.com/deepsweet/mustache-loader)模块,然后做如下配置:
      module: {
          loaders: [{
              test: /.html$/,
              loader: 'mustache'
          }]
      }
      
      通过 2. 和 3. 两个例子,大家可以看出test就是要处理的文件类型,loader就是处理文件的加载器。
      
    4. 调试仍然是个比较大的问题,虽然webpack提供了各种调试模式(在devtool中配置,实现sourcemap的调试),但在实际使用时,经常会遇到跳过断点的问题,不知道是不是gulp-webserver和webpack不适配的原因。所以在实际开发中,我会先把uglify给注释掉,这样至少能在一个未混淆压缩的文件里调试。如果大家有好的方法,麻烦告知,谢谢~

    5. 生成文件对应的 md5(版本号),并在 html 中引用文件时添加

    依赖模块:gulp-rev gulp-rev-collector

    说明:在实际生产环境中,我们页面引用的css和js文件的文件名都是带版本号的,这样方便回滚和防止缓存。通常我们使用文件的md5编码作为版本号。

    使用:

    var rev = require('gulp-rev'),
        revCollector = require('gulp-rev-collector');
    
    var cssDistFiles = [
        'app/prd/styles/index.css'
    ];
    
    var jsDistFiles = [
        'app/prd/scripts/index.js'
    ];
    
    // prd文件加md5后缀,并生成替换map
    gulp.task('ver', function() {
        gulp.src(cssDistFiles)
            .pipe(rev())
            .pipe(gulp.dest('app/prd/styles'))  // 生成 name-md5.css
            .pipe(rev.manifest()) 
            .pipe(gulp.dest('app/ver/styles')); // 生成 rev-manifest.json(映射)
    
        gulp.src(jsDistFiles)
            .pipe(rev())
            .pipe(gulp.dest('app/prd/scripts'))
            .pipe(rev.manifest())
            .pipe(gulp.dest('app/ver/scripts'));
    });
    
    // html文件添加md5引用
    gulp.task('html', function() {
        gulp.src(['app/ver/**/*.json', 'app/*.html'])   
            .pipe(revCollector())
            .pipe(gulp.dest('app/'));
    });
    

    结果如下:

    index.html
    
    <script src="prd/scripts/index.js"></script>
    =>
    <script src="prd/scripts/index-0884a4f91b.js"></script>
    

    注意:

    1. 我把生产md5和替换html中的版本号拆分为了两步,之前是放在一起,结果会出现用上一次版本号替换html中文件名的问题。

    6. 将外部引用的资源文件内联到 html 中

    依赖模块:gulp-inline-source

    说明:一些touch上的活动页,样式和脚本都不多,与其增加额外的请求数,不如把样式和脚本都以内联的方式嵌到html文件中。

    使用:

    gulpfile.js
    // 把css、js以inline的形式插入html
    gulp.task('inlinesource', function () {
        return gulp.src('app/index.html')
            .pipe(inlinesource())
            .pipe(gulp.dest('dist'));
    });
    
    index.html
    <link rel="stylesheet" href="../prd/styles/index.css " inline>
    <script src="../prd/scripts/index.js" inline></script>
    

    这一个月来,我用到的基本就这么多了,其实回头看看,自己东拼西凑也算是造了一个自己的构建小工具。同时,我也更深入地理解了fekit的设计思路和一些原理。

    不过,类似FIS的smarty模板、fekit的velocity mock等功能,我还没发现怎么在gulp中来实现。简单的说,就是能在前端环境开发jsp、velocity或者php(我目前的需求是php),数据采用mock方式,不依赖于后端,从而把view的控制权完全拿到前端,实现前后端的分离(非ajax方式)。如果大家有任何建议,麻烦指教!


    更新于08.11

    7、模版层面的前后端分离

    依赖模块: gulp-swig

    说明:swig 是一个类django、twig模板的前端模版,说是类似,基本语法其实一样,这样前端开发用swig,后端用对应的模板引擎(比如python的django、php用twig等),这样一套模版文件在前后端都能解析,从而实现前后端分离。

    由于swig和twig在一些语法上存在差异,我们需要扩展swig:

    swig.setDefaults({
        cache: false,
        loader: swig.loaders.fs(path.join(appbase)),
        locals: {
            environment: "local", // 全局变量,表示本地环境,用于区分swig和twig不一样的地方
            range: function (start, end) {
                return (new Array(end-start+1)).join().split(',').map(function (n, idx) { return idx + start; });
            }
        }
    });
    swig.setFilter('length', function (input) {
      return input.length;
    });
    swig.setFilter('slice', function(input, begin, len){
        return input.slice(begin, len);
    });
    swig.setFilter('json_encode', function(input){
        return JSON.stringify(input);
    });
    swig.setFilter('replace', function(input, obj) {
        var output = input;
        for (var key in obj) {
            output = output.replace(key, obj[key]);
        }
        return output;
    });

    另外我在locals里面设置了environment这个字段,这样在某些地方可以通过environment判断是否是本地环境,从而解决swig和twig不兼容的问题:

    {% if environment == 'local' %}
        {% set tpl_path = './components/ctn_publish/' + type + '/index.tpl' %}
    {% else %}
        {% set tpl_path = './components/ctn_publish/' ~ type ~ '/index.tpl' %}
    {% endif %}
  • 相关阅读:
    ES6新特性概览
    JavaScript一些不常用的写法
    使用HTML5的十大原因
    利用HTML5开发Android(7)---HTML5本地存储之Database Storage
    利用HTML5开发Android(6)---构建HTML5离线应用
    利用HTML5开发Android(5)---HTML5地理位置服务在Android中的应用
    利用HTML5开发Android(4)---HTML5本地存储之Web Storage
    利用HTML5开发Android(3)---Android中的调试
    利用HTML5开发Android(2)---Android中构建HTML5应用
    javascript中的prototype和constructor
  • 原文地址:https://www.cnblogs.com/chris-oil/p/5277700.html
Copyright © 2011-2022 走看看