GULP er som bekendt en Task-runner eller opgave-knuser på linje med f.eks. Grunt, Hvis du oplever, at der er arbejdsprocesser, som du foretager igen og igen, så er det som regel værd at overveje, om GULP kan hentes ind over.
AMP (Accelerated Mobile Pages) er et nemt framework at arbejde med, men rummer også mange trivielle processer, der gør det oplagt at koble det med GULP.
AMP er et koncept, der er udviklet af oraklerne fra Google, i første omgang i samarbejde med nyhedsmedier, der ønskede kortere responstider fra mobil på deres sites. Nyheds-junkies har meget begrænset tålmodighed. AMP blev præsenteret i oktober 2015 og selv om det – af mange fornuftige grund – har skabt en del kontrovers, har udviklingen siden vist, at det næppe er gavnligt at ignorere det i web-strategien. Med Google’s egne ord, så går vi med AMP fra “mobile first” til “user-first”. Median-tiden for at loade en AMP-side fra Google-søgning er under et halvt sekund. Det hører med i billedet, at Google fremadrettet vil favorisere AMP-sider på mobile Google-søgning. Alt efter temperament kan man kalde det enten pisk eller gulerod.
AMP er også hurtigt at udvikle i – og kan gøres endnu hurtige med hjælp fra f.eks. GULP. Fort at kridte banen hurtigt op, så indebærer udvikling af en AMP-validerbar side nogle restriktioner, hvoraf de væsentligste er:
- Du kan ikke bruge javascript (ud over framework-ets egne), så glem alt om jQuery, bootstrap.js og hvad du ellers har troet uundværligt.
- Du kan max. bruge 50 kB CSS, der skal in-lines i HTML-siden.
- Der er forbud mod flere elementer i CSS, f.eks. er det slut med enhver anvendelse af !important; (hvilket i forvejen også er en dårlig skik!)
I det følgende vil blive demonstreret, hvorledes nogle GULP-funktioner kan tage brodden af stort set alle trivielle processer i AMP-udviklingen og nedsætte produktionstiden væsentligt. Den samlede kode kan downloades

Gulp
Du har brug for både at have installeret Node og Gulp/Gulp CLI. Node kan du hente på node.js . Herfter kan du installere Gulp med:
npm install gulp-cli -g npm install gulp -D
I denne serie af blog-indlæg vil følgende Gulp Plugins blive anvendt:
Plugin | Installer | Beskrivelse |
gulp-tap | npm i -D gulp-tap | Tap ind til hver enkelt fil i pipen |
gulp-sass | npm i -D node-sass gulp-sass | Kompiler SCSS.filer til CSS |
gulp-rename | npm i -D gulp-rename | Omdøb filnavn |
gulp-purgecss | npm i -D gulp-purgecss | Frasorter ikke anvendt CSS |
gulp-run | npm i -D gulp-run | Shell kommando i gulp |
gulp-open | npm i -D gulp-open | åbn fil eller Url |
gulp-amphtml-validator | npm i -D gulp-amphtml-validator | |
gulp-html-partial | npm i -D gulp-html-partial | |
gulp-changed | npm i -D gulp-changed | Registrer hvorvidt en nydannet eller redigeret |
gulp-clean | npm i -D gulp-clean | |
gulp-copy | npm i -D gulp-copy | |
gulp-json-editor | npm i -D gulp-json-editor | |
gulp-imagemin | npm i -D gulp-imagemin | |
fs | Node FileSystem Modul | |
path | Node Path modul | |
3dub | npm i 3dub | Dev Server med support for https/http2 |
Hvis du vil slippe nemt om ved det, kan du kopiere nedenstående til package.json og køre:
npm install
{ "name": "amp-boilerplate", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "devDependencies": { "3dub": "^1.1.0", "gulp": "^4.0.0", "gulp-amphtml-validator": "^1.0.2", "gulp-changed": "^3.2.0", "gulp-clean": "^0.4.0", "gulp-cssnano": "^2.1.3", "gulp-purgecss": "^1.1.1", "gulp-sass": "^4.0.2", "gulp-strip-comments": "^2.5.2" }, "dependencies": { "gulp-cli": "^2.0.1", "gulp-contains": "^1.2.0", "gulp-copy": "^4.0.1", "gulp-html-partial": "^1.0.1", "gulp-htmlmin": "^5.0.1", "gulp-imagemin": "^5.0.3", "gulp-json-editor": "^2.5.0", "gulp-open": "^3.0.1", "gulp-rename": "^1.4.0", "gulp-run": "^1.7.1", "gulp-tap": "^1.0.1" } }
AMP
At udvikle Accelerated Mobile Pages kræver ikke særlig opsætning, men er regulær HTML-sider med nogle væsentlige modifikationer og tilføjelser.
Som udgangspunkt skal følgende krav skal være opfyldt, for at din AMP-side kan valideres:
1. HTML-siden starter med <!doctype html> efterfulgt af <html ⚡> eller <html amp>
2. <meta charset=”utf-8″> som første element i <head>
3. <script async src=”https://cdn.ampproject.org/v0.js”></script> i <head> for at loade AMP JS library.
4. <link rel=”canonical” href=”$SOME_URL”> i <head> der peger til den regulære version af AMP-siden.
5. Angiv viewport, f.eks. <meta name=”viewport” content=”width=device-width,minimum-scale=1″>
6. Indsæt standard AMP Boilerplate CSS i <style amp-boilerplate>. Væsentligste funktion er at forhindre FUIC (Flash of Unstyled Content) før AMP JS er loaded.
I sin enkleste form vil en validerbar AMP-side derfor se udsom f.eks.:
<!doctype html> <html amp lang="en"> <head> <meta charset="utf-8"> <script async src="https://cdn.ampproject.org/v0.js"></script> <title>Hello, AMPs</title> <link rel="canonical" href="http://example.ampproject.org/article-metadata.html"> <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"> <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript> </head> <body> <h1>Welcome to the mobile web</h1> </body> </html>
Struktur
Projektet er delt op i folderne src, build og dist. Src-folderen og sass-foldseren (med scss-filedr) er de eneste steder, der kodes. Build er en mellemstation, hvor css-filer m.v. placeres for debug. Dist-folderen indeholder de færdige AMP-sider, der med live-reload vises i browseren.

1. Fra Sass/Less til <style amp-custom>
Et af kritikpunkterne i forhold til AMP er, at frameworkets skræddersyede moduler går AMP-sider kedelige og ensartede. Man behøver ikke at kigge sig langt omkring for at få bekræftet, at den kedelige tendens er der. Den individuelle styling er derfor væsentlig og det vanskeliggøres så igen af AMP-konceptets noget rigide holdning til brugen af CSS, hvor det ikke er muligt at linke til blot en enkelt css-fil. For at gøre det muligt at arbejde videre med Sass/Less og fortsat have Live-Reload af AMP-sider er det oplagt at inddrage GULP. Følgende opgaver skal løses:
- Kompile scss-filer til css.
- Tree-shake den dannede CSS-fil til hver enkelt AMP-side, så der kun medtage styles, som rent faktisk bliver anvendt på siden.
- Komprimere den nødvendige CSS til hver enkelt side.
- Indsætte CSS’et i <style amp-custom> tag’et så vi får opdateret den færdige AMP-side med det samme.
Der findes mere end 3500 plugins til GULP og for at løse nærværende opgave vil jeg først bruge gulp-sass :
npm i gulp-sass
Med følgende GULP-task konverteres SCSS-filer i folderen “./src/sass” til en samlet CSS-fil i Build-folderen:
const sass = require('gulp-sass'); sass.compiler = require('node-sass'); const buildPath = 'build'; const scssPath = 'src/scss'; '; gulp.task('sass', () => { return gulp.src(scssPath + '/*.scss') .pipe(sass()) .on('error', (error) => { console.log(error.toString()); // this.emit('end') }) .pipe(gulp.dest(buildPath)); });
Næste skridt er, for hver enkelt AMP HTML-fil i Folderen “./src” at udtrække de dele af CSS.filen, der anvendes i den enkelte HTML. Til det formål er plugin’et Gulp-css-purge nyttigt.
Ryst css-træet
Css-filer er et sted, hvor der traditionelt ophober sig uanede mængder af overflødig bloat-code. Derfor er purgeCSS en dejlig opfindelse, der analyser html for at finde hvilken css-kode, der er nødvendig. For index.html i dette projekt betyder det, at CSS reduceres til at fylde mindre end 1/6 del af den oprindelige størrelse.
Fremgangsmåden har den bagside, at purgeCSS naturligvis ikke kan ane, hvad der i fremtiden kan blive kaldt af klasser i f.eks. dynamisk indhold fra database eller javascript. I nærværende eksempel hentes indhold til amp-carousel og amp-list fra JSON. Heri defineres CSS-klasser, der sætter baggrundsfarve for badges. Det har purgeCSS ingen anelse om, og de vil derfor mangle i <style amp-custom>.
Problemet kan løses på flere måder. Den mest autoriserede er at bruge tags i SCSS/CSS koden, der fortæller purgeCSS, at den skal gå uden om markeret kode, når den renser ud. I dette tilfælde bruges klasserne bg-[farve]-dark til at sætte baggrundsfarve for badges. Rundt om det sted, hvor de bliver defineret i SCSS sættes purgecss-ignore-tags:
@mixin dark($name, $color) { /* purgecss start ignore */ .#{"bg-" + $name + "-dark"} { background-color: $color; color: "#FFFFFF"; } /* purgecss end ignore */ .#{"color-" + $name + "-dark"} { color: $color; } .#{"border-" + $name + "-dark"} { border: solid 1px $color; } }
/* purgecss start ignore */ medfører at ignorerede kode frem til /* purgecss end ignore */ vil blive medtaget i alle AMP-sider. Den skal derfor kun bruges til det mest nødvendige.
En alternativ løsning kunne være at indsætte en dummy i den relevante HTML-fil, som indeholder de baggrundsfarver, der bliver brug for:
<span class="bg-blue-dark bg-green-dark bg-red2-dark bg-orange2-dark bg-night-dark bg-magenta-dark"></span>
Herefter vil purgeCSS medtaget klasserne. Begge måder løser problemet, og sidstnævnte kan i en håndevending være at foretrække, hvis det på forhånd står klart, at css-undtagelse kun er relevant for en enkelt HTML-fil og ikke for 50 andre.
const tap = require('gulp-tap') const fs = require("fs"); const purgecss = require('gulp-purgecss') const rename = require("gulp-rename"); const path = require('path'); gulp.task('createAMPCustom', () => { return gulp.src(srcPath + '/*.html') .pipe(tap((file) => { var fname = path.basename(file.path); return gulp.src(buildPath + '/style.css') .pipe(purgecss({ content: [buildPath + '/' + fname] })) .pipe(cssnano()) .pipe(rename(fname.replace(".html", ".min.css"))) .pipe(gulp.dest(buildPath)); })); });