YUI3中,爲了不js文件過大,各個功能模塊是拆分的。它有一個「種子」的概念:先下載一個小的核心的js文件到瀏覽器端,再經過這個小的js文件去加載其它所需的模塊。 css
這種按需加載雖然解決了單個js過大的問題,可是隨之帶來另一個問題:若是一個頁面使用了YUI的a、b、c功能,那麼瀏覽器就要向服務器請求a.js、b.js、c.js三個文件,這樣增長了瀏覽器向服務器的溝通次數。 java
爲了解決後面的問題,YUI3又有一個combine的概念,預先下載的那個核心的js,把頁面上須要的a、b、c模塊合併成一個請求發給服務器,相似這樣:http://mydomain.com/conbine?a.js&b.js&c.js。 web
這要求服務器接收到http://mydomain.com/conbine請求後,將參數取出來,找到對應的a、b、c的js文件,合併成一個js文件,返回給客戶端。Combo Handler是Yahoo!開發的一個Apache模塊,專門來幹這個事情的。 apache
若是隻用java的web容器,能夠把這項工做委託給servlet: 瀏覽器
<servlet> <servlet-name>yuicombo</servlet-name> <servlet-class>org.siqisource.mozo.servlets.YuiCombineServlet</servlet-class> </servlet>對應的YUI配置爲:
YUI.GlobalConfig = { combine: true, comboBase: '<mz:webRoot/>/yuicombo?', root: 'uilibrary/yui/', };
在servlet代碼中,我使用了YUI的yuicompressor來壓縮js和css文件,下面是maven配置。 緩存
<dependency> <groupId>com.yahoo.platform.yui</groupId> <artifactId>yuicompressor</artifactId> <version>2.4.7</version> </dependency>
其中的Path類只是爲了得到web應用物理路徑,在是用的時候替換一下便可。 服務器
目前已知的缺陷:對於css的按需加載,瀏覽器會請求客戶端兩次,目前不清楚是否是YUI3(測試版本:3.7.2)的問題。 app
具體代碼以下: dom
package org.siqisource.mozo.servlets; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.FileDeleteStrategy; import org.apache.commons.io.FileUtils; import org.siqisource.mozo.context.Path; import com.yahoo.platform.yui.compressor.CssCompressor; import com.yahoo.platform.yui.compressor.JavaScriptCompressor; public class YuiCombineServlet extends HttpServlet { private static Map<String, String> cachedResources = new HashMap<String, String>(); private String cacheContextPath = "uilibrary/yui/cache/"; private String cacheDir = Path.getPhysicalPath() + cacheContextPath; int linebreakpos = -1; boolean munge = true; boolean verbose = false; boolean preserveAllSemiColons = false; boolean disableOptimizations = false; @Override public void init() throws ServletException { // 重置緩存文件夾 File catchedDir = new File(cacheDir); if (catchedDir.exists()) { FileDeleteStrategy strategy = FileDeleteStrategy.FORCE; try { strategy.delete(catchedDir); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catchedDir.mkdirs(); super.init(); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String queryString = request.getQueryString(); String resourcePath = cachedResources.get(queryString); // 已緩存 if (resourcePath == null) { String[] resources = queryString.split("&"); String firstResource = resources[0]; String fileName = UUID.randomUUID().toString(); if (firstResource.endsWith(".js")) { fileName += ".js"; Writer writer = new FileWriter(cacheDir + fileName); for (String resource : resources) { Reader reader = new FileReader(Path.getPhysicalPath() + resource); JavaScriptCompressor compressor = new JavaScriptCompressor( reader, null); compressor.compress(writer, linebreakpos, munge, verbose, preserveAllSemiColons, disableOptimizations); reader.close(); } writer.flush(); writer.close(); } else if (resources[0].endsWith(".css")) { fileName += ".css"; Writer writer = new FileWriter(cacheDir + fileName); for (String resource : resources) { Reader reader = new FileReader(replacedUrlFile(resource)); CssCompressor compressor = new CssCompressor(reader); compressor.compress(writer, linebreakpos); reader.close(); } writer.flush(); writer.close(); } resourcePath = cacheContextPath + fileName; cachedResources.put(queryString, resourcePath); } request.getRequestDispatcher(resourcePath).forward(request, response); return; } public String replacedUrlFile(String fileName) throws IOException { String cssfilePath = Path.getPhysicalPath() + fileName; File cssFile = new File(cssfilePath); String tempCssFilePath = cacheDir + "tmp-css-" + cssFile.getName(); File tempCssFile = new File(tempCssFilePath); if (tempCssFile.exists()) { return tempCssFilePath; } // 判斷是否須要替換 String css = FileUtils.readFileToString(cssFile); int maxIndex = css.length() - 1; int appendIndex = 0; Pattern p = Pattern.compile("url\\(\\s*([\"']?)"); if (!p.matcher(css).find()) { return cssfilePath; } // 真的須要替換 Matcher m = p.matcher(css); String url = fileName.substring(0, fileName.lastIndexOf('/')); url = Path.getContextPath() + "/" + url + "/"; StringBuffer replacedUrlCss = new StringBuffer(); while (m.find()) { int startIndex = m.start() + 4; // "url(".length() String terminator = m.group(1); // ', " or empty (not quoted) if (terminator.length() == 0) { terminator = ")"; } boolean foundTerminator = false; int endIndex = m.end() - 1; while (foundTerminator == false && endIndex + 1 <= maxIndex) { endIndex = css.indexOf(terminator, endIndex + 1); if ((endIndex > 0) && (css.charAt(endIndex - 1) != '\\')) { foundTerminator = true; if (!")".equals(terminator)) { endIndex = css.indexOf(")", endIndex); } } } // Enough searching, start moving stuff over to the buffer replacedUrlCss.append(css.substring(appendIndex, m.start())); if (foundTerminator) { String token = css.substring(startIndex, endIndex); token = token.replaceAll("\\s+", ""); String preserver = "url('" + url + token + "')"; replacedUrlCss.append(preserver); appendIndex = endIndex + 1; } else { // No end terminator found, re-add the whole match. Should we // throw/warn here? replacedUrlCss.append(css.substring(m.start(), m.end())); appendIndex = m.end(); } } FileUtils.writeStringToFile(tempCssFile, replacedUrlCss.toString()); return tempCssFilePath; } }