avatar nyne
Open App

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