/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jooby.assets;
import java.util.List;
import org.jooby.Env;
import org.jooby.Jooby;
import org.jooby.Router;
import org.jooby.handlers.AssetHandler;
import org.jooby.internal.assets.AssetHandlerWithCompiler;
import org.jooby.internal.assets.AssetVars;
import org.jooby.internal.assets.LiveCompiler;
import com.google.common.collect.ImmutableList;
import com.google.inject.Binder;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
/**
* <h1>assets</h1>
* <p>
* The asset module is library to concatenate, minify or compress JavaScript and CSS
* assets. It also adds the ability to write these assets in other languages and process/compile
* them to another language. Finally, it help you to write high quality code by validate JavaScript
* and CSS too.
* </p>
* <p>
* A variety of processors are available (jshint, csslint, jscs, uglify, closure-compiler, etc..),
* but
* also you might want to write your owns.
* </p>
*
* <h2>getting started</h2>
* <p>
* The first thing you need to do is to define your assets. Definition is done in your
* <code>.conf</code> file or in a special file: <code>assets.conf</code>.
* </p>
*
* <strong>assets.conf</strong>
* <pre>
* assets {
* fileset {
* home: [assets/home.js, assets/home.css]
* }
* }
* </pre>
*
* <strong>App.java</strong>
* <pre>
* {
* use(new Assets());
* }
* </pre>
*
* <p>
* The assets module will publish 4 request local variables for <code>home</code> fileset:
* <code>_css</code> and <code>_js</code> each of these variables is a list of string with the
* corresponding files. There are two more variables: <code>_styles</code> and <code>_scripts</code>
* :
* </p>
*
* <pre>
* <html>
* <head>
* {{{home_styles}}}
* <body>
* ...
* {{{home_scripts}}
* </body>
* </head>
* </html>
* </pre>
*
* <p>
* The variables: <code>_styles</code> and <code>_scripts</code> produces one ore more
* <code>link</code> and <code>script</code> tags. The example above, shows you how to
* render these variables in the template engine of your choice (handlebars, here).
* </p>
*
* <p>
* Now, let's see how to configure the Maven plugin to process our assets at build-time:
* </p>
*
* <strong>pom.xml</strong>
*
* <pre>
* <plugin>
* <groupId>org.jooby</groupId>
* <artifactId>jooby-maven-plugin</artifactId>
* <executions>
* <execution>
* <goals>
* <goal>assets</goal>
* </goals>
* </execution>
* </executions>
* </plugin>
* </pre>
*
* <p>
* The plugin will process all your assets and include them to the final <code>.jar</code>,
* <code>.zip</code> or <code>.war</code>.
* </p>
*
* <p>
* Cool, isn't?
* </p>
*
* <h2>how it works?</h2>
* <p>
* The <code>assets.fileset</code> defines all your assets. In <code>dev</code> assets are
* rendered/processed at runtime. In <code>prod</code> at built-time.
* </p>
* <p>
* Assets are rendered at runtime using <code>*_styles</code> or <code>*_scripts
* </code> variables. So you define your assets in one single place: <code>assets.conf</code>.
* </p>
* <p>
* Also, at build-time, the asset compiler concatenates all the files from a fileset and
* generate a fingerprint. The fingerprint is a SHA-1 hash of the content of the fileset. Thanks to
* the fingerprint an asset can be cached it for ever! Defaults cache max age is:
* <code>365 days</code>.
* </p>
* <p>
* That isn't all! the <code>*_styles</code> and <code>*_scripts</code> are updated with the
* fingerprint version of assets, so you don't have to do or change anything in your views! It just
* works!!!
* </p>
*
* <h2>fileset</h2>
* <p>
* A fileset is a group of assets within a name. The fileset name is expanded into 4 request local
* variables, for example:
* </p>
* <pre>
* assets {
* fileset {
* home: [assets/home.js, assets/home.css]
* pageA: [assets/pageA.js, assets/pageA.css]
* }
* }
* </pre>
*
* <p>
* Produces 4 variables for <code>home</code>:
* </p>
*
* <ul>
* <li>home_css: a list of all the <code>css</code> files</li>
* <li>home_styles: a string, with all the <code>css</code> files rendered as <code>link</code> tags
* </li>
* <li>home_js: a list of all the <code>js</code> files</li>
* <li>home_scripts: a string, with all the <code>js</code> files rendered as <code>script</code>
* tags</li>
* </ul>
*
* <p>
* Another 4 variables will be available for the <code>pageA</code> fileset!
* </p>
*
* <h3>extending filesets</h3>
* <p>
* Extension or re-use of filesets is possible via the: <code><</code> operator:
* </p>
* <pre>
* assets {
* fileset {
* base: [js/lib/jquery.js, css/normalize.css]
* home < base: [js/home.js]
* pageA < base: [js/pageA.js]
* }
* }
* </pre>
*
* <h2>processors</h2>
* <p>
* An {@link AssetProcessor} usually checks or modify an asset content in one way or another. They
* are defined in the <code>assets.conf</code> files using the <code>pipeline</code> construction:
* </p>
*
* <pre>
* assets {
* fileset {
* home: [js/home.js, css/home.css]
* }
*
* pipeline {
* dev: [jshint, jscs, csslint, sass]
* dist: [uglify, sass, clean-css]
* }
* }
* </pre>
*
* <p>
* Example above, defines a <strong>pipeline</strong> for development (dev) and one generic for prod
* (dist).
* </p>
* <p>
* In <code>dev</code> the code will be checked it against js-hint, jscs and csslint! But
* also, we want to use sass for css!!
* </p>
* <p>
* The generic <code>dist</code> will be used it for any other environment and here we just want to
* optimize our javascript code with uglify, compile sass to css and then optimize the css using
* clean-css!!
* </p>
*
* <p>
* For more information about processor, have a look at the {@link AssetProcessor} doc.
* </p>
*
* <h2>aggregators</h2>
* <p>
* Contributes new or dynamically generated content to a <code>fileset</code>. Content generated by
* an aggregator might be processed by an {@link AssetProcessor}.
* </p>
*
* <h3>how to use it?</h3>
* <p>
* First thing to do is to add the dependency:
* </p>
* <pre>
* <dependency>
* <groupId>org.jooby</groupId>
* <artifactId>jooby-assets-dr-svg-sprites</artifactId>
* <scope>provided</scope>
* </dependency>
* </pre>
*
* <p>
* Did you see the <strong>provided</strong> scope? We just need the aggregator for development,
* because assets are processed at runtime. For <code>prod</code>, assets are processed at
* built-time via Maven/Gradle plugin, so we don't need it. This also, helps to keep our
* dependencies and the jar size small.
* </p>
*
* <p>
* Now we have the dependency all we have to do is to add the <code>svg-sprites</code> aggregator to
* a fileset:
* </p>
*
* <pre>
* assets {
* fileset {
* home: [
* // 1) Add the aggregator to a fileset
* svg-sprites,
* css/style.css,
* js/app.js
* ]
* }
*
* svg-sprites {
* // 2) The `css/sprite.css` file is part of the `home` fileset.
* spritePath: "css/sprite.css"
*
* spriteElementPath: "images/svg",
* }
* }
* </pre>
*
* <p>
* Here for example, the <code>svg-sprites</code> aggregator contributes the
* <code>css/sprite.css</code> file to the <code>home</code> fileset. The fileset then looks like:
* </p>
*
* <pre>
* assets {
* fileset {
* home: [
* css/sprite.css,
* css/style.css,
* js/app.js
* ]
* }
* }
* </pre>
* <p>
* Replaces the aggregator name with one or more files from {@link AssetAggregator#fileset()}
* method.
* </p>
*
* @author edgar
* @since 0.11.0
*/
public class Assets implements Jooby.Module {
@Override
public void configure(final Env env, final Config config, final Binder binder) throws Throwable {
String envname = env.name();
boolean dev = "dev".equals(envname);
ClassLoader loader = getClass().getClassLoader();
Config conf = conf(dev, loader, config);
String cpath = config.getString("application.path");
AssetCompiler compiler = new AssetCompiler(loader, conf);
Router routes = env.router();
List<String> dist = dev ? ImmutableList.of("dev") : ImmutableList.of(envname, "dist");
String prefix = dist.stream()
.filter(it -> conf.hasPath("assets." + it + ".prefix"))
.findFirst()
.map(it -> conf.getString("assets." + it + ".prefix"))
.orElse(cpath);
routes.use("*", "*", new AssetVars(compiler, prefix, !dev)).name("/assets/vars");
// live compiler?
boolean watch = conf.hasPath("assets.watch") ? conf.getBoolean("assets.watch") : dev;
if (watch) {
LiveCompiler liveCompiler = new LiveCompiler(conf, compiler);
env.onStart(liveCompiler::start);
env.onStop(liveCompiler::stop);
routes.use("*", "*", liveCompiler).name("/assets/compiler");
}
AssetHandler handler = dev
? new AssetHandlerWithCompiler("/", compiler)
: new AssetHandler("/");
handler.etag(conf.getBoolean("assets.etag"))
.cdn(conf.getString("assets.cdn"))
.lastModified(conf.getBoolean("assets.lastModified"));
handler.maxAge(conf.getString("assets.cache.maxAge"));
compiler.patterns().forEach(pattern -> routes.get(pattern, handler));
}
private Config conf(final boolean dev, final ClassLoader loader, final Config conf) {
final Config[] confs;
if (!dev) {
confs = new Config[]{
ConfigFactory.parseResources(loader,
"assets." + conf.getString("application.env").toLowerCase() + ".conf"),
ConfigFactory.parseResources(loader, "assets.dist.conf"),
ConfigFactory.parseResources(loader, "assets.conf") };
for (Config it : confs) {
if (!it.isEmpty()) {
return it.withFallback(conf).resolve();
}
}
}
return ConfigFactory.parseResources(loader, "assets.conf").withFallback(conf).resolve();
}
}