by @tednaleid
<link rel="stylesheet" href="/test-app/assets/main.css" />
<link rel="stylesheet" href="/test-app/assets/mobile.css" />
<link rel="stylesheet" href="/test-app/assets/app.css" />
<script src="/test-app/assets/vendor/angular/angular-route.js" type="text/javascript" ></script>
<script src="/test-app/assets/vendor/jquery/jquery.js" type="text/javascript" ></script>
<script src="/test-app/assets/vendor/mongolab/mongolab-resource.js" type="text/javascript" ></script>
<script src="/test-app/assets/app/admin/admin.js" type="text/javascript" ></script>
<script src="/test-app/assets/app/admin/projects/admin-projects.js" type="text/javascript" ></script>
<script src="/test-app/assets/app/admin/users/admin-users-edit.js" type="text/javascript" ></script>
<script src="/test-app/assets/app/admin/users/admin-users-list.js" type="text/javascript" ></script>
<script src="/test-app/assets/app/admin/users/admin-users.js" type="text/javascript" ></script>
<script src="/test-app/assets/app/admin/users/uniqueEmail.js" type="text/javascript" ></script>
<script src="/test-app/assets/app/admin/users/validateEquals.js" type="text/javascript" ></script>
<script src="/test-app/assets/app/app.js" type="text/javascript" ></script>
<script src="/test-app/assets/app/dashboard/dashboard.js" type="text/javascript" ></script>
<script src="/test-app/assets/app/projects/productbacklog/productbacklog.js" type="text/javascript" ></script>
<script src="/test-app/assets/app/projects/projects.js" type="text/javascript" ></script>
<script src="/test-app/assets/app/projects/sprints/sprints.js" type="text/javascript" ></script>
<script src="/test-app/assets/app/projects/sprints/tasks/tasks.js" type="text/javascript" ></script>
<script src="/test-app/assets/app/projectsinfo/projectsinfo.js" type="text/javascript" ></script>
<script src="/test-app/assets/app.js" type="text/javascript" ></script>
<link rel="stylesheet" href="/test-app/assets/app-08f92c4662379e3e9a56541af70c871c.css"/>
<script src="/test-app/assets/app-e1ec7cb3d4c455fac6e62f1faa6232f9.js" type="text/javascript" ></script>
grails-app/assets
//= encoding UTF-8
//= require lib/angular.js
//= require_self
//= require_tree model
(function () {
'use strict';
console.log("loaded app.js");
…
});
})();
assets/javascripts/app.js
/*
*= encoding UTF-8
*= require fonts
*= require bootstrap
*= require_self
*/
input.ng-invalid.ng-dirty:not(:focus) {
border-color: #d43232;
background-color: #f2dede;
}
…
assets/stylesheets/app.css
<head>
<title><g:layoutTitle default="Some Title" /></title>
…
<asset:link rel="shortcut icon" href="favicon.ico" type="image/x-icon"/>
<asset:stylesheet href="app.css"/>
<g:layoutHead />
</head>
<body>
<asset:image src="logo.png" width="200" height="200"/>
…
<g:layoutBody />
…
<asset:javascript src="app.js"/>
<asset:deferredScripts/>
</body>
grails-app/views/layouts/main.gsp
<div>
<h1>foobar</h1>
<asset:script type="text/javascript">
console.log("on foobar page");
</asset:script>
</div>
grails-app/views/user/index.gsp
emitted at bottom of layout at <asset:deferredScripts/>
grails run-app
// from: <asset:stylesheet href="app.css"/>
<link rel="stylesheet" href="/myapp/assets/fonts.css" />
<link rel="stylesheet" href="/myapp/assets/bootstrap.css" />
<link rel="stylesheet" href="/myapp/assets/app.css" />
// from: <asset:javascript src="app.js"/>
<script src="/myapp/assets/lib/angular.js" type="text/javascript" ></script>
<script src="/myapp/assets/app.js" type="text/javascript" ></script>
<script src="/myapp/assets/model/Foo.js" type="text/javascript" ></script>
<script src="/myapp/assets/model/Bar.js" type="text/javascript" ></script>
// HTTP 1.1
Cache-Control:no-cache, no-store, must-revalidate
// HTTP 1.0
Pragma:no-cache
// for proxies
Expires:Thu, 01 Jan 1970 00:00:00 GMT
grails war
or grails run-war
assetClean
and assetCompile
eventsassets
Folder…
assets/logo-7b9776076d5fceef4993b55c9383dedd.jpg
assets/logo.jpg
…
assets/app-2b367068131a9dd4f31c18f635eb7e6c.css
assets/app-2b367068131a9dd4f31c18f635eb7e6c.css.gz
assets/app.css
assets/app.css.gz
…
assets/app-4bf9739e18a6d5f665c42ecb7edbd339.js
assets/app-4bf9739e18a6d5f665c42ecb7edbd339.js.gz
assets/app.js
assets/app.js.gz
…
assets/manifest.properties
…
manifest.properties
File With ETag Mapping For Each Asset…
logo.jpg=logo-7b9776076d5fceef4993b55c9383dedd.jpg
…
app.css=app-2b367068131a9dd4f31c18f635eb7e6c.css
bootstrap.css=bootstrap-2b367068131a9dd4f31c18f635eb7e6c.css
fonts.css=fonts-81f3e38a6e878acd57e3d51912c37b04.css
…
lib/angular.js=lib/angular-de39960f9b36bbbd76e76e7ed0086922.js
app.js=app-4bf9739e18a6d5f665c42ecb7edbd339.js
…
AssetPipelineFilter
Processes /assets/*
Requests/assets
grails.assets.url = "https://cdn.example.com/"
// or
grails.assets.url = { request ->
if(request.isSecure()) {
return "https://cdn.example.com/"
} else {
return "http://cdn.example.com/"
}
}
(in Config.groovy
)
automate uploading with the cdn-asset-pipeline plugin
AssetFile
package asset.pipeline
import asset.pipeline.AbstractAssetFile
class MyAssetFile extends AbstractAssetFile { // implements AssetFile
static final String contentType = 'application/javascript'
static extensions = ['js-myfile'] // MUST BE UNIQUE ACROSS ALL `AssetFile`s
static final String compiledExtension = 'js'
static processors = [MyFileProcessor]
String directiveForLine(String line) {
// identifies the directive in manifest lines at top of file
// i.e. those starting with '//='
line.find(/\/\/=(.*)/) { fullMatch, directive -> directive }
}
}
package asset.pipeline
class MyFileProcessor implements Processor {
MyFileProcessor(AssetCompiler compiler) {
// compiler gives us access to the options/rules/paths
// that asset-pipeline is running with
super(compiler)
}
// inputText - a String holding the contents of the asset
// assetFile - an AssetFile instance, has `file` and `baseFile` props
// expected to return - a String holding the processed asset text
def process(inputText, assetFile) {
// ex processor to turn the asset into all UPPER CASE
return inputText.toUpperCase()
}
}
// MyAssetPipelineGrailsPlugin.groovy
import asset.pipeline.AssetHelper
import asset.pipeline.MyAssetFile
class MyAssetPipelineGrailsPlugin {
// …
def doWithDynamicMethods = { ctx ->
AssetHelper.assetSpecs << MyAssetFile
}
}
// scripts/_Events.groovy
eventAssetPrecompileStart = { assetConfig ->
assetConfig.specs << 'asset.pipeline.MyAssetFile'
}
// AngularJS magic injects $window using arg name
// MINIFICATION UNSAFE
myApp.controller('MyCtrl', function($window) {
$window.alert("Hello World");
});
// SAFE for minification
myApp.controller('MyCtrl', ['$window', function($window) {
$window.alert("Hello World");
}]);
Either turn variable mangling off in Config.groovy
or
use Craig Burke's angular-annotate-asset-pipeline plugin
// original: /grails-app/assets/templates/my-app/app-section/index.tpl.htm
// <h1>Hello World!</h1>
angular.module('myApp.appSection')
.run(['$templateCache', function($templateCache) {
$templateCache.put('index.htm', '<h1>Hello World!</h1>');
}]);
$templateCache
<r:require modules="app"/>
<asset:stylesheet href="app.css"/>
<asset:javascript src="app.js"/>
<r:external uri="/img/favicon.ico"/>
<asset:link rel="shortcut icon" href="favicon.ico" type="image/x-icon"/>
<g:img dir="img" file="logo.jpg" alt="The Logo" />
<asset:image src="logo.png" alt="The Logo"/>
<r:script disposition="defer">alert('foo');</r:script>
<r:layoutResources/>
<asset:script>alert('foo');</asset:script>
<asset:deferredScripts/>
href="${resource(dir: 'css', file: 'errors.css')}"
href="${asset.assetPath(src: 'errors.css')}"