liyang24 @meituan
Grunt基础介绍
Grunt API
Grunt Plugins
版本号生成Demo => fe-version
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!
.
|____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
编译工具
审查工具
上线前对静态资源的处理
编译工具:编译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']);
grunt.registerTask('pre',[
'clean:build',
'version:img',
'mt_cssimg',
'mtfe-grunt-buildmeta'
]);
预处理
给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文件是否存在
上线前依旧会执行buildmeta操作,
做JavaScript模块依赖最后的检查工作。
grunt.registerTask('build',[
'version:js', 'mt_uglify',
'checkimgext', 'mt_imagemin',
'version:css', 'mt_cssmin',
'rsync:build', 'after'
]);