はじめに
こんにちは。オリンピック、サッカー準決勝のチケットを持っているのですが、観客人数制限に伴い、再抽選するそうで、複雑な気持ちです。いっそ外れてしまえば、行く行かないで迷わないで済みそうですよね 筆者です。
さて、今回はGatsbyプロジェクトをAMPに対応させるために行ったことを記事にしよう思います
参考になれば幸いです!
方針
-
gatsby build
で通常のHTML作成. - 1をAMP用に変換.
- publicに配置.
参考にさせていただいた記事
GatsbyプロジェクトをAMP対応する
上記記事を参考に、自分のプロジェクトに合わせていろいろいじくりました。
それを記していきます!
1. 必要なnode modulesをインストールする
参考にさせていただいた記事では、recursive-readdir
を使用していましたが、後続でエラーが出るので、今回はglob
を使用しています。
また、ampifyjs
をnpmでinstallして使用はしません。
理由は、後続で説明しますが、ざっくり私の場合都合が悪かったためです。
$ npm install glob node-sass cheerio
2. ampify.jsをコピーする
以下を一旦プロジェクトルートに配置します。
以下コマンドで取ってこれます
$ wget https://raw.githubusercontent.com/chiedo/gatsby-amp-starter-blog/master/ampify.js
3. node modulesのampifyjs
を2のコードに取り込む
node modulesのampyfyjsのコードはこちらです↓
取り込むとこんな感じです↓
'use strict';
const recursive = require('recursive-readdir');
const fs = require('fs');
const ampify = require('ampifyjs');
const GA_TRACKING_ID = 'UA-SOME_ANALYTICS_ID-1';
const sass = require('node-sass');
// The director that we will be creating an amp verion of.
// Creating an amp version ultimately means creating an 'amp'
// directory in that with amp versions of each file from the source.
const inputDir = 'public/amp';
// This is where we will populate the last of files to convert
let filesToConvert = [];
// Get a list of all the files in the public directory. But ignore the amp dir
recursive(inputDir, [], (err, files) => {
// Files is an array of file paths. Lets just get the html files
for (let file of files) {
// Only select files that end in '.html'.
if(file.endsWith('.html')) {
filesToConvert.push(file);
}
}
// For each file, modify it to add the amp page reference and then create the amp
// version
for(let fileToConvert of filesToConvert) {
// Add the amp url link to the top of the page then Save the file
fs.writeFileSync(fileToConvert, (() => {
const baseUrl = fileToConvert.replace(inputDir, ''); // No inputDir in the URL
const html = fs.readFileSync(fileToConvert, 'utf8');
// The tags that we will convert to amp versions
const tags = {
amp: ['img', 'video', 'iframe'],
};
// Load the html so we can manipulate it with jQuery syntax on the server
const $ = cheerio.load(html, {
normalizeWhitespace: false,
xmlMode: false,
decodeEntities: false,
cwd: '',
round: true,
});
/**************************************************************************************
* GROUNDWORK
*************************************************************************************/
//remove all script tags. If any specific script tags are needed
//they can be added back later. This gives us a clean slate though
$('script').each(function() {
// Dont remove structured data though
if($(this).attr('type') !== 'application/ld+json'){
$(this).remove();
}
});
// Remove google back analytics
$('script[custom-element="amp-analytics"]').remove();
// Add AMP HTML5 Video Support
if($('body video').length > 0) {
$('script[custom-element="amp-video"]').remove();
$('head').append('<script async custom-element="amp-video" src="https://cdn.ampproject.org/v0/amp-video-0.1.js"></script>');
}
// Add AMP Iframe Support
if($('body iframe').length > 0) {
$('script[custom-element="amp-iframe"]').remove();
$('head').append('<script async custom-element="amp-iframe" src="https://cdn.ampproject.org/v0/amp-iframe-0.1.js"></script>');
}
// Remove existing noscripts and styles
$('noscript').remove();
$('style').remove();
//Add the amp attribute to the html
$('html').attr('amp', '');
//Add the required meta charset tags if not already present
if ($('head meta[charset="utf-8"]').length === 0) {
$('head').append('<meta charset="utf-8">');
}
// The amp version of the site should not have any amphtml.
// We'll set the correct canonical link later
$('head').find('link[rel="amphtml"]').remove();
$('head').find('link[rel="canonical"]').remove();
// Remove preloader code
$('head').find('link[as="script"]').remove();
// Add the canonical link to the URL as specified by the AMP standards
$('head').append(`<link rel="canonical" href="${baseUrl}" />`);
// If the viewport meta isn't correctly set in regards to the amp standards, then set it
if ($('head meta[content="width=device-width,minimum-scale=1,initial-scale=1"]').length === 0) {
// Remove the viewport meta if it exists
$('head meta[name="viewport"]').remove();
// Add the correct viewport meta
$('head').prepend('<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">');
}
// add the main amp library
if ($('head script[src="https://cdn.ampproject.org/v0.js"]').length === 0) {
$('head').append('<script async src="https://cdn.ampproject.org/v0.js"></script>');
}
$('head').prepend('<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>');
// Remove any styles we have that weren't added the 'amp' way
$('link[rel=stylesheet]').each(function() {
$(this).remove();
});
// remove style attributes from everything. No inline styles with amp
$( '*' ).removeAttr('style');
/**************************************************************************************
* AMP CONVERSIONS
*************************************************************************************/
// convert img tags to amp-img tags and video tags to amp-video tags, etc.
$(tags.amp.join(',')).each(function() {
this.name = 'amp-' + this.name;
});
// Set the layouts for all the images
$('.main-pane amp-img, .page amp-img').each(function(){
if($(this).attr('data-layout')) {
$(this).attr('layout', $(this).attr('data-layout'));
} else {
// For images that are really large, let them be responsive and allow them to go full screen
// Fixed images that are really large don't scale down well with AMP for some reason. So this
// is somewhat of a hack fix
if($(this).attr('width') > 700 ) {
$(this).attr('layout', 'responsive');
// For most images, just let them be fixed and css will scale them down
} else {
$(this).attr('layout', 'fixed');
}
}
});
/**************************************************************************************
* Replace certain elements on the page for AMP specifically
*************************************************************************************/
extras($);
/**************************************************************************************
* Replace certain elements on the page for AMP specifically
*************************************************************************************/
$('amp-iframe').attr('sandbox','allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox');
$('amp-iframe').attr('layout','responsive');
$('amp-video').attr('layout', 'responsive');
$('amp-video').attr('height', '270');
$('amp-video').attr('width', '480');
// Google Analytics
$('head').append('<script async custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script>');
$('amp-analytics').remove();
$('body').prepend('<amp-analytics type="googleanalytics" id="analytics1">\
<script type="application/json">{"vars": {"account": "'+GA_TRACKING_ID+'"},"triggers": {"trackPageview": {"on": "visible","request": "pageview"}}}</script>\
</amp-analytics>');
/**************************************************************************************
* STYLES
*************************************************************************************/
// We are using Sass so we need to get each of the styles we need for the amp version of the pages
// and compile it to minified sass.
let css;
css = sass.renderSync({file: 'css/zenburn.css', outputStyle: 'compressed'}).css.toString() +
sass.renderSync({ file: 'css/amponly.scss', outputStyle: 'compressed'}).css.toString();
// Remove all important tags since they are not permitted in amp styles
css = css.replace(/!important/g, '');
// Add our new style to the head as required my amp
$('head').prepend(`<style amp-custom>${css}</style>`);
/**************************************************************************************
* DONE
*************************************************************************************/
return $.html();
})()
}
console.log('The site is now AMP ready');
});
4. ampify.jsを調整します
調整する点は以下の通りです。
- 使用するnode modulesのimport文追加と不要なimport文削除
- 1に伴いコードの変更
- amp-imgタグにwidthとheightを設定
- pictureタグをdivタグに書き換える
- CSSの読み込み方法を変更
4.1. 使用するnode modulesのimport文追加と不要なimport文削除
- const recursive = require('recursive-readdir');
+ const glob = require('glob')
const fs = require('fs');
- const ampify = require('ampifyjs');
+ const cheerio = require('cheerio');
4.2. 4.1に伴いコードの変更
recursive-readdir
使用箇所をglob
に変更します。
glob
にしたことで従来に比べコード量が少なくすることができました
// Get a list of all the files in the public directory. But ignore the amp dir
- recursive(inputDir, [], (err, files) => {
- // Files is an array of file paths. Lets just get the html files
- for (let file of files) {
- // Only select files that end in '.html'.
- if(file.endsWith('.html')) {
- filesToConvert.push(file);
- }
- }
+ glob("public/amp/**/*.html", (e, filesToConvert) => {
// For each file, modify it to add the amp page reference and then create the amp
// version
for(let fileToConvert of filesToConvert) {
// Add the amp url link to the top of the page then Save the file
fs.writeFileSync(fileToConvert, (() => {
// ...
// ...
// ...
}
4.3 amp-imgタグにwidthとheightを設定
こちらは参考にさせていただいた記事と同様のことを行います。
fs.writeFileSync(fileToConvert, (() => {
const baseUrl = fileToConvert.replace(inputDir, ''); // No inputDir in the URL
const html = fs.readFileSync(fileToConvert, 'utf8');
// ...
// ...
// ...
+ $('amp-img').attr('layout', 'responsive');
+ $('amp-img').attr('width', '450');
+ $('amp-img').attr('height', '270');
+ $('amp-img').removeAttr('loading');
/**************************************************************************************
* DONE
*************************************************************************************/
return $.html();
})()
4.4. pictureタグをdivタグに書き換える
こちらも参考にさせていただいた記事と同様のことを行います。
fs.writeFileSync(fileToConvert, (() => {
const baseUrl = fileToConvert.replace(inputDir, ''); // No inputDir in the URL
const html = fs.readFileSync(fileToConvert, 'utf8');
// ...
// ...
// ...
+ const pictures = $('picture');
+ if (pictures.length > 0) {
+ pictures.children('source').remove();
+ pictures.each((_, element) => {
+ const ampImg = $(element).html().trim();
+ $(element).replaceWith(ampImg);
+ });
+ }
/**************************************************************************************
* DONE
*************************************************************************************/
return $.html();
})()
4.5. CSSの読み込み方法を変更
こちらは参考にさせていただいた記事をベースに各種変更を加えています。
変更点は以下の通りです。
- 外部cssの取得
- inline埋め込みcssの取得
- node modulesのampify.jsで行っているcss関連の処理を調整
4.5.1. 外部cssの取得
public/webpack.stats.json
からcssのパスを取得します。
参考にさせていただいた記事と異なるオブジェクト構造でしたので、各自確認が必要だと思います。
自分は以下の感じでした。
fs.writeFileSync(fileToConvert, (() => {
// ...
// ...
// ...
/**************************************************************************************
* STYLES
*************************************************************************************/
// We are using Sass so we need to get each of the styles we need for the amp version of the pages
// and compile it to minified sass.
- let css;
- css = sass.renderSync({file: 'css/zenburn.css', outputStyle: 'compressed'}).css.toString() +
- sass.renderSync({ file: 'css/amponly.scss', outputStyle: 'compressed'}).css.toString();
+ const webpackStats = JSON.parse(
+ fs.readFileSync('public/webpack.stats.json')
+ );
+
+ const files = webpackStats.namedChunkGroups['component---src-templates-page-js'].assets
+ .filter(f => f.name.includes('.css'));
+ let css = files.map((file) => sass.renderSync({
+ file: `public/${file.name}`,
+ outputStyle: 'compressed'
+ }).css.toString()).join('');
// Remove all important tags since they are not permitted in amp styles
css = css.replace(/!important/g, '');
// Add our new style to the head as required my amp
$('head').prepend(`<style amp-custom>${css}</style>`);
/**************************************************************************************
* DONE
*************************************************************************************/
return $.html();
})()
4.5.2. inline埋め込みcssの取得外部cssの取得
私の場合、上記外部cssとは別でheadタグ内にinlineでグローバルのcssを含んでいたため、それを取り出す必要がありました。
また、node modulesのampify.js内で先にすべてのstyleタグを削除しているので、その処理を無効化する必要があります。
// Remove existing noscripts and styles
$('noscript').remove();
- $('style').remove();
//Add the amp attribute to the html
$('html').attr('amp', '');
そのあと、inlineのcssをすべて取得し変数cssに付け足していきます。
このとき、node modulesのampify.jsでも処理しているように、AMPで使用できない!important;
を取り除く処理も行っています。
fs.writeFileSync(fileToConvert, (() => {
/**************************************************************************************
* STYLES
*************************************************************************************/
// We are using Sass so we need to get each of the styles we need for the amp version of the pages
// and compile it to minified sass.
// ...
// ...
// ...
+ $('style').each((i, e) => {
+ css += $(e).html()
+ })
// Remove all important tags since they are not permitted in amp styles
css = css.replace(/!important/g, '');
// Add our new style to the head as required my amp
$('head').prepend(`<style amp-custom>${css}</style>`);
/**************************************************************************************
* DONE
*************************************************************************************/
return $.html();
})()
4.5.3. node modulesのampify.jsで行っているcss関連の処理を調整
4.5.2で無効化した不要なstyleタグの削除を行う必要があるのでそのコードをコピペします。
また、amp-boilerplate
のコードもコピペして下の方に持ってきます。
上の方に書いてあったamp-boilerplate
は削除してください。
fs.writeFileSync(fileToConvert, (() => {
/**************************************************************************************
* STYLES
*************************************************************************************/
// We are using Sass so we need to get each of the styles we need for the amp version of the pages
// and compile it to minified sass.
// ...
// ...
// ...
$('style').each((i, e) => {
css += $(e).html()
})
// Remove all important tags since they are not permitted in amp styles
css = css.replace(/!important/g, '');
+ $('style').remove();
// Add our new style to the head as required my amp
$('head').prepend(`<style amp-custom>${css}</style>`);
+ $('head').prepend('<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>');
/**************************************************************************************
* DONE
*************************************************************************************/
return $.html();
})()
5. ビルドコマンドを作成する
ビルドする際に処理が多いのでビルド用のshellスクリプトを作成します。
全体の流れは以下の通りです。
- gatsby buildしてstaticなファイルを生成する
- 1ををコピーする
- 2からAMPにしたいHTML以外削除する
- ampify.jsを走らせる
自分は以下のようになりました。
# !/bin/sh
# ①
# build
gatsby build
# ②
# amp
rm -rf public/amp
cp -r public amp
mv amp public
cd public/amp
# ③
# delete files exclude .html
find ./ -type f ! -name "*.html" -delete
# delete empty folders
find ./ -type d -empty -delete
# delete not support amp folders
ls -d */ | grep -P "^\d+\/$" | xargs rm -rfv
# delete other unuse files and folders
rm -rf tag/ offline-plugin-app-shell-fallback/ privacy-policy/ 404.html index.html
cd ../../
# ④
# convert html to amp
node ampify.js
6. package.jsonのscriptsを変更
{
"scripts": {
"build": "./build.sh"
}
}
7. ホスティング先のビルドコマンドの設定を変更
今まで、 gatsby build
にしていた人は npm run build
に変更してください!
おわりに
かなりこねくり回しましたが、これで私のGatsbyプロジェクトはAMP対応しました
AMPにしてみたい方ぜひこの機会にいかがでしょうか