Single Page JavaScript WebApps A Gradle Story Kon Soulianidis MelbJVM July 2014
This talk is about Our App Why we picked Gradle Gradle Plugins for JS / CSS Organising your Build Taking advantage of Java Features
They Who Built The Proof of Concept that Grew
Our App Single page app Choose reports to run, filter by an order or client Query the endpoint, Google BigQuery Datastore with language specific APIs for Java + JavaScript Return interactive charts
Libraries JQuery Bootstrap Google API’s JS libraries Google Charts & Visualisations API
The PoC that Grew Proof of Concept Successful At this stage a couple of monolithic JS files held together by a static html page Time to think about some proper JS tooling
Image Source: JHipster.github.io
To market, to market, to become a JS- Hipster Set proxy $ npm config set https- proxy="https://NTLMREALMuser:pword@proxy.corp.com:8080/" $ npm config set proxy=“http://NTLMREALMuser:pword@proxy.corp.com:8080/" Run Npm… $ npm install npm npm http GET https://registry.npmjs.org/npm npm http GET https://registry.npmjs.org/npm npm http GET https://registry.npmjs.org/npm … Looks Promising…
Victory Snatched by the Jaws of Defeat But alas, our clients site hit a problem npm ERR! Error: tunneling socket could not be established, cause=140230033848128:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:../deps/openssl/openssl/ssl/s23_clnt.c:766:
SSL23_GET_SERVER_HELLO:unkno wn protocol Stack overflow features many casualties across all kinds of staple unix tools that use HTTPS Solutions say configure tool or proxy to not verify SSL (you like man in the middle vulnerabilities?) In corporate IT at client site we can’t & wont do this.
So much for being a hipster Image Source Photobucket
JS - Climbing the development tools tree, Taking each branch on the fall down
1984
1984
The Big G
Gradle CSS & JS Plugins Eric Wendelin Gradle JS Plugin Gradle Css Plugin Google Closure Compiler Yahoo UI Compressor
Getting Started apply plugin: 'js' apply plugin: 'css' // then add some dependency and build script dependencies
plugins.gradle.org apply plugin: 'js' apply plugin: 'css' // define the dependencies gradle buildscript will use to build the app (not the app itself) buildscript { repositories { mavenLocal() mavenCentral() } dependencies { classpath 'com.eriwen:gradle-css-plugin:1.11.1' classpath 'com.eriwen:gradle-js-plugin:1.11.1' } }
plugins.gradle.org apply plugin: 'js' apply plugin: 'css' // define the dependencies gradle buildscript will use to build the app (not the app itself) buildscript { repositories { mavenLocal() mavenCentral() } dependencies { classpath 'com.eriwen:gradle-css-plugin:1.11.1' classpath 'com.eriwen:gradle-js-plugin:1.11.1' } }
plugins.gradle.org
plugins.gradle.org
If something doesn't work take a look at issues and pull requests in Github Support
Gradle Tasks Build tasks ----------- combineCss - Combine many CSS files into one combineJs - Combine many JavaScript files into one gzipCss - GZip a given CSS file gzipJs - GZip a given JavaScript file lesscss - Compiles LESS files into CSS minifyCss - Minify CSS using YUI Minifier minifyJs - Minify JavaScript using Closure Compiler props2js - Convert Java properties files for use with JavaScript requireJs - Run the r.js Optimizer to produce Require.js output
Ok we are setup, now what? Combination, Minification GZipping
Project Layout
Old Fashioned Download dependencies From the internet Manually!
CSS Lets combine and minify our CSS
CSS // Declare your sources css.source { dev { css { srcDir "app/styles" include "*.css" exclude "*.min.css" } } }
CSS // Specify a collection of files to be combined, then minified and finally GZip compressed. combineCss { source = css.source.dev.css.files dest = "${buildDir}/all.css" }
CSS minifyCss { source = combineCss dest = "${buildDir}/all-min.css" yuicompressor { // Optional lineBreakPos = -1 } }
CSS gzipCss { source = minifyCss dest = "${buildDir}/all.min.css.gz" }
Combine kon@Kostas-MBP ~/Projects/gradle-summit- presentation $ gradle combineCss :combineCss BUILD SUCCESSFUL Total time: 5.812 secs kon@Kostas-MBP ~/Projects/gradle-summit- presentation $ ls build/ ./ ../ all.css
CSS Minify In Action kon@Kostas-MBP ~/Projects/gradle-summit- presentation $ gradle minifyCss :combineCss UP-TO-DATE :minifyCss BUILD SUCCESSFUL Total time: 3.545 secs
CSS Minify In Action kon@Kostas-MBP ~/Projects/gradle-summit- presentation $ gradle minifyCss :combineCss UP-TO-DATE :minifyCss BUILD SUCCESSFUL Total time: 3.545 secs kon@Kostas-MBP ~/Projects/gradle-summit- presentation $ ls build ./ ../ all-min.css all.css
CSS Minify In Action kon@Kostas-MBP ~/Projects/gradle-summit-presentation $ gradle minifyCss :combineCss UP-TO-DATE :minifyCss BUILD SUCCESSFUL Total time: 3.545 secs kon@Kostas-MBP ~/Projects/gradle-summit-presentation $ ls build ./ ../ all-min.css all.css kon@Kostas-MBP ~/Projects/gradle-summit-presentation $ cat build/all-min.css body{padding-top:50px}.sub-header{padding-bottom:10px;border-bottom:1px solid #eee}.sidebar{display:none}@media(min- width:768px){.sidebar{position:fixed;top:51px;bottom:0;left:0;z- index:1000;display:block;padding:20px;overflow-x:hidden;overflow-y:auto;background- color:#f5f5f5;border-right:1px solid #eee}}.nav-sidebar{margin-right:-21px;margin-bottom:20px;margin- left:-20px}.nav-sidebar>li>a{padding-right:20px;padding-left:20px}.nav- sidebar>.active>a{color:#fff;background-color:#428bca}.main{padding:20px}@media(min- width:768px){.main{padding-right:40px;padding-left:40px}}.main .page-header{margin- top:0}.placeholders{margin-bottom:30px;text-align:center}.placeholders h4{margin- bottom:0}.placeholder{margin-bottom:20px}.placeholder img{display:inline-block;border- radius:50%}.navbar{background-image:url('../images/gradle_small.png');background-position- x:30%;background-repeat:no-repeat;background-size:contain}
gzipCss kon@Kostas-MBP ~/Projects/gradle-summit- presentation $ gradle gzipCss :combineCss UP-TO-DATE :minifyCss UP-TO-DATE :gzipCss BUILD SUCCESSFUL Total time: 4.881 secs
gzipCss kon@Kostas-MBP ~/Projects/gradle-summit-presentation $ gradle gzipCss :combineCss UP-TO-DATE :minifyCss UP-TO-DATE :gzipCss BUILD SUCCESSFUL Total time: 4.881 secs kon@Kostas-MBP ~/Projects/gradle-summit-presentation $ ls -la build total 24 drwxr-xr-x 5 kon wheel 170 10 Jun 19:05 ./ drwxr-xr-x 12 kon wheel 408 10 Jun 18:51 ../ -rw-r--r-- 1 kon wheel 917 10 Jun 18:55 all-min.css -rw-r--r-- 1 kon wheel 1539 10 Jun 18:51 all.css -rw-r--r-- 1 kon wheel 438 10 Jun 19:04 all.min.css.gz
gzipCss kon@Kostas-MBP ~/Projects/gradle-summit-presentation $ gradle gzipCss :combineCss UP-TO-DATE :minifyCss UP-TO-DATE :gzipCss BUILD SUCCESSFUL Total time: 4.881 secs kon@Kostas-MBP ~/Projects/gradle-summit-presentation $ ls -la build total 24 drwxr-xr-x 5 kon wheel 170 10 Jun 19:05 ./ drwxr-xr-x 12 kon wheel 408 10 Jun 18:51 ../ -rw-r--r-- 1 kon wheel 917 10 Jun 18:55 all-min.css -rw-r--r-- 1 kon wheel 1539 10 Jun 18:51 all.css -rw-r--r-- 1 kon wheel 438 10 Jun 19:04 all.min.css.gz And Proof kon@Kostas-MBP ~/Projects/gradle-summit-presentation $ cat build/all.min.css.gz pnK?i?Cċ?%?g?(?D2?C?X‫?5??]-??++-ڦ‬XvN???3Ci?q?Ԓc?Sn????$ (????i?a|oA ?mQ?/?a/ϯ????x h4??Q$-?tI^(?C??&??QH???2?g1Ke?I?9????"=k??*Gƻ`??W?ҙN Re????g??P6?j~ ??;?N?‫ݤ‬w.Ͳ???? ?] x??M?c>?J???x?4ʫ???E}]???~?ZV|?-G=l?{$ۣ ??????2??f?r???n??#?㩠1*"~8??q???+??U??sJ????{?q???r]D? u?!??뜺???>???2r|??+E??7w?‫ؗ‬=?{??_ox??????<~??1???C???kon@Kostas-MBP ~/Projects/gradle-summit-presentation $
combine, minify, gzip JS combineJs { // pull together the source from string & file lists // eg. def core = ["$webAppDirName/init.js",...] source = core + application + devmode + bigquery + javascript.source.externalLibs.js.files + dfpFilters + chartdef + uxFilters // show the resolved files when gradle is run with -d source.each{ logger.debug ("$it") } dest = file("${buildDir}/all.js") }
Configuring Sources
MinifyJS minifyJs { source = combineJs dest = file("${buildDir}/all.min.js") sourceMap = file("${buildDir}/all.sourcemap.json") closure { warningLevel = 'QUIET' compilerOptions.defineReplacements = ['MY_DEBUG_FLAG':false] } }
Closure Compiler Options A bit hard to find them all. You can find them in a source file in the Closure compiler project. https://github.com/google/closure- compiler/blob/master/src/com/google/javascript/jsc omp/CompilerOptions.java
defineReplacements // config.js /** * Flag to indicate console and extra logging throughout the app * @define {boolean} allow the value of this bool to be * overwritten at closure compiler / minification time * @const * @type {boolean} */ var MY_DBUG_FLAG = true;
if (MY_DBUG_FLAG) { $('.dashboard').append( '&lt;p&gt;Here's some dev info you wouldn't normally see&lt;/p&gt;' ); }
Gzip gzipJs { source = minifyJs.dest dest = file(“${buildDir}/all.min.js.gz”) }
Chain Together tasks.minifyJs.dependsOn tasks.combineJs tasks.gzipJs.dependsOn tasks.minifyJs
Break It Down Multiple Project Builds
Break It Down
Referenced from build.gradle
Deploy to a Container
Use Tomcat Plugin
Run Tomcat PrintGCTimeStampsPrint
Your Own Tasks Write your own tasks to change code
Head.js or direct script include in different environments
–Johnny Appleseed “Type a quote here.”PrintGCTimeStampsPrint
Bringing Java Back
Servlet Security Demo
–Johnny Appleseed “Type a quote here.”PrintGCTimeStampsPrint
Grunt
Remember! Gradle works for building HTML/ JS / CSS assets Embedded Server from Checkout End 2 End Visibility Proxies
Thanks Blog goes into more detail http://blog.shinetech.com/2014/03/19/javascript-webapps- with-gradle/ Photos Tom Botton - MelbJVM September Brigitte Schuckert - Gradleware Keynote Kon Soulianidis - Pisa & Monkey looking in lens Climbing the Tree - Mary Evans. Licensed Shipping Containers - Jim Bahn. Flickr. CC License Animated Gifs - Photobucket Duke, Grunt, Gradle logos, respective owners

Single Page JavaScript WebApps... A Gradle Story

  • 1.
    Single Page JavaScript WebApps AGradle Story Kon Soulianidis MelbJVM July 2014
  • 2.
    This talk isabout Our App Why we picked Gradle Gradle Plugins for JS / CSS Organising your Build Taking advantage of Java Features
  • 4.
    They Who Built TheProof of Concept that Grew
  • 5.
    Our App Single pageapp Choose reports to run, filter by an order or client Query the endpoint, Google BigQuery Datastore with language specific APIs for Java + JavaScript Return interactive charts
  • 9.
    Libraries JQuery Bootstrap Google API’s JSlibraries Google Charts & Visualisations API
  • 10.
    The PoC thatGrew Proof of Concept Successful At this stage a couple of monolithic JS files held together by a static html page Time to think about some proper JS tooling
  • 11.
  • 12.
    To market, tomarket, to become a JS- Hipster Set proxy $ npm config set https- proxy="https://NTLMREALMuser:pword@proxy.corp.com:8080/" $ npm config set proxy=“http://NTLMREALMuser:pword@proxy.corp.com:8080/" Run Npm… $ npm install npm npm http GET https://registry.npmjs.org/npm npm http GET https://registry.npmjs.org/npm npm http GET https://registry.npmjs.org/npm … Looks Promising…
  • 13.
    Victory Snatched bythe Jaws of Defeat But alas, our clients site hit a problem npm ERR! Error: tunneling socket could not be established, cause=140230033848128:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:../deps/openssl/openssl/ssl/s23_clnt.c:766:
  • 14.
    SSL23_GET_SERVER_HELLO:unkno wn protocol Stack overflowfeatures many casualties across all kinds of staple unix tools that use HTTPS Solutions say configure tool or proxy to not verify SSL (you like man in the middle vulnerabilities?) In corporate IT at client site we can’t & wont do this.
  • 15.
    So much forbeing a hipster Image Source Photobucket
  • 16.
    JS - Climbingthe development tools tree, Taking each branch on the fall down
  • 17.
  • 18.
  • 19.
  • 20.
    Gradle CSS &JS Plugins Eric Wendelin Gradle JS Plugin Gradle Css Plugin Google Closure Compiler Yahoo UI Compressor
  • 21.
    Getting Started apply plugin:'js' apply plugin: 'css' // then add some dependency and build script dependencies
  • 22.
    plugins.gradle.org apply plugin: 'js' applyplugin: 'css' // define the dependencies gradle buildscript will use to build the app (not the app itself) buildscript { repositories { mavenLocal() mavenCentral() } dependencies { classpath 'com.eriwen:gradle-css-plugin:1.11.1' classpath 'com.eriwen:gradle-js-plugin:1.11.1' } }
  • 23.
    plugins.gradle.org apply plugin: 'js' applyplugin: 'css' // define the dependencies gradle buildscript will use to build the app (not the app itself) buildscript { repositories { mavenLocal() mavenCentral() } dependencies { classpath 'com.eriwen:gradle-css-plugin:1.11.1' classpath 'com.eriwen:gradle-js-plugin:1.11.1' } }
  • 24.
  • 25.
  • 26.
    If something doesn'twork take a look at issues and pull requests in Github Support
  • 27.
    Gradle Tasks Build tasks ----------- combineCss- Combine many CSS files into one combineJs - Combine many JavaScript files into one gzipCss - GZip a given CSS file gzipJs - GZip a given JavaScript file lesscss - Compiles LESS files into CSS minifyCss - Minify CSS using YUI Minifier minifyJs - Minify JavaScript using Closure Compiler props2js - Convert Java properties files for use with JavaScript requireJs - Run the r.js Optimizer to produce Require.js output
  • 28.
    Ok we aresetup, now what? Combination, Minification GZipping
  • 30.
  • 32.
  • 33.
    CSS Lets combine andminify our CSS
  • 34.
    CSS // Declare yoursources css.source { dev { css { srcDir "app/styles" include "*.css" exclude "*.min.css" } } }
  • 35.
    CSS // Specify acollection of files to be combined, then minified and finally GZip compressed. combineCss { source = css.source.dev.css.files dest = "${buildDir}/all.css" }
  • 36.
    CSS minifyCss { source =combineCss dest = "${buildDir}/all-min.css" yuicompressor { // Optional lineBreakPos = -1 } }
  • 37.
    CSS gzipCss { source =minifyCss dest = "${buildDir}/all.min.css.gz" }
  • 38.
    Combine kon@Kostas-MBP ~/Projects/gradle-summit- presentation $gradle combineCss :combineCss BUILD SUCCESSFUL Total time: 5.812 secs kon@Kostas-MBP ~/Projects/gradle-summit- presentation $ ls build/ ./ ../ all.css
  • 39.
    CSS Minify InAction kon@Kostas-MBP ~/Projects/gradle-summit- presentation $ gradle minifyCss :combineCss UP-TO-DATE :minifyCss BUILD SUCCESSFUL Total time: 3.545 secs
  • 40.
    CSS Minify InAction kon@Kostas-MBP ~/Projects/gradle-summit- presentation $ gradle minifyCss :combineCss UP-TO-DATE :minifyCss BUILD SUCCESSFUL Total time: 3.545 secs kon@Kostas-MBP ~/Projects/gradle-summit- presentation $ ls build ./ ../ all-min.css all.css
  • 41.
    CSS Minify InAction kon@Kostas-MBP ~/Projects/gradle-summit-presentation $ gradle minifyCss :combineCss UP-TO-DATE :minifyCss BUILD SUCCESSFUL Total time: 3.545 secs kon@Kostas-MBP ~/Projects/gradle-summit-presentation $ ls build ./ ../ all-min.css all.css kon@Kostas-MBP ~/Projects/gradle-summit-presentation $ cat build/all-min.css body{padding-top:50px}.sub-header{padding-bottom:10px;border-bottom:1px solid #eee}.sidebar{display:none}@media(min- width:768px){.sidebar{position:fixed;top:51px;bottom:0;left:0;z- index:1000;display:block;padding:20px;overflow-x:hidden;overflow-y:auto;background- color:#f5f5f5;border-right:1px solid #eee}}.nav-sidebar{margin-right:-21px;margin-bottom:20px;margin- left:-20px}.nav-sidebar>li>a{padding-right:20px;padding-left:20px}.nav- sidebar>.active>a{color:#fff;background-color:#428bca}.main{padding:20px}@media(min- width:768px){.main{padding-right:40px;padding-left:40px}}.main .page-header{margin- top:0}.placeholders{margin-bottom:30px;text-align:center}.placeholders h4{margin- bottom:0}.placeholder{margin-bottom:20px}.placeholder img{display:inline-block;border- radius:50%}.navbar{background-image:url('../images/gradle_small.png');background-position- x:30%;background-repeat:no-repeat;background-size:contain}
  • 42.
    gzipCss kon@Kostas-MBP ~/Projects/gradle-summit- presentation $gradle gzipCss :combineCss UP-TO-DATE :minifyCss UP-TO-DATE :gzipCss BUILD SUCCESSFUL Total time: 4.881 secs
  • 43.
    gzipCss kon@Kostas-MBP ~/Projects/gradle-summit-presentation $gradle gzipCss :combineCss UP-TO-DATE :minifyCss UP-TO-DATE :gzipCss BUILD SUCCESSFUL Total time: 4.881 secs kon@Kostas-MBP ~/Projects/gradle-summit-presentation $ ls -la build total 24 drwxr-xr-x 5 kon wheel 170 10 Jun 19:05 ./ drwxr-xr-x 12 kon wheel 408 10 Jun 18:51 ../ -rw-r--r-- 1 kon wheel 917 10 Jun 18:55 all-min.css -rw-r--r-- 1 kon wheel 1539 10 Jun 18:51 all.css -rw-r--r-- 1 kon wheel 438 10 Jun 19:04 all.min.css.gz
  • 44.
    gzipCss kon@Kostas-MBP ~/Projects/gradle-summit-presentation $gradle gzipCss :combineCss UP-TO-DATE :minifyCss UP-TO-DATE :gzipCss BUILD SUCCESSFUL Total time: 4.881 secs kon@Kostas-MBP ~/Projects/gradle-summit-presentation $ ls -la build total 24 drwxr-xr-x 5 kon wheel 170 10 Jun 19:05 ./ drwxr-xr-x 12 kon wheel 408 10 Jun 18:51 ../ -rw-r--r-- 1 kon wheel 917 10 Jun 18:55 all-min.css -rw-r--r-- 1 kon wheel 1539 10 Jun 18:51 all.css -rw-r--r-- 1 kon wheel 438 10 Jun 19:04 all.min.css.gz And Proof kon@Kostas-MBP ~/Projects/gradle-summit-presentation $ cat build/all.min.css.gz pnK?i?Cċ?%?g?(?D2?C?X‫?5??]-??++-ڦ‬XvN???3Ci?q?Ԓc?Sn????$ (????i?a|oA ?mQ?/?a/ϯ????x h4??Q$-?tI^(?C??&??QH???2?g1Ke?I?9????"=k??*Gƻ`??W?ҙN Re????g??P6?j~ ??;?N?‫ݤ‬w.Ͳ???? ?] x??M?c>?J???x?4ʫ???E}]???~?ZV|?-G=l?{$ۣ ??????2??f?r???n??#?㩠1*"~8??q???+??U??sJ????{?q???r]D? u?!??뜺???>???2r|??+E??7w?‫ؗ‬=?{??_ox??????<~??1???C???kon@Kostas-MBP ~/Projects/gradle-summit-presentation $
  • 45.
    combine, minify, gzipJS combineJs { // pull together the source from string & file lists // eg. def core = ["$webAppDirName/init.js",...] source = core + application + devmode + bigquery + javascript.source.externalLibs.js.files + dfpFilters + chartdef + uxFilters // show the resolved files when gradle is run with -d source.each{ logger.debug ("$it") } dest = file("${buildDir}/all.js") }
  • 46.
  • 47.
    MinifyJS minifyJs { source =combineJs dest = file("${buildDir}/all.min.js") sourceMap = file("${buildDir}/all.sourcemap.json") closure { warningLevel = 'QUIET' compilerOptions.defineReplacements = ['MY_DEBUG_FLAG':false] } }
  • 48.
    Closure Compiler Options Abit hard to find them all. You can find them in a source file in the Closure compiler project. https://github.com/google/closure- compiler/blob/master/src/com/google/javascript/jsc omp/CompilerOptions.java
  • 49.
    defineReplacements // config.js /** * Flagto indicate console and extra logging throughout the app * @define {boolean} allow the value of this bool to be * overwritten at closure compiler / minification time * @const * @type {boolean} */ var MY_DBUG_FLAG = true;
  • 50.
    if (MY_DBUG_FLAG) { $('.dashboard').append( '&lt;p&gt;Here'ssome dev info you wouldn't normally see&lt;/p&gt;' ); }
  • 51.
    Gzip gzipJs { source =minifyJs.dest dest = file(“${buildDir}/all.min.js.gz”) }
  • 52.
  • 53.
    Break It Down MultipleProject Builds
  • 54.
  • 55.
  • 56.
    Deploy to aContainer
  • 58.
  • 59.
  • 60.
    Your Own Tasks Writeyour own tasks to change code
  • 62.
    Head.js or directscript include in different environments
  • 63.
    –Johnny Appleseed “Type aquote here.”PrintGCTimeStampsPrint
  • 64.
  • 65.
  • 66.
    –Johnny Appleseed “Type aquote here.”PrintGCTimeStampsPrint
  • 67.
  • 68.
    Remember! Gradle works forbuilding HTML/ JS / CSS assets Embedded Server from Checkout End 2 End Visibility Proxies
  • 69.
    Thanks Blog goes intomore detail http://blog.shinetech.com/2014/03/19/javascript-webapps- with-gradle/ Photos Tom Botton - MelbJVM September Brigitte Schuckert - Gradleware Keynote Kon Soulianidis - Pisa & Monkey looking in lens Climbing the Tree - Mary Evans. Licensed Shipping Containers - Jim Bahn. Flickr. CC License Animated Gifs - Photobucket Duke, Grunt, Gradle logos, respective owners

Editor's Notes

  • #11 Initially app was only JavaScript client talking directly to the datasource. Users auth’d with BigQuery directly
  • #12 So people usually ask when I tell them we used Gradle to make a single page web app , why not Grunt or Gulp, or whatever else is fancy and in vogue? Dream of being a JS hipster aka Instant Gratification Monkey Now the chance to learn all the cool stuff Angular Grunt Bower I took this picture from a site There is a project called JHipster that combines Maven, Grunt & Spring MVC, AngularJS with Yeoman. A few weeks ago it had a minor release that added Gradle support. JHipster is a Yeoman generator, used to create a Maven + Spring + AngularJS project, with full hot reload of Java and JavaScript code. Yeoman needs npm, so does grunt so still a fail for us. But going in the right direction.
  • #17 JavaScript has been climbing the tree of software development tools, then reaching the top and falling, taking every branch on the tree down.
  • #31 So far we’ve just got some cookie cutter setup for the first version You can see pretty simply, the html, js and css driving the show But we’ve got this libs folder…
  • #32 Our dependencies bring in their own things into the mix. Bootstrap has 3 resources folders, css, js and fonts directories Holder and Jquery bring in their js libs. Most libraries provide both minified and regular versions of their libs Some have map files that we may want to deploy with the minified versions so we can debug in chrome/firefox And just for the fun of it, bootstrap decides to include fonts And this build.gradle file just seems out of place in the web folder
  • #33 GASP SHOCK HORROR! Find Sound Effects and Clip art (eg 1950’s woman with hands around mouth). Sound effects of screams, shrieks In todays talk we’ll build from the ground up an application to interact with a RESTful API To start with, we’ll use Twitter Bootstrap’s dashboard theme
  • #40 For learning Gradle using another language is a great experience
  • #41 For learning Gradle using another language is a great experience
  • #42 For learning Gradle using another language is a great experience
  • #44 Notice the gz is only 438 bytes
  • #45 And proof
  • #59 show build-tomcat.gradle
  • #61 build-webserver.gradle
  • #66 ScriptRunnerServlet