同构渲染Angular

同构渲染遇到的一些问题和解决方案

从最初部署开始就用了 Angular Universal 来做同构渲染,不过效果是渐进的。最初的时候,连服务器端语言切换都没有实现,就是说,无论你的 URL 是 /en 还是 /zh 打头的,渲染出来的 HTML 都是英文。目前我还不知道原因是啥,不过把语言从普通的 service 的成员,用 angular-redux 改造为 observable 之后,语言的问题暂时解决了。(其实还有部分页面有问题)

这种状态维持了很久,直到今天早上进行了第二阶段改造。这次把 API 调用也挪到服务器上了。其实本来就应该是在服务器上的,不过一直没有配 CORS,导致每次都调用失败。改造当中还有一个副产品,就是发现了跨域请求的 OPTIONS 会阻塞真正的请求,相当于延迟 * 2 了,延迟一大体验就十分糟糕,所以我把 API 代理到同一域名了。

第三阶段就是继续优化。首屏可以在服务器渲染全部内容之后,又有一个新的问题,就是闪烁问题。它的原因是,在服务器填充了内容,在本地页面初始化的时候,又重新去加载 API,这时候就进入了「加载中」的状态,加载完之后又会变回原来的样子,这就是「闪烁」。

理想中的思路很简单,要是能把服务器的状态延续到桌面端就好了。我的加载是订阅的路由变化,所以想像中如果路由状态能延续,就可以避免重新加载了。我在这里查到有个配置叫 RouterModule.forRoot(routes, {initialNavigation: 'enabled'}),可以把第一次加载的路径直接作为 app 的路径,而不是从 baseUrl 跳转过来的。可惜用了之后我的加载事件还是被触发了,原因正在研究中。

所以我又退而求其次,能不能转移一些别的状态,比如 apollo client 的缓存。Angular 专门有个叫 TransferState 的模块做这个,简单的可以参考这篇。利用 TransferState,我们可以把 apollo client 的缓存、redux 的 store 都序列化之后再在本地回复,理想情况下就不会闪烁了。但我做了之后还是闪。

这是为什么呢?因为我当时针对 apollo client 的 refetch 有时候不能正常工作的问题,自己造了一套缓存失效标记的系统——简而言之就是给每个请求加上一个叫 cacheKey 的参数,如果 cacheKey 不同,apollo client 自然就不缓存了。再用一个唯一的键去识别一个请求,如果它需要缓存失效,就把这个键标记为 true,每次请求的时候去找自己对应的键的值,看是不是失效了,失效了就重新生成一个随机的 cacheKey,没失效就用原来存起来的 cacheKey。而我这个缓存系统同样需要状态转移。等我转移好之后,确实就不闪烁了。

转移的时候还遇到一个问题。我的系统本来是 Map 而不是普通 object,结果遇到了Map 序列化的问题。折腾了半天 Map 的序列化搞不定(因为我是多层的,而且为了类型安全,用了 tagged union,导致有 Map 套 object 套 Map),最终还是暴力把 Map 迁移到 object 了(暴力在为了减少代码更改,在 Object.prototype 上定义一些类似 get, set 的方法……)

现在工作是正常了,但还有一个隐患——生成的 HTML 太大了。这些 store 都以 json 的形式保存在了 HTML 中,本来可能接近 100 KB 的页面,暴涨到 250 KB 左右。现在我正在寻求这个问题的解决方案。可能用 GraphQL 的时候要克制一点了,不要贪图方便,而加入无关的 field。

Colliot12/16/2017, 1:48:16 AM

我想到了一个折中的方法,就是只序列化一部分 redux store(因为页面信息其实是 apollo cache 里面也有一份……如果 redux store 里也放一份,那连上 HTML 渲染出来的,就有三份同样信息了)。这似乎已经是最佳状态了……好像不太可能直接从 HTML 反解析出 cache。

Colliot12/16/2017, 2:08:18 AM

Preview:

Cancel

Elsewhere

Colliot replied to 我感觉苹果系都对单页应用不友好……

@Amethyts,就是浏览器的返回键,就行了。

Colliot replied to 意见反馈发在哪 /没有专门的地方 委屈巴巴

你说的问题都很有道理 我之前的想法是用户自己添加,但是这样似乎并不现实。我将会去找一些免费的词典数据。 我正在寻求解决方案,目前我还没想到。

Amethyts replied to 我感觉苹果系都对单页应用不友好……

我在电脑上打开网页,不知道怎么在打开一个帖子之后回退到上一页面。/托腮

zuozijian3720 replied to 爱情是什么?标题为啥至少10个字?

应该给用户浏览并选择现有标签,现有标签里没有就新建

Colliot replied to even loop 是啥?

就是跟时间分片相对应的一种多任务处理方式?我也不太懂,可能需要好好学习一下。

Nicekingwei replied to 【汉乐府】况是青春日将暮,桃花乱落如红雨

这首也不错。李贺的诗很有意思。

Colliot replied to 【汉乐府】况是青春日将暮,桃花乱落如红雨

这首确实蛮有名的。我觉得色彩也应该是 IDE 和网站的要素……说到李贺我一定要说一首《高轩过》,我最早是在秋水无涯的日记里读到这首的,感觉他的日记写得也很好,对于做学术很有启发 华裾织翠青如葱,金环压辔摇玲珑。 马蹄隐耳声隆隆,入门下马气如虹。 云是东京才子,文章巨公。 二十八宿罗心胸,元精耿耿贯当中。 殿前作赋声摩空,笔补造化天无功。 庞眉书客感秋蓬,谁知死草生华风。 我今垂翅附冥鸿,他日不羞蛇作龙。

Nicekingwei replied to 【汉乐府】况是青春日将暮,桃花乱落如红雨

@Colliot,原来是这样。针对评论进行回复,我觉得是必要的功能。

Colliot replied to 【汉乐府】况是青春日将暮,桃花乱落如红雨

刘伶是竹林七贤,以喝酒闻名。

Colliot replied to 爱情是什么?标题为啥至少10个字?

这个「涉及的对象」 (Entities involved) 其实是一个「标签」功能,猜想中应该填「爱情」之类的……但是大家好像都填了「所有人」……我考虑怎么修改一下引导比较好。