[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"content-migrate-blog-to-amp":3,"$f20xtly9k1v4a8":21},{"id":4,"type":5,"slug":6,"title":7,"date":8,"category":9,"tags":10,"body_markdown":12,"permalink":13,"excerpt_src":14,"media_type":14,"media_title":14,"media_author":14,"media_url":14,"rating":14,"layout":14,"pv":15,"admin_only":15,"created_at":16,"updated_at":16,"deleted_at":14,"status":17,"html":18,"excerpt":19,"cover":20},172,"article","migrate-blog-to-amp","轻松迁移博客到AMP","2017-08-29 20:18","web",[11],"AMP","\nAMP 全称 Accelerated Mobile Pages ，是由 Google 提出的一种移动端页面的规范。相比普通 HTML 而言，最大的特点是对页面中可用元素进行了严格的限制，以确保高性能。此外，Google 还对 AMP 页面提供了高速缓存，如果从 Google 搜索中打开 AMP 页面，速度非常快，几乎是秒开。\n\n前几天将我的博客完全迁移到了 AMP，在此也做一个记录。\n\n## 作为单独页面存在的 AMP\n\n在很长一段时间内，我都以为 AMP 只适用于移动端某些特殊场景，至少文章页应该是适用的，于是自然而然就想到了博客应该是非常适合 AMP 的场景。因为我的博客使用的是著名的静态博客程序 [Hexo](https:\u002F\u002Fhexo.io)，所以也就很自然地想到了，会不会有人已经写好了 Hexo 的 AMP 插件？搜了一下，果然没有失望，找到了一个名为 [hexo-generator-amp](https:\u002F\u002Fgithub.com\u002Ftea3\u002Fhexo-generator-amp)的插件。\n\n这个插件不会修改已有的文章页面，而是会为文章页面再生成一个合法的 AMP 页面。这两个页面可以互相引用，表示一个是普通页面，一个是 AMP 页面。这种方式也是 Google 搜索等平台认可的 AMP 生成方式。\n\n按照插件的文档，使用起来还是比较简单的，具体的方式就不写了，直接参考文档即可。有几个值得注意的点：\n\n1. 一定要修改文章页的模板，添加`link[rel=amphtml]`链接，要不然搜索无法找到 AMP 页面\n2. AMP 页面会自动在`head`中添加`canonical`链接回原有的文章页\n\n这个插件支持自己修改模板。鉴于我对它的样式并不是很满意，没有修改的欲望，于是也就没有去改它。如果你需要修改 AMP 模板的话，可以在网址中添加`#development=1`，然后在控制台中查看 AMP 验证结果。\n\n\u003C!-- more -->\n\n最后效果：\n\n![Google搜索显示AMP](\u002Fassets\u002Fmigrate-blog-to-amp\u002F1-google-search.jpeg)\n\n从 Google 搜索的时候能看到明显的“AMP”和闪电标记\n\n![AMP使用Google域名缓存](\u002Fassets\u002Fmigrate-blog-to-amp\u002F1-google-cache.jpeg)\n\n打开后就能看到这个插件提供的默认模板长什么样。\n\n另外可以看到，从搜索中直接点击时，访问的域名是 Google 的，这便是经常被说到的，Google 对 AMP 页面提供了高速缓存。按照 Google 的说法，这种状态下，页面的逻辑仍然会运行，此时页面既是在 Google 那里的，也是在网站作者的控制下的。\n\n![hexo-generator-amp默认样式](\u002Fassets\u002Fmigrate-blog-to-amp\u002F1-footer.jpeg)\n\n这是插件默认模板的 footer，可以说是相当大，而且个人不太喜欢。但不管怎么说，我的博客有 AMP 页面了，并且能在 Google 搜索结果中被标识出来，点击时还能秒开，这确实是一种非常不错的使用体验。\n\n## 全站 AMP\n\n后来我看到了[《澄清对AMP的十个误解》](\u002Farticle\u002Fweb\u002F2017\u002F10-misconceptions-about-amp.html)这篇文章，才知道原来 AMP 既不是移动端的专属应用，也不是能力非常受限的技术。因此萌生了将博客整站都改成 AMP 的想法。\n\n首先，既然全站都要改成 AMP 了，那么为文章页再单独生成一个 AMP 页面就显得不必要了，因此我去掉了上面说的 hexo-generator-amp 插件，而使用完全手工修改的方式来改造。\n\n### 1. 照葫芦画瓢\n\n前面说过，当在地址栏中加上`#development=1`时，浏览器控制台就会出现 AMP 的验证信息。于是我想着那就先打开这个验证信息，然后跟着错误一个一个改吧，于是也就直接访问了`http:\u002F\u002Flocalhost:4000\u002F#development=1`，结果控制台空空如也。通过[查询文档](https:\u002F\u002Fwww.ampproject.org\u002Fdocs\u002Ftutorials\u002Fcreate\u002Fbasic_markup)，才知道原来要打开 AMP 验证，至少还是得做一点前置工作的，最起码你得让浏览器知道“我是打算变成 AMP 的，请按 AMP 的标准来要求我”。于是按照文档一一照做：\n\n- Doctype 是必须要有：无需改动\n- 包含一个顶层的`\u003Chtml ⚡>`或者`\u003Chtml amp>`：打开 Hexo 模板，给`html`加上闪电属性`⚡`\n- 包含`\u003Chead>`和`\u003Cbody>`：无需改动\n- `\u003Chead>`的第一个子节点是`\u003Cmeta charset=\"utf-8\">`：无需改动\n- `\u003Chead>`的第二个子节点是`\u003Cscript async src=\"https:\u002F\u002Fcdn.ampproject.org\u002Fv0.js\">\u003C\u002Fscript>`：照做\n- 在`\u003Chead>`中通过`\u003Clink rel=\"canonical\" href=\"$SOME_URL\">`指向非 AMP 版本的页面，如果只有 AMP 版本则指向自身：照做，指向自身\n    ```html\n    link(rel=\"canonical\",href=url_for(page.path))\n    ```\n- 在`\u003Chead>`中包含`\u003Cmeta name=\"viewport\" content=\"width=device-width,minimum-scale=1,initial-scale=1\">`：无需改动\n- 包含一段 AMP [必须有的代码](https:\u002F\u002Fwww.ampproject.org\u002Fdocs\u002Freference\u002Fspec\u002Famp-boilerplate.html)：照做，注意这里不要对代码进行格式化，按原样一行复制下来就好，否则会验证不通过\n\n做完回头看一下，其实改动并不是很多。然后再次刷新浏览器，就能看到验证信息了：\n\n![AMP验证截图](\u002Fassets\u002Fmigrate-blog-to-amp\u002F2-getting-started.png)\n\n在图上，我们可以看到大概有这样几个问题：\n\n- 使用`link[rel-stylesheet]`加载了一个 CDN 域名上的字体\n- `script` 标签不允许（3次）\n- `img`的`src`属性缺失\n- `img`不允许出现，可能需要`amp-img`\n\n对这些问题一一进行解释和解决。\n\n### 2. 样式表\n\n第一个问题，它说我加载了一个 CDN 域名上的字体，这是怎么回事呢？同样，首先[查看文档](https:\u002F\u002Fwww.ampproject.org\u002Fdocs\u002Freference\u002Fspec#custom-fonts)，发现文档上说，只允许加载以下几个域名的自定义字体：\n\n- https:\u002F\u002Ffast.fonts.net\n- https:\u002F\u002Ffonts.googleapis.com\n- https:\u002F\u002Fmaxcdn.bootstrapcdn.com\n\n但下面也说，在自定义 CSS 中，可以使用`@font-face`来引用字体，这种方式不受域名限制。\n\n我的博客主题确实用到了自定义字体，而且确实是在我自己的 CSS 中定义的，而我的字体托管域名并不是上面那几个域名，所以肯定是无法加载了。于是我将字体的定义从`CSS`中移到了`\u003Chead>`中：\n\n```html\n\u003Cstyle>\n@font-face {\n    font-family: 'sourcesanspro';\n    src: url('\u002F\u002Ftoobug.s.f2er.info\u002Ffont\u002Fsourcesanspro.woff2') format('woff2'),\n         url('\u002F\u002Ftoobug.s.f2er.info\u002Ffont\u002Fsourcesanspro.woff') format('woff');\n    font-weight: normal;\n    font-style: normal;\n}\n\u003C\u002Fstyle>\n```\n\n满心希望地刷新了一下，结果发现这个错误依然存在。在花了三十分钟百思不得其解之后，我终于想到了去翻一下 [AMP 关于样式的说明](https:\u002F\u002Fwww.ampproject.org\u002Fdocs\u002Fguides\u002Fresponsive\u002Fstyle_pages)，文档中明确说：“AMP pages can’t include external stylesheets, with the exception of custom fonts”，即 AMP 页面中不允许加载外部样式，除了自定义字体。\n\n再明确一下，AMP 中不允许使用 CSS 样式表加载样式。唯一可以用 CSS 来加载的只有字体，而用来加载字体的 CSS 必须是上面的三个网址之一。\n\n那么解决方式就简单了，使用`\u003Cstyle amp-custom>`将样式文件内联进来即可，具体到 jade 模板中，只要`include`一下就好：\n\n```\nstyle(amp-custom)\n    include ..\u002F..\u002Fsource\u002Fcss\u002Fapollo.css\n```\n\n### 3. 脚本\n\nAMP 页面中不允许以我们熟悉的方式引入脚本，或者也可以先简单理解为不允许使用脚本。而我的页面上使用了 3 个脚本：\n\n1. 用于将 HTTP 访问跳转到 HTTPS 的脚本\n2. 用于图片 lazyload 的脚本\n3. Google Analytics 统计脚本\n\n第 1 个有一定历史原因，因为最早将博客托管在 Gitlab.com 上，不支持自动 HTTPS 跳转，所以只能使用脚本。现在使用了自己的服务器，可以直接使用301跳转，并支持 HSTS，所以这个脚本直接去掉即可。\n\n第 2 个是自己写的一个简单的图片 lazyload 的脚本，即构建时将`img`的`src`属性换成`data-src`，然后在图片滚动到当前视野中时再加载。因为 AMP 并不支持`img`，且`amp-img`有 lazyload 的特性，所以直接去掉。（关于图片的问题下文详述。）\n\n第 3 个，GA 统计的脚本，AMP 有官方的组件可以支持，通过[查看文档](https:\u002F\u002Fwww.ampproject.org\u002Fdocs\u002Freference\u002Fcomponents\u002Famp-analytics)，只要先引入`amp-analytics`组件脚本，然后将 GA 的代码替换掉就可以解决：\n\n```html\n\u003C!-- 在head区 AMP 脚本之前引入 -->\n\u003Cscript async custom-element=\"amp-analytics\" src=\"https:\u002F\u002Fcdn.ampproject.org\u002Fv0\u002Famp-analytics-0.1.js\">\u003C\u002Fscript>\n\n\u003C!-- 将统计脚本替换成如下代码 -->\n\u003Camp-analytics type=\"googleanalytics\" id=\"UA-XXXXXXXX\">\n    \u003Cscript type=\"application\u002Fjson\">\n    {\n        \"vars\": {\n            \"account\": \"UA-XXXXXXXX\"\n        },\n        \"triggers\": {\n            \"trackPageview\": {\n                \"on\": \"visible\",\n                \"request\": \"pageview\"\n            }\n        }\n    }\n    \u003C\u002Fscript>\n\u003C\u002Famp-analytics>\n```\n\n至此，脚本的问题解决了。\n\n### 4. 图片\n\nAMP 有一个很大的特点，就是强调页面的静态布局。\n\n举个例子，当浏览器加载一张图片时，如果图片没有被显式指定宽高，此时图片的占位大小是不确定的，因此浏览器会先对后面的内容进行排版，等图片加载完之后再回来重新计算图片占的位置，此时就会造成页面布局的变化。\n\n而 AMP 强调页面布局应该是确定的，因此不允许像上面这样的页面布局变化存在。针对图片，AMP 中需要使用`\u003Camp-img>`元素来替代`\u003Cimg>`，并且强制要求一定要指定图片的布局方式和宽高。\n\n首先第一张要处理的图是博客顶部的 Logo，直接在模板中将它修改成`\u003Camp-img>`：\n\n```\na.logo-link(href=url_for())\n    amp-img(layout=\"fixed\",width=\"60\",height=\"60\",src=theme.logo)\n```\n\n这里我使用了`layout=\"fixed\"`，表示这张图片的大小是固定的。\n\n接下来要处理的文章正文中的图片，我利用了 Hexo 主题的勾子（Script）特性。按官方文档的说法，只要在与`source`同级的目录创建一个`scripts`目录，里面的脚本就会被执行。但是我是将`scripts`目录放到了主题的根目录下，同样会被执行。\n\n```javascript\nvar imageSize = require('image-size');\nvar path = require('path');\n\nhexo.extend.filter.register('after_render:html', (source) => {\n    return source.replace(\u002F\u003Cimg src=\"(.+?)\"\u002Fg, function(str, src){\n        var imagePath = path.join(process.cwd(), 'source' , src);\n        var size = imageSize(imagePath);\n        if(!size){\n            size = {\n                width: 800,\n                height: 500\n            };\n        }\n        return `\u003Camp-img layout=\"responsive\" width=\"${size.width}\" height=\"${size.height}\" src=\"\u002F\u002Ftoobug.s.f2er.info${src}\"`;\n    });\n});\n```\n\n这段代码注册了一个勾子，在渲染 HTML 之后执行，所以可以对 HTML 中的`\u003Cimg>`进行替换。\n\n因为`\u003Camp-img>`要求必须指定宽高，因此使用了`image-size`这个 npm 模块来获取图片宽高。最后将`\u003Cimg>`替换成`\u003Camp-img>`即可。因为是文章正文中的图片，`layout`指定了`responsive`，这样就可以在不同宽度下自适应。最后我在替换的时候顺手加了上 CDN 的前缀。\n\n至此，首页就已经改造完成了，刷新一下，看到错误信息已经没有了。\n\n### 5. 评论框\n\n进入文章详情页，仍然会有一个使用了脚本的提示，这是因为引入了 disqus 评论组件。\n\n```javascript\nvar disqus_shortname = '#{theme.disqus}';\nvar disqus_identifier = '#{page.path}';\nvar disqus_title = '#{page.title}';\nvar disqus_url = '#{config.url}\u002F#{page.path}';\n(function() {\n    var dsq = document.createElement('script'); dsq.type = 'text\u002Fjavascript'; dsq.async = true;\n    dsq.src = '\u002F\u002F' + disqus_shortname + '.disqus.com\u002Fembed.js';\n    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);\n})();\n```\n\n首先动用搜索，看看 disqus 官方是否向 GA 那样有提供官方组件。结论是……没有。但是，找到一篇官方的 AMP 页面下[使用指南](https:\u002F\u002Fgithub.com\u002Fdisqus\u002Fdisqus-install-examples\u002Ftree\u002Fmaster\u002Fgoogle-amp)。\n\n事实上，这篇指南写得并不是十分清楚，并且照做的话会有一些错误信息，也不能正常显示和发布评论。在尝试好几次并做了反复修改之后，才终于弄懂它的含义。它的大致原理是使用`amp-iframe`组件，将评论放在一个独立的 iframe 中去。这是利用了 AMP 的规则，虽然 AMP 页面不允许有脚本存在，但是可以通过`amp-iframe`来包含页面，将脚本放到这个单独的页面中即可。\n\n第一步，我们要准备一下这个被嵌入的页面，它将包含主要的 disqus 相关的代码：\n\n```html\n\u003C!-- 用于显示评论框和评论列表的容器 -->\n\u003Cdiv id=\"disqus_thread\">\u003C\u002Fdiv>\n\u003Cscript>\n\u002F\u002F 监听disqus组件传递的消息\n\u002F\u002F 其中有一个是`resize`，是disqus用于告诉当前页面\n\u002F\u002F “我加载完了，我的尺寸是XXX”\nwindow.addEventListener('message', receiveMessage, false);\nfunction receiveMessage(event)\n{\n    if (event.data) {\n        var msg;\n        try {\n            msg = JSON.parse(event.data);\n        } catch (err) {\n            \u002F\u002F Do nothing\n        }\n        if (!msg)\n            return false;\n\n        if (msg.name === 'resize') {\n            \u002F\u002F 向 AMP 页面发送消息，要求重新设置 amp-iframe的高度\n            window.parent.postMessage({\n              sentinel: 'amp',\n              type: 'embed-size',\n              height: msg.data.height\n            }, '*');\n        }\n    }\n}\n\u003C\u002Fscript>\n\u003Cscript>\n    \u002F\u002F 用于解析url参数\n    function getQueryVariable(variable) {\n        var query = window.location.search.substring(1);\n        var vars = query.split(\"&\");\n        for (var i=0;i\u003Cvars.length;i++) {\n            var pair = vars[i].split(\"=\");\n            if(pair[0] == variable){return pair[1];}\n        }\n        return(false);\n    }\n    \u002F\u002F 通过url参数获取当前 AMP 页面对应的文章相关参数\n    \u002F\u002F 并赋值给page变量，稍后disqus将读取page变量\n    var disqus_config = function () {\n        this.page.title = decodeURIComponent(getQueryVariable(\"title\"));\n        this.page.url = decodeURIComponent(getQueryVariable(\"url\"));\n        this.page.identifier = decodeURIComponent(getQueryVariable(\"identifier\"));\n    };\n\n    \u002F\u002F 引入disqus脚本\n    (function() {  \u002F\u002F DON'T EDIT BELOW THIS LINE\n        var d = document, s = d.createElement('script');\n\n        s.src = '\u002F\u002Ftoobug.disqus.com\u002Fembed.js';\n\n        s.setAttribute('data-timestamp', +new Date());\n        (d.head || d.body).appendChild(s);\n    })();\n\u003C\u002Fscript>\n```\n\n我已经在代码中标上了注释，这里面有几个关键点需要理解：\n\n1. 这个页面将会被`amp-iframe`引用，因此 AMP 页面是父页面，本页面是指上面的这段代码所在页面\n2. disqus 的脚本在加载后，会将一个新的 iframe 写到容器`#disqus_thread`中\n3. disqus 会从新的 iframe 中发消息，告知本页面自己的状态，其中一种消息是尺寸\n4. disqus 需要知道 AMP 页面对应的 url、标题等信息，我们通过本页面的`location.search`获取\n5. `amp-iframe` 可以接受本页面的消息，动态设置高度\n\n第二步，需要将这个页面放到一个单独的域名上，不能和 AMP 页面同一个域名。刚好我使用了一个 CDN ，有独立的域名，因此下面就直接用 CDN 的域名进行引入。\n\n第三步，使用`amp-iframe`将这个页面引入，并且在 url 中传入文章的相关参数：\n\n```html\n\u003Camp-iframe\n    width=\"600\"\n    height=\"140\"\n    layout=\"responsive\"\n    sandbox=\"allow-scripts allow-same-origin allow-modals allow-popups allow-forms\"\n    resizable\n    src=\"https:\u002F\u002Ftoobug.s.f2er.info\u002Famp\u002Fdisqus\u002Ftoobug.html?title=#{page.title}&url=#{config.url}\u002F#{page.path}&identifier=#{page.path}\">\n    \u003Cdiv\n        overflow\n        tabindex=0\n        role=button\n        aria-label=\"Disqus Comments\"\n    >Disqus Comments\u003C\u002Fdiv>\n\u003C\u002Famp-iframe>\n```\n\n值得注意的点：\n\n1. `layout`写`responsive`以便自适应宽度\n2. `sandbox`需要写明权限，否则可能导致评论无法操作\n3. 要有`resizable`属性，这样会接受 iframe 中页面的消息，重新计算高度\n4. 要有`div[overflow]`子元素，否则会有报错“Overflow element must be defined for resizable frames”\n\n这样就完成了 disqus 评论的改造。\n\n![amp-iframe加载中](\u002Fassets\u002Fmigrate-blog-to-amp\u002F3-comments-1.jpeg)\n\n图：amp-iframe加载中\n\n![disqus加载中](\u002Fassets\u002Fmigrate-blog-to-amp\u002F3-comments-2.jpeg)\n\n图：disqus加载中\n\n![disqus评论](\u002Fassets\u002Fmigrate-blog-to-amp\u002F3-comments-3.jpeg)\n\n图：disqus评论正常显示\n\n回顾一下完整的原理：\n\n- 使用`amp-iframe`来包含评论的逻辑\n- 接受 disqus 的消息，如果是高度变更了，那么向 AMP 页面发送一个消息，要求重新计算高度\n- 通过`amp-iframe`的`src`参数来指定`disqus`相关的参数\n\n> 改造完之后发现一个疑似 AMP 的 bug：在移动端 Chrome 上访问时，评论框显示不完整，也就是说，高度调整并没有完成。按照文档中的说法，说这个高度调整不一定是立即完成，AMP 会判断需要调整的时候再做调整。估计是这个判断什么时候进行调整有 bug 。如果选中文字往下拖到底，则有一定机率高度会变正常。目前尚未找到更好的解决方案。\n\n## 结\n\n以上就是本博客的改造过程。将博客改造成全站 AMP 并不是很困难。一方面是因为基本上没有什么逻辑，另一方面以文章为主的站点非常符合 AMP 的定位。\n\n如果你的站点逻辑非常多，或者并不是以文章、资讯为主的站点，可能还需要再考量一下是否有必要。\n\n最后再附一张改造之后的 Google AMP 缓存访问的图：\n\n![Google AMP 缓存](\u002Fassets\u002Fmigrate-blog-to-amp\u002F4-google-cache-final.jpeg)\n\n比`hexo-generator-amp`生成的样式顺眼多啦。\n","\u002Farticle\u002Fmigrate-blog-to-amp.html",null,0,"2026-08-28 04:37:17","published","\u003Cp>AMP 全称 Accelerated Mobile Pages ，是由 Google 提出的一种移动端页面的规范。相比普通 HTML 而言，最大的特点是对页面中可用元素进行了严格的限制，以确保高性能。此外，Google 还对 AMP 页面提供了高速缓存，如果从 Google 搜索中打开 AMP 页面，速度非常快，几乎是秒开。\u003C\u002Fp>\n\u003Cp>前几天将我的博客完全迁移到了 AMP，在此也做一个记录。\u003C\u002Fp>\n\u003Ch2>作为单独页面存在的 AMP\u003C\u002Fh2>\n\u003Cp>在很长一段时间内，我都以为 AMP 只适用于移动端某些特殊场景，至少文章页应该是适用的，于是自然而然就想到了博客应该是非常适合 AMP 的场景。因为我的博客使用的是著名的静态博客程序 \u003Ca href=\"https:\u002F\u002Fhexo.io\">Hexo\u003C\u002Fa>，所以也就很自然地想到了，会不会有人已经写好了 Hexo 的 AMP 插件？搜了一下，果然没有失望，找到了一个名为 \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Ftea3\u002Fhexo-generator-amp\">hexo-generator-amp\u003C\u002Fa>的插件。\u003C\u002Fp>\n\u003Cp>这个插件不会修改已有的文章页面，而是会为文章页面再生成一个合法的 AMP 页面。这两个页面可以互相引用，表示一个是普通页面，一个是 AMP 页面。这种方式也是 Google 搜索等平台认可的 AMP 生成方式。\u003C\u002Fp>\n\u003Cp>按照插件的文档，使用起来还是比较简单的，具体的方式就不写了，直接参考文档即可。有几个值得注意的点：\u003C\u002Fp>\n\u003Col>\n\u003Cli>一定要修改文章页的模板，添加\u003Ccode>link[rel=amphtml]\u003C\u002Fcode>链接，要不然搜索无法找到 AMP 页面\u003C\u002Fli>\n\u003Cli>AMP 页面会自动在\u003Ccode>head\u003C\u002Fcode>中添加\u003Ccode>canonical\u003C\u002Fcode>链接回原有的文章页\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>这个插件支持自己修改模板。鉴于我对它的样式并不是很满意，没有修改的欲望，于是也就没有去改它。如果你需要修改 AMP 模板的话，可以在网址中添加\u003Ccode>#development=1\u003C\u002Fcode>，然后在控制台中查看 AMP 验证结果。\u003C\u002Fp>\n\u003Cp>最后效果：\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fmigrate-blog-to-amp\u002F1-google-search.jpeg\" alt=\"Google搜索显示AMP\">\u003C\u002Fp>\n\u003Cp>从 Google 搜索的时候能看到明显的“AMP”和闪电标记\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fmigrate-blog-to-amp\u002F1-google-cache.jpeg\" alt=\"AMP使用Google域名缓存\">\u003C\u002Fp>\n\u003Cp>打开后就能看到这个插件提供的默认模板长什么样。\u003C\u002Fp>\n\u003Cp>另外可以看到，从搜索中直接点击时，访问的域名是 Google 的，这便是经常被说到的，Google 对 AMP 页面提供了高速缓存。按照 Google 的说法，这种状态下，页面的逻辑仍然会运行，此时页面既是在 Google 那里的，也是在网站作者的控制下的。\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fmigrate-blog-to-amp\u002F1-footer.jpeg\" alt=\"hexo-generator-amp默认样式\">\u003C\u002Fp>\n\u003Cp>这是插件默认模板的 footer，可以说是相当大，而且个人不太喜欢。但不管怎么说，我的博客有 AMP 页面了，并且能在 Google 搜索结果中被标识出来，点击时还能秒开，这确实是一种非常不错的使用体验。\u003C\u002Fp>\n\u003Ch2>全站 AMP\u003C\u002Fh2>\n\u003Cp>后来我看到了\u003Ca href=\"\u002Farticle\u002Fweb\u002F2017\u002F10-misconceptions-about-amp.html\">《澄清对AMP的十个误解》\u003C\u002Fa>这篇文章，才知道原来 AMP 既不是移动端的专属应用，也不是能力非常受限的技术。因此萌生了将博客整站都改成 AMP 的想法。\u003C\u002Fp>\n\u003Cp>首先，既然全站都要改成 AMP 了，那么为文章页再单独生成一个 AMP 页面就显得不必要了，因此我去掉了上面说的 hexo-generator-amp 插件，而使用完全手工修改的方式来改造。\u003C\u002Fp>\n\u003Ch3>1. 照葫芦画瓢\u003C\u002Fh3>\n\u003Cp>前面说过，当在地址栏中加上\u003Ccode>#development=1\u003C\u002Fcode>时，浏览器控制台就会出现 AMP 的验证信息。于是我想着那就先打开这个验证信息，然后跟着错误一个一个改吧，于是也就直接访问了\u003Ccode>http:\u002F\u002Flocalhost:4000\u002F#development=1\u003C\u002Fcode>，结果控制台空空如也。通过\u003Ca href=\"https:\u002F\u002Fwww.ampproject.org\u002Fdocs\u002Ftutorials\u002Fcreate\u002Fbasic_markup\">查询文档\u003C\u002Fa>，才知道原来要打开 AMP 验证，至少还是得做一点前置工作的，最起码你得让浏览器知道“我是打算变成 AMP 的，请按 AMP 的标准来要求我”。于是按照文档一一照做：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Doctype 是必须要有：无需改动\u003C\u002Fli>\n\u003Cli>包含一个顶层的\u003Ccode>&lt;html ⚡&gt;\u003C\u002Fcode>或者\u003Ccode>&lt;html amp&gt;\u003C\u002Fcode>：打开 Hexo 模板，给\u003Ccode>html\u003C\u002Fcode>加上闪电属性\u003Ccode>⚡\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>包含\u003Ccode>&lt;head&gt;\u003C\u002Fcode>和\u003Ccode>&lt;body&gt;\u003C\u002Fcode>：无需改动\u003C\u002Fli>\n\u003Cli>\u003Ccode>&lt;head&gt;\u003C\u002Fcode>的第一个子节点是\u003Ccode>&lt;meta charset=&quot;utf-8&quot;&gt;\u003C\u002Fcode>：无需改动\u003C\u002Fli>\n\u003Cli>\u003Ccode>&lt;head&gt;\u003C\u002Fcode>的第二个子节点是\u003Ccode>&lt;script async src=&quot;https:\u002F\u002Fcdn.ampproject.org\u002Fv0.js&quot;&gt;&lt;\u002Fscript&gt;\u003C\u002Fcode>：照做\u003C\u002Fli>\n\u003Cli>在\u003Ccode>&lt;head&gt;\u003C\u002Fcode>中通过\u003Ccode>&lt;link rel=&quot;canonical&quot; href=&quot;$SOME_URL&quot;&gt;\u003C\u002Fcode>指向非 AMP 版本的页面，如果只有 AMP 版本则指向自身：照做，指向自身  \u003Cpre>\u003Ccode>    link(rel=&quot;canonical&quot;,href=url_for(page.path))\n  \u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fli>\n\u003Cli>在\u003Ccode>&lt;head&gt;\u003C\u002Fcode>中包含\u003Ccode>&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width,minimum-scale=1,initial-scale=1&quot;&gt;\u003C\u002Fcode>：无需改动\u003C\u002Fli>\n\u003Cli>包含一段 AMP \u003Ca href=\"https:\u002F\u002Fwww.ampproject.org\u002Fdocs\u002Freference\u002Fspec\u002Famp-boilerplate.html\">必须有的代码\u003C\u002Fa>：照做，注意这里不要对代码进行格式化，按原样一行复制下来就好，否则会验证不通过\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>做完回头看一下，其实改动并不是很多。然后再次刷新浏览器，就能看到验证信息了：\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fmigrate-blog-to-amp\u002F2-getting-started.png\" alt=\"AMP验证截图\">\u003C\u002Fp>\n\u003Cp>在图上，我们可以看到大概有这样几个问题：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>使用\u003Ccode>link[rel-stylesheet]\u003C\u002Fcode>加载了一个 CDN 域名上的字体\u003C\u002Fli>\n\u003Cli>\u003Ccode>script\u003C\u002Fcode> 标签不允许（3次）\u003C\u002Fli>\n\u003Cli>\u003Ccode>img\u003C\u002Fcode>的\u003Ccode>src\u003C\u002Fcode>属性缺失\u003C\u002Fli>\n\u003Cli>\u003Ccode>img\u003C\u002Fcode>不允许出现，可能需要\u003Ccode>amp-img\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>对这些问题一一进行解释和解决。\u003C\u002Fp>\n\u003Ch3>2. 样式表\u003C\u002Fh3>\n\u003Cp>第一个问题，它说我加载了一个 CDN 域名上的字体，这是怎么回事呢？同样，首先\u003Ca href=\"https:\u002F\u002Fwww.ampproject.org\u002Fdocs\u002Freference\u002Fspec#custom-fonts\">查看文档\u003C\u002Fa>，发现文档上说，只允许加载以下几个域名的自定义字体：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Ffast.fonts.net\">https:\u002F\u002Ffast.fonts.net\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Ffonts.googleapis.com\">https:\u002F\u002Ffonts.googleapis.com\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Fmaxcdn.bootstrapcdn.com\">https:\u002F\u002Fmaxcdn.bootstrapcdn.com\u003C\u002Fa>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>但下面也说，在自定义 CSS 中，可以使用\u003Ccode>@font-face\u003C\u002Fcode>来引用字体，这种方式不受域名限制。\u003C\u002Fp>\n\u003Cp>我的博客主题确实用到了自定义字体，而且确实是在我自己的 CSS 中定义的，而我的字体托管域名并不是上面那几个域名，所以肯定是无法加载了。于是我将字体的定义从\u003Ccode>CSS\u003C\u002Fcode>中移到了\u003Ccode>&lt;head&gt;\u003C\u002Fcode>中：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;style&gt;\n@font-face {\n    font-family: 'sourcesanspro';\n    src: url('\u002F\u002Ftoobug.s.f2er.info\u002Ffont\u002Fsourcesanspro.woff2') format('woff2'),\n         url('\u002F\u002Ftoobug.s.f2er.info\u002Ffont\u002Fsourcesanspro.woff') format('woff');\n    font-weight: normal;\n    font-style: normal;\n}\n&lt;\u002Fstyle&gt;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>满心希望地刷新了一下，结果发现这个错误依然存在。在花了三十分钟百思不得其解之后，我终于想到了去翻一下 \u003Ca href=\"https:\u002F\u002Fwww.ampproject.org\u002Fdocs\u002Fguides\u002Fresponsive\u002Fstyle_pages\">AMP 关于样式的说明\u003C\u002Fa>，文档中明确说：“AMP pages can’t include external stylesheets, with the exception of custom fonts”，即 AMP 页面中不允许加载外部样式，除了自定义字体。\u003C\u002Fp>\n\u003Cp>再明确一下，AMP 中不允许使用 CSS 样式表加载样式。唯一可以用 CSS 来加载的只有字体，而用来加载字体的 CSS 必须是上面的三个网址之一。\u003C\u002Fp>\n\u003Cp>那么解决方式就简单了，使用\u003Ccode>&lt;style amp-custom&gt;\u003C\u002Fcode>将样式文件内联进来即可，具体到 jade 模板中，只要\u003Ccode>include\u003C\u002Fcode>一下就好：\u003C\u002Fp>\n\u003Cpre class=\"shiki\" style=\"background-color:#121212;color:#dbd7caee\" tabindex=\"0\">\u003Ccode>style(amp-custom)\n    include ..\u002F..\u002Fsource\u002Fcss\u002Fapollo.css\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>3. 脚本\u003C\u002Fh3>\n\u003Cp>AMP 页面中不允许以我们熟悉的方式引入脚本，或者也可以先简单理解为不允许使用脚本。而我的页面上使用了 3 个脚本：\u003C\u002Fp>\n\u003Col>\n\u003Cli>用于将 HTTP 访问跳转到 HTTPS 的脚本\u003C\u002Fli>\n\u003Cli>用于图片 lazyload 的脚本\u003C\u002Fli>\n\u003Cli>Google Analytics 统计脚本\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>第 1 个有一定历史原因，因为最早将博客托管在 Gitlab.com 上，不支持自动 HTTPS 跳转，所以只能使用脚本。现在使用了自己的服务器，可以直接使用301跳转，并支持 HSTS，所以这个脚本直接去掉即可。\u003C\u002Fp>\n\u003Cp>第 2 个是自己写的一个简单的图片 lazyload 的脚本，即构建时将\u003Ccode>img\u003C\u002Fcode>的\u003Ccode>src\u003C\u002Fcode>属性换成\u003Ccode>data-src\u003C\u002Fcode>，然后在图片滚动到当前视野中时再加载。因为 AMP 并不支持\u003Ccode>img\u003C\u002Fcode>，且\u003Ccode>amp-img\u003C\u002Fcode>有 lazyload 的特性，所以直接去掉。（关于图片的问题下文详述。）\u003C\u002Fp>\n\u003Cp>第 3 个，GA 统计的脚本，AMP 有官方的组件可以支持，通过\u003Ca href=\"https:\u002F\u002Fwww.ampproject.org\u002Fdocs\u002Freference\u002Fcomponents\u002Famp-analytics\">查看文档\u003C\u002Fa>，只要先引入\u003Ccode>amp-analytics\u003C\u002Fcode>组件脚本，然后将 GA 的代码替换掉就可以解决：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;!-- 在head区 AMP 脚本之前引入 --&gt;\n&lt;script async custom-element=&quot;amp-analytics&quot; src=&quot;https:\u002F\u002Fcdn.ampproject.org\u002Fv0\u002Famp-analytics-0.1.js&quot;&gt;&lt;\u002Fscript&gt;\n\n&lt;!-- 将统计脚本替换成如下代码 --&gt;\n&lt;amp-analytics type=&quot;googleanalytics&quot; id=&quot;UA-XXXXXXXX&quot;&gt;\n    &lt;script type=&quot;application\u002Fjson&quot;&gt;\n    {\n        &quot;vars&quot;: {\n            &quot;account&quot;: &quot;UA-XXXXXXXX&quot;\n        },\n        &quot;triggers&quot;: {\n            &quot;trackPageview&quot;: {\n                &quot;on&quot;: &quot;visible&quot;,\n                &quot;request&quot;: &quot;pageview&quot;\n            }\n        }\n    }\n    &lt;\u002Fscript&gt;\n&lt;\u002Famp-analytics&gt;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>至此，脚本的问题解决了。\u003C\u002Fp>\n\u003Ch3>4. 图片\u003C\u002Fh3>\n\u003Cp>AMP 有一个很大的特点，就是强调页面的静态布局。\u003C\u002Fp>\n\u003Cp>举个例子，当浏览器加载一张图片时，如果图片没有被显式指定宽高，此时图片的占位大小是不确定的，因此浏览器会先对后面的内容进行排版，等图片加载完之后再回来重新计算图片占的位置，此时就会造成页面布局的变化。\u003C\u002Fp>\n\u003Cp>而 AMP 强调页面布局应该是确定的，因此不允许像上面这样的页面布局变化存在。针对图片，AMP 中需要使用\u003Ccode>&lt;amp-img&gt;\u003C\u002Fcode>元素来替代\u003Ccode>&lt;img&gt;\u003C\u002Fcode>，并且强制要求一定要指定图片的布局方式和宽高。\u003C\u002Fp>\n\u003Cp>首先第一张要处理的图是博客顶部的 Logo，直接在模板中将它修改成\u003Ccode>&lt;amp-img&gt;\u003C\u002Fcode>：\u003C\u002Fp>\n\u003Cpre class=\"shiki\" style=\"background-color:#121212;color:#dbd7caee\" tabindex=\"0\">\u003Ccode>a.logo-link(href=url_for())\n    amp-img(layout=&quot;fixed&quot;,width=&quot;60&quot;,height=&quot;60&quot;,src=theme.logo)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这里我使用了\u003Ccode>layout=&quot;fixed&quot;\u003C\u002Fcode>，表示这张图片的大小是固定的。\u003C\u002Fp>\n\u003Cp>接下来要处理的文章正文中的图片，我利用了 Hexo 主题的勾子（Script）特性。按官方文档的说法，只要在与\u003Ccode>source\u003C\u002Fcode>同级的目录创建一个\u003Ccode>scripts\u003C\u002Fcode>目录，里面的脚本就会被执行。但是我是将\u003Ccode>scripts\u003C\u002Fcode>目录放到了主题的根目录下，同样会被执行。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>var imageSize = require('image-size');\nvar path = require('path');\n\nhexo.extend.filter.register('after_render:html', (source) =&gt; {\n    return source.replace(\u002F&lt;img src=&quot;(.+?)&quot;\u002Fg, function(str, src){\n        var imagePath = path.join(process.cwd(), 'source' , src);\n        var size = imageSize(imagePath);\n        if(!size){\n            size = {\n                width: 800,\n                height: 500\n            };\n        }\n        return `&lt;amp-img layout=&quot;responsive&quot; width=&quot;${size.width}&quot; height=&quot;${size.height}&quot; src=&quot;\u002F\u002Ftoobug.s.f2er.info${src}&quot;`;\n    });\n});\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这段代码注册了一个勾子，在渲染 HTML 之后执行，所以可以对 HTML 中的\u003Ccode>&lt;img&gt;\u003C\u002Fcode>进行替换。\u003C\u002Fp>\n\u003Cp>因为\u003Ccode>&lt;amp-img&gt;\u003C\u002Fcode>要求必须指定宽高，因此使用了\u003Ccode>image-size\u003C\u002Fcode>这个 npm 模块来获取图片宽高。最后将\u003Ccode>&lt;img&gt;\u003C\u002Fcode>替换成\u003Ccode>&lt;amp-img&gt;\u003C\u002Fcode>即可。因为是文章正文中的图片，\u003Ccode>layout\u003C\u002Fcode>指定了\u003Ccode>responsive\u003C\u002Fcode>，这样就可以在不同宽度下自适应。最后我在替换的时候顺手加了上 CDN 的前缀。\u003C\u002Fp>\n\u003Cp>至此，首页就已经改造完成了，刷新一下，看到错误信息已经没有了。\u003C\u002Fp>\n\u003Ch3>5. 评论框\u003C\u002Fh3>\n\u003Cp>进入文章详情页，仍然会有一个使用了脚本的提示，这是因为引入了 disqus 评论组件。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>var disqus_shortname = '#{theme.disqus}';\nvar disqus_identifier = '#{page.path}';\nvar disqus_title = '#{page.title}';\nvar disqus_url = '#{config.url}\u002F#{page.path}';\n(function() {\n    var dsq = document.createElement('script'); dsq.type = 'text\u002Fjavascript'; dsq.async = true;\n    dsq.src = '\u002F\u002F' + disqus_shortname + '.disqus.com\u002Fembed.js';\n    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);\n})();\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>首先动用搜索，看看 disqus 官方是否向 GA 那样有提供官方组件。结论是……没有。但是，找到一篇官方的 AMP 页面下\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fdisqus\u002Fdisqus-install-examples\u002Ftree\u002Fmaster\u002Fgoogle-amp\">使用指南\u003C\u002Fa>。\u003C\u002Fp>\n\u003Cp>事实上，这篇指南写得并不是十分清楚，并且照做的话会有一些错误信息，也不能正常显示和发布评论。在尝试好几次并做了反复修改之后，才终于弄懂它的含义。它的大致原理是使用\u003Ccode>amp-iframe\u003C\u002Fcode>组件，将评论放在一个独立的 iframe 中去。这是利用了 AMP 的规则，虽然 AMP 页面不允许有脚本存在，但是可以通过\u003Ccode>amp-iframe\u003C\u002Fcode>来包含页面，将脚本放到这个单独的页面中即可。\u003C\u002Fp>\n\u003Cp>第一步，我们要准备一下这个被嵌入的页面，它将包含主要的 disqus 相关的代码：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;!-- 用于显示评论框和评论列表的容器 --&gt;\n&lt;div id=&quot;disqus_thread&quot;&gt;&lt;\u002Fdiv&gt;\n&lt;script&gt;\n\u002F\u002F 监听disqus组件传递的消息\n\u002F\u002F 其中有一个是`resize`，是disqus用于告诉当前页面\n\u002F\u002F “我加载完了，我的尺寸是XXX”\nwindow.addEventListener('message', receiveMessage, false);\nfunction receiveMessage(event)\n{\n    if (event.data) {\n        var msg;\n        try {\n            msg = JSON.parse(event.data);\n        } catch (err) {\n            \u002F\u002F Do nothing\n        }\n        if (!msg)\n            return false;\n\n        if (msg.name === 'resize') {\n            \u002F\u002F 向 AMP 页面发送消息，要求重新设置 amp-iframe的高度\n            window.parent.postMessage({\n              sentinel: 'amp',\n              type: 'embed-size',\n              height: msg.data.height\n            }, '*');\n        }\n    }\n}\n&lt;\u002Fscript&gt;\n&lt;script&gt;\n    \u002F\u002F 用于解析url参数\n    function getQueryVariable(variable) {\n        var query = window.location.search.substring(1);\n        var vars = query.split(&quot;&amp;&quot;);\n        for (var i=0;i&lt;vars.length;i++) {\n            var pair = vars[i].split(&quot;=&quot;);\n            if(pair[0] == variable){return pair[1];}\n        }\n        return(false);\n    }\n    \u002F\u002F 通过url参数获取当前 AMP 页面对应的文章相关参数\n    \u002F\u002F 并赋值给page变量，稍后disqus将读取page变量\n    var disqus_config = function () {\n        this.page.title = decodeURIComponent(getQueryVariable(&quot;title&quot;));\n        this.page.url = decodeURIComponent(getQueryVariable(&quot;url&quot;));\n        this.page.identifier = decodeURIComponent(getQueryVariable(&quot;identifier&quot;));\n    };\n\n    \u002F\u002F 引入disqus脚本\n    (function() {  \u002F\u002F DON'T EDIT BELOW THIS LINE\n        var d = document, s = d.createElement('script');\n\n        s.src = '\u002F\u002Ftoobug.disqus.com\u002Fembed.js';\n\n        s.setAttribute('data-timestamp', +new Date());\n        (d.head || d.body).appendChild(s);\n    })();\n&lt;\u002Fscript&gt;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>我已经在代码中标上了注释，这里面有几个关键点需要理解：\u003C\u002Fp>\n\u003Col>\n\u003Cli>这个页面将会被\u003Ccode>amp-iframe\u003C\u002Fcode>引用，因此 AMP 页面是父页面，本页面是指上面的这段代码所在页面\u003C\u002Fli>\n\u003Cli>disqus 的脚本在加载后，会将一个新的 iframe 写到容器\u003Ccode>#disqus_thread\u003C\u002Fcode>中\u003C\u002Fli>\n\u003Cli>disqus 会从新的 iframe 中发消息，告知本页面自己的状态，其中一种消息是尺寸\u003C\u002Fli>\n\u003Cli>disqus 需要知道 AMP 页面对应的 url、标题等信息，我们通过本页面的\u003Ccode>location.search\u003C\u002Fcode>获取\u003C\u002Fli>\n\u003Cli>\u003Ccode>amp-iframe\u003C\u002Fcode> 可以接受本页面的消息，动态设置高度\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>第二步，需要将这个页面放到一个单独的域名上，不能和 AMP 页面同一个域名。刚好我使用了一个 CDN ，有独立的域名，因此下面就直接用 CDN 的域名进行引入。\u003C\u002Fp>\n\u003Cp>第三步，使用\u003Ccode>amp-iframe\u003C\u002Fcode>将这个页面引入，并且在 url 中传入文章的相关参数：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>&lt;amp-iframe\n    width=&quot;600&quot;\n    height=&quot;140&quot;\n    layout=&quot;responsive&quot;\n    sandbox=&quot;allow-scripts allow-same-origin allow-modals allow-popups allow-forms&quot;\n    resizable\n    src=&quot;https:\u002F\u002Ftoobug.s.f2er.info\u002Famp\u002Fdisqus\u002Ftoobug.html?title=#{page.title}&amp;url=#{config.url}\u002F#{page.path}&amp;identifier=#{page.path}&quot;&gt;\n    &lt;div\n        overflow\n        tabindex=0\n        role=button\n        aria-label=&quot;Disqus Comments&quot;\n    &gt;Disqus Comments&lt;\u002Fdiv&gt;\n&lt;\u002Famp-iframe&gt;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>值得注意的点：\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Ccode>layout\u003C\u002Fcode>写\u003Ccode>responsive\u003C\u002Fcode>以便自适应宽度\u003C\u002Fli>\n\u003Cli>\u003Ccode>sandbox\u003C\u002Fcode>需要写明权限，否则可能导致评论无法操作\u003C\u002Fli>\n\u003Cli>要有\u003Ccode>resizable\u003C\u002Fcode>属性，这样会接受 iframe 中页面的消息，重新计算高度\u003C\u002Fli>\n\u003Cli>要有\u003Ccode>div[overflow]\u003C\u002Fcode>子元素，否则会有报错“Overflow element must be defined for resizable frames”\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>这样就完成了 disqus 评论的改造。\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fmigrate-blog-to-amp\u002F3-comments-1.jpeg\" alt=\"amp-iframe加载中\">\u003C\u002Fp>\n\u003Cp>图：amp-iframe加载中\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fmigrate-blog-to-amp\u002F3-comments-2.jpeg\" alt=\"disqus加载中\">\u003C\u002Fp>\n\u003Cp>图：disqus加载中\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fmigrate-blog-to-amp\u002F3-comments-3.jpeg\" alt=\"disqus评论\">\u003C\u002Fp>\n\u003Cp>图：disqus评论正常显示\u003C\u002Fp>\n\u003Cp>回顾一下完整的原理：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>使用\u003Ccode>amp-iframe\u003C\u002Fcode>来包含评论的逻辑\u003C\u002Fli>\n\u003Cli>接受 disqus 的消息，如果是高度变更了，那么向 AMP 页面发送一个消息，要求重新计算高度\u003C\u002Fli>\n\u003Cli>通过\u003Ccode>amp-iframe\u003C\u002Fcode>的\u003Ccode>src\u003C\u002Fcode>参数来指定\u003Ccode>disqus\u003C\u002Fcode>相关的参数\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n\u003Cp>改造完之后发现一个疑似 AMP 的 bug：在移动端 Chrome 上访问时，评论框显示不完整，也就是说，高度调整并没有完成。按照文档中的说法，说这个高度调整不一定是立即完成，AMP 会判断需要调整的时候再做调整。估计是这个判断什么时候进行调整有 bug 。如果选中文字往下拖到底，则有一定机率高度会变正常。目前尚未找到更好的解决方案。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch2>结\u003C\u002Fh2>\n\u003Cp>以上就是本博客的改造过程。将博客改造成全站 AMP 并不是很困难。一方面是因为基本上没有什么逻辑，另一方面以文章为主的站点非常符合 AMP 的定位。\u003C\u002Fp>\n\u003Cp>如果你的站点逻辑非常多，或者并不是以文章、资讯为主的站点，可能还需要再考量一下是否有必要。\u003C\u002Fp>\n\u003Cp>最后再附一张改造之后的 Google AMP 缓存访问的图：\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fmigrate-blog-to-amp\u002F4-google-cache-final.jpeg\" alt=\"Google AMP 缓存\">\u003C\u002Fp>\n\u003Cp>比\u003Ccode>hexo-generator-amp\u003C\u002Fcode>生成的样式顺眼多啦。\u003C\u002Fp>\n","\u003Cp>AMP 全称 Accelerated Mobile Pages ，是由 Google 提出的一种移动端页面的规范。相比普通 HTML 而言，最大的特点是对页面中可用元素进行了严格的限制，以确保高性能。此外，Google 还对 AMP 页面提供了高速缓存，如果从 Google 搜索中打开 AMP 页面，速度非常快，几乎是秒开。\u003C\u002Fp>\n\u003Cp>前几天将我的博客完全迁移到了 AMP，在此也做一个记录。\u003C\u002Fp>\n\u003Ch2>作为单独页面存在的 AMP\u003C\u002Fh2>\n\u003Cp>在很长一段时间内，我都以为 AMP 只适用于移动端某些特殊场景，至少文章页应该是适用的，于是自然而然就想到了博客应该是非常适合 AMP 的场景。因为我的博客使用的是著名的静态博客程序 \u003Ca href=\"https:\u002F\u002Fhexo.io\">Hexo\u003C\u002Fa>，所以也就很自然地想到了，会不会有人已经写好了 Hexo 的 AMP 插件？搜了一下，果然没有失望，找到了一个名为 \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Ftea3\u002Fhexo-generator-amp\">hexo-generator-amp\u003C\u002Fa>的插件。\u003C\u002Fp>\n\u003Cp>这个插件不会修改已有的文章页面，而是会为文章页面再生成一个合法的 AMP 页面。这两个页面可以互相引用，表示一个是普通页面，一个是 AMP 页面。这种方式也是 Google 搜索等平台认可的 AMP 生成方式。\u003C\u002Fp>\n\u003Cp>按照插件的文档，使用起来还是比较简单的，具体的方式就不写了，直接参考文档即可。有几个值得注意的点：\u003C\u002Fp>\n\u003Col>\n\u003Cli>一定要修改文章页的模板，添加\u003Ccode>link[rel=amphtml]\u003C\u002Fcode>链接，要不然搜索无法找到 AMP 页面\u003C\u002Fli>\n\u003Cli>AMP 页面会自动在\u003Ccode>head\u003C\u002Fcode>中添加\u003Ccode>canonical\u003C\u002Fcode>链接回原有的文章页\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>这个插件支持自己修改模板。鉴于我对它的样式并不是很满意，没有修改的欲望，于是也就没有去改它。如果你需要修改 AMP 模板的话，可以在网址中添加\u003Ccode>#development=1\u003C\u002Fcode>，然后在控制台中查看 AMP 验证结果。\u003C\u002Fp>\n","https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fmigrate-blog-to-amp\u002F1-google-search.jpeg",{"total":22,"totalRoots":22,"comments":23,"pv":15},1,[24],{"id":25,"content":26,"user_id":27,"nick":28,"link":29,"date":30,"rid":15,"vote_up":15,"vote_down":15,"visible":31},155,"\u003Cp>大佬厉害了，可惜我不会写前端，不然也能折腾一番了。\u003C\u002Fp>",74,"StackHub","","2019-07-25 11:56:21",true]