perf(首页): 协作弹框改异步加载,首屏 JS 少三分之一
Some checks failed
Deploy / deploy (push) Has been cancelled

CollabHost 静态 import CollabModal,把整套 CodeMirror(view / state /
language / autocomplete / lang-*)拖进了入口 chunk,而那个组件是
v-if="isTeacher" 的——学生根本不渲染,字节却人人下、人人解析。改成
defineAsyncComponent 之后:

  入口 eager JS  1355 KB → 717 KB(gzip 447 → 234)
  首页 JS 总量   1923 KB → 1285 KB(gzip 642 → 428)

机房那批 Chrome 91 省下的不只是下载,还有六百多 KB 的解析。老师端代价
是第一次接单时多一次 chunk 请求。

顺带修掉题目列表的重复请求:onMounted 拉一次,profile 回来时那个
watch 又拉一次。但请求本来就带 cookie(withCredentials),后端
optionalAuth 认的也是 cookie,首屏那一份的状态列本来就是全的。watcher
改成拿 storage 里的登录态做基线,只在登录态真的变了才补拉。

这条在本机复现不了(/api/me 1ms 就回,路由 chunk 还没挂载完),给
preview 代理的 /api/me 加 300ms 延迟、用 production 构建验证:改前
/problems 发两遍,改后一遍且状态列有值,登录/退出两个切换仍各触发
一次重拉。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tj3959yNB2srvc1oL3PaZH
This commit is contained in:
2026-09-01 00:49:56 -06:00
parent a2f67eebf6
commit ec35400249
2 changed files with 27 additions and 6 deletions

View File

@@ -3,6 +3,8 @@ import { Icon } from "@iconify/vue"
import { NFlex, NTag } from "naive-ui" import { NFlex, NTag } from "naive-ui"
import { useRouteQuery } from "@vueuse/router" import { useRouteQuery } from "@vueuse/router"
import { getProblemList } from "oj/api" import { getProblemList } from "oj/api"
import { STORAGE_KEY } from "utils/constants"
import storage from "utils/storage"
import { getTagColor } from "utils/functions" import { getTagColor } from "utils/functions"
import type { ProblemFiltered, Tag as ContractTag } from "utils/types" import type { ProblemFiltered, Tag as ContractTag } from "utils/types"
import { getProblemTagList } from "shared/api" import { getProblemTagList } from "shared/api"
@@ -129,12 +131,19 @@ watch(
}, },
) )
// 这里只补「用登录框登进来」那一下:那时页面不刷新,列表还是匿名时拉的。
//
// 进站时的那次 listProblems 不需要它跟着再来一遍 —— 请求带 cookieaxios 开了
// withCredentials后端 optionalAuth 认的也是 cookie所以首屏那一份里状态列
// 本来就是全的。基线取 storage 里的登录态,它和 cookie 同生共死,正好用来区分
// 「profile 回来了,还是原来那个人」和「刚登进来 / 刚退出去」。
let authedAtLoad: boolean = storage.get(STORAGE_KEY.AUTHED) ?? false
watch( watch(
() => userStore.isFinished && userStore.isAuthed, () => [userStore.isFinished, userStore.isAuthed],
(isAuthenticatedAndFinished) => { ([isFinished, isAuthed]) => {
if (isAuthenticatedAndFinished) { if (!isFinished || isAuthed === authedAtLoad) return
listProblems() authedAtLoad = isAuthed
} listProblems()
}, },
) )

View File

@@ -1,7 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { CollabRequestItem } from "shared/composables/websocket" import type { CollabRequestItem } from "shared/composables/websocket"
import { useCollabStore } from "shared/store/collab" import { useCollabStore } from "shared/store/collab"
import CollabModal from "./CollabModal.vue"
import HelpRequestList from "./HelpRequestList.vue" import HelpRequestList from "./HelpRequestList.vue"
/** /**
@@ -15,6 +14,19 @@ import HelpRequestList from "./HelpRequestList.vue"
* *
* 位置要求n-message-provider 的后代useMessage 需要)。 * 位置要求n-message-provider 的后代useMessage 需要)。
*/ */
/**
* 协作弹框异步加载。
*
* 这个组件静态 import 进来的话,整套 CodeMirrorview / state / language /
* autocomplete / lang-*)就跟着 App.vue 进了入口 chunk —— 首屏白白多下 640 KB
* gzip 后 210 KB而下面那个 v-if 决定了学生根本不渲染它。机房那批
* Chrome 91 的老机器解析这些字节是实打实的开销。
*
* 拆成异步之后首页的 JS 从 1.9 MB 降到 1.3 MBgzip 642 KB → 428 KB
* 老师那边只是在第一次接单时多一次 chunk 请求。
*/
const CollabModal = defineAsyncComponent(() => import("./CollabModal.vue"))
const collabStore = useCollabStore() const collabStore = useCollabStore()
const message = useMessage() const message = useMessage()