Preprocessing content by integrating stylus

If you have taken a look at the available modules on the Play framework site, there are at the time of this writing two modules, which help the developer to write CSS. On one the hand is the SASS module and on the other hand the less module.

Both modules address the same problem: On every change of the source file, either SASS or less follows some recompilation into the destination CSS file. Only the most up to date CSS file may be delivered to the client. In development mode every incoming request should always create the most up to date CSS output, where as in production it is sufficient to create those files seldom and cache them in order to increase performance.

The source code of the example is available at examples/chapter5/stylus.

Getting ready

In case you have never heard what SASS, less CSS, or stylus do, now would be the perfect time to learn. Check out the URL of each tool at the end of this recipe. In short, they are simple preprocessors, which parse an arbitrary definition of CSS similar content and create CSS content out of it.

This implies different behaviors in production and development mode. In development mode every incoming request should always create the most up to date CSS output, where as in production it is sufficient to create those files seldom and cache them in order to increase performance.

So, a HTTP request to the URI /public/test.styl should result in a pure CSS output, where the original content of the test.styl file was compiled and then returned to the client.

As usual, a test is the first point to start with, after installing stylus of course:

public class StylusCompilerTest extends UnitTest {

    @Test
    public void checkThatStylusCompilerWorks() throws Exception {
       StylusCompiler compiler = new StylusCompiler();
       File file = Play.getFile("test/test.styl");
       String result = compiler.compile(file);
       
       File expectedResultFile = Play.getFile("test/test.styl.result");
       String expectedResult = FileUtils.readFileToString(expectedResultFile);
       
       assertEquals(expectedResult, result);
    }
}

This simple test takes a prepared input file, compiles it, and checks whether the output is similar to an already parsed file. For simplicity, the file used in this test is the example from the stylus readme at https://github.com/LearnBoost/stylus/blob/master/Readme.md.

In case you are asking why only the compiler is tested, and not the whole plugin, including the preceding HTTP request: at the time of this writing there was a special handling for non controller resources, which could not be handled in functional tests. If you look at the source code of this recipe, you will see how an example functional test should look.

How to do it...

This plugin is rather short. It consists of two classes, first the StylusCompiler doing all the hard work. I skipped the creation of a new module and the play.plugins file in this case:

public class StylusCompiler {

   public String compile(File realFile) throws Exception {
      if (!realFile.exists() || !realFile.canRead()) {
         throw new FileNotFoundException(realFile + " not found");
      }
      String stylusPath = Play.configuration.getProperty("stylus.executable", "/usr/local/share/npm/bin/stylus");

      File stylusFile = new File(stylusPath);
      if (!stylusFile.exists() || !stylusFile.canExecute()) {
         throw new FileNotFoundException(stylusFile + " not found");
      }

      Process p = new ProcessBuilder(stylusPath).start();
      byte data[] = FileUtils.readFileToByteArray(realFile);
      p.getOutputStream().write(data);
      p.getOutputStream().close();

      InputStream is = p.getInputStream();
      String output = IOUtils.toString(is);
      is.close();

      return output;
   }
}

The second class is the plugin itself, which catches every request on files ending with styl and hands them over to the compiler:

public class StylusPlugin extends PlayPlugin {

   StylusCompiler compiler = new StylusCompiler();
   
   @Override
   public void onApplicationStart() {
      Logger.info("Loading stylus plugin");
   }
   
    @Override
    public boolean serveStatic(VirtualFile file, Request request, Response response) {
       String fileEnding = Play.configuration.getProperty("stylus.suffix", "styl");
       if(file.getName().endsWith("." + fileEnding)) {
          response.contentType = "text/css";
          response.status = 200;
          try {
             String key = "stylusPlugin-" + file.getName();
             String css = Cache.get(key, String.class);
             if (css == null) {
                css = compiler.compile(file.getRealFile());
             }
                
           // Cache in prod mode
           if(Play.mode == Play.Mode.PROD) {
              Cache.add(key, css, "1h");
              response.cacheFor(Play.configuration.getProperty("http.cacheControl", "3600") + "s");
           }
           response.print(css);
         } catch(Exception e) {
             response.status = 500;
             response.print("Stylus processing failed
");
             if (Play.mode == Play.Mode.DEV) {
                e.printStackTrace(new PrintStream(response.out));
             } else {
                Logger.error(e, "Problem processing stylus file");
             }
         }
         return true;
        }

     return false;
    }
}

How it works...

If the requested file ends with a certain configured suffix or styl, the response is taken care of in the serveStatic() method. If the content is not in the cache, the compiler creates it. If the system is acting in production mode, the created content is put in the cache for an hour, otherwise it is returned. Also, exceptions are only returned to the client in development mode, otherwise they are logged. As the return type of the method is Boolean, it should return true, if the plugin took care of delivering the requested resource.

The most notable thing about the stylus compiler is the way it is invoked. If you use stylus on the command line, you would execute it like stylus < in.styl in order to read the input from a file and print the output to the console, or more generally spoken, to stdout. As the < operator is a bash feature, we need to supply the input by writing the requested file into the output stream and reading the output from the input stream. This might be vice versa. This is what the StylusCompiler class mimics, when using the ProcessBuilder to invoke the external process.

Just to give you a feeling, how much a cache is needed: on my MacBook one hundred invocations on a small stylus file takes 16 seconds, including opening a new HTTP connection on each request. In production mode these hundred requests take less than one second.

A possible improvement for this plugin could be better handling of exceptions. The Play framework does a great job of exposing nice looking exceptions with exact error messages. This has not been accounted for in this example.

Stylus also supports CSS compression, so another improvement could be to build it into the compiler. However, this would merely be useful for production mode as it makes debugging more complex.

There's more...

There are lots of CSS preprocessors out there, so take the one you like most and feel comfortable with, as integration will be quite simple with the Play framework. Also you should take a look at the SASS, less, press, and greenscript modules of Play.

More information about CSS preprocessing

If you want to get more information about the CSS preprocessors mentioned in this recipe, go to http://sass-lang.com/, http://lesscss.org/, and https://github.com/LearnBoost/stylus.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.163.13