ホームページ >ウェブフロントエンド >htmlチュートリアル >フロントエンドjsとcssの圧縮とマージ wro4j_html/css_WEB-ITnose
私: 注:
調べてみても、Java コミュニティーやノードコミュニティーで満足のいく解決策が見つからず、常にさまざまな問題がありました。
おそらく Ruby の精神のおかげで、ror コミュニティは依然として最も強力な設計およびアーキテクチャ能力を備えているようです。
最も素晴らしい人が 1 人か 2 人だけで十分な場合もあります。
次の記事はまだ良いですが、wro4j の設計が愚かで複雑なのが残念です。
最近、Web システムでパフォーマンスの最適化を行っています
そして、圧縮と統合について。
静的リソース ファイルの削減は、フロントエンドのパフォーマンスの最適化において非常に重要な部分です。
その利点は、リクエストされたファイルのサイズを削減できるだけでなく、ブラウザによる http リクエストの数も削減できることです。 .
これは Java ベースの Web システムであり、サーバーとして nginx+tomcat を使用するためです。
最後に、コンパイル中に静的リソースを圧縮するために wro4j と Maven プラグインを使用することを検討してください。 🎜>最適化前:
基本的にすべての JSP はこの大きな静的ファイルを参照します:
<link rel="stylesheet" type="text/css" href="${ctxPath}/css/skin.css"/> <link rel="stylesheet" type="text/css" href="${ctxPath}/css/jquery-ui-1.8.23.custom.css"/> <link rel="stylesheet" type="text/css" href="${ctxPath}/css/validationEngine.jquery.css"/> <script type="text/javascript">var GV = {ctxPath: '${ctxPath}',imgPath: '${ctxPath}/css'};</script> <script type="text/javascript" src="${ctxPath}/js/jquery-1.7.2.min.js"></script> <script type="text/javascript" src="${ctxPath}/js/jquery-ui-1.8.23.custom.min.js"></script> <script type="text/javascript" src="${ctxPath}/js/jquery.validationEngine.js"></script> <script type="text/javascript" src="${ctxPath}/js/jquery.validationEngine-zh_CN.js"></script> <script type="text/javascript" src="${ctxPath}/js/jquery.fixedtableheader.min.js"></script> <script type="text/javascript" src="${ctxPath}/js/roll.js"></script> <script type="text/javascript" src="${ctxPath}/js/jquery.pagination.js"></script> <script type="text/javascript" src="${ctxPath}/js/jquery.rooFixed.js"></script> <script type="text/javascript" src="${ctxPath}/js/jquery.ui.datepicker-zh-CN.js"></script> <script type="text/javascript" src="${ctxPath}/js/json2.js"></script> <script type="text/javascript" src="${ctxPath}/js/common.js"></script>
参照されるファイルが多数あり、ファイル サイズは圧縮されていません。
もう 1 つの問題は、ブラウザのキャッシュを最大限に活用するには、静的リソースのファイル名にバージョン管理を実装するのが最善であるということです。
このようにして、フロントエンド Web サーバーは、静的リソース ファイルが変更されると、によってファイル名が再生成されるため、キャッシュの有効期限を気にすることなく、安全にキャッシュ機能を有効にすることができます。 >
以下では、私自身のプロジェクトに基づいて、これら 2 つの問題をより適切に解決する方法を紹介します。
最初のステップ: 導入します。 wro4j を実行し、コンパイル中に散在する上記の複数のファイルをいくつかのファイルに統合し、ファイルを最小化します。
ステップ 2: 生成された静的リソース ファイルのファイル名に時刻情報を追加します。
これは、 2 段階の最適化後の参照状況:
1 つの css ファイルと 2 つの js ファイルのみが参照され、全体のファイル サイズが 10 から 3 に減少します。
${platform:cssFile("/wro/basic") } <script type="text/javascript">var GV = {ctxPath: '${ctxPath}',imgPath: '${ctxPath}/css'};</script> ${platform:jsFile("/wro/basic") } ${platform:jsFile("/wro/custom") }最適化プロセスを以下に紹介します。
1. wro4j の Maven 依存関係を追加します。 🎜>
2. wro4j maven プラグインを追加します
開発環境が Eclipse の場合は、m2e-wro4j プラグインをダウンロードできます
ダウンロードアドレス: http://download.jboss .org/jbosstools/updates/m2e-wro4j/<wro4j.version>1.6.2</wro4j.version> ... <dependency> <groupId>ro.isdc.wro4j</groupId> <artifactId>wro4j-core</artifactId> <version>${wro4j.version}</version> <exclusions> <exclusion> <!-- 因为项目中的其他jar包已经引入了不同版本的slf4j,所以这里避免jar重叠所以不引入 --> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </exclusion> </exclusions> </dependency>
このプラグインの主な機能は、開発環境で対応する静的ファイルを変更するのに役立つことです。 pom.xml ファイルと css ファイルを作成するときに、パッケージ化された js が自動的に生成されます。
<plugin> <groupId>ro.isdc.wro4j</groupId> <artifactId>wro4j-maven-plugin</artifactId> <version>${wro4j.version}</version> <executions> <execution> <phase>compile</phase> <goals> <goal>run</goal> </goals> </execution> </executions> <configuration> <targetGroups>basic,custom</targetGroups> <!-- 这个配置是告诉wro4j在打包静态资源的时候是否需要最小化文件,开发的时候可以设成false,方便调试 --> <minimize>true</minimize> <destinationFolder>${basedir}/src/main/webapp/wro/</destinationFolder> <contextFolder>${basedir}/src/main/webapp/</contextFolder> <!-- 这个配置是第二步优化需要用到的,暂时忽略 --> <wroManagerFactory>com.rootrip.platform.common.web.wro.CustomWroManagerFactory</wroManagerFactory> </configuration> </plugin>ソース ファイルを変更するだけで、変更された結果が表示されます。 WEB-INF ディレクトリ内の wro.xml ファイル。このファイルの目的は、wro4j に jss および css ファイルをパッケージ化する方法を指示することです。
公式ドキュメント: http:/ /code.google.com/p/wro4j /wiki/WroFileFormat
実際、この設定ファイルは、公式ドキュメントを読みたくない場合に簡単に理解できるものです。
上記の設定の目的は、wro4j に
3a674e06c111e35179a3a4f5628fe265/css/basic.css84ec6c146f52e6ae776096803a76e1bb
<?xml version="1.0" encoding="UTF-8"?> <groups xmlns="http://www.isdc.ro/wro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.isdc.ro/wro wro.xsd"> <group name="basic"> <css>/css/basic.css</css> <css>/css/skin.css</css> <css>/css/jquery-ui-1.8.23.custom.css</css> <css>/css/validationEngine.jquery.css</css> <js>/js/jquery-1.7.2.min.js</js> <js>/js/jquery-ui-1.8.23.custom.min.js</js> <js>/js/jquery.validationEngine.js</js> <js>/js/jquery.fixedtableheader.min.js</js> <js>/js/roll.js</js> <js>/js/jquery.pagination.js</js> <js>/js/jquery.rooFixed.js</js> <js>/js/jquery.ui.datepicker-zh-CN.js</js> <js>/js/json2.js</js> </group> <group name="custom"> <js>/js/jquery.validationEngine-zh_CN.js</js> <js>/js/common.js</js> </group> </groups>3a674e06c111e35179a3a4f5628fe265/css を使用するように指示することです。 /skin.css84ec6c146f52e6ae776096803a76e1bb
3a674e06c111e35179a3a4f5628fe265/ css/jquery-ui-1.8.23.custom.css84ec6c146f52e6ae776096803a76e1bb
3a674e06c111e35179a3a4f5628fe265 .jquery.css84ec6c146f52e6ae776096803a76e1bb
これら 4 つのファイルを統合して、basic.css というファイルを指定したディレクトリ (wro4j-maven-plugin で構成) に生成し、
を追加します72000b4a954cd2ca4a34d0811ce6bdcb/js/jquery-1.7.2.min.jsb81592373e6c2422260df82717a613ae
72000b4a954cd2ca4a34d0811ce6bdcb/js/jquery-ui-1.8.23.custom.min.jsb242bdceb8449df260065b85db88d6a7
72000b4a954cd2ca4a34d0811ce6bdcb/js/jquery.validationEngine.jsb242bdceb8449df260065b85db88d6a7
72000b4a954cd2ca4a34d0811ce6bdcb/js/jquery.fixedtableheader.min.jsb242bdceb8449df260065b85db88d6a7 🎜>72000b4a954cd2ca4a34d0811ce6bdcb/js/roll.jsb242bdceb8449df260065b85db88d6a7
72000b4a954cd2ca4a34d0811ce6bdcb/js/jquery.pagination.jsb242bdceb8449df260065b85db88d6a7
72000b4a954cd2ca4a34d0811ce6bdcb /jquery.rooFixed.jsb242bdceb8449df260065b85db88d6a7
72000b4a954cd2ca4a34d0811ce6bdcb /json2.jsb242bdceb8449df260065b85db88d6a7 これらのファイルをすべて統合し、指定したディレクトリに Basic.js というファイルを生成します。 72000b4a954cd2ca4a34d0811ce6bdcb/js/common.jsb242bdceb8449df260065b85db88d6a7 これら 2 つのファイルは、指定されたディレクトリにあるcustom.jsというファイル。 第一步搞定,这时候如果你的开发环境是eclipse并且安装了插件的话,应该就能在你工程的%your webapp%/wor/目录下看见生成好的 basic.css,basic.js和custom.js这三个文件了. 然后你再将你的静态资源引用路径改成 就ok了.每次修改被引用到的css或js文件的时候,这些文件都将重新生成. 如果开发环境是eclipse但是没有安装m2e-wro4j插件的话,pom.xml可能需要额外配置. 请参考: https://community.jboss.org/en/tools/blog/2012/01/17/css-and-js-minification-using-eclipse-maven-and-wro4j 第二步:给生成的文件名称中加入时间信息并通过el自定义函数引用脚本文件. 1. 创建DailyNamingStrategy类 2.创建CustomWroManagerFactory类 上面这两个类的作用是使用wro4j提供的文件命名策略,这样生成的文件名就会带上时间信息了. 例如:basic-2013020217.js 但是现在又会发现一个问题:如果静态资源文件名称不固定的话,那怎么样引用呢? 这时候就需要通过动态生成3f1c4e4b6b16bbbd69b2ee476dc4f83a与2cdf5bf648cf2f33323966d7f58a7f3f来解决了. 因为项目使用的是jsp页面,所以通过el自定义函数来实现标签生成. 3.创建PlatformFunction类 对应的tld文件就不给出了,根据方法签名编写就行了. 其中的cssFile和jsFile方法分别实现引用css和js文件. 在页面使用的时候类似这样: ${platform:cssFile("/wro/basic") } ${platform:jsFile("/wro/custom") } 这个类的主要功能就是使用jdk7的WatchService监控wro目录的新增文件事件, 一旦有新的文件加到目录里,判断这个文件是不是最新的,如果是的话则使用这个文件名称引用. 这样一旦有新加的资源文件放到wro目录里,则能够自动被引用,不需要做任何代码上的修改,并且基本不影响性能. 到此为止功能已经实现. 但是我考虑到还有两个问题有待完善: 1.因为生成的文件名称精确到小时,如果这个小时之内有多次代码修改,生成的文件名都完全一样. 这样就算线上的代码有修改,对于已经有该文本缓存的浏览器来说,不会重新请求文件,也就看不到文件变化. 不过一般来说线上代码不会如此频繁改动,对于大多数应用来说影响不大. 2.在开发环境开发一段时间之后,wro目录下会生成一大堆的文件(因为m2e-wro4j插件在生成新的文件的时候不会删除旧文件,如果文件名相同会覆盖掉以前的文件), 这时候就需要手动删除时间靠前的旧文件,虽然系统会忽略旧文件,但是我相信大多数程序员和我一样是有些许洁癖的吧. 解决办法还是不少,比如可以写脚本定期清理掉旧文件. 时间有限,有些地方考虑的不是很完善,欢迎拍砖. 参考资料: http://meri-stuff.blogspot.sk/2012/08/wro4j-page-load-optimization-and-lessjs.html#Configuration https://community.jboss.org/en/tools/blog/2012/01/17/css-and-js-minification-using-eclipse-maven-and-wro4j http://code.google.com/p/wro4j/wiki/MavenPlugin http://code.google.com/p/wro4j/wiki/WroFileFormat + + + - + + +<link rel="stylesheet" type="text/css" href="${ctxPath}/wro/basic.css"/> <script type="text/javascript" src="${ctxPath}/wro/basic.js"></script> <script type="text/javascript" src="${ctxPath}/wro/custom.js"></script>
public class DailyNamingStrategy extends TimestampNamingStrategy { protected final Logger log = LoggerFactory.getLogger(DailyNamingStrategy.class); @Override protected long getTimestamp() { String dateStr = DateUtil.formatDate(new Date(), "yyyyMMddHH"); return Long.valueOf(dateStr); } }
//这个类就是在wro4j-maven-plugin里配置的wroManagerFactory参数 public class CustomWroManagerFactory extends DefaultStandaloneContextAwareManagerFactory { public CustomWroManagerFactory() { setNamingStrategy(new DailyNamingStrategy()); } }
public class PlatformFunction { private static Logger log = LoggerFactory.getLogger(PlatformFunction.class); private static ConcurrentMap<String, String> staticFileCache = new ConcurrentHashMap<>(); private static AtomicBoolean initialized = new AtomicBoolean(false); private static final String WRO_Path = "/wro/"; private static final String JS_SCRIPT = "<script type=\"text/javascript\" src=\"%s\"></script>"; private static final String CSS_SCRIPT = "<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">"; private static String contextPath = null; /** * 该方法根据给出的路径,生成js脚本加载标签 * 例如传入参数/wro/custom,该方法会寻找webapp路径下/wro目录中以custom开头,以js后缀结尾的文件名称名称. * 然后拼成<script type="text/javascript" src="${ctxPath}/wro/custom-20130201.js"></script>返回 * 如果查找到多个文件,返回根据文件名排序最大的文件 * @param str * @return */ public static String jsFile(String filePath) { String jsFile = staticFileCache.get(buildCacheKey(filePath, "js")); if(jsFile == null) { log.error("加载js文件失败,缓存中找不到对应的文件[{}]", filePath); } return String.format(JS_SCRIPT, jsFile); } /** * 该方法根据给出的路径,生成css脚本加载标签 * 例如传入参数/wro/custom,该方法会寻找webapp路径下/wro目录中以custom开头,以css后缀结尾的文件名称名称. * 然后拼成<link rel="stylesheet" type="text/css" href="${ctxPath}/wro/basic-20130201.css">返回 * 如果查找到多个文件,返回根据文件名排序最大的文件 * @param str * @return */ public static String cssFile(String filePath) { String cssFile = staticFileCache.get(buildCacheKey(filePath, "css")); if(cssFile == null) { log.error("加载css文件失败,缓存中找不到对应的文件[{}]", filePath); } return String.format(CSS_SCRIPT, cssFile); } public static void init() throws IOException { if(initialized.compareAndSet(false, true)) { ServletContext sc = Platform.getInstance().getServletContext(); if(sc == null) { throw new PlatformException("查找静态资源的时候的时候发现servlet context 为null"); } contextPath = Platform.getInstance().getContextPath(); File wroDirectory = new ServletContextResource(sc, WRO_Path).getFile(); if(!wroDirectory.exists() || !wroDirectory.isDirectory()) { throw new PlatformException("查找静态资源的时候发现对应目录不存在[" + wroDirectory.getAbsolutePath() + "]"); } //将wro目录下已有文件加入缓存 for(File file : wroDirectory.listFiles()) { handleNewFile(file); } //监控wro目录,如果有文件生成,则判断是否是较新的文件,是的话则把文件名加入缓存 new Thread(new WroFileWatcher(wroDirectory.getAbsolutePath())).start(); } } private static void handleNewFile(File file) { String fileName = file.getName(); Pattern p = Pattern.compile("^(\\w+)\\-\\d+\\.(js|css)$"); Matcher m = p.matcher(fileName); if(!m.find() || m.groupCount() < 2) return; String fakeName = m.group(1); String fileType = m.group(2); //暂时限定只能匹配/wro/目录下的文件 String key = buildCacheKey(WRO_Path + fakeName, fileType); if(staticFileCache.putIfAbsent(key, fileName) != null) { synchronized(staticFileCache) { String cachedFileName = staticFileCache.get(key); if(fileName.compareTo(cachedFileName) > 0) { staticFileCache.put(key, contextPath + WRO_Path + fileName); } } } } private static String buildCacheKey(String fakeName, String fileType) { return fakeName + "-" + fileType; } static class WroFileWatcher implements Runnable { private static Logger log = LoggerFactory.getLogger(WroFileWatcher.class); private String wroAbsolutePathStr; public WroFileWatcher(String wroPathStr) { this.wroAbsolutePathStr = wroPathStr; } @Override public void run() { Path path = Paths.get(wroAbsolutePathStr); File wroDirectory = path.toFile(); if(!wroDirectory.exists() || !wroDirectory.isDirectory()) { String message = "监控wro目录的时候发现对应目录不存在[" + wroAbsolutePathStr + "]"; log.error(message); throw new PlatformException(message); } log.warn("开始监控wro目录[{}]", wroAbsolutePathStr); try { WatchService watcher = FileSystems.getDefault().newWatchService(); path.register(watcher, StandardWatchEventKinds.ENTRY_CREATE); while (true) { WatchKey key = null; try { key = watcher.take(); } catch (InterruptedException e) { log.error("", e); continue; } for (WatchEvent<?> event : key.pollEvents()) { if (event.kind() == StandardWatchEventKinds.OVERFLOW) { continue; } WatchEvent<Path> e = (WatchEvent<Path>) event; Path filePath = e.context(); handleNewFile(filePath.toFile()); } if (!key.reset()) { break; } } } catch (IOException e) { log.error("监控wro目录发生错误", e); } log.warn("停止监控wro目录[{}]", wroAbsolutePathStr); } } }