前段时间公司做网站的优化,其中就有将HTML文件中用到的多个JS压缩成一个min.js。现在做一个总结:
css js 压缩用的是 gulp,只要写一个gulpfile.js脚本即可,非常方便
css 目前只是将每个源CSS文件压缩了一下,没有进行合并。
JS 做了两步工作
A. 将每个源JS文件压缩
这个简单,不细说,下面会贴出代码。
gulp.task('minifyjs-root',fistGulpTask, function() {
return gulp.src(rootDir+'/*.js').pipe(gulp.dest(rootDir+'js')).pipe(rename({suffix: ''})).pipe(uglify()).pipe(gulp.dest(rootDir+'js'));
});
B. 将HTML文件中包含的JS合并为一个JS并且进行压缩
我当时第一个想法就是先将HTML文件用到的JS手动找出来放到gulpfile.js中的一个数组中,然后再将它合并成一个min.js。
如HTML文件中包含myjs1.js, myjs2.js, myjs3.js,要将它们合并压缩为myjs.min.js.
源HTML文件(本地开发文件)
<!-- JS_MERGE_FLAG:this is flag for js files join to one min.js -->
<script type="text/javascript" src="sresource/js/myjs1.js"></script>
<script type="text/javascript" src="sresource/js/myjs2.js"></script>
<script type="text/javascript" src="sresource/js/myjs3.js"></script>
合并压缩为myjs.min.js(发布文件)
<!-- JS_MERGE_FLAG:this is flag for js files join to one min.js -->
<script type="text/javascript" src="sresource/js/myjs.min.js"></script>
可以发现这种做法会导致本地开发环境的HTML文件与发布时的不一样。那就有下列问题
1.发布时手动来修改HTML文件吗?(将引用的JS修改为myjs.min.js)
2.当开发新功能时HTML要增加JS,那我就要修改gulpfile.js中的那个数组。
3.当有多个HTML文件要合并压缩引用到的JS,那就要在gulpfile.js文件中维护多个数组。
4.当有部署时有使用jenkins打包(不能手动修改HTML文件)时,我应该怎么办。维护两个HTML,
一个是本地开发环境的HTML,另一个是发布的?
发现有这些问题的存在将使得工程难以维护,或者说发布时胆战心惊。
当时有发现gulp是构建在node.js基础上的,那么能不能让nodejs去查找html文件中引用了哪些js,并且将它们提取出来。
最后用它修改HTML文件(为发布文件)。好!有想法,那就试一试呗。下面是我的代码。可以实现这个功能。
//gulpfile.js
var gulp = require('gulp'),
minifycss = require('gulp-minify-css'),
concat = require('gulp-concat'),
uglify = require('gulp-uglify'),
rename = require('gulp-rename'),
jshint=require('gulp-jshint');
var glpTask = ['minifycss','minifyjs-root'];
//var glpTask = [];
var fistGulpTask=[];
var fs = require('fs');
var path = require('path');
var rootDir = 'src/main/webapp/sresource/'; //这个目录下有 js html css 文件夹
var tmpDir = 'src/main/webapp/tmpjs';
var webappDir = 'src/main/webapp/';
var sourceDir = rootDir+'js';
var destDir = sourceDir;
funcCompressJs();
function funcGulp(){
gulp.task('minifycss', fistGulpTask, function() {
return gulp.src([rootDir+'css/*.css',rootDir+'css/*/*.css'])
.pipe(rename({suffix: ''}))
.pipe(minifycss())
.pipe(gulp.dest(rootDir+'css/'));
});
gulp.task('minifyjs-root',fistGulpTask, function() {
return gulp.src(rootDir+'js'+'/*.js').pipe(gulp.dest(rootDir+'js')).pipe(rename({suffix: ''})).pipe(uglify()).pipe(gulp.dest(rootDir+'js'));
});
// 其它压缩任务
gulp.task('default',[],function() {
gulp.start(fistGulpTask.concat(glpTask));
});
}
function funcCompressJs() {
for (i = 0; i < joinJsHtml.length; i++) {
funcHtmlJoinJsPro(webappDir + joinJsHtml[i]);
}
funcGulp();
}
//js合并
function funcJsMerge(taskName, srcJs, targetJs){
console.log("taskName:"+taskName+", targetJs:"+targetJs);
console.log("source js files:");
for(var i=0; i<srcJs.length; i++){
console.log(" "+srcJs[i]);
}
gulp.task(taskName, function(){
return gulp.src(srcJs) //需要操作的文件
.pipe(concat(targetJs)) //合并所有js到main.js
.pipe(gulp.dest(rootDir+'js')) //输出到文件夹
.pipe(rename({suffix: '.min'})) //rename压缩后的文件名
.pipe(uglify()) //压缩
.pipe(gulp.dest(rootDir+'js')); //输出
});
}
//要合并JS的HTML
var jsMergeFg = "JS_MERGE_FLAG"; //<!--JS_MERGE_FLAG-->这个标志意味着从这里开始的JS 要合并为一个
var joinJsHtml = ["mb.html"]; //要进行JS合并的HTML文件
function funcHtmlJoinJsPro(file) {
var readmaxLen = 3072;
var writeBuffer;
var readBuffer = new Buffer(readmaxLen);
var strReadBuf;
var taskName = file.substring(file.lastIndexOf('/')+1);
var targetName = taskName.replace(/\./g,"-");
var targetStr = "";
taskName = "minifyjs-"+targetName;
var fd;
var i=0;
var fileStats = fs.statSync(file);
var offset = fileStats.size-readmaxLen;
//console.log("-1---offset:"+offset);
fd = fs.openSync(file, 'r+');
if(!fd){
console.error("open file "+file+" error!");
throw "open file "+file+" error!";
return;
}
var readCunt = fs.readSync(fd, readBuffer, 0, readBuffer.length, offset);
strReadBuf = readBuffer.toString();
//console.log("strReadBuf:"+strReadBuf);
var startIndex = strReadBuf.indexOf(jsMergeFg);
//console.log("indexOf(jsMergeFg):"+startIndex);
if(startIndex<0){
console.error("file:"+file+" can not find js join flag:"+jsMergeFg);
console.log("read finle content:"+strReadBuf);
console.log("read finle length:"+strReadBuf.length);
throw "file:"+file+" can not find js join flag:"+jsMergeFg;
return ;
}
var lastIndex = strReadBuf.lastIndexOf('</script>')+'</script>'.length;
var endIndex = strReadBuf.lastIndexOf('</html')+'</html>'.length;
//console.log('1-startIndex'+startIndex+'---strReadBuf.length'+strReadBuf.length+'-----strReadBuf--'+strReadBuf);
var joinFgStr = strReadBuf.substring(startIndex);
//console.log("--1--d--joinFgStr:"+joinFgStr);
var tempStrAry = [];
var tempIndex = joinFgStr.indexOf("-->")+'-->'.length;
startIndex += tempIndex;
//console.log("1--1--joinFgStr:"+joinFgStr);
//console.log("----startIndex:"+startIndex+", tempIndex:"+tempIndex);
joinFgStr = joinFgStr.substring(tempIndex);
//console.log("--1----joinFgStr:"+joinFgStr);
var constStr = strReadBuf.substring(0, startIndex);
var constBuf = new Buffer(constStr);
var constLen = constBuf.length;
offset += constLen;
//console.log("1----constStr:"+constStr);
tempStrAry = joinFgStr.split(/\r|\n/g);
//提取要压缩的JS
var souceJs=[];
var sourceJsStr;
for(i=0; i<tempStrAry.length; i++){
sourceJsStr = tempStrAry[i].match(/\"([^\"]+)\.js/g);
if(sourceJsStr) {
if(sourceJsStr[0].indexOf('"/')==0) {
souceJs.push(sourceJsStr[0].replace(/^\"\//, '')); //1. 去掉 "/
}else if(sourceJsStr[0].indexOf('"')==0){
souceJs.push(sourceJsStr[0].replace(/^\"/, '')); //1. 去掉 "
}
}
}
//console.log("a---souceJs.length:"+souceJs.length+", souceJs:"+souceJs);
funcChangeSourceJs2TempJsDri(file,souceJs);
//console.log("souceJs.length:"+souceJs.length+", souceJs:"+souceJs);
var targetMinJS = targetName+'.min.js';
var targetJoinJS = targetName+'.js';
funcJsMerge(taskName, souceJs, targetJoinJS);
fistGulpTask.push(taskName);
//delFiles.push(targetMinJS);
var strHtml = '<script src="/sresource/js/'+targetMinJS+'" type="text/javascript"></script>';
targetStr = '\n' + strHtml;
targetStr += strReadBuf.substring(lastIndex);
//console.log("html:"+targetStr);
endIndex = targetStr.lastIndexOf('</html')+'</html>'.length;
var newLen = fileStats.size-new Buffer(joinFgStr).length+new Buffer(targetStr.substring(0,endIndex)).length;
writeBuffer = new Buffer(readmaxLen-constLen);
for(i=0; i<writeBuffer.length; i++){
writeBuffer[i]=13;
}
fs.writeSync(fd, writeBuffer, 0, writeBuffer.length, offset);
writeBuffer = new Buffer(targetStr);
fs.writeSync(fd, writeBuffer, 0, writeBuffer.length, offset);
fs.ftruncateSync(fd, newLen);
fs.closeSync(fd);
}
//将要合并的JS定位完全路径
function funcChangeSourceJs2TempJsDri(htmlFile, sourceJs){
var file = htmlFile;
var relPath;
var i=0;
for(i=0; i<sourceJs.length; i++) {
//源JS定位于sresource
if (sourceJs[i].indexOf('sresource') == 0) {
}else{
//相对路径形式 ../
relPath = path.resolve(file, sourceJs[i]);
sourceJs[i] = path.relative(rootDir, relPath);
}
}
for(i=0; i<sourceJs.length; i++) {
sourceJs[i] = webappDir+sourceJs[i];
}
}
注意我这里有几个重要的点
1. 将要合并压缩的JS放在HTML文件的尾部,有利于网页的加载反应速度。其它JS或者HTML依赖的JS才放在头部,如jQuery
2. 我在HTML文件中增加<!-- JS_MERGE_FLAG -->标志,这个模块下面的JS将会被合并为压缩为一个.min.js文件。
3. 我是将html文件的尾部读出来的,因为我的HTML文件比较大。
4. 我的工程目录是 src/main/webapp/sresource/, 以gulpfile.js所在的目录为根。代码中“sresource”字符串是与路径有关的,我没有提取出来(本人比较懒)。
代码流程
1. 将html文件的尾部读出来
2. 查找标志 JS_MERGE_FLAG ,从这个标志开始提取JS
3. JS文件的路径处理
4. 增加JS合并压缩GULP任务
5. 修改HTML文件
好了,现在只需要发布前在工程根目录下,打开CMD,输入gulp就OK了,如果配置有jenkins,只要将gulp命令配置到execute shell中即可。
由于本人能力水平所限,文中难免出错,欢迎指正。另如果大家有好的实现方法请提出,学习一下