Memento开发记录
Memento是一个用户自建的多用户笔记服务, 本文记录开发时的各种难点
SEO
对于拥有大量公开文章的服务, SEO是必不可少的, flutter以及各种SPA前端自身不具备SEO的能力, 为了实现SEO, 必须由后端进行特殊处理
动态生成index.html
SPA使用一个 index.html
作为入口, 然后加载js进行渲染, 大多数搜索引擎爬虫不能执行js, 因此要对index.html进行处理
首先修改 index.html
, 将<head>
部分修改成这样
<base href="/">
<meta name="description" content="{{Description}}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{Title}}">
<meta name="twitter:description" content="{{Description}}">
<meta name="twitter:image" content="{{Preview}}">
<meta property="og:title" content="{{Title}}">
<meta property="og:type" content="website">
<meta property="og:url" content="{{Url}}">
<meta property="og:image" content="{{Preview}}">
<meta property="og:description" content="{{Description}}">
<meta property="og:site_name" content="{{SiteName}}">
<title>{{Title}}</title>
修改 <body>
部分
<body>
<!-- SEO article -->
<div style="display:none">
<!-- SEO article Body-->
</div>
<script src="flutter_bootstrap.js" async></script>
</body>
这些工作是为了方便后端进行处理
在后端部分, 根据请求的路径, 从数据库中拿取需要的数据, 然后替换 index.html
中的参数
func seoHtml(html string, reqPath string) string {
siteName := memento.GetConfig().SiteName
description := memento.GetConfig().Description
title := siteName
url := scheme + "://" + domain + reqPath
preview := "/icons/Icon-192.png"
seoArticle := ""
// hide
description = strings.ReplaceAll(description, "\n", " ")
description = strings.ReplaceAll(description, "\r", " ")
preview = scheme + "://" + domain + "/api" + preview
html = strings.ReplaceAll(html, "{{Title}}", title)
html = strings.ReplaceAll(html, "{{Description}}", description)
html = strings.ReplaceAll(html, "{{SiteName}}", siteName)
html = strings.ReplaceAll(html, "{{Url}}", url)
html = strings.ReplaceAll(html, "{{Preview}}", preview)
html = strings.Replace(html, "<!-- SEO article Body-->", seoArticle, 1)
return html
}
生成robots.txt和sitemap.xml
对于本服务, 文章数量可能相当多, 每次请求时生成一次sitemap.xml显然不合适, 因此在创建, 修改, 删除文章后生成sitemap.xml
flutter web优化
使用html渲染器
flutter自3.22开始, 将canvaskit渲染器设为了默认值, 由于其没有调用系统字体的能力, 显示中文字体需要下载十几MB的字体, 这显然是不能接受的, 因此使用html渲染器
删除不必要的assets
某些第三方库携带了很多用不上的assets, 每次构建后手动删除显然很麻烦, 因此将其clone至项目下的pkgs目录内, 对其进行修改
减少main.dart.js的大小
构建后main.dart.js大小达到了4868KB, 在网络连接较慢的情况下, 加载时间会达到十几秒甚至更长
flutter web不支持大小分析, 因此使用 flutter build apk --analyze-size --target-platform android-arm64
分析apk大小的来源
Package | Size |
---|---|
package:flutter | 3 MB |
package:highlight | 1 MB |
package:image | 861 KB |
package:flutter_math_fork | 479 KB |
package:frontend | 345 KB |
dart:core | 300 KB |
dart:typed_data | 210 KB |
dart:ui | 205 KB |
package:vector_graphics_compiler | 164 KB |
dart:collection | 148 KB |
dart:async | 140 KB |
dart:io | 131 KB |
package:markdown | 110 KB |
package:material_color_utilities | 86 KB |
dart:convert | 85 KB |
package:dio | 76 KB |
package:petitparser | 62 KB |
package:vector_math | 52 KB |
package:markdown_widget | 51 KB |
package:source_span | 43 KB |
可以看到 hightlight
, image
, flutter_math_fork
3个package非常大, 因此要对它们进行优化
hightlight
hightlight库用于对markdown中的代码高亮, 猜测是由于支持的语言过多导致体积庞大
查看源代码后发现, 它居然支持184种语言
通过只保留主要语言, 编译为apk时其大小从1MB降低至165 KB
image
image库用于调整上传的头像的大小, 这是一个很重的库, 对于web来说显然不合适, 优化也比较麻烦, 直接将此过程放在后端
flutter_math_fork
这个库用于显示数学公式, 其代码较为复杂, 不便于直接对代码进行操作, 可以采用 Deferred components
方法优化. 这样, 仅在需要显示数学公式时才加载相关的代码
经过上述操作后, main.dart.js减小到了2977KB, gzip压缩后仅有910KB