My boss wants me to setup AMP on the website.
Should I quit my job?
一年365天歡迎餵食,請多指教 ヽ(●´∀`●)ノ
Not this one 👉
What is AMP?
What is AMP?
latency
3G
Mobile device
Poor network
performance
User dissatisfaction
What is AMP?
✅
Why people build AMP pages
website traffic
$$$$
❗ AMP itself is not a ranking factor, but page speed is.
page speed
ranking
What is AMP?
What is AMP?
1. Load JS asynchronously
to prevent render blocking
parsing HTML
fetch script
execute
fetch script
execute
fetch script
e...
parsing
wait........
parsing HTML
fetch script A
execute
fetch script B
execute
fetch script C
execute
resume parsing
wait........
What is AMP?
2. Disallow external stylesheet
to prevent render blocking
What is AMP?
3. Get dimensions before assets is full loaded
<amp-img
alt="A image of ..."
src="image.jpg"
width="900"
height="600"
layout="responsive"
>
</amp-img>
What is AMP?
3. Get dimensions before assets is full loaded
Cumulative Layout Shift!
What is AMP?
4. Only run GPU-accelerated animations
transition
、@keyframes
:
What is AMP?
5. Resources lazy loading
What is AMP?
5. Resources lazy loading
bandwidth saving
fast load times
What is AMP?
6. AMP Google/Bing/... Cache
What is AMP?
6. AMP Google/Bing/... Cache
What is AMP?
What is AMP?
limits
developer-unfriendly
no external stylesheet
photo source: 貓咪迷因cat memes
limit CSS sizes
limit script sizes
limitation on custom script
dimensions should be provided on the element
no animation without GPU-accelerated
you have to do everything in the AMP way!!
img, video, iframe have to be replace with AMP components
No Javascript in AMP?
AMP do not support custom javascript
until amp-script is released in 2019
There are still lots of limits for having your script running in amp-script
serverside rendering
Rails (api) + Next.js
👀
Good choice if your site is based on Rails view
Hard to prerender the page
Load data with amp-list
amp-script + preact (or other frameworks)
👀
<amp-img
alt="A image of ..."
src="image.jpg"
width="900"
height="600"
layout="responsive"
>
</amp-img>
<amp-img
fallback=""
alt="A image of ..."
class="i-amphtml-layout-responsive i-amphtml-layout-size-defined i-amphtml-element i-amphtml-layout"
src="image.jpg"
layout="responsive"
width="900"
height="600"
i-amphtml-layout="responsive">
<i-amphtml-sizer slot="i-amphtml-svc" style="padding-top: 66.6667%;"></i-amphtml-sizer>
<img decoding="async" alt="A image of ..." src="image.jpg" class="i-amphtml-fill-content i-amphtml-replaced-content">
</amp-img>
<amp-img layout='fixed'
width='800'
height='600'
src="https://source.unsplash.com/kAiyiesI_Kk/800x600"
alt="a lovely fox"
/>
跟平常的 <img>比起來多了 layout 的部分,以後還會常常看到它 :P
AMP pages only
Normal / AMP
AMP only for news, media etc.
.amp
Mime::Type.register_alias "text/html", :amp
config/initializers/mime_types.rb
respond_to do |format|
format.html
format.amp
end
app/controllers/xxx_controller.rb
app/views/xxx/index.amp.erb
blahblahblah...
mobile
# use gem browser(https://github.com/fnando/browser)
request.variant = [:amp] if browser.device&.mobile?
app/controllers/xxx_controller.rb
app/views/xxx/index.html+amp.erb
blahblahblah...
desktop
Layout
<!doctype html>
<html amp lang="<%= I18n.locale %>">
<head>
<meta charset="utf-8">
<script async src="https://cdn.ampproject.org/v0.js"></script>
<title><%= yield(:title) %></title>
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<script type="application/ld+json">
<%= yield :structured_data %>
</script>
<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>
<style amp-custom>
<%= File.read File.join(Webpacker.instance.config.public_path, Webpacker.instance.manifest.lookup('amp_desktop.css')) %>
</style>
<%= content_for(:amp_script) %>
</head>
<body>
<%= yield %>
</body>
</html>
internal CSS stylesheet
<style amp-custom>
<%= File.read File.join(Webpacker.instance.config.public_path,
Webpacker.instance.manifest.lookup('amp.css')) %>
</style>
// config/webpack/development.js
environment.config.merge({
devServer: {
writeToDisk: true
}
})
Webpacker
CSS
Without adding this to the config,
Webpacker::DevServerRunner would serve stylesheets from memory
Canonical
JSDC 2019 - 先別管大亂鬥了, 你聽過 AMP 嗎?
Canonical
On your AMP page
<link rel="canonical" href="https://www.example.com/url/to/full/document.html">
<link rel="amphtml" href="https://www.example.com/url/to/amp/document.html">
On your non-AMP page
Whitelisted Font Providers
Custom Fonts
include with @font-face
background-image: url("../image.jpg");
<amp-img
alt="A image of ..."
src="image.jpg"
width="900"
height="600"
layout="responsive"
>
</amp-img>
amp-img
stylesheet
Don't rely on this too much
⚠️ intrinsic is not available on IE11
⚠️ No !important in your CSS
fixed
fixed-height
flex-item
responsive
fill
intrinsic
# app/helpers/application_helper.rb
# remember to require 'fastimage' on the top 😉
def amp_img(source, options = {})
width, height = FastImage.size(source)
options[:width] ||= width
options[:height] ||= height
options[:src] = source
case options[:layout]
when 'fill', 'flex-item', 'nodisplay'
options.delete(:width, :height)
when 'fixed-height'
options[:width] = 'auto'
when 'fixed', 'intrinsic', 'responsive'
else
options[:layout] = 'nodisplay'
end
content_tag(:'amp-img', options) do
yield if block_given?
end
end
<%= amp_img asset_pack_url('media/images/blahblahblah.jpg'),
layout: 'fixed' %>
<%= amp_img asset_pack_url('media/images/blahblahblah.jpg'),
layout: 'fixed',
width: '128',
height: '128') %>
Get dimensions from FastImage
Specify size of an image
configure an parser!
# frozen_string_literal: true
require 'scrubbers/image'
require 'scrubbers/youtube'
class RichContentParser
def initialize(content)
@fragment = Loofah.fragment(content)
end
def parse
self.class.scrubbers.each { |scrubber| @fragment.scrub!(scrubber) }
@fragment.to_s
end
def self.scrubbers
[Scrubbers::Image.new,
Scrubbers::Youtube.new]
end
end
Parser
# frozen_string_literal: true
module Scrubbers
class Youtube < Loofah::Scrubber
YOUTUBE_LINK_PATTERN = %r{www\.youtube\.com/embed/(.+)}i.freeze
def scrub(node)
return if node.name != 'iframe' || node['src'] !~ YOUTUBE_LINK_PATTERN
youtube_uri = URI(node['src'])
node.name = 'amp-youtube'
node['layout'] ||= 'responsive'
node['width'] ||= 480
node['height'] ||= 270
scrub_styles(node)
scrub_attributes(node)
append_data_params(node, youtube_uri)
end
# ...
end
end
Scrubber
# app/models/your_model.rb
before_validation :ampify
def ampify
self.amp_content = RichContentParser.new(content).parse
end
Ampify Content
amp-sidebar
amp-social-share
amp-ad
amp-facebook
<amp-list>
<div>
<a>
<p>
require script
<script
async
custom-element="amp-bind"
src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"
></script>
<span [text]="myText">
binding
<button on="tap:AMP.setState({ myText: '...' })">
...</button>
event
action
Same as data-amp-bind-text
Contents won't change until AMP.setState({})
You can only bind attributes of an HTML element
[text]
[src]
[disabled]
[class]
[width]
[height]
[hidden]
[aria-label]
{
"TYP_0100041": true,
"TYP_0100042": false,
"TYP_0100043": false,
"TYP_0100044": true,
"TYP_0100045": true,
}
[{
"ID": "TYP_0100041",
"OPEN": true
},
{
"ID": "TYP_0100042",
"OPEN": false
},
{
"ID": "TYP_0100043",
"OPEN": false
},
{
"ID": "TYP_0100044",
"OPEN": true
}
]
1. append #development=amp
2. AMP.printState()
to see states
amp-bind Expression complexity limit
amp-bind element's JSON data size limit
😣 hard to read, hard to maintain, and hard to debug
😣 limits to total script size
😣 limits to the size of amp-state element's JSON data
Display lastest infos on your AMP cache ✨
require script
<script
async
custom-element="amp-list"
src="https://cdn.ampproject.org/v0/amp-list-0.1.js"
></script>
🌟 src can be changed with [src]
require script
<script
async
custom-template="amp-mustache"
src="https://cdn.ampproject.org/v0/amp-mustache-0.2.js"
></script>
{{myVariable}}
{{#isOpen}}開放中{{/isOpen}}
{{^isOpen}}未開放{{/isOpen}}
{{#cart_items}}
<li>{{name}}: ${{price}}</li>{{/cart_items}}
mySite.blah
mySite-blah.cdn.ampproject.org
???
module Api
class BaseController < ApplicationController
before_action :cors_verification
ALLOWED_ORIGINS = [
'https://mySite.blah',
'https://mySite-blah.cdn.ampproject.org',
'https://mySite.blah.amp.cloudflare.com'
]
def cors_verification
if request.headers['origin'].in?(ALLOWED_ORIGINS)
origin = request.headers['origin']
elsif request.headers['origin'].nil? && request.headers['AMP-same-origin']&.casecmp?('true')
origin = request.base_url
else
render(json: { message: 'Unauthorized Request' }, status: :forbidden) && return
end
response.set_header('Access-Control-Allow-Credentials', 'true')
response.set_header('Access-Control-Allow-Origin', origin)
end
end
end
I AM NOT MAD
I JUST NEED TO RUN MY CUSTOM SCRIPT
<head>
<meta
name="amp-script-src"
content="sha384-....."
/>
</head>
def amp_script_hash(module_name)
module_content = File.read(File.join('public', Webpacker.instance.manifest.lookup(module_name)))
Base64.urlsafe_encode64(Digest::SHA2.new(384).hexdigest(module_content), padding: false)
end
<meta name='amp-script-src' content=<%=amp_script_hash('my_custom_script.js')%> />
<amp-script
layout='fixed'
width='500'
height='500'
src=<%=Webpacker.instance.manifest.lookup('my_custom_script.js').gsub(/^\//, root_url)%>
></amp-script>
👉 allowed APIs 👈
Element.querySelector 🔺 (Partial support)
document.querySelector ❌
Element.innerHTML ❌
Event.preventDefault ❌
HTMLElement.dataset ❌
HTMLElement.innerText ❌
requires user gestures to change page content.
Get amp-state
AMP.getState("yourStateName").then( /* ... */ )
Set amp-state
AMP.setState({myStateName: myObject});
Things is not working in amp-script?
Try amp-iframe!
amp-facebook
amp-instagram
amp-twitter
amp-vimeo
amp-youtube
...
prerendering
&
optimizations
valid AMP page
AMP Cache
&
AMP search result
Google AMP Viewer URL
Original AMP Source
⚠️ Only supported on Chrome
AMP 很難搞,像極了愛情(Meng-Ying Tsai,民109)。
Nice page speed
Nice Core Web Vitals scores
Hard to customize
Hard to maintain
Interactive & gorgeous
or
fast & simple
🤔
祝大家都能離職成功保住飯碗!