在美团主站的应用

liyang24 @meituan

  • Grunt基础介绍

    • ​Grunt API

    • Grunt Plugins

  • 版本号生成Demo => fe-version

  • Grunt 在美团主站项目中的应用

    • 主站仓库中基于Grunt实现的工具

    • 上线发布脚本对静态文件的处理

Grunt 要干啥?

输入 + 配置 =》 输出

Gruntfile.js + packages.json(npm) + Task

Gruntfile.js => 配置

module.exports = function(grunt) {

  grunt.initConfig({
    jshint: {
      files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
      options: {
        globals: {
          jQuery: true
        }
      }
    },
    watch: {
      files: ['<%= jshint.files %>'],
      tasks: ['jshint']
    }
  });

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');

  grunt.registerTask('default', ['jshint']);

};

packages.json => 依赖

{
  "name": "fe-version",
  "version": "1.0.0",
  "description": "version number of static files.",
  "main": "lib/version.js",
  "scripts": {
    "test": "jest"
  },
  "keywords": [
    "version"
  ],
  "author": "solome",
  "license": "MIT",
  "devDependencies": {
    "jest-cli": "^0.4.0"
  },
  "dependencies": {
    "grunt": "^0.4.5",
    "grunt-contrib-watch": "^0.6.1"
  }
}

Task => 具体的处理逻辑


module.exports = function(grunt) {

    var version = require('../lib/version');

    grunt.registerMultiTask('fe-version', '给静态文件添加版本号', function() {
        var task = this;
        var options = task.options();

        var files = task.files;

        files.forEach(function(file){
            file.src.forEach(function(f) {
                var v = version(f);
                console.log(f, '=>', version(f));
                grunt.file.copy(f, f + '.' + v);
            });
        });
    });
}

Grunt API

不建议过度使用Grunt API,容易造成与Grunt的深耦合!

Grunt Plugins

JavaScript Plugins

CSS Plugins

Other Plugins

Grunt最大的优势:官方提供了许多高质量的Plugins

.
|____Gruntfile.js
|____lib
| |____version.js
|____package.json
|____task
| |____fe-version.js
|____test
| |____test.js

核心逻辑:lib/version.js

'use strict';

var crypto = require('crypto');
var fs = require('fs');

/**
 * 计算文件的md5加密值
 */
function md5(fp) {
    var md5 = crypto.createHash('md5');

    md5.update(fs.readFileSync(fp));
    return md5.digest('hex').substring(0, 8);
}

/**
 * 获取文件的版本号
 */
function version(file) {
    if (fs.existsSync(file)) {
        return md5(file);
    }

    return false;
}

module.exports = version;

npm依赖包:packages.json

{
  "name": "fe-version",
  "version": "1.0.0",
  "description": "version number of static files.",
  "main": "lib/version.js",
  "scripts": {
    "test": "jest"
  },
  "keywords": [
    "version"
  ],
  "author": "solome",
  "license": "MIT",
  "devDependencies": {
    "jest-cli": "^0.4.0"
  },
  "dependencies": {
    "grunt": "^0.4.5",
    "grunt-contrib-watch": "^0.6.1"
  }
}

Task模块:task/fe-version.js

'use strict';


module.exports = function(grunt) {

    var version = require('../lib/version');

    grunt.registerMultiTask('fe-version', '给静态文件添加版本号', function() {
        var task = this;
        var options = task.options();

        var files = task.files;

        files.forEach(function(file){
            file.src.forEach(function(f) {
                var v = version(f);
                console.log(f, '=>', version(f));
                grunt.file.copy(f, f + '.' + v);
            });
        });
    });
}

配置:Gruntfile.js

module.exports = function(grunt) {
    'use strict';

    var appPath = '/Users/ivanlyons/app/raspberrypi/';

    grunt.loadTasks('./task');

    // 配置
    grunt.initConfig({
        appPath: appPath,
        'fe-version': {
            src: ['<%= appPath %>/**/*.js','<%= appPath %>/**/*.css'],
            options: {
                flag: 'v'
            }
        },
        watch: {
            files: ['<%= appPath %>/**/*.js'],
            tasks: ['fe-version']
        }
    });

    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.file.setBase(appPath);
    grunt.registerTask('default', ['fe-version']);
};
$ npm install
$ grunt fe-version
.
|____css
| |____common.css
| |____common.css.3fb999ef
| |____ready2retire.css
| |____ready2retire.css.32fc7c82
|____js
| |____common.js
| |____common.js.7c5b2157
| |____jquery.min.js
| |____jquery.min.js.397754ba
| |____jquery.min.map
| |____ready2retire.js
| |____ready2retire.js.7a405d55

Grunt在主站的应用

  • 编译工具

  • 审查工具

  • 上线前对静态资源的处理

www仓库中的Grunt

  • 编译工具

  • 审查工具

编译工具:编译LightnCandy模板文件.hbs => .php

grunt.registerMultiTask(
    'candyCompile',
    'compile lightncandy template',
    function() {}
);
$ grunt candyCompile:file:{filePath}

$ grunt candyCompile:file:partials

=> grunt/task/candyCompile.js

// 快速配置一个component
grunt.loadNpmTasks('mtfe-grunt-component');
// JavaScript 模块依赖文件生成
grunt.loadNpmTasks('@mtfe/grunt-buildmeta');
// 将svg矢量图标编译成使用
grunt.loadNpmTasks('mtfe_fe.iconfont');
$ grunt mtfe-grunt-buildmeta
$ grunt iconfont

JavaScript 模块依赖文件生成

将svg矢量图标编译成字體文件

编译工具

$ grunt jshint

审查工具:保障源码质量

$ grunt csshint
$ grunt scsshint
$ grunt checkimgext
$ grunt phphint

检查JavaScript语法

检查CSS/SCSS源码

检查PHP源码语法

检查图片资源后缀名的合法性

这么多的Plugins・・・・!

主站www仓库所有的Grunt Task

上线发布工具

美团主站平均每天上线40次左右!

从前端工程师的角度来看,上线前都做了什么呢!?

每次上线平均耗时两分钟左右!

主要对静态文件的处理

grunt.registerTask('pre',
    ['clean:build', 'version:img', 'mt_cssimg', 'mtfe-grunt-buildmeta']
);

grunt.registerTask('build',
    ['version:js', 'mt_uglify', 'checkimgext', 'mt_imagemin',
     'version:css', 'mt_cssmin', 'rsync:build', 'after']
);

grunt.registerTask('default', ['build']);
  • 'clean:build' 清理目录
  • 'version:img' 给图片资源生成版本号
  • 'mt_cssimg' 给CSS文件中的图片url增加版本号
  • 'mtfe-grunt-buildmeta' 生成JavaScript文件依赖

 

grunt.registerTask('pre',[
    'clean:build',
    'version:img',
    'mt_cssimg',
    'mtfe-grunt-buildmeta'
]);

预处理

Task=> version

基于md5算法计算文件的版本号

  • version:css
  • version:js
  • version:img

Task => css_img

  • 给CSS文件中的图片url增加版本号

.slider-sprite{
    background-image:url(si/slider.png);
    background-repeat:no-repeat
}

.slider-sprite{
    background-image:url(si/slider.v32cb9c24.png);
    background-repeat:no-repeat
 }
  • 检查CSS文件中的图片url是否存在

=> 即判断si/slider.png文件是否存在

Task => mtfe-grunt-buildmeta

上线前依旧会执行buildmeta操作,

JavaScript模块依赖最后的检查工作。

  • mt_uglify          => 压缩JavaScript文件
  • checkimgext   => 检查图片资源后缀名的合法性
  • mt_imagemin => 压缩图片
  • mt_cssmin       => 压缩css文件
  • rsync                 => 拷贝构建涉及的静态文件到缓存目录
  • after                  => 生成version.json,供FileHelper使用
grunt.registerTask('build',[
    'version:js', 'mt_uglify',
    'checkimgext', 'mt_imagemin',
    'version:css', 'mt_cssmin',
    'rsync:build', 'after'
]);

构建:压缩 & 版本号文件生成

构建流程

本地模拟上线

Grunt vs Gulp

Thanks!

Grunt 在美團主站的應用

By Ivan Lyons

Grunt 在美團主站的應用

主要介绍Grunt在美团主站的应用,如javascript/scc压缩,模块依赖,源码审查等。 https://github.com/solome/fe-version

  • 2,547