Code-split shell.js: lazy-load Automerge/WASM offline chunk
CI/CD / deploy (push) Failing after 7s
Details
CI/CD / deploy (push) Failing after 7s
Details
Move RSpaceOfflineRuntime, CommunitySync, OfflineStore, and RStackHistoryPanel into a new shell-offline.ts chunk loaded via dynamic import(). This removes ~2.5MB of Automerge WASM from the critical path, reducing blocking JS from ~960KB to ~150KB brotli. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9fd3ca931c
commit
6018a88d26
|
|
@ -77,6 +77,7 @@ export default defineConfig({
|
|||
rollupOptions: {
|
||||
output: {
|
||||
entryFileNames: "shell.js",
|
||||
chunkFileNames: "assets/[name]-[hash].js",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -1416,7 +1417,8 @@ export default defineConfig({
|
|||
// Compute 8-char SHA-256 content hashes for module + shell assets
|
||||
const hashableFiles = allFiles.filter((f) =>
|
||||
(f.startsWith("/modules/") && (f.endsWith(".js") || f.endsWith(".css")) && !f.includes("-demo.js")) ||
|
||||
f === "/shell.js" || f === "/shell.css" || f === "/theme.css"
|
||||
f === "/shell.js" || f === "/shell.css" || f === "/theme.css" ||
|
||||
(f.startsWith("/assets/shell-") && f.endsWith(".js"))
|
||||
);
|
||||
const hashes: Record<string, string> = {};
|
||||
for (const file of hashableFiles) {
|
||||
|
|
@ -1441,7 +1443,8 @@ export default defineConfig({
|
|||
f === "/shell.css" ||
|
||||
f === "/theme.css" ||
|
||||
f === "/favicon.png" ||
|
||||
f === "/manifest.json"
|
||||
f === "/manifest.json" ||
|
||||
(f.startsWith("/assets/shell-") && f.endsWith(".js"))
|
||||
).map((f) => hashes[f] ? `${f}?v=${hashes[f]}` : f);
|
||||
// Ensure root URL is present
|
||||
if (!core.some((f) => f === "/" || f.startsWith("/?v="))) core.unshift("/");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* Lazy-loaded offline runtime chunk.
|
||||
*
|
||||
* Contains Automerge + WASM (~2.5MB) — split from shell.ts so the main
|
||||
* shell bundle stays small and doesn't block first paint.
|
||||
*/
|
||||
|
||||
import { RStackHistoryPanel } from "../shared/components/rstack-history-panel";
|
||||
import { RSpaceOfflineRuntime } from "../shared/local-first/runtime";
|
||||
import { CommunitySync } from "../lib/community-sync";
|
||||
import { OfflineStore } from "../lib/offline-store";
|
||||
|
||||
// Define the history panel component (depends on Automerge)
|
||||
RStackHistoryPanel.define();
|
||||
|
||||
export function initOffline(spaceSlug: string) {
|
||||
const runtime = new RSpaceOfflineRuntime(spaceSlug);
|
||||
(window as any).__rspaceOfflineRuntime = runtime;
|
||||
|
||||
// Configure module scope resolution from server-rendered data
|
||||
try {
|
||||
const scopeJson = document.body?.getAttribute("data-scope-overrides");
|
||||
const overrides: Record<string, string> = scopeJson ? JSON.parse(scopeJson) : {};
|
||||
const moduleList: Array<{ id: string; scoping?: { defaultScope: string } }> =
|
||||
(window as any).__rspaceModuleList || [];
|
||||
const scopes: Array<{ id: string; scope: 'global' | 'space' }> = moduleList.map(m => ({
|
||||
id: m.id,
|
||||
scope: (overrides[m.id] || m.scoping?.defaultScope || 'space') as 'global' | 'space',
|
||||
}));
|
||||
runtime.setModuleScopes(scopes);
|
||||
} catch { /* scope config unavailable — defaults to space-scoped */ }
|
||||
|
||||
runtime.init().catch((e: unknown) => {
|
||||
console.warn("[shell] Offline runtime init failed — REST fallback only:", e);
|
||||
});
|
||||
|
||||
// Flush pending writes before the page unloads
|
||||
window.addEventListener("beforeunload", () => {
|
||||
runtime.flush();
|
||||
});
|
||||
|
||||
// ── CommunitySync (tab list Automerge sync) ──
|
||||
const offlineStore = new OfflineStore();
|
||||
const communitySync = new CommunitySync(spaceSlug, offlineStore);
|
||||
(window as any).__communitySync = communitySync;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await offlineStore.open();
|
||||
await communitySync.initFromCache();
|
||||
} catch (e) {
|
||||
console.warn("[shell] CommunitySync cache init failed:", e);
|
||||
}
|
||||
document.dispatchEvent(new CustomEvent("community-sync-ready", {
|
||||
detail: { sync: communitySync, communitySlug: spaceSlug },
|
||||
}));
|
||||
|
||||
const proto = location.protocol === "https:" ? "wss:" : "ws:";
|
||||
const wsUrl = `${proto}//${location.host}/ws/${spaceSlug}`;
|
||||
communitySync.connect(wsUrl);
|
||||
})();
|
||||
|
||||
window.addEventListener("beforeunload", () => {
|
||||
communitySync.saveBeforeUnload();
|
||||
});
|
||||
}
|
||||
|
|
@ -15,7 +15,6 @@ import { RStackTabBar } from "../shared/components/rstack-tab-bar";
|
|||
import { RStackMi } from "../shared/components/rstack-mi";
|
||||
|
||||
import { RStackModuleSetup } from "../shared/components/rstack-module-setup";
|
||||
import { RStackHistoryPanel } from "../shared/components/rstack-history-panel";
|
||||
import { RStackOfflineIndicator } from "../shared/components/rstack-offline-indicator";
|
||||
import { RStackSharePanel } from "../shared/components/rstack-share-panel";
|
||||
import { RStackCommentBell } from "../shared/components/rstack-comment-bell";
|
||||
|
|
@ -23,9 +22,6 @@ import { RStackCollabOverlay } from "../shared/components/rstack-collab-overlay"
|
|||
import { RStackUserDashboard } from "../shared/components/rstack-user-dashboard";
|
||||
import { rspaceNavUrl } from "../shared/url-helpers";
|
||||
import { TabCache } from "../shared/tab-cache";
|
||||
import { RSpaceOfflineRuntime } from "../shared/local-first/runtime";
|
||||
import { CommunitySync } from "../lib/community-sync";
|
||||
import { OfflineStore } from "../lib/offline-store";
|
||||
|
||||
// Expose URL helper globally (used by shell inline scripts + components)
|
||||
(window as any).__rspaceNavUrl = rspaceNavUrl;
|
||||
|
|
@ -42,67 +38,19 @@ RStackTabBar.define();
|
|||
RStackMi.define();
|
||||
|
||||
RStackModuleSetup.define();
|
||||
RStackHistoryPanel.define();
|
||||
RStackOfflineIndicator.define();
|
||||
RStackSharePanel.define();
|
||||
RStackCommentBell.define();
|
||||
RStackCollabOverlay.define();
|
||||
RStackUserDashboard.define();
|
||||
|
||||
// ── Offline Runtime ──
|
||||
// Instantiate the shared runtime from the space slug on the <body> tag.
|
||||
// Components access it via window.__rspaceOfflineRuntime.
|
||||
// ── Offline Runtime (lazy-loaded) ──
|
||||
// Automerge + WASM (~2.5MB) loaded in a separate chunk to avoid blocking first paint.
|
||||
// Components that depend on window.__rspaceOfflineRuntime already handle late init.
|
||||
const spaceSlug = document.body?.getAttribute("data-space-slug");
|
||||
if (spaceSlug) {
|
||||
const runtime = new RSpaceOfflineRuntime(spaceSlug);
|
||||
(window as any).__rspaceOfflineRuntime = runtime;
|
||||
|
||||
// Configure module scope resolution from server-rendered data
|
||||
try {
|
||||
const scopeJson = document.body?.getAttribute("data-scope-overrides");
|
||||
const overrides: Record<string, string> = scopeJson ? JSON.parse(scopeJson) : {};
|
||||
// Build scope config: merge module defaults with space overrides
|
||||
const moduleList: Array<{ id: string; scoping?: { defaultScope: string } }> =
|
||||
(window as any).__rspaceModuleList || [];
|
||||
const scopes: Array<{ id: string; scope: 'global' | 'space' }> = moduleList.map(m => ({
|
||||
id: m.id,
|
||||
scope: (overrides[m.id] || m.scoping?.defaultScope || 'space') as 'global' | 'space',
|
||||
}));
|
||||
runtime.setModuleScopes(scopes);
|
||||
} catch { /* scope config unavailable — defaults to space-scoped */ }
|
||||
|
||||
runtime.init().catch((e: unknown) => {
|
||||
console.warn("[shell] Offline runtime init failed — REST fallback only:", e);
|
||||
});
|
||||
|
||||
// Flush pending writes before the page unloads
|
||||
window.addEventListener("beforeunload", () => {
|
||||
runtime.flush();
|
||||
});
|
||||
|
||||
// ── CommunitySync (tab list Automerge sync) ──
|
||||
const offlineStore = new OfflineStore();
|
||||
const communitySync = new CommunitySync(spaceSlug, offlineStore);
|
||||
(window as any).__communitySync = communitySync;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await offlineStore.open();
|
||||
await communitySync.initFromCache();
|
||||
} catch (e) {
|
||||
console.warn("[shell] CommunitySync cache init failed:", e);
|
||||
}
|
||||
document.dispatchEvent(new CustomEvent("community-sync-ready", {
|
||||
detail: { sync: communitySync, communitySlug: spaceSlug },
|
||||
}));
|
||||
|
||||
const proto = location.protocol === "https:" ? "wss:" : "ws:";
|
||||
const wsUrl = `${proto}//${location.host}/ws/${spaceSlug}`;
|
||||
communitySync.connect(wsUrl);
|
||||
})();
|
||||
|
||||
window.addEventListener("beforeunload", () => {
|
||||
communitySync.saveBeforeUnload();
|
||||
import('./shell-offline').then(m => m.initOffline(spaceSlug)).catch(e => {
|
||||
console.warn("[shell] Failed to load offline chunk:", e);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue