大家好,这几天正好做站点优化,也看了写工具,在线的非在线的。
如:grunt,php的minify,yui等
今天给大家介绍下我引用了YUI做了在线的效果,主要生成文件以及合并CSS的效果。下面介绍下过程;
1、由于我使用的是Freemarker 去做的View 所有我做了个标签; package me.duzhi.blog.plugins.compress;
import com.jfinal.kit.StrKit;
import freemarker.core.Environment;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.BeansWrapperBuilder;
import freemarker.template.*;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* @author [email protected]
* @date 一月 07, 2017
*/
public class CompressDirective implements TemplateDirectiveModel {
@SuppressWarnings("rawtypes")
public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
throws TemplateException, IOException {
Object var = params.get("var");
if (var == null) {
throw new TemplateModelException("assets tag attribute var can not be null!");
}
String varName = var.toString();
if (StrKit.isBlank(varName)) {
throw new TemplateModelException("assets tag attribute var can not be null!");
}
Object file = params.get("file");
if (file == null) {
throw new TemplateModelException("assets tag attribute file can not be null!");
}
String fileName = file.toString();
if (StrKit.isBlank(fileName)) {
throw new TemplateModelException("assets tag attribute file can not be null!");
}
Object ido = params.get("id");
if (ido == null) {
throw new TemplateModelException("assets tag attribute id can not be null!");
}
String id = ido.toString();
if (StrKit.isBlank(id)) {
throw new TemplateModelException("assets tag attribute id can not be null!");
}
List path = CompressKit.getPath(id, fileName);
BeansWrapper beansWrapper = new BeansWrapperBuilder(Configuration.getVersion()).build();
for (String s : path) {
env.setVariable(varName, beansWrapper.wrap(s));
body.render(env.getOut());
}
}
}
上面是标签的代码;
在下面是核心代码: package me.duzhi.blog.plugins.compress;
import com.jfinal.kit.HashKit;
import com.jfinal.kit.PathKit;
import com.jfinal.kit.StrKit;
import com.jfinal.log.Log;
import com.yahoo.platform.yui.compressor.CssCompressor;
import com.yahoo.platform.yui.compressor.JavaScriptCompressor;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.mozilla.javascript.ErrorReporter;
import org.mozilla.javascript.EvaluatorException;
import java.io.*;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author [email protected]
* @date 一月 07, 2017
*/
public class CompressKit {
private static final Log log = Log.getLog(CompressKit.class);
private static final Charset UTF_8 = Charset.forName("UTF-8");
private static final String JS_EXT = ".js", CSS_EXT = ".css";
private static final String PROTOCOL = "^https?://.+$";
// 考虑到线上环境基本不会频繁更改css,js文件为了性能故缓存
public static ConcurrentMap COMBO_MAP = new ConcurrentHashMap();
/**
* 压缩工具
*
*
* @param name
* @param fileName 待压缩的文件列表文件 /assets/assets.jjs
* @return String 返回压缩完成之后的路径
* @throws IOException 文件不存在时异常
*/
public static List getPath(String name, String fileName) throws IOException {
List pathList = new ArrayList();
String rootPath = PathKit.getWebRootPath();
// 路径判读
if (!fileName.startsWith("/")) {
fileName = "/" + fileName;
}
String path = COMBO_MAP.get(fileName);
if (StrKit.isBlank(path)) {
return combo(rootPath, fileName);
}
File assetsFile = new File(rootPath + path);
// 文件存在则直接返回路径
if (assetsFile.exists()) {
pathList.add(path);
return pathList;
}
return combo(rootPath, fileName);
}
/**
* 压缩css,js帮助
*
* @param rootPath 项目路径
* @param fileList 合并压缩的文件列表
* @param isCss 是否是css
* @param out 输出流
* @throws IOException Io异常
*/
private static void compressorHelper(String rootPath, List fileList, boolean isCss, Writer out) throws IOException {
Reader in = null;
InputStream input = null;
try {
if (isCss) {
for (String path : fileList) {
boolean isRomte = isRomte(path);
input = isRomte ? new URL(path).openStream() : new FileInputStream(rootPath + path);
// css 文件内容,并处理路径问题
String context = repairCss(IOUtils.toString(input, UTF_8), path);
if (path.indexOf(".min.") > 0 || isRomte) {// 对.min.css的css放弃压缩
out.append(context);
} else {
CssCompressor css = new CssCompressor(new StringReader(context));
css.compress(out, -1);
}
input.close();
input = null;
}
} else {
// nomunge: 混淆,verbose:显示信息消息和警告,preserveAllSemiColons:保留所有的分号 ,disableOptimizations 禁止优化
boolean munge = true, verbose = false, preserveAllSemiColons = false, disableOptimizations = false;
for (String path : fileList) {
boolean isRomte = isRomte(path);
input = isRomte ? new URL(path).openStream() : new FileInputStream(rootPath + path);
in = new InputStreamReader(input, UTF_8);
if (path.indexOf(".min.") > 0 || isRomte) { // 对.min.js,和远程js放弃压缩
out.append(IOUtils.toString(in));
} else {
JavaScriptCompressor compressor = new JavaScriptCompressor(in, new ErrorReporter() {
public void warning(String message, String sourceName,
int line, String lineSource, int lineOffset) {
if (line < 0) {
log.error("\n[WARNING] " + message);
} else {
log.error("\n[WARNING] " + line + ':' + lineOffset + ':' + message);
}
}
public void error(String message, String sourceName,
int line, String lineSource, int lineOffset) {
if (line < 0) {
log.error("\n[ERROR] " + message);
} else {
log.error("\n[ERROR] " + line + ':' + lineOffset + ':' + message);
}
}
public EvaluatorException runtimeError(String message, String sourceName,
int line, String lineSource, int lineOffset) {
error(message, sourceName, line, lineSource, lineOffset);
return new EvaluatorException(message);
}
});
compressor.compress(out, -1, munge, verbose, preserveAllSemiColons, disableOptimizations);
}
input.close();
input = null;
in.close();
in = null;
}
}
out.flush();
} catch (IOException e) {
throw e;
} finally {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(input);
}
}
/**
* 将css文件里的图片相对路径修改为绝对路径
*
* @param content 内容
* @param path 路径
* @return String css
*/
private static String repairCss(String content, String path) {
Pattern p = Pattern.compile("url\\([\\s]*['\"]?((?!['\"]?https?://|['\"]?data:|['\"]?/).*?)['\"]?[\\s]*\\)"); // 感谢Code Life(程式人生)的正则
Matcher m = p.matcher(content);
StringBuffer sb = new StringBuffer();
while (m.find()) {
String url = m.group(1).trim();
StringBuffer cssPath = new StringBuffer("url(").append(FilenameUtils.getFullPath(path)).append(url).append(")");
m.appendReplacement(sb, cssPath.toString());
}
m.appendTail(sb);
content = sb.toString();
return content;
}
/**
* 压缩工具
*
* @param rootPath 项目路径
* @param fileName 待压缩的文件列表文件 /assets/assets.jjs
* @return String 返回压缩完成之后的路径
* @throws IOException 文件不存在时异常
*/
private static List combo(String rootPath, String fileName) throws IOException {
File assetsConfig = new File(rootPath + fileName);
// 待压缩的文件列表不存在时抛出异常
if (!assetsConfig.exists()) {
throw new IOException(fileName + " not found...");
}
// 读取文件中的js或者css路径
List list = FileUtils.readLines(assetsConfig, UTF_8);
// 文件内容md5
StringBuilder fileMd5s = new StringBuilder();
StringBuilder config = new StringBuilder();
Iterator it = list.iterator();
String lines;
while (it.hasNext()) {
lines = it.next();
config.append(lines);
if (StrKit.isBlank(lines)) {
it.remove();
continue;
}
// 去除首尾空格
lines = lines.trim();
// #开头的行注释
if (lines.startsWith("#")) {
it.remove();
continue;
}
// 远程服务器上的资源文件,不参与MD5
if (isRomte(lines)) {
continue;
}
// 对错误地址修复
if (!lines.startsWith("/")) {
lines = "/" + lines;
}
String filePath = rootPath + lines;
File file = new File(filePath);
if (!file.exists()) {
throw new IOException(file.getName() + " not found...");
}
String content = FileUtils.readFileToString(file, UTF_8);
fileMd5s.append(HashKit.md5(content));
}
fileMd5s.append(HashKit.md5(config.toString()));
/* if (Jpress.isDevMode()) {
return list;
}*/
// 文件更改时间集合hex,MD5取中间8位
String hex = HashKit.md5(fileMd5s.toString()).substring(8, 16);
boolean isCss = true;
if (fileName.endsWith(".jjs")) {
isCss = false;
}
// /assets/assets.jjs
int index = fileName.lastIndexOf("/");
fileName = fileName.substring(0,index+1)+"min/"+fileName.substring(index+1);
String comboName = fileName.substring(0, fileName.indexOf('.'));
String newFileName = comboName + '-' + hex + (isCss ? CSS_EXT : JS_EXT);
String newPath = rootPath + newFileName;
File file = new File(newPath);
// 判断文件是否已存在,已存在直接返回
if (file.exists()) {
list.clear();
list.add(newFileName);
return list;
}
// 将合并的结果写入文件,异常时将文件删除
OutputStream output = null;
Writer out = null;
try {
output = new FileOutputStream(newPath);
out = new OutputStreamWriter(output, UTF_8);
compressorHelper(rootPath, list, isCss, out);
// 装载文件路径
COMBO_MAP.put(fileName, newFileName);
} catch (Exception e) {
FileUtils.deleteQuietly(file);
log.error(e.getMessage(), e);
throw new RuntimeException(fileName + " 压缩异常,请检查是否有依赖问题!");
} finally {
IOUtils.closeQuietly(output);
IOUtils.closeQuietly(out);
}
list.clear();
list.add(newFileName);
return list;
}
/**
* 判断文件是否为远程资源文件,远程资源文件不进行压缩
*
* @param path
* @return
*/
private static boolean isRomte(String path) {
if (StrKit.isBlank(path)) {
return false;
}
return path.trim().matches(PROTOCOL);
}
}
大家自行参考哦,又需要的可以根据需要更新。
更新个:
com.yahoo.platform.yui
yuicompressor
2.4.8
commons-io
commons-io
2.5
最终效果: