PageRenderTime 38ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/amps-maven-plugin/src/main/java/com/atlassian/maven/plugins/amps/minifier/ResourcesMinifier.java

https://bitbucket.org/atlassian/amps
Java | 235 lines | 166 code | 27 blank | 42 comment | 17 complexity | dc68b9a55831d8024a938490f88ee529 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause
  1. package com.atlassian.maven.plugins.amps.minifier;
  2. import com.atlassian.maven.plugins.amps.code.Sources;
  3. import com.atlassian.maven.plugins.amps.minifier.strategies.NoMinificationStrategy;
  4. import com.atlassian.maven.plugins.amps.minifier.strategies.XmlMinifierStrategy;
  5. import com.atlassian.maven.plugins.amps.minifier.strategies.googleclosure.GoogleClosureJsMinifierStrategy;
  6. import com.atlassian.maven.plugins.amps.minifier.strategies.yui.YUICompressorCssMinifierStrategy;
  7. import com.atlassian.maven.plugins.amps.minifier.strategies.yui.YUICompressorJsMinifierStrategy;
  8. import org.apache.commons.lang3.StringUtils;
  9. import org.apache.maven.model.Resource;
  10. import org.apache.maven.plugin.MojoExecutionException;
  11. import org.apache.maven.plugin.logging.Log;
  12. import org.codehaus.plexus.util.DirectoryScanner;
  13. import javax.annotation.Nonnull;
  14. import java.io.File;
  15. import java.io.IOException;
  16. import java.nio.charset.Charset;
  17. import java.util.ArrayList;
  18. import java.util.Arrays;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.Objects;
  22. import java.util.stream.Collectors;
  23. import static java.util.Collections.singletonList;
  24. import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
  25. import static org.apache.commons.io.FileUtils.copyFile;
  26. import static org.apache.commons.io.FileUtils.forceMkdir;
  27. import static org.apache.commons.io.FileUtils.readFileToString;
  28. import static org.apache.commons.io.FileUtils.writeStringToFile;
  29. import static org.apache.commons.io.FilenameUtils.getExtension;
  30. import static org.apache.commons.io.FilenameUtils.removeExtension;
  31. /**
  32. * Iterates through Maven {@link Resource} declarations, applying a {@link Minifier} to files inside them as
  33. * appropriate.
  34. *
  35. * @since 8.1
  36. */
  37. public class ResourcesMinifier {
  38. private static final List<String> MINIFIED_FILENAME_SUFFIXES = Arrays.asList("-min", ".min");
  39. private final MinifierParameters minifierParameters;
  40. public ResourcesMinifier(MinifierParameters minifierParameters) {
  41. this.minifierParameters = Objects.requireNonNull(minifierParameters);
  42. }
  43. public void minify(List<Resource> resources, String outputPath) throws MojoExecutionException {
  44. for (Resource resource : resources) {
  45. minify(resource, outputPath);
  46. }
  47. }
  48. public void minify(Resource resource, String outputPath) throws MojoExecutionException {
  49. final File resourceDir = new File(resource.getDirectory());
  50. if (!resourceDir.exists()) {
  51. return;
  52. }
  53. final File outputDir = new File(outputPath);
  54. final String targetPath = resource.getTargetPath();
  55. final File destDir = StringUtils.isNotBlank(targetPath) ? new File(outputDir, targetPath) : outputDir;
  56. // Process all relevant filetypes in the resource dir
  57. for (String type : getFiletypesToProcess()) {
  58. processFiletypeInDirectory(type, resourceDir, destDir, resource.getIncludes(), resource.getExcludes());
  59. }
  60. }
  61. /**
  62. * Discovers all files in a directory with the given filetype, then runs their contents through an appropriate
  63. * minifier. The minified content of each input file will be written to a file with a `-min.[extname]` suffix
  64. * in the provided destination directory.
  65. *
  66. * @param filetype the filetype to find in the resource dir, such as "js", "css", "xml", etc.
  67. * @param resourceDir the folder in which to search for input files.
  68. * @param destDir the folder where minified files will be written.
  69. * @param includes a list of filepaths that should be processed, regardless of their filetype.
  70. * @param excludes any filepath in this list will not be processed, even if it matches the given filetype.
  71. * @throws MojoExecutionException
  72. */
  73. public void processFiletypeInDirectory(
  74. @Nonnull final String filetype,
  75. final File resourceDir,
  76. final File destDir,
  77. final List<String> includes,
  78. final List<String> excludes) throws MojoExecutionException {
  79. DirectoryScanner scanner = new DirectoryScanner();
  80. scanner.setBasedir(resourceDir);
  81. // Add included files to the scanner from the build, if they are configured.
  82. // Otherwise, fall back to finding all files of type extname.
  83. if (isNotEmpty(includes)) {
  84. scanner.setIncludes(includes.toArray(new String[0]));
  85. } else {
  86. scanner.setIncludes(singletonList("**/*." + filetype).toArray(new String[0]));
  87. }
  88. // Add excluded files to the scanner from the build, if they are configured.
  89. if (isNotEmpty(excludes)) {
  90. scanner.setExcludes(excludes.toArray(new String[0]));
  91. }
  92. scanner.addDefaultExcludes();
  93. // Collect all files to be processed.
  94. scanner.scan();
  95. processFileList(filetype, destDir, Arrays.stream(scanner.getIncludedFiles())
  96. .filter(s -> getExtension(s).endsWith(filetype))
  97. .collect(Collectors.toMap(s -> s, s -> new File(resourceDir, s))));
  98. }
  99. private void processFileList(
  100. @Nonnull final String extname,
  101. final File destDir,
  102. final Map<String, File> filenames) throws MojoExecutionException {
  103. final Log log = minifierParameters.getLog();
  104. final Minifier strategy = getMinifierStrategy(extname);
  105. int minified = 0;
  106. int copied = 0;
  107. for (Map.Entry<String, File> entry : filenames.entrySet()) {
  108. final String path = entry.getKey();
  109. final File sourceFile = entry.getValue();
  110. try {
  111. if (sourceFile.canRead()) {
  112. if (maybeCopyPreminifiedFileToDest(sourceFile, destDir)) {
  113. copied++;
  114. continue;
  115. }
  116. // I do not like this... but until I refactor to extract a config signal for minifying "in-place", this will have to do...
  117. final String destFilename = "xml".equals(extname) ? path : getMinifiedFilepath(path);
  118. final File destFile = new File(destDir, destFilename);
  119. if (destFile.exists() && destFile.lastModified() > sourceFile.lastModified()) {
  120. log.debug("Nothing to do, " + destFile.getAbsolutePath() + " is younger than the original");
  121. continue;
  122. }
  123. log.debug("minifying to " + destFile.getAbsolutePath());
  124. final Charset cs = minifierParameters.getCs();
  125. final Sources input = new Sources(readFileToString(sourceFile, cs));
  126. final Sources output = processSources(input, strategy);
  127. forceMkdir(destFile.getParentFile());
  128. writeStringToFile(destFile, output.getContent(), cs);
  129. if (output.hasSourceMap()) {
  130. final File sourceMapFile = new File(destFile.getAbsolutePath() + ".map");
  131. writeStringToFile(sourceMapFile, output.getSourceMapContent(), cs);
  132. }
  133. minified++;
  134. }
  135. } catch (IOException e) {
  136. throw new MojoExecutionException("IOException when minifying '" + path + "'", e);
  137. }
  138. }
  139. log.info(String.format("%d %s file(s) were output to target directory %s", minified + copied, extname, destDir.getAbsolutePath()));
  140. }
  141. /**
  142. * If the file is determined to be already minified (by checking the file basename for a ".min" or "-min" suffix),
  143. * then it will copy the file to the destination.
  144. *
  145. * @param sourceFile Source file
  146. * @param destDir The output directory where the minified code should end up
  147. * @return true if and only if the file name ends with .min.js or -min.js
  148. * @throws IOException If an error is encountered reading or writing the source or destination file.
  149. */
  150. private boolean maybeCopyPreminifiedFileToDest(final File sourceFile, final File destDir) throws IOException {
  151. final Log log = minifierParameters.getLog();
  152. final String path = sourceFile.getName();
  153. final String pathNoExt = removeExtension(path);
  154. for (String s : MINIFIED_FILENAME_SUFFIXES) {
  155. if (pathNoExt.endsWith(s)) {
  156. String pathNoSuffix = pathNoExt.substring(0, pathNoExt.length() - s.length()) + "." + getExtension(path);
  157. File destFile = new File(destDir, getMinifiedFilepath(pathNoSuffix));
  158. log.debug(String.format("Copying pre-minified file '%s' to destination '%s' file ends in '%s'", path, destFile.getName(), s));
  159. copyFile(sourceFile, destFile);
  160. return true;
  161. }
  162. }
  163. return false;
  164. }
  165. /**
  166. * Minifies given source contents. If contents is empty, then no minifier
  167. * is invoked and the unmodified input is returned.
  168. * @param input Input source to minify
  169. * @param strategy Minifier to use
  170. * @return Minified source
  171. * @throws IOException If an error is encountered reading or writing the source or destination file.
  172. */
  173. private Sources processSources(final Sources input, final Minifier strategy) throws IOException {
  174. return input.getContent().isEmpty()
  175. ? input
  176. : strategy.minify(input, minifierParameters);
  177. }
  178. // Determine which filetypes to process based on build parameters
  179. private List<String> getFiletypesToProcess() {
  180. List<String> types = new ArrayList<>();
  181. if (minifierParameters.isCompressJs()) {
  182. types.add("js");
  183. }
  184. if (minifierParameters.isCompressCss()) {
  185. types.add("css");
  186. }
  187. types.add("xml");
  188. return types;
  189. }
  190. private Minifier getMinifierStrategy(String extname) {
  191. final boolean withClosure = minifierParameters.isUseClosureForJs();
  192. switch (extname) {
  193. case "js":
  194. return withClosure ? new GoogleClosureJsMinifierStrategy() : new YUICompressorJsMinifierStrategy();
  195. case "css":
  196. return new YUICompressorCssMinifierStrategy();
  197. case "xml":
  198. return new XmlMinifierStrategy();
  199. default:
  200. return new NoMinificationStrategy();
  201. }
  202. }
  203. private static String getMinifiedFilepath(String path) {
  204. final String filepathSansExtension = removeExtension(path);
  205. final String minifiedExtension = "-min." + getExtension(path);
  206. return filepathSansExtension + minifiedExtension;
  207. }
  208. }