在项目开发过程中和发布阶段需要在开发环境(dev)和生产环境(pro)之间切换,静态文件引用的切换等等。
使用grunt要如何解决上述问题,这里提供一个案列供参考。
用到的grunt插件:
文件合并:grunt-contrib-concat
javascript压缩:grunt-contrib-uglify
css 压缩:grunt-css
临时文件清理:grunt-contrib-clean
javascript代码检测:grunt-contrib-jshint
文件替换插件:grunt-string-replace
根据内容是否变化生成有哈希值文件名插件:grunt-rev
插件的具体用法可以到npm官网查看:https://www.npmjs.org/
在dev与pro之间切换的时候我们需要把页面上引用的未压缩合并的静态文件(A)和压缩合并后的文件(B)进行对应的切换操作,并且当文件内容改变后需要重新生成新的压缩合并文件用来处理cdn缓存问题。
考虑到随时可以进行环境切换所以项目中静态文件保留两份A和B,
在view页面上引入静态文件的地方加上标记用来方便查找切换,比如:
<!-- grunt-import-css bootstripCss --> <link href="/Css/dest/603d3616.bootstrip.min.css" rel="stylesheet" /> <!--/grunt-import --> <!-- grunt-import-js mainJs --> <script src="/Js/dest/3e083a76.main.min.js"></script> <!--/grunt-import -->
标记自己配置的,只要方便查找就行。
在切换的时候遍历对应的文件夹里面所有的文件,查找文件里面出现匹配的标记,然后替换。(我这里用的是grunt-string-replace插件 ,也有类似其他的插件)
从dev切换到pro的时候需要检测压缩和的文件内容是否变化,变化了就生成对应的新的文件。
Gruntfile.js文件:
1 module.exports = function(grunt) { 2 var fs = require('fs'); 3 4 // 配置 5 var isDev = false; //is develop 6 7 var cssLink = '<link href="importUrl" rel="stylesheet" />', 8 jsLink = '<script src="importUrl"></script>'; 9 //视图文件路径 10 var viewPath = 'Views'; 11 //dev、pro环境对应静态文件关联配置 12 var staticConfig = { 13 'bootstripCss':{ 14 dev:[ 15 'Css/bootstrap.css', 16 'Css/font-awesome.min.css' 17 ], 18 pro:'Css/dest/bootstrip.min.css' 19 }, 20 'IEhtml5Js':{ 21 dev:[ 22 'Js/html5shiv.js', 23 'Js/respond.min.js' 24 ], 25 pro:'Js/dest/IEhtml5Js.min.js' 26 }, 27 'mainJs':{ 28 dev:['Js/Common.js',Js/main.js'], 29 pro:'/Js/dest/main.min.js' 30 } 31 }; 32 33 //concatConfig合并配置 uglifyJsConfig js压缩配置 cssminConfig css压缩配置 34 var concatConfig = {}, uglifyJsConfig = {}, cssminConfig = {}; 35 var fileType = 'js'; 36 var proFiles = []; //所有生产环境文件 37 for(var i in staticConfig){ 38 if(/css$/i.test(i)){ 39 fileType = 'css'; 40 }else if(/js$/i.test(i)){ 41 fileType = 'js'; 42 } 43 proFiles.push(staticConfig[i]['pro']); 44 //配置合并的文件 45 concatConfig[i] = { 46 'files' : {} //{a:[b,c]} 目标文件,源文件 47 }; 48 if(staticConfig[i]['options']){ 49 //合并配置项 50 concatConfig[i]['options'] = staticConfig[i]['options']; 51 } 52 //合并的文件临时存放目录tmp 53 concatConfig[i]['files']['tmp/concat/'+i+'.'+fileType] = staticConfig[i]['dev'].concat([]); 54 //js 压缩 55 if(fileType == 'js'){ 56 uglifyJsConfig[i] = { 57 'files' : {} //{a:[b,c]} 目标文件,源文件 58 }; 59 uglifyJsConfig[i]['files'][staticConfig[i]['pro']] = ['tmp/concat/'+i+'.'+fileType]; 60 if(staticConfig[i]['options']){ 61 //压缩配置项 62 uglifyJsConfig[i]['options'] = staticConfig[i]['options']; 63 }else{ 64 uglifyJsConfig[i]['options'] = { 65 banner : '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */ ' 66 } 67 } 68 }else if(fileType == 'css'){ 69 //css 压缩 70 cssminConfig[i] = { 71 'files' : {} //{a:[b,c]} 目标文件,源文件 72 }; 73 cssminConfig[i]['files'][staticConfig[i]['pro']] = ['tmp/concat/'+i+'.'+fileType]; 74 if(staticConfig[i]['options']){ 75 //压缩配置项 76 cssminConfig[i]['options'] = staticConfig[i]['options']; 77 } 78 } 79 } 80 //获取对应路径里的文件 81 function getFileInfoFn(path){ 82 var fileInfo = [], 83 files = fs.readdirSync(path); 84 files.forEach(function(item) { 85 var tmpPath = path + '/' + item; 86 var stat = fs.lstatSync(tmpPath); 87 if (!stat.isDirectory()){ 88 fileInfo.push({'file':tmpPath,'cTime':fs.statSync(tmpPath).ctime}) 89 } else { 90 fileInfo = fileInfo.concat(getFileInfoFn(tmpPath)); 91 } 92 }); 93 return fileInfo; 94 } 95 96 //视图文件 97 var viewFiles = getFileInfoFn(viewPath); 98 //replaceConfig 在切换dev、pro环境时需要替换文件路径的视图文件配置 99 //gruntImportReg 替换的正则 100 var gruntImportReg = /<!--s*grunt-import-w+s+w+s*-->[sS]*?<!--s*/grunt-imports*-->/ig; 101 var gruntImportItemReg = /(<!--s*grunt-import-(w+)s+(w+)s*-->)([sS]*?)(<!--s*/grunt-imports*-->)/i; 102 var replaceConfig = { 103 'dist':{ 104 options: { 105 replacements: [ 106 { 107 pattern: gruntImportReg, 108 replacement: function(matchStr){ 109 //搜索合并压缩的最新文件 110 var fileInfo = getFileInfoFn('/Js/dest').concat(getFileInfoFn('Css/dest')); 111 fileInfo = fileInfo.sort(function(a, b){ 112 return a['cTime'] - b['cTime']; 113 }) 114 for(var i in staticConfig){ 115 var proFile = staticConfig[i]['pro'].split('/'); 116 proFile = proFile[proFile.length -1].replace(/./g,'\.'); 117 fileInfo.forEach(function(v, k){ 118 if(new RegExp("\."+proFile).test(v['file'])){ 119 staticConfig[i]['pro'] = v['file']; 120 return false; 121 } 122 }) 123 } 124 125 gruntImportItemReg.lastIndex = 0; 126 var matchItem = matchStr.match(gruntImportItemReg); 127 var files = [], importLink = '', 128 ret = matchItem[1]+' '; 129 if(isDev){ 130 files = staticConfig[matchItem[3]]['dev']; 131 }else{ 132 files = [staticConfig[matchItem[3]]['pro']]; 133 } 134 if(matchItem[2] == 'js'){ 135 importLink = jsLink; 136 }else if(matchItem[2] == 'css'){ 137 importLink = cssLink; 138 } 139 files.forEach(function(v, k){ 140 ret += importLink.replace('importUrl', v); 141 ret += ' '; 142 }); 143 ret += matchItem[5]; 144 return ret; 145 } 146 } 147 ] 148 }, 149 files:{} 150 } 151 }; 152 viewFiles.forEach(function(v, k){ 153 replaceConfig['dist']['files'][v['file']] = v['file']; 154 }); 155 //grunt 配置 156 grunt.initConfig({ 157 'pkg' : grunt.file.readJSON('package.json'), 158 'concat' : concatConfig, //合并任务 159 'uglify' : uglifyJsConfig, //uglify js 压缩, 160 'cssmin': cssminConfig, //css 压缩, 161 'clean': { 162 test: ['tmp'] //创建的临时文件 163 }, 164 'jshint': { 165 js: ['Js/*.js', 'Js/**/*.js','Js/**/**/*.js'] 166 }, 167 'string-replace':replaceConfig, //替换路径 168 'watch': { 169 scripts: { 170 files: ['Js/*.js', 'Js/**/*.js','Js/**/**/*.js'], 171 tasks: ['jshint'] 172 } 173 }, 174 'rev': { 175 files: { 176 src: proFiles 177 } 178 } 179 }); 180 181 // loadNpmTasks 182 grunt.loadNpmTasks('grunt-contrib-concat'); 183 184 grunt.loadNpmTasks('grunt-contrib-uglify'); 185 186 grunt.loadNpmTasks('grunt-css'); 187 //clear 188 grunt.loadNpmTasks('grunt-contrib-clean'); 189 190 grunt.loadNpmTasks('grunt-contrib-jshint'); 191 192 //dev、production 193 194 grunt.loadNpmTasks('grunt-string-replace'); 195 196 grunt.loadNpmTasks('grunt-contrib-watch') 197 198 grunt.loadNpmTasks('grunt-rev'); 199 200 //注册任务: 201 202 // 默认任务 203 grunt.registerTask('default', ['concat', 'uglify','cssmin','clean']); 204 205 grunt.registerTask('jsHint', ['jshint']); 206 207 grunt.registerTask('watch', ['watch']); 208 209 //根据文件内容生产文件 210 grunt.registerTask('setCacheFile',['rev']); 211 //切换dev pro 环境 212 grunt.registerTask('transfer',['string-replace']); 213 214 grunt.registerTask('quick', ['default', 'setCacheFile', 'transfer']); 215 216 };
package.json:
1 { 2 "name": "test2", 3 "version": "0.1.0", 4 "author": "bossliu", 5 "homepage": "###", 6 "devDependencies": { 7 "grunt": "~0.4.0", 8 "grunt-contrib-clean":"~0.4.0rc5", 9 "grunt-contrib-jshint": "~0.1.1rc5", 10 "grunt-contrib-uglify": "~0.1.2", 11 "grunt-contrib-concat": "~0.1.1", 12 "grunt-string-replace":"~0.2.7", 13 "grunt-contrib-watch":"~0.6.1", 14 "grunt-rev":"~0.1.0", 15 "grunt-css": ">0.0.0" 16 } 17 }
index.html:
<!DOCTYPE html> <html> <head> <title>grunt test</title> <!-- grunt-import-css bootstripCss --> <link href="Css/dest/603d3616.bootstrip.min.css" rel="stylesheet" /> <!--/grunt-import --> </head> <body> grunt test <!-- grunt-import-js IEhtml5Js --> <script src="Js/dest/56b83730.IEhtml5Js.min.js"></script> <!--/grunt-import --> <!-- grunt-import-js mainJs --> <script src="Js/dest/3e083a76.main.min.js"></script> <!--/grunt-import --> </body> </html>
参考文档:
http://www.infoq.com/cn/news/2014/03/env-spec-build-tool-compare/
http://www.infoq.com/cn/articles/front-end-engineering-and-performance-optimization-part1