自动提取HTML中的JS进行合并与压缩

赵融
• 阅读 6041

前段时间公司做网站的优化,其中就有将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中即可。

由于本人能力水平所限,文中难免出错,欢迎指正。另如果大家有好的实现方法请提出,学习一下

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Peter20 Peter20
4年前
mysql中like用法
like的通配符有两种%(百分号):代表零个、一个或者多个字符。\(下划线):代表一个数字或者字符。1\.name以"李"开头wherenamelike'李%'2\.name中包含"云",“云”可以在任何位置wherenamelike'%云%'3\.第二个和第三个字符是0的值wheresalarylike'\00%'4\
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这