跳到正文
W Winse Blog
dev bigdata 4 min read

Maven压缩js/css功能实践

为了节约网络带宽,一般在发布项目时对资源(js/css)文件进行压缩(去掉空行、精简代码等)。但是要做到兼容开发与生产还是的下一番功夫才行。

$ ls -l src/main/webapp/static/assets/js/ | head
total 3120
-rwxrwxr--+ 1 winse None  24804 Aug 10 17:40 bootbox.js
-rwxrwxr--+ 1 winse None  71315 Aug 10 17:40 bootstrap.js
-rwxrwxr--+ 1 winse None  13905 Aug 10 17:40 bootstrap-colorpicker.js
-rwxrwxr--+ 1 winse None  49319 Aug 10 17:40 bootstrap-multiselect.js
...

$ ls -l target/dist/js/ | head
total 1368
-rwxrwx---+ 1 winse None   8943 Aug 19 16:53 bootbox-min.js
-rwxrwx---+ 1 winse None   8057 Aug 19 16:53 bootstrap-colorpicker-min.js
-rwxrwx---+ 1 winse None  38061 Aug 19 16:53 bootstrap-min.js
-rwxrwx---+ 1 winse None  18232 Aug 19 16:53 bootstrap-multiselect-min.js
...

项目中原本使用dist(压缩)、assets目录放置js/css等资源,在部署的时刻替换dist为assets,有点麻烦。首先想到的用nginx进行url重写,但是需要增加一个服务有点麻烦,能不能直接用spring来实现呢?

  • 自定义一个handler类

查看Spring的 mvc:resources 实现,相当于注册了一个 location -> ResourceHttpRequestHandler 的映射。 第一种尝试自动化的方式就是自定义handler类来进行资源的定位。增加 StaticRequestHandler 的处理类,增加配置 location 和 compressLocation 的配置:首先去查找压缩文件([NAME]-min.js),找不到然后再找源文件([NAME].js)位置。

主要修改 getResource 方法,具体完整代码如下:

## java
public class StaticRequestHandler extends ResourceHttpRequestHandler {

	private final static Log logger = LogFactory.getLog(ResourceHttpRequestHandler.class);

	private String location;
	private String compressLocation;

	private Resource locationResource;
	private Resource compressLocationResource;

	public void setLocation(String location) {
		this.location = location;
	}

	public void setCompressLocation(String compressLocation) {
		this.compressLocation = compressLocation;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		super.afterPropertiesSet();

		this.locationResource = getWebApplicationContext().getResource(location);
		super.setLocations(Collections.singletonList(this.locationResource));

		this.compressLocationResource = getWebApplicationContext().getResource(compressLocation);
	}

	@Override
	protected Resource getResource(HttpServletRequest request) {
		String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
		if (path == null) {
			throw new IllegalStateException("Required request attribute '"
					+ HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE + "' is not set");
		}

		if (!StringUtils.hasText(path) || isInvalidPath(path)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Ignoring invalid resource path [" + path + "]");
			}
			return null;
		}

		Resource res = null;
		if (path.endsWith(".css")) {
			res = findResource(compressLocationResource, path.substring(0, path.length() - 4) + ".min.css");
		} else if (path.endsWith(".js")) {
			res = findResource(compressLocationResource, path.substring(0, path.length() - 3) + ".min.js");
		}

		if (res == null) {
			res = findResource(locationResource, path);
		}

		return res;
	}

	private Resource findResource(Resource location, String path) {
		try {
			if (logger.isDebugEnabled()) {
				logger.debug("Trying relative path [" + path + "] against base location: " + location);
			}
			Resource resource = location.createRelative(path);
			if (resource.exists() && resource.isReadable()) {
				if (logger.isDebugEnabled()) {
					logger.debug("Found matching resource: " + resource);
				}
				return resource;
			} else if (logger.isTraceEnabled()) {
				logger.trace("Relative resource doesn't exist or isn't readable: " + resource);
			}
		} catch (IOException ex) {
			logger.debug("Failed to create relative resource - trying next resource location", ex);
		}

		return null;
	}

}

## spring config
	<!-- 静态资源 -->
	<!-- <mvc:resources mapping="/static/**" location="/static/" /> -->

	<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
		<property name="mappings">
			<value>
				/static/assets/**=staticRequestHandler
			</value>
		</property>
	</bean>
	<bean id="staticRequestHandler" class="com.hotel.servlet.resource.StaticRequestHandler">
		<property name="location" value="/static/assets/" />
		<property name="compressLocation" value="/static/dist/" />
	</bean>

这种方式实现了自动定位压缩资源 min.js 的功能,但是压缩还是不能自动化而且不能实时的更新(min要单独压缩产生),并且调试和生产环境还是需要手动的修改配置来切换。

有没有更好的自动化的实现开发环境和生产环境分开呢?

  • Maven打包时压缩然后替换源文件

使用 yuicompressor-maven-plugin 插件压缩资源,然后把压缩资源打包放置到assets目录下。

注意: yuicomressor 插件的 nosuffix 配置为 true ! 这样压缩后的文件名和源文件名称才一样。

## spring config
	<!-- 静态资源 -->
	<mvc:resources mapping="/static/**" location="/static/" />

## maven pom.xml
		<profile>
			<id>release</id>

			<build>
				<plugins>
					<!-- http://alchim.sourceforge.net/yuicompressor-maven-plugin/compress-mojo.html -->
					<plugin>
						<groupId>net.alchim31.maven</groupId>
						<artifactId>yuicompressor-maven-plugin</artifactId>
						<version>1.3.2</version>
						<executions>
							<execution>
								<id>compress_js_css</id>
								<phase>process-resources</phase>
								<goals>
									<goal>compress</goal>
								</goals>
							</execution>
						</executions>
						<configuration>
							<encoding>UTF-8</encoding>
							<nosuffix>true</nosuffix>
							<skip>false</skip>

							<jswarn>false</jswarn>
							<nomunge>false</nomunge>
							<preserveAllSemiColons>false</preserveAllSemiColons>

							<sourceDirectory>src/main/webapp/static/assets</sourceDirectory>
							<outputDirectory>${project.build.directory}/dist</outputDirectory>
						</configuration>
					</plugin>

					<plugin>
						<artifactId>maven-war-plugin</artifactId>
						<version>2.6</version>
						<configuration>
							<webResources>
								<resource>
									<directory>${project.build.directory}/dist</directory>
									<targetPath>static/assets</targetPath>
									<filtering>false</filtering>
								</resource>
							</webResources>
						</configuration>
					</plugin>
					
				</plugins>
			</build>
		</profile>

war插件添加了自定义webResources资源,首先把压缩的文件拷贝到对应目录,maven发现文件已经存在就不会再拷贝同名的文件。这样源文件就相当于被替换成压缩的资源了。

# 总结

使用maven插件压缩打包,完美的解决js/css压缩导致的开发和生产不兼容问题。

# 后记

jsp使用了tag的地方总是会产生很多的空行,看着挺烦的。其实可以通过在jsp开头添加 trimDirectiveWhitespaces 属性来去掉空行:

<%@ page language="java" trimDirectiveWhitespaces="true" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>

–END

在 GitHub 上讨论

欢迎通过 GitHub Issue 留言或反馈。每条讨论都会关联到对应文章的源文件路径。

2016-08-19-j2ee-maven-resources-compress.md

Related posts