blutorange / closure-compiler-maven-plugin

Combine and minimize JavaScript with Closure Compiler.
http://blutorange.github.com/closure-compiler-maven-plugin
Apache License 2.0
52 stars 6 forks source link

Closure Compiler Maven Plugin

Forked from Minify Maven Plugin. That project seems to be inactive. In line with the principle of single responsibility, this fork is meant only for processing JavaScript files. The YUI Compressor is dead, so what remains is a maven plugin for Google Closure Compiler. I found some plugins for the closure compiler, but found them all to be lacking - by not having a recent version of closure compile, not exposing many of its options or not handling files well. So I decided to fork the excellent Minify Maven Plugin as a base for a closure compiler maven plugin.

This plugin combines and minimizes JavaScript files. It produces a merged and a minified version.

Requires at least Java 11.

Notes regarding changes and updates

Usage

Configure your project's pom.xml to run the plugin during the project's build cycle.

<build>

  <!-- Exclude the sources in "src/main/resources/includes" -->
  <!-- Include the transpiled files in "target/generated-resources/includes" -->
  <resources>
    <resource>
      <directory>${project.basedir}/src/main/resources</directory>
      <excludes>
        <exclude>includes/**/*.js</exclude>
      </excludes>
      </resource>
      <resource>
        <directory>${project.basedir}/target/generated-resources</directory>
      </resource>
  </resources>

  <!-- Transpiled all sources from               -->
  <!--     "src/main/resources/includes"         -->
  <!--  to                                       -->
  <!--     "target/generated-resources/includes" -->
  <plugins>
    <plugin>
      <groupId>com.github.blutorange</groupId>
      <artifactId>closure-compiler-maven-plugin</artifactId>
      <version>${closure-compiler-maven-plugin.version}</version>
      <configuration>
        <!-- Base configuration for all executions (bundles) -->
        <baseSourceDir>${project.basedir}/src/main/resources</baseSourceDir>
        <baseTargetDir>${project.build.directory}/generated-resources</baseTargetDir>
      </configuration>
      <executions>
        <!-- Process all files in the "includes" directory individually-->
        <execution>
          <id>default-minify</id>
          <configuration>
            <encoding>UTF-8</encoding>
            <sourceDir>includes</sourceDir>
            <targetDir>includes</targetDir>
            <includes>
              <include>**/*.js</include>
            </includes>
            <skipMerge>true</skipMerge>
            <closureLanguageOut>ECMASCRIPT5</closureLanguageOut>
          </configuration>
          <goals>
            <goal>minify</goal>
          </goals>
          <phase>generate-resources</phase>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Documentation

For more information, check the documentation or the test projects.

Paths, directories and files

To process our files, we need to know where they are located and where we want the output to go to. This sound simple, but it gets more complicated as you also want files to be ordered the right way, use wild cards, have relative paths, create source maps etc. This is short explanation of how file handling works with this plugin.

Any transpilation process consists of two main ingredients: The source (or input) files; and the target (or output) files. First we need to establish the base directory, which needs to be an absolute path on the file system:

For larger projects, we may want to run closure compiler multiple times on different sets of files. We can do this with multiple executions of this plugin. But as we are still working within the same project, we don't want to set the absolute path to our project each and every time. For each execution, we only want to specify a relative path:

Next, we want to specify some actual files to process:

So how are these source files ordered? We could take all includes and excludes, figure out all matching files, and sort them alphabetically. But this means we could not specify the order of files manually if we ever needed it. On the other hand, when we use wild cards to specify a set of files, we probably want these to be sorted. To get the best of both world, this plugin orders files like this:

Another thing to mention here is how the files are passed to closure compiler. Closure compiler never reads files from the files system itself. When ES modules are used, it does not look in the file system for the imported files. It expects that we give it all the inpt files it needs. To pass a source file to closure compiler, we need to give it the content of the file, as well as a (possibly relative) path to the file. If we pass the wrong path, closure compiler may not be able to resolve reference between files. This plugin always uses the path of a source file on the file system, relative to the sourceDir. So if we set the baseSourceDir to /home/john/git/project/src/webapp and sourceDir to js; and have a file at /home/john/git/project/src/webapp/js/logic/model.js: the file is passed to closure compiler with the file name logic/model.js By doing it this way, we also get closure compiler to "just work" with node.js projects: We just need to set the source directory to the main directory of the node project (with the node_modules folder) and set closureModuleResolution to NODE.

Finally, we want to tell the plugin where to place the output file(s). There are two cases here, depending on whether we want to merge all input files into once large file; or just process each file separately and place these files into the target directory:

As a bonus, we may sometimes want to create a source map as well. The easiest and quickest way to get working source maps is to:

This includes the entire source map as well as the original source file content in the minified file itself. As soon as the browser loads the minfied file, it's got everything it needs and the source map feature just works. Now in case we do not like our source maps being that large, we need to keep the source map as a separate file. That involves several paths:

Before we can worry about that, we need to specify where to put the generated source map:

Now we can worry about the paths mentioned above. The first two are easy: we know the location of the minified file and the source map files, so we just use the corresponding relative paths. And normally, both the minified file and the source map are put inside the same directory.

The last one - the path from the source map to the original source files - is not quite as easy. The source map is generated by closure compiler and by default, closure compiler just uses the name of the source file as it was passed to it: that is, relative to the sourceDir This usually won't work, because the source map is placed in the targetDir, so the relative path won't be correct. Fortunately, closure compiler offer an option to remap the location of (path to the) source files when it creates the source map. By default, this plugin sets this option so that the paths are correct with respect to the underlying file system. So for example, if we set

Then we get the two output files

The source map bundle.min.map.js now references the source files as ../../src/webapp/js/index.js. When your project directory structure resembles your directory structure on the server, then by default, every will just work. If the directory structure is diffrent, closure compiler offers the option source_map_location_mapping. For this plugin, this is set with the option:

For the example above, this means that the source file name would be js/index.js. We could now set this option to replace js/ with https://example.com/sources/. Now the source map contains a reference to the source file as https://example.com/sources/index.js.

Build site

Release

Test

The test projects need a built version of the plugin, so make a full local build first:

mvn clean install -DskipTests

You may need to run an install on a test project first to download the required dependencies:

cd src/test/resources/projects/minimal/
mvn install
cd ../../../../../

Now test away

mvn clean package test

To run only a single test for debugging, use

# nameOfTestMethod is one of the methods annotated with @Test in MinifyMojoTest
# For example: testOutputFilename
mvn test -Dtest=MinifyMojoTest#nameOfTestMethod

To add a new test, go to src/test/resources/projects/, copy one of the test projects as a base (except parent). Open the pom.xml and change the artifactId to a new name. Edit the closure compiler configuration as necessary. Add input JavaScript files to the directory test and the expected output files to the directory expected. Finally open MinifyMojoTest and add a new test method:

  @Test
  public void testMyproject() throws Exception {
    runMinify("myproject");
  }

License

This distribution is licensed under the terms of the Apache License, Version 2.0 (see LICENSE.txt).