本文做者:任家乐css
原创声明:本文为阅文前端团队 YFE 成员出品,请尊重原创,转载请联系公众号 ( id: yuewen_YFE ) 获取受权,并注明做者、出处和连接。前端
「用户体验 > 开发体验」Google AMP 的设计准则绝不掩饰地标记了它的核心观念,从一开始就认可了它对开发者的不友好。react
「Webnovel m 站」做为国内首批使用 AMP 技术的产品「关于 AMP,Webnovel 都作了什么?」之一,结论上创造了显著提高的性能数据,过程当中正如谷歌核心观点所说,开发体验很是差。最近得来一次机会,咱们 Webnovel 面向非洲用户的新产品「Ficool m 站」实践了全站 AMP,从中找到了使用 AMP 的新姿式 - 「Next.js + AMP + Preact」,开发体验提高了不止一个档次,在此分享给你们。webpack
做为 Webnovel 面向非洲地区的站点,它不只覆盖 web 渠道,其借助 Google TWA 技术打包而成的轻量 APP 还将预装到 Android Go 系统的手机上,此系统对 APP 内存、CPU 占用有着严格的要求,现已成功入库。目前「Ficool m 站」大多数页面还暂未被 Google 搜索引擎收录,后续能够跟你们分享一下实际的效果以及咱们在服务端作的进阶优化。git
比起「Ficool m 站是什么?」,对本文来讲更重要的是「Again,Why AMP ?」github
今年 AMP conf 中一位伊拉克小哥的演讲使人印象深入。web
「No poser, no internet, no support: How AMP bridges the app gap in Iraq and other war-impacked region」后端
身处于战乱、网络条件落后的国家,他们选择使用 AMP,并借助 AMP 提供的一系列组件及优化方案一步步地填补了 APP 在体验、兼容性方面的落差,从而创建起了 APP 和曾经他们主动放弃的一批落后用户之间的桥梁。跨域
国内的咱们天天享受着 4G 可能某一天享受的就是 5G,很难想象遥远的非洲兄弟们忍受着怎样的网络条件... 咱们的产品也终于有机会去了一趟非洲「肯尼亚」,并带回了一些「前线」消息 “ 那里的网速虽然没有想象中差,但广泛使用 3G 网络且速度有限,同时移动网络套餐相比国内要贵不少。”sass
这 3 点已经足够推动「Ficool m 站」全站 AMP 的想法了,不只如此,咱们还走了一条 AMP 的小众路线 - 「Next.js + AMP + Preact」。
有了 「Webnovel m 站」的经验教训,咱们预留时间作了大量的前期调研,并设定了一些目标:
最终 get 了 Next.js + AMP + Preact 这样的组合方式,听起来好像比 AMP + HTML 洋气了一些?这实际上是一个极少人尝试过的 AMP 开发形式,在开发时间的压迫下一步棋走错全盘皆输!其过程采坑无数,你们不妨看看咱们都经历了哪些历程。
回顾「Webnovel m 站」 AMP 的旧开发方式:直接在 HTML 中引入 AMP 组件,以开发 HTML 的方式编写 AMP 页面 - 不可忍!
实际上,Next.js 在 8.1 版本以上就已经支持了 AMP 的开发,这意味着咱们彻底能够直接用 React 开发 AMP 页面。
旧方式「图 1」对于组件的提取较为困难,借助 EJS 模板当然能够抽离 template 的部分并使用 include 语法将其引入,但 amp-list
的部分很难提取成组件(template 的部分是不肯定的),而在 React AMP 中,咱们能够将其提取成组件,并经过增长失败「fallback」、加载中「placeholder」等功能「图3」,使 amp-list
用起来更灵活。
「图 1:列表页 - HTML 旧方式开发」
「图 2:列表页 - React 方式开发」
「图 3:列表页 amp-list
组件 - React」
一样的,旧开发模式下能难作到服务端渲染(直出)部分、异步渲染部分的模板统一,这是因为 HTML 直出模板「 EJS 模板」语法和 AMP 的 「mustache template 模板」语法是大相径庭的,然而在 React AMP 中咱们却能够:
例如「图 2」中的 <BookItemTtoB>
组件,直出部分天然是正常传递 props,而 amp-list
的模板部分只须要用 mustache 模板语法 {{ bookId }}
传递值便可 ,<BookItemTtoB>
也就达到了两种情景下的复用。
继续回顾以前的旧开发方式:样式必须内联放于 HTML 中的 <style amp-custom>
标签内,就当时的本地开发流程来讲,要实现直接编译 scss 文件并将生成的 css 内联到 <style amp-custom>
标签内,改造起来远没有添加一条 webpack 配置那么简单,最终咱们粗暴地将 css 文件用 EJS 引入模板的方式 include 到了 <style>
标签内,这显然不太优雅。
新的开发流程下,咱们只需在 next.config.js
中拓展 webpack 的配置便可:引入 sass-loader
以及 Next.js 内置的 styled-jsx
「图4」,Next.js 会直接将编译好的 css 内联到 <style amp-custom>
标签内「图 5」,就是这么简单。
「图 4:next.config.js
中编译 css 相关配置」
「图 5:组件中引入样式」
总结:
一、用 React 方式开发 AMP 页面,组件的提取更简单。
二、页面的逻辑相比冗长的 HTML 代码更易维护且风格统一「单一 React 风格 VS HTML + EJS + mustache 混用」。
三、样式的编写更加容易、开发形式也吻合当前趋势。
amp-script
第一阶段咱们实现了用 React 愉快地进行 AMP 的开发,但局限是:React 的生命周期函数不能用、一切自定义的 JS 交互依然不能实现(AMP 不容许引入咱们本身的 JavaScript )。
好消息!在今年 4 月中旬 AMP 发布了 amp-script
,借助它就能够写咱们本身的脚本了!固然,它有必定局限性:大小上限( uncompressed 150KB),数量限制(每一个页面只容许引入 1 个 amp-script
)以及 API 限制 ... 无论有多少限制,为了实现更多的复杂交互,咱们须要使用这个能力。
amp-script
很是规用法 - 使用 PreactAMP 官方提供的 amp-script
demo 基本都是原生 Javascript 的写法,而在咱们的 React AMP 项目中很不但愿维护 2 种风格的代码,所以咱们探索了用 React 开发 amp-script
的可能性。
首先是解决大小限制问题:
「各框架源码 uncompressed 大小」
size ( uncompressed ) | amp-script 剩余空间 | |
---|---|---|
React | 110kb | 150 - 110 = 40kb |
Preact | 8.2kb | 150 - 8.2 = 141.8kb |
「图 6:React vs Preact in amp-script」
React 源码足足有 110kb,若是使用 React,那咱们本身的脚本只能写 40kb,这显然不够,所以咱们选择了既能保持 React 开发风格、又能控制 amp-script
大小的 「Preact,React 的轻量版」。
amp-script
的编译&打包amp-script
的引入方法不一样于普通的 React 组件「图 七、图 8」,其中 amp-script
组件必须有 src 属性,其值为你引入的脚本文件 url(绝对地址),因为每一个页面只能引 1 个脚本,咱们须要保证这个脚本已是打包后的最终脚本,此时毫无疑问要使用 webpack 进行依赖分析及打包了。
「图 8:amp-script 的引入脚本的方式」
咱们从新为 amp-script
的打包写了个独立的 webpack 配置「webpack.amp.config」,使其编译打包过程更加单一简单,尽可能不和 next.config.js
的配置项搞混。
此 webpack.amp.config
的目标:对目标脚本进行依赖分析,最终通过 babel 编译打包成咱们所需的 es5 脚本(此脚本就是 amp-script 最终引用的脚本)。
这个功能算是 webpack 比较基本的功能了,按 webpack 官网 demo 使用便可,重点配置项可参考「图 9」,其中 getAllAmpScriptEntries() 会获取全部须要编译的 amp-script
脚本路径,最终生成的脚本存放于 Next.js 托管的静态资源文件夹下 ./static
。
「图 9:amp-script
webpack 配置项重点部分」
amp-script
开发方式尘埃落定至此,咱们已经能够正常享用 Preact 版 amp-script
了。若是只是简单的 DOM 操做,能够将须要被操做的 DOM 放在 amp-script
标签内、经过 ID 选择器获取元素便可,但咱们既然使用了 Preact, 更推荐的作法是:须要操做的 DOM 全权由 amp-script
引用的 Preact 脚本渲染出来。
例如「图 10」中蓝色吸底栏,它涉及到的交互有:点击后展现 confirm 弹窗、请求后端进行相关操做(加入/移除书架)、接口返回成功后进行前端回显,普通页面的交互不外乎如此,具体实现思路是:React AMP 页面准备一个空的具备 ID 标识的 div 容器,借助 Preact 的 render 方法渲染出吸底栏组件,直接在 Preact 中进行事件的绑定、setState 更新 UI 等逻辑。
「图 10:详情页吸底栏使用 amp-script
脚本渲染」
amp-script
脚本 CDN 化借助 CDN 能够减轻服务器的压力、最快地响应用户,所以全部的 JavaScript 资源都应该放于 CDN 上,amp-script
固然不能例外。
amp-script
的打包编译流程是独立于 Next.js 的,但当咱们打包 Next 脚本时,两个独立流程的融合不可避免,咱们但愿执行 build 命令后,amp-script
的 src 地址将会被替换为 CDN 域名,同时 amp-script
脚本文件名也会加上 md5 码... 来继续倒腾 next.config.js
吧!
amp-script
的 src 路径替换 + 文件名 md5 化,这个需求和绝大多数图片的引入基本同样,所以用 webpack 的 file-loader 就能够实现「图 11」。
「图 11:file-loader
替换 amp-script
路径」
为避免报错,test 对应的正则建议只匹配 amp-script
的脚本,同时 publicPath 也需进行「本地/线上」的区分用以保证本地可以正常引用 amp-script 脚本(本地依旧访问 /static/
路径下的脚本)。为配合 file-loader
的使用,AMP 页面中也需把 amp-script
脚本的引用方式替换为 require()
方法引用。
一套流程下来,彻底解决了 amp-script
脚本路径替换 + 文件名加 md5 的需求。
amp-script
脚本跨域问题完美解决到了这一步,眼看着 amp-script
享受到了 CDN 的待遇、Next 服务减轻了一些压力,然而却爆出了跨域错误「图 12」。
此跨域问题是 amp-script
自身的一套安全策略抛出的异常,官方说明可参考「图 13」,大体意思是,若是咱们引用的 amp-script
脚本和页面 URL 域名不一样,须要在各页面 meta 标签内,添加 amp-script-src 对应的 hash code ,此 code 根据脚本内容、基于 Content Security
Policy 「CSP」生成,是每一个脚本独一无二的编码。
「图 12:amp-script
跨域报错」
「图 13:amp-script
安全策略」
幸亏 AMP 官方也提供了 CSP hash code
的生成工具@ampproject/toolbox-script-csp
,咱们初步的思路定了下来:
amp-script
脚本,同目录下生成对应的 hash code 文件。raw-loader
,实现 meta 标签内 content 内容替换为 hash code 文件内容。Step 1: webpack 自定义 plugin
此 plugin 功能单一还算好写,能够参考以下「图 14」,重点是获取到编译后的 amp-script
脚本内容、生成 hash code 、存放于同一路径下的 hash.txt 文件内。最终在 webpack 配置文件中 plugins 中引入此插件并实例化便可。
「图 14:webpack 自定义 plugin」
Step 2 & Step 3: next.config.js
配置 raw-loader
,AMP 页面引入 hash code 文件
页面中的改动以下所示:
「图 15:AMP 页面中引入 hash code 文件,添加 meta 标签」
next.config.js
中也须要配置 raw-loader
,实现 meta 标签内 content 值的替换。
「图 16:next.config.js
中配置 raw-loader」
跨域问题得以解决!
总结:实现了 amp-script
React 方式开发的全过程。有了自定义的脚本能力,AMP 页面的开发更加灵活。
至此,开发过程当中不免还会面临一些 amp-script
都没法实现的交互形式,这样的交互着实挑战了 AMP 的设计原则,换句话说,若是可以作到交互形式上彻底遵循 AMP 的规范,开发体验则会畅通无阻!
AMP 设计原则中,没有规定你具体要怎样设计、怎样实施,但核心原则必须是:只作对用户体验有利的事情!其中有2条原则对咱们开发者来讲会有必定的启发「图 17」
「图 17:AMP 设计原则 2 条」
4.涉及到开发的各个层面应职责分明,在正确的层解决问题。
5.只作可让网页变快的事情。
「涉及到开发的各个层面应职责分明,在正确的层解决问题」- 若是逻辑放于后端来讲对用户体验较为友好,请不要仅仅由于前端实现起来简单,而把全部的逻辑都放在前端。
这里有一些场景能够参考:
amp-mustache
组件(弱逻辑的模板语言,不支持运算、正则以及一些复杂判断逻辑)时,不少逻辑应该尽可能让后端同窗实现,例如:搜索页关键词高亮逻辑「图 18」,后端能够直接返回带有高亮样式的 HTML 元素。amp-list
组件,但这须要后端协助加字段返回下一页的请求 URL 并携带参数、同时须要判断是不是最后一页而作逻辑的变动。固然前端也能够用 amp-script
实现下拉加载,可是其效果远没有直接引用 amp-list
好,此时逻辑后置颇有必要。「图 18:搜索页关键词蓝色高亮」
只作可让网页变快的事情 - AMP 不推荐引入任何会致使动画帧率变慢、页面加载速度受限的组件、特性。
我对它的理解是,若是不能保证本身写的组件彻底符合 AMP 标准,那就尽可能用 AMP 提供的组件吧!这里不得再也不泼一下冷水了,amp-script
真的有不少限制,它并不能实现全部交互逻辑,只是给开发者开了个不大不小的窗户而已。这种状况只有 2 条路可选了:
若是你提早看到了此文,那么状况可能并无那么糟,由于你能够防范于未然,如下就是良方。
AMP 的设计原则有个很好的初衷:
「 These design principles are meant to guide the ongoing design and development of AMP. They should help us make internally consistent decisions. 」
(让咱们作出一致的决定)
其中 “咱们”,指的不只是 AMP 页面前端开发者,而是参与这个项目的全部人,这个出发点很是重要,这也是 AMP 2019 CONF 「AMP core mindset」提到的主要观念。
产品须要了解 AMP 的限制,避免天马行空的需求设计;设计也须要了解 AMP 的组件规范,进而节约设计成本、产出能够用 AMP 技术实现出来的组件,最终避免开发成本的增长。例如, 图片轮播组件 AMP 自身已经实现的很好了「图 19」,设计若是没有特殊要求,不须要再设计新的风格。
「图 19:amp 轮播图组件 amp-carousel」
从开发层面来说,前端在拿到设计稿后,就能够清楚的看出页面能够用到的 AMP 组件,对于后端同窗来讲,开发前期就知道该补什么字段、作哪些逻辑调整。
通过踩坑无数的坎坷历程,「Next.js + AMP + Preact 」这个 AMP 的新尝试在「Ficool m站」完美收工。尽管「Next.js + AMP + Preact」的组合在其余非 AMP 项目中几乎不会用到,但文中提到的每个坑想必其余团队也很难回避,但愿本文能让你们少走点弯路。
最后,团队的共识很重要,除了能节约沟通成本,更多的是你们都知道要作什么、为何去作,咱们是在实现同一个目标 - 更好的用户体验。