This addon makes it possible to render valid Google's Accelerated Mobile Pages project (AMP) pages with your ember app and Fastboot.
First, install the addon:
ember install ember-cli-amp
Then, in order for your pages to render valid AMP you must:
- add
amp
options toember-cli-build.js
- edit
outputPaths
inember-cli-build.js
- add or update a
head.hbs
template to render the amp partial
ember-cli-amp augments the ember build
process to write a valid AMP HTML file in your build output directory, with
your AMP css inlined into it (due to AMP's restriction on externally-linked stylesheets).
To make this happen you need to specify options to tell it what CSS file to read your AMP CSS from and what filename to use for the built AMP HTML file.
In your ember-cli-build.js
file add the following options:
// ember-cli-build.js
const app = new EmberApp(defaults, {
// ...
// Add this section:
amp: {
css: 'relative/path/to/built/css/file', // e.g.: "assets/my-amp-styles.css"
// The index file will include the inlined the contents of the above CSS file
// and appear with the given name in your output dir (e.g. `dist/`) alongside
// your index.html file
index: 'desired_name_of_amp_index_html_file' // e.g.: "amp-index.html"
}
});
ember-cli-amp
reads your compiled CSS amp file and inlines it into the amp index html
file in your build output.
You will likely want to use the outputPaths
option to instruct ember-cli to build
a specific CSS file for you to use for AMP. For example, if you have the file styles/amp.scss
that you want to become your AMP CSS file, you should do:
// ember-cli-build.js
const app = new EmberApp(defaults, {
// ...
outputPaths: {
app: {
css: {
// Specify where 'amp.scss' should be built to. This value will be the value
// you use for the 'css' option in the `amp` section of your options
// (as described above)
amp: 'assets/my-amp-styles.css'
}
}
}
});
This configuration instructs ember-cli to build the file styles/amp.scss
and
write it to the location assets/my-amp-styles.css
in your output dir (e.g., dist/
).
In order to create valid AMP pages, you must serve your AMP routes with Fastboot.
AMP pages require a certain HTML boilerplate that differs from the index.html file that an ember app typically uses (and that Fastboot typically renders its content into). You need to add a setting for Fastboot to instruct it to use your AMP index.html file instead of the standard index.html file.
Fastboot allows you to change your own HTML file for it to insert rendered content into by setting the
fastboot.htmlFile
in your config/environment.js
:
// config/environment.js
// ...
fastboot: {
// This value must be the same as the one you use for the `amp.index`
// configuration option in your ember-cli-build.js file
htmlFile: 'amp-index.html'
}
ember-cli-amp
uses ember-cli-head
to render content in the <head>
tag (such as title, canonicalUrl, etc).
In order for this to work properly, you must add the head.hbs
template to your app and render the amp/-head
partial:
- Create the file
app/templates/head.hbs
(if you have ahead.hbs
because you are already usingember-cli-head
, skip this step) - Add the line
{{partial "amp/-head"}}
to this template. ember-cli-amp will use this partial to render the necessary<head>
content for AMP pages
Now, serve your app through Fastboot!
ember fastboot
ember-cli-amp
exposes the following primitives for use in your app:
A valid AMP page requires a title and a canonical URL in the <head>
. Inject the amp
service
and set the following properties:
title
- The title of the pagecanonicalUrl
- ember-cli-amp will use this to render the required canonical url<link>
in the<head>
of your pageisAmp
- If you have an ember app that will render AMP for some routes only, you should set and unset this property in theactivate
/deactivate
methods of your amp route so that other parts of your app can act accordingly (e.g., you may want or need to render templates differently for your amp routes)
These properties should be set on the amp
service, typically in the model
or afterModel
hook in your route:
// route.js
// ...
afterModel(post) {
this.get('amp').set('canonicalUrl', post.get('url'));
this.get('amp').set('title', post.get('title'));
}
In addition, the amp
service provides the method registerExtension
which you can use to opt-in to one of the
extensions that AMP provides
(e.g. amp-instagram, amp-twitter, etc). AMP requires one to include the appropriate script tag for
each extension in the document's <head>
. Adding extensions with registerExtension
will cause the
appropriate script tags to be added to the document when rendering.
registerExtension
accepts the name of the extension without the "amp-" prefix, e.g.:
this.get('amp').registerExtension('twitter'); // registers the `amp-twitter` extension
The {{amp-sidebar}}
component can be used to display AMP's <amp-sidebar>
.
AMP requirement the sidebar to be a direct child of <body>
, which is difficult to naturally do to in Ember.
The {{amp-sidebar}}
extension includes the code necessary to reposition the sidebar appropriately when the app renders.
AMP does not allow any <script>
tags on the page (aside from those required in its boilerplate or for AMP extensions).
If you are using the Fastboot shoebox utility, Fastboot will render <script>
tags on the page to hold your data and AMP will mark the page as invalid.
In order to fix this, you can use the deactivateShoebox
utility method that ember-cli-amp provides:
import deactivateShoebox from 'ember-cli-amp/utils/deactivate-shoebox';
...
afterModel() {
deactivateShoebox(this);
}
deactivateShoebox
will empty any existing contents in the shoebox and replace fastboot.shoebox
with a stub object so that any shoebox data set elsewhere in the app will be ignored.
AMP has several constraints surrounding CSS. Externally-linked CSS files are not allowed, so ember-cli-amp
handles inlining the CSS into the AMP index.html file it builds.
AMP also disallows certain types of CSS rules (e.g., !important
). ember-cli-amp
adds a CSS validation step to the build process. If your CSS is invalid you will see errors printed to the terminal at build time.
AMP does not allow externally-linked CSS files or JS files, so:
- The AMP HTML file that ember-cli-amp generates does not have
<script>
tags. Unlike typical fastboot, your ember app won't boot up and "hydrate" the DOM when the page loads. - The CSS file you specify in options is injected into the AMP HTML file directly.
Read more about AMP at the AMP docs page.
- How do I include other custom AMP extension scripts?
Use the registerExtension
method of the amp
service, e.g.:
this.get('amp').registerExtension('iframe');
You can use this code in a component that needs that extension, or make an instance initializer for your app that registers all extensions.
- How do I specify other required AMP metadata, e.g.
canonicalUrl
ortitle
?
Set the property on the amp
service in your route's model
or afterModel
hook.
- Should I use a separate repo/project for my ember AMP pages?
It definitely requires some work, but it is possible to make your ember app a "hybrid" where some routes are AMP and some are not. Doing so will allow you to reuse parts of your existing codebase.
Juggling the multiple contexts in which your ember app may be run ("normal" JS ember app, Fastboot, Fastboot+AMP) requires some finesse, so it's up to you to weigh the pros and cons.
If you do mix AMP routes with non-AMP routes in your AMP, you should set/unset the isAmp
property on the amp
service in your activate
/deactivate
route methods. This will make it easier to tweak the rendering of your templates in the two contexts (typically you will need to simplify your templates to make them valid AMP, or leverage AMP extensions like <amp-twitter>
etc. if you are rendering embedded tweets and other "rich" content).
- How do I deploy AMP pages?
The same way you would deploy Fastboot, with an important caveat: for AMP routes, Fastboot needs to inject your rendered app into the AMP html boilerplate (the filename that you specified with the amp.index
property in ember-cli-build.js
, and referenced in the fastboot.htmlFile
property of your config/environment.js
). But for your non-AMP routes, Fastboot should inject the content into the standard index.html
file.
How you accomplish this will depend on your setup, but you may need to ensure your deployment environment can direct requests for the AMP routes to a Fastboot instance that is configured to use the AMP boilerplate, and requests for non-AMP routes to a Fastboot that's configured using the defaults.
If you are using Fastboot
directly, you can pass an html
option to instruct it on a per-request basis:
const FastBoot = require('fastboot');
let app = new FastBoot({
distPath: 'path/to/dist'
});
let url = '/photos/1/amp';
let options = {};
if (routeIsAMP(url)) {
options.html = 'path/to/dist/amp-index.html'
} else {
// Fastboot will inject your app's content into the default "index.html"
}
app.visit(url, options)
.then(result => result.html())
.then(html => res.send(html));