Como criar uma página AMP para seu conteúdo dinâmico no Rails

O Google AMP está aqui. E isso é ótimo!

AMP é um novo padrão para criar páginas para dispositivos móveis mais rápidas, baseadas em HTML, que permite o carregamento instantâneo da página.

Como configurar uma página AMP para artigos gerados por usuários no Rails? Aqui está meu tutorial!

Em primeiro lugar, criei um repositório de exemplo aqui . Estou usando isso como referência para este artigo.

Introdução

Vamos começar com um modelo!

No meu exemplo, temos um Articlemodelo, que armazena um titlee um content. O conteúdo pode armazenar algum HTML gerado por um editor WYSIWYG, como Tinymce.

class Article < ActiveRecord::Base
belongs_to
:user
validates
:title, :content, presence: true
end

E aqui está o controlador:

class ArticlesController < ApplicationController
def show
@article = Article.find(params[:id])
end
end

A vista:

<h1><%= @article.title %></h1>
<p><%= @article.content.html_safe %></p>

e é roteador:

resources :articles, only: :show

Não mais do que um template padrão para um aplicativo Rails.
Agora, o que queremos é criar uma view alternativa para a página deste artigo, que siga o padrão AMP.

Defina um novo tipo mime

Uma maneira fácil de criar uma nova visualização para as páginas existentes, sem alterar o controlador, é definir um novo tipo MIME.
Por exemplo, queremos que /articles/1carregue nosso artigo padrão. Em vez disso, o carregamento /articles/1.ampcarregará sua versão AMP.

Em primeiro lugar, vamos criar nosso tipo MIME, adicionando-o ao nosso config/initializers/mime_types.rb:

Mime::Type.register 'text/html', :amp

Agora, vamos criar um novo layout e uma nova visualização.

app / views / layouts / application.amp.erb:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link rel="canonical" href="
<%= url_for(format: :html, only_path: false) %>" >
<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>
<script async src="https://cdn.ampproject.org/v0.js"></script>
<script async custom-element="amp-iframe" src="https://cdn.ampproject.org/v0/amp-iframe-0.1.js"></script>
<script async custom-element="amp-youtube" src="https://cdn.ampproject.org/v0/amp-youtube-0.1.js"></script>
</head>
<body>
<div class="amp">
<%= yield %>
</div>
</body>
</html>

Como você pode ver, é basicamente o modelo HTML de AMP padrão, encontrado aqui . Eu injetei também e marcas personalizadas, desde os meus artigos podem conter vídeos incorporados. Além disso, defini o link canônico rel, que se refere à minha versão não AMP da página. Isso é feito simplesmente usando o método, passando o formato.amp-iframeamp-youtubeurl_forhtml

Está bem! Agora podemos definir nossa visualização personalizada para este artigo emapp/views/articles/show.amp.erb

Trabalhe com CSS personalizado

O AMP requer a inclusão apenas de css incorporado, não de <link>tags externas . Aqui está um truque para continuar usando nosso pipeline de ativos e o css compilado em nossa visão.

Em primeiro lugar, vamos criar um novo arquivo sass em :app/assets/stylesheets/amp/application.scss

body {
...some styles here...
}

Agora, vamos registrá-lo na pré-compilação, adicionando esta linha ao nosso arquivo.config/application.rb

config.assets.precompile << 'amp/application.scss'

Isso diz aos rails para compilá-lo como um arquivo, em vez de empacotá-lo dentro de nosso application.css padrão.

Agora, vamos adicionar isso ao nosso layout <head>:

<% if Rails.application.assets && Rails.application.assets['amp/application'] %>
<style amp-custom><%= Rails.application.assets['amp/application'].to_s.html_safe %></style>
<% else %>
<style amp-custom><%= File.read "#{Rails.root}/public#{stylesheet_path('amp/application', host: nil)}" %></style>
<% end %>

Isso simplesmente copia todo o atrevimento compilado em nossa visão.
Existem dois casos:
* Em desenvolvimento, podemos usar o helper, que contém os dados compilados sass de nossos arquivos. * Em produção, infelizmente, esta variável é , mas podemos o arquivo compilado da pasta. Observe que eu uso com . Isso ocorre porque, normalmente, ele anexa o nome do host ao caminho.Rails.application.assets
nilreadpublic/assetsstylesheet_pathhost: nil

Renderizando o conteúdo do artigo

Agora temos um grande problema. Em nossa visualização padrão, poderíamos simplesmente imprimir nosso em um DOM, sem qualquer alteração.@article.content

AMP, em vez disso, tem muitas limitações nas tags permitidas. Além disso, existem também algumas tags que precisam ser alteradas um pouco para funcionar em AMP. Por exemplo, uma tag “img” torna-se “amp-img”. O mesmo é para iframes.

Como podemos lidar com isso?
Bem, minha solução foi implementar um método scrubberinterno personalizado para o ActionView sanitize.

Ele usa uma abordagem de melhor esforço: tenta converter o máximo possível do DOM original e remove as partes ilegíveis para tornar a página AMP válida.

Veja como está agora:

class AmpScrubber < Rails::Html::PermitScrubber
TAG_MAPPINGS
= {
'img' => lambda { |node|
if node['width'] && node['height']
node
.name = 'amp-img'
node
['layout'] = 'responsive'
node
['srcset'] = node['src']
else
node
.remove
end
},
'iframe' => lambda { |node|
find_parent
(node).add_child(node)

node
['src'] = node['src'].gsub(%r{^(//|http://)}, 'https://')
url
= URI(node['src'])
node
['layout'] = 'responsive'

if url.host.include?('youtube.com')
node
.name = 'amp-youtube'
node
['data-videoid'] = node['src'].match(%r{(/embed/|watch?v=)(.*)})[2]
node
.remove_attribute('src')
else
node
.name = 'amp-iframe'
end
}
}.freeze

def initialize
super
@tags = %w(a em p span h1 h2 h3 h4 h5 h6 div strong s u br blockquote)
@attributes = %w(style contenteditable frameborder allowfullscreen)
end

def self.find_parent(node)
node
= node.parent while node.parent
node

end

protected

def scrub_attribute?(name)
!super
end

def scrub_node(node)
if node.name.in?(TAG_MAPPINGS.keys)
remap_node
! node, TAG_MAPPINGS[node.name]
else
super
end
end

def remap_node!(node, filter)
case filter
when String
node
.name = filter
when Proc
filter
.call(node)
end
end
end

E aqui está como usá-lo. Basta criar um arquivo emapp/views/articles/show.amp.erb

<h1><%= @article.title %></h1>

<div>
<%= sanitize @article.content, scrubber: AmpScrubber.new %>
</div>

Você pode verificar aqui a visualização resultante:

http://amp-example.herokuapp.com/articles/1.amp

Consulte nossa página AMP de nosso padrão

Agora temos nossa página AMP. Como dizer ao Google para indexá-lo? Bem, seguindo a documentação, só precisamos adicionar algumas tags no cabeçalho da página principal.<link rel>

Veja como editar nosso :layout.html.erb

<link rel="canonical" href="<%= url_for(format: :html, only_path: false) %>" >
<link rel="amphtml" href="
<%= url_for(format: :amp, only_path: false) %>" >

Conclusão

Vimos como criar uma versão AMP para nosso conteúdo dinâmico.

Você pode encontrar o exemplo completo no github .

Deixe-me agora se você tiver idéias, sugestões ou dúvidas!