前端工程自动批量压缩代码

一、情景描述

对已有MPA项目源代码source下的Html、Js及Css文件代码进行压缩,并将压缩代码生成于dist目录。

二、代码实现

在source同级目录下新建compress.js:

const { log } = require('console');
let fs = require('graceful-fs');
let path = require('path');
let UglifyJS = require("uglify-js");
let minify = require('html-minifier').minify;
let CleanCSS = require('clean-css');

let distDir = path.join(__dirname, './dist'); //压缩后的目录
let sourceDir = path.join(__dirname, './source'); //项目源代码
let showCompress = true;
let minifyJS = showCompress ? {
  compress: {
    warnings: false,
    drop_debugger: true,
    drop_console: true
  }
} : true;//配置压缩js,showCompress为true时压缩代码并去除console,debugger控制台提示,正式发布上线可开启,否则只压缩js

deleteFolder(distDir); //清除打包后的目录

// 清除目录
function deleteFolder(paths) {
  var files = [];
  if (fs.existsSync(paths)) {
    files = fs.readdirSync(paths);
    files.forEach(function (file, index) {
      var curPath = paths + "/" + file;
      if (fs.statSync(curPath).isDirectory()) { // recurse
        deleteFolder(curPath);
      } else { // delete file
        fs.unlinkSync(curPath);
      }
    });
    fs.rmdirSync(paths);
  }
}

var copyFile = function (srcPath, tarPath, cb) {
  var rs = fs.createReadStream(srcPath)
  rs.on('error', function (err) {
    if (err) {
      console.log('read error', srcPath)
    }
    cb && cb(err)
  })

  var ws = fs.createWriteStream(tarPath)
  ws.on('error', function (err) {
    if (err) {
      console.log('write error', tarPath)
    }
    cb && cb(err)
  })
  ws.on('close', function (ex) {
    cb && cb(ex)
  })

  rs.pipe(ws)
}
var copyFolder = function (srcDir, tarDir, cb) {

  fs.readdir(srcDir, function (err, files) {
    var count = 0
    var checkEnd = function () {
      ++count == files.length && cb && cb()
    }

    if (err) {
      checkEnd()
      return
    }

    files.forEach(function (file) {
      var srcPath = path.join(srcDir, file)
      var tarPath = path.join(tarDir, file)

      fs.stat(srcPath, function (err, stats) {
        if (stats.isDirectory()) {
          console.log('mkdir', tarPath)
          fs.mkdir(tarPath, function (err) {
            if (err) {
              console.log(err)
              return
            }

            copyFolder(srcPath, tarPath, checkEnd)
          })
        } else {
          copyFile(srcPath, tarPath, checkEnd)
        }
      })
    })

    //为空时直接回调
    files.length === 0 && cb && cb()
  })
}

//创建dist目录
function creatDir(dir) {
  return new Promise((resolve, reject) => {
    fs.mkdir(dir, function (err) {
      if (err) {
        reject(err);
      } else {
        console.log("目录创建成功。");
        resolve()

      }
    });
  })
}

/**
 * 文件遍历方法
 * @param filePath 需要遍历的文件路径
 */
function fileDisplay(filePath) {
  //根据文件路径读取文件,返回文件列表
  fs.readdir(filePath, function (err, files) {
    if (err) {
      console.warn(err)
    } else {
      //遍历读取到的文件列表
      files.forEach(function (filename) {
        //获取当前文件的绝对路径
        var filedir = path.join(filePath, filename);
        //根据文件路径获取文件信息,返回一个fs.Stats对象
        fs.stat(filedir, function (eror, stats) {
          if (eror) {
            console.warn('获取文件stats失败');
          } else {
            var isFile = stats.isFile(); //是文件
            var isDir = stats.isDirectory(); //是文件夹
            if (isFile && /\.htm/.test(filedir)) { //压缩.htm或.html文件
              miniHtml(filedir)
            }
            if (isFile && /\.js$/.test(filedir)) { //压缩js文件
              miniJs(filedir)
            }
            if (isFile && /\.css$/.test(filedir)) { //压缩css文件
              miniCss(filedir)
            }
            if (isDir) {
              fileDisplay(filedir); //递归,如果是文件夹,就继续遍历该文件夹下面的文件
            }
          }
        })
      });
    }
  });
}

function miniCss(filedir){
  console.log(filedir)
  fs.readFile(filedir, 'utf8', function (err, data) {
    if (err) {
      throw err;
    }
    let options = { /* options */ };
    let result = new CleanCSS(options).minify(data);
    if(result.warnings.length>0){//压缩错误处理
      console.log(filedir,result.warnings);
    }else{
      fs.writeFile(filedir,result.styles, function () {
        console.log(filedir,'success');
      });
    }
   
  });
}
function miniJs(filedir){
  fs.readFile(filedir, 'utf8', function (err, data) {
    if (err) {
      throw err;
    }
    let result = UglifyJS.minify(data);
    if (!result.code) {//压缩错误处理
      console.log(filedir, result.error);
    } else {
      fs.writeFile(filedir,result.code , function () {
        console.log(filedir,'success');
      });
    }
  });
}
function miniHtml(filedir){
  fs.readFile(filedir, 'utf8', function (err, data) {
    if (err) {
      throw err;
    }

    fs.writeFile(filedir, minify(data, { //主要压缩配置
      processScripts: ['text/html'],
      collapseWhitespace: true,
      minifyJS: minifyJS,
      minifyCSS: true,
      removeComments: true, //删除注释
      removeCommentsFromCDATA: true, //从脚本和样式删除的注释
      continueOnParseError: true
    }), function () {
      console.log(filedir,'success');
    });
  });
}

// 开始压缩
creatDir(distDir).then(() => {
  copyFolder(sourceDir, distDir, function (err) {
    if (err) {
      return
    }
    console.log('代码复制完成');
    fileDisplay(distDir);
  });
}).catch((err) => {

});

考虑到浏览器缓存问题比较常见,对于Html文件内引入的js及css链接考虑携带上对应文件的MD5戳:

const { log } = require('console');
let fs = require('graceful-fs');
let path = require('path');
let UglifyJS = require("uglify-js");
let minify = require('html-minifier').minify;
let CleanCSS = require('clean-css');
let md5 = require('blueimp-md5')

let distDir = path.join(__dirname, './dist'); //压缩后的目录
let sourceDir = path.join(__dirname, './source'); //项目源代码
let showCompress = true;
let minifyJS = showCompress ? {
  compress: {
    warnings: false,
    drop_debugger: true,
    drop_console: true
  }
} : true;//配置压缩js,showCompress为true时压缩代码并去除console,debugger控制台提示,正式发布上线可开启,否则只压缩js

deleteFolder(distDir); //清除打包后的目录

// 清除目录
function deleteFolder(paths) {
  var files = [];
  if (fs.existsSync(paths)) {
    files = fs.readdirSync(paths);
    files.forEach(function (file, index) {
      var curPath = paths + "/" + file;
      if (fs.statSync(curPath).isDirectory()) { // recurse
        deleteFolder(curPath);
      } else { // delete file
        fs.unlinkSync(curPath);
      }
    });
    fs.rmdirSync(paths);
  }
}

var copyFile = function (srcPath, tarPath, cb) {
  var rs = fs.createReadStream(srcPath)
  rs.on('error', function (err) {
    if (err) {
      console.log('read error', srcPath)
    }
    cb && cb(err)
  })

  var ws = fs.createWriteStream(tarPath)
  ws.on('error', function (err) {
    if (err) {
      console.log('write error', tarPath)
    }
    cb && cb(err)
  })
  ws.on('close', function (ex) {
    cb && cb(ex)
  })

  rs.pipe(ws)
}
var copyFolder = function (srcDir, tarDir, cb) {

  fs.readdir(srcDir, function (err, files) {
    var count = 0
    var checkEnd = function () {
      ++count == files.length && cb && cb()
    }

    if (err) {
      checkEnd()
      return
    }

    files.forEach(function (file) {
      var srcPath = path.join(srcDir, file)
      var tarPath = path.join(tarDir, file)

      fs.stat(srcPath, function (err, stats) {
        if (stats.isDirectory()) {
          console.log('mkdir', tarPath)
          fs.mkdir(tarPath, function (err) {
            if (err) {
              console.log(err)
              return
            }

            copyFolder(srcPath, tarPath, checkEnd)
          })
        } else {
          copyFile(srcPath, tarPath, checkEnd)
        }
      })
    })

    //为空时直接回调
    files.length === 0 && cb && cb()
  })
}

//创建dist目录
function creatDir(dir) {
  return new Promise((resolve, reject) => {
    fs.mkdir(dir, function (err) {
      if (err) {
        reject(err);
      } else {
        console.log("目录创建成功。");
        resolve()

      }
    });
  })
}

/**
 * 文件遍历方法
 * @param filePath 需要遍历的文件路径
 */
function fileDisplay(filePath) {
  //根据文件路径读取文件,返回文件列表
  fs.readdir(filePath, function (err, files) {
    if (err) {
      console.warn(err)
    } else {
      //遍历读取到的文件列表
      files.forEach(function (filename) {
        //获取当前文件的绝对路径
        var filedir = path.join(filePath, filename);
        //根据文件路径获取文件信息,返回一个fs.Stats对象
        fs.stat(filedir, function (eror, stats) {
          if (eror) {
            console.warn('获取文件stats失败');
          } else {
            var isFile = stats.isFile(); //是文件
            var isDir = stats.isDirectory(); //是文件夹
            if (isFile && /\.htm/.test(filedir)) { //压缩.htm或.html文件
              miniHtml(filedir)
            }
            if (isFile && /\.js$/.test(filedir)) { //压缩js文件
              miniJs(filedir)
            }
            if (isFile && /\.css$/.test(filedir)) { //压缩css文件
              miniCss(filedir)
            }
            if (isDir) {
              fileDisplay(filedir); //递归,如果是文件夹,就继续遍历该文件夹下面的文件
            }
          }
        })
      });
    }
  });
}

function replacePath(content,exp,md5str) {
  var matchs = content.matchAll(exp)
  var arr = Array.from(matchs)
  arr.forEach(o=> {
    content = content.replace(o[1], o[1].indexOf('?')>=0?`${o[1]}&${md5str}`:`${o[1]}?${md5str}`)
  })

  return content
}

function appendMD5Str(data) {
  const scriptExp = /<script(?:(?:[^>])*(?:src="([^"]*))+[^>]*)?>(.*?)<\/script\s*>/ig
  const linkExp = /<link(?:(?:[^>])*(?:href="([^"]*))[^>]*)+>/ig;
  const md5str = md5(data)
  let content = replacePath(data, scriptExp, md5str)
  content = replacePath(content, linkExp, md5str)
  return content
}

function miniCss(filedir){
  console.log(filedir)
  fs.readFile(filedir, 'utf8', function (err, data) {
    if (err) {
      throw err;
    }
    let options = { /* options */ };
    let result = new CleanCSS(options).minify(data);
    if(result.warnings.length>0){//压缩错误处理
      console.log(filedir,result.warnings);
    }else{
      fs.writeFile(filedir,result.styles, function () {
        console.log(filedir,'success');
      });
    }
   
  });
}

function miniJs(filedir){
  fs.readFile(filedir, 'utf8', function (err, data) {
    if (err) {
      throw err;
    }
    let result = UglifyJS.minify(data);
    if (!result.code) {//压缩错误处理
      console.log(filedir, result.error);
    } else {
      fs.writeFile(filedir,result.code , function () {
        console.log(filedir,'success');
      });
    }
  });
}

function miniHtml(filedir){
  fs.readFile(filedir, 'utf8', function (err, data) {
    if (err) {
      throw err;
    }

    fs.writeFile(filedir, minify(appendMD5Str(data), { //主要压缩配置
      processScripts: ['text/html'],
      collapseWhitespace: true,
      minifyJS: minifyJS,
      minifyCSS: true,
      removeComments: true, //删除注释
      removeCommentsFromCDATA: true, //从脚本和样式删除的注释
      continueOnParseError: true
    }), function () {
      console.log(filedir,'success');
    });
  });
}

// 开始压缩
creatDir(distDir).then(() => {
  copyFolder(sourceDir, distDir, function (err) {
    if (err) {
      return
    }
    console.log('代码复制完成');
    fileDisplay(distDir);
  });
}).catch((err) => {

});

三、运行压缩

在compress.js文件所在目录运行:
node ./compress.js

四、问题记录

1、在使用 var fs=require('fs')时,由于读取文件时打开的文件太多导致异常:

在这里插入图片描述

解决方案:

var fs=require('fs')改用 const fs = require('graceful-fs');

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓风伴月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值