Slide 1

Slide 1 text

Using Grails Asset-Pipeline by @tednaleid 1

Slide 2

Slide 2 text

What is an “asset”? 2

Slide 3

Slide 3 text

assets are “static” files CSS, JavaScript, HTML & Images 3

Slide 4

Slide 4 text

Why do assets need a “pipeline”? 4

Slide 5

Slide 5 text

the asset you should serve is not the asset you develop 5

Slide 6

Slide 6 text

JavaScript should be served concatenated, minified & gzipped with long-lived cache headers 6

Slide 7

Slide 7 text

JavaScript should be developed as many small files Just like your Groovy and Java code 7

Slide 8

Slide 8 text

Your source code might not even be JavaScript (i.e. CoffeeScript, ClojureScript, ES6, Dart, GrooScript, …) 8

Slide 9

Slide 9 text

The best place to do all of this is compile-time then you're just serving static files 9

Slide 10

Slide 10 text

Behavior With & Without Asset-Pipeline 10

Slide 11

Slide 11 text

Without Asset-Pipeline Your layout is littered with tags 11

Slide 12

Slide 12 text

Without Asset-Pipeline Each tag is a request to the server 12

Slide 13

Slide 13 text

With Asset-Pipeline Assets transpiled, concatenated, minified, gzipped into one file per type 13

Slide 14

Slide 14 text

With Asset-Pipeline One server hit per file type 14

Slide 15

Slide 15 text

Without Asset-Pipeline Browser cache isn't properly used 15

Slide 16

Slide 16 text

With Asset-Pipeline Initial Request - Good Cache Headers 16

Slide 17

Slide 17 text

With Asset-Pipeline Next Request - Cache Hit 17

Slide 18

Slide 18 text

Without Asset-Pipeline Limited to browser-supported languages i.e. CSS and JavaScript 18

Slide 19

Slide 19 text

With Asset-Pipeline Can use anything that transpiles to browser-supported languages ex: SASS, LESS, CoffeeScript, ClojureScript … 19

Slide 20

Slide 20 text

Developing With Asset-Pipeline 20

Slide 21

Slide 21 text

Files go in grails-app/assets 21

Slide 22

Slide 22 text

JavaScript Dependency Manifest //= encoding UTF-8 //= require lib/angular.js //= require_self //= require_tree model (function () { 'use strict'; console.log("loaded app.js"); … }); })(); assets/javascripts/app.js 22

Slide 23

Slide 23 text

CSS Dependency Manifest /* *= 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 23

Slide 24

Slide 24 text

Use Taglibs in Layout GSP <g:layoutTitle default="Some Title" /> … … … grails-app/views/layouts/main.gsp 24

Slide 25

Slide 25 text

Defer JavaScript Snippets in Views

foobar

console.log("on foobar page");
grails-app/views/user/index.gsp emitted at bottom of layout at 25

Slide 26

Slide 26 text

Dev Mode is Tuned for Quick Iteration grails run-app 26

Slide 27

Slide 27 text

All Files Served With Real Names // from: // from: 27

Slide 28

Slide 28 text

In Development Files Will Not Cache // 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 Response Cache Headers 28

Slide 29

Slide 29 text

War File Serves Processed Assets grails war or grails run-war 29

Slide 30

Slide 30 text

War Compilation Puts All Assets Through the Full Pipeline adds assetClean and assetCompile events 30

Slide 31

Slide 31 text

War File has assets 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 … 31

Slide 32

Slide 32 text

Creates 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 … 32

Slide 33

Slide 33 text

AssetPipelineFilter Processes /assets/* Requests 33

Slide 34

Slide 34 text

looks for file in /assets if found returns (gzipped?) file with ETag from manifest 34

Slide 35

Slide 35 text

Optionally use CDN or Nginx 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 35

Slide 36

Slide 36 text

Writing Your Own Asset-Pipeline Plugin 36

Slide 37

Slide 37 text

Implement/Override an 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 } } } 37

Slide 38

Slide 38 text

Create 1..N Processors 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() } } 38

Slide 39

Slide 39 text

Configure Asset-Pipeline // 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' } 39

Slide 40

Slide 40 text

Asset-Pipeline AngularJS Tips 40

Slide 41

Slide 41 text

AngularJS - Minification Breaks Magic // 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 41

Slide 42

Slide 42 text

angular-template-asset-pipeline // original: /grails-app/assets/templates/my-app/app-section/index.tpl.htm //

Hello World!

angular.module('myApp.appSection') .run(['$templateCache', function($templateCache) { $templateCache.put('index.htm', '

Hello World!

'); }]); Compile HTML templates Into $templateCache 42

Slide 43

Slide 43 text

Advantages Over the Resources Plugin 43

Slide 44

Slide 44 text

Allows the use of a CDN 44

Slide 45

Slide 45 text

No Waiting to Recompile in Development 45

Slide 46

Slide 46 text

Precompilation Allows Faster War Startup 46

Slide 47

Slide 47 text

Dependencies Are Inside Assets 47

Slide 48

Slide 48 text

Actively Developed 48

Slide 49

Slide 49 text

Migrating from Resources Plugin 49

Slide 50

Slide 50 text

Resources layout: Asset-Pipeline layout: bottom of layout : 50

Slide 51

Slide 51 text

Resources Asset-Pipeline 51

Slide 52

Slide 52 text

Resources Asset-Pipeline 52

Slide 53

Slide 53 text

Resources gsp: alert('foo'); layout: Asset-Pipeline gsp: alert('foo'); layout: 53

Slide 54

Slide 54 text

Resources in gsp: href="${resource(dir: 'css', file: 'errors.css')}" Asset-Pipeline in gsp: href="${asset.assetPath(src: 'errors.css')}" 54

Slide 55

Slide 55 text

Asset-Pipeline Alternatives 55

Slide 56

Slide 56 text

gradle not part of grails build without more work, doesn't solve caching/etags 56

Slide 57

Slide 57 text

grunt/gulp requires buy-in/support of node.js ecosystem, doesn't solve caching/etags 57

Slide 58

Slide 58 text

resources plugin deprecated, more painful to work with 58

Slide 59

Slide 59 text

Questions? 59