AMP

The AMP Camp build process

Websites

What’s AMP Camp?

It’s one thing to work as a Developer Advocate and talk about how AMP can be used to create quick, interactive sites. It’s quite another to actually build one! So a few of us decided to create a simple but fully functional e-commerce website, covering everything until the checkout. We hoped to learn the subtleties involved in creating these interactions, to create guidance for others, and to make some nice feature requests.

And, thus, the AMP CAMP demo site was born!

https://blog.amp.dev/wp-content/uploads/2020/03/AMP-Camp-blog-post-video-compressed-by-Paul-1-2.mp4

We learned a whole lot making this site, and we want to share this with you. In this series of articles, we’ll explain how we built the site and created its interactions. You can also watch the talk at the end of this post.

First things first: let’s start with an article about the AMP Camp build process.

Why did we use a build process?

It’s common to compile software from a set of source files into the files that are used in production. This is called a build process. Sometimes it’s convenient to do this with HTML – to create resources in a more convenient form, then automatically compile that down to the HTML that gets served to browsers.

A build process is especially convenient with AMP. After all, the information required to render an AMP webpage lives in a single file. Its CSS is included inside the HTML in <style> tags. And if you’re not using <amp-script>, you won’t even be providing separate JavaScript resources. In contrast, almost all non-AMP webpages refer to many other CSS and JS files. AMP is simpler!

Nonetheless, while you’re creating your AMP pages, you probably won’t want to create each page within a single file unless your site is very simple. At the very least, unless you’re using you’re combining CSS and HTML intentionally in JSX or web components, it’s messy to work with source files that contain both CSS and HTML. You’ll likely at least want to create CSS and HTML in separate files, then use a build process to combine them.

Thinking about this further, your webpages will usually share much of the same HTML. For example, they may have the same navigation menu, or similar footers. It’s simply not a best practice to copy identical HTML from page to page. What if you want to change that menu? You’d have to copy and paste the new HTML across your site! That menu should live in its own file instead. More generally, you want to build pages from HTML partials. Since AMP is an HTML framework, in which HTML describes user interactions and can even contain logic, this holds doubly true.

Similarly, it’s rarely best to write all your CSS in one jumbo-size file and make every page load it. You might argue that, once this large file loads, it will likely be cached, so as the user progresses through your site, they won’t experience that 200K render-blocking hit on each page. But if that first page loads slowly, the user may never get to that second page! You really want each page to load a smaller CSS file, containing mostly only the styles used on that page.

You can’t use more than 75K of CSS if you want your AMP page to pass validation. Plus, that CSS must be contained inside the HTML to save a render-blocking network call! Hence, it’s best to only add the CSS to a page that is needed. All this is much easier if you use a build process to assemble your CSS from partials, then merge that into each HTML file.

So, just as it’s often easier and more convenient to assemble webpages from smaller parts with a build process, it’s easier and more convenient for AMP web pages. As a bonus, plenty of build tools have been created specifically for AMP, either to make AMP pages easier to create, or to make them faster for users! To learn more, let’s take a look at the build process used in the AMP Camp demo site.

The AMP Camp site is built with node.js and gulp. Throughout this article, we’ll refer to the code on github, especially the gulpfile. We’ll also refer to ways in which you can make your build process better than ours!

Building our HTML

const fileinclude = require('gulp-file-include');

AMP Camp builds its HTML from partials, in much the way discussed above. Since our site is relatively simple, we used fileinclude. That said, if we expand the project just a little, we’ll probably want to migrate to a more powerful tool.

To see this in action, check out the start of index.html, which includes separate partials for the <head>, the page header, the navigation menu, and some other things:

<!doctype html> %%include('../partials/copyright.html') <html ="" lang="en"> <head> %%include('../partials/head.html', { "pageType": "index" }) </head> <body> %%include('../partials/header.html', { "pageType": "index" }) %%include('../partials/menu.html')

Some of these partials contain other partials. Notably, we decided to put our analytics in its own partial, analytics.html, which header.html then imports.

Building our CSS

AMP Camp’s head.html partial is built from subpartials as well. These subpartials are not HTML files, but CSS files. head.html includes CSS files that are common to each page:

<style amp-custom> %%include('../../css/global.css') %%include('../../css/header.css') %%include('../../css/menu.css') %%include('../../css/footer.css') %%include('../../css/social.css')

as well as CSS that’s specific to each page:

%%if (context.pageType == 'index') { %%include('../../css/index.css') }

To build more efficient CSS, you can divide your HTML into smaller partials and subpartials and associate the appropriate CSS with each. This way, each page of the site truly only contains the CSS it needs. For example, amp.dev organizes its CSS with atomic design, a system in which larger UI design elements contain progressively smaller ones, all the way down to “atoms”. Check it out here! Another option is to use a framework like Next.js, which lets you do a React-style CSS/HTML association. (We encourage you to learn about Next.js’ AMP support here – or try this tutorial.)

const sass = require('gulp-sass');

AMP Camp builds its CSS from SASS, a popular higher-level language that compiles down to CSS. This works with AMP pages just as it does with non-AMP pages!

However, if your SASS happens to contain extended characters, the SASS processor will automatically output a rule to specify the character encoding:

@charset "UTF-8";

Unfortunately, such rules aren’t standard for CSS that lives inside a <style> tag. Thus this rule inspires the AMP validator (see below) to throw an error!

Our solution isn’t pretty, but it’s simple and robust: after SASS does its work, we postprocess the resulting CSS to remove any such rules:

gulp.task('styles', function buildStyles() { const cssEncodingDirective = '@charset "UTF-8";'; return gulp.src(paths.css.src) .pipe(plumber()) .pipe(sass(options.env === 'dist' ? { outputStyle: 'compressed' } : {})) .pipe(options.env === 'dev' ? replace(cssEncodingDirective, '') : noop()) .pipe(autoprefixer('last 10 versions')) .pipe(mergeMediaQuery({log: true})) .pipe(gulp.dest(paths.css.dest)); });

AMP-specific tools

const gulpAmpValidator = require('gulp-amphtml-validator');

Speaking of AMP validation, we want to ensure that our pages are always meeting AMP’s standards, so that they’ll be eligible for AMP caches, and so we’ll avoid errors, but also because we want to serve fast, accessible pages. Consequently, our build process includes the AMP Validator. We’ve set it up so that when any page isn’t valid AMP, the build breaks.

gulp.task('validate', function validate() { return gulp.src(paths.html.dest + '/**/*.html') .pipe(gulpAmpValidator.validate()) .pipe(gulpAmpValidator.format()) .pipe(gulpAmpValidator.failAfterError()); });

If we didn’t want any invalid page to break the build, we could omit the failAfterError() step above.

Note that some of AMP Camp’s pages are rendered from templates on the server. We don’t want to run the validator in the server! Fortunately, since what we substitute into those templates is simple and highly predictable, we feel confident running the validation on these templates. In a larger project, it would be safer to render some sample pages during the build process and test those for validity.

const ampOptimizer = require('@ampproject/toolbox-optimizer');

Finally, we use the AMP Optimizer for a number of purposes!

  • Many AMP components require their own small script to function. The developer needs to keep track of these for each page and ensure each is included in the <head>. That’s why, for this project, we developed the amphtml-autoscript gulp module, which detects which components a page uses and automatically inserts the necessary scripts! This functionality has now been incorporated into the AMP Optimizer.
  • AMP pages contain boilerplate that hides the page until the AMP runtime loads, gets parsed, and executes. When it starts up, the AMP runtime performs various transformations on the DOM, then unhides the page. AMP caches perform these transformations in advance, serving the modified HTML right to your browser and speeding things up substantially. The AMP Optimizer does this right on your origin! This can make AMP pages twice as fast on your origin, so we think it’s invaluable.
  • Finally, it automatically inserts mandatory AMP tags so we don’t have to.

AMP Camp runs the AMP Optimizer during the build process on our compiled templates. This means our template delimiters can’t contain angle brackets, like <% and %>, because the Optimizer would parse these as HTML. We use square brackets ([% and %]) instead.

AMP Optimizer can be used in your server as well, at runtime. If you do this, you should cache optimized pages to avoid excess server load.

And there you have it

We’re hoping the build process we used for AMP Camp helps make your own AMP project easier! Again, to see a build process for a larger site, check out the way amp.dev is built. Or, to see an interesting React-based approach, check out the Concert sample or Article sample.

Stay tuned for the next article in the AMP CAMP series – how we used templates on the client and on the server.

Happy building!

Written by Ben Morss, Developer Advocate