From 90cfd965c7d654da754e3296b4b2844d691256b4 Mon Sep 17 00:00:00 2001 From: thephez Date: Wed, 6 May 2026 12:30:45 -0400 Subject: [PATCH 1/2] perf(dashnote): defer SDK value imports in note + contract helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NotesWorkspace, SessionContext, and LoginModal statically imported contract.ts, createNote.ts, and updateNote.ts, each of which pulled Document/DataContract/Identifier from @dashevo/evo-sdk. That anchored the 8 MB SDK chunk to the entry graph via a hoisted static import, so the browser fetched it before first paint despite the modulePreload filter in vite.config.ts. Move the value imports behind a lazy module cache (matching the loginWithPrivateKey pattern in 2b2c5a0) so the chunk loads only when an authenticated write actually needs it. Throttled Lighthouse (Slow 4G + 4× CPU, simulated mobile): LCP 30.8 s → 2.0 s, FCP 16.1 s → 1.7 s, perf score 0.55 → 0.98. Co-Authored-By: Claude Opus 4.7 (1M context) --- example-apps/dashnote/src/dash/contract.ts | 21 ++++++++++++++++++-- example-apps/dashnote/src/dash/createNote.ts | 18 +++++++++++++++-- example-apps/dashnote/src/dash/updateNote.ts | 18 +++++++++++++++-- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/example-apps/dashnote/src/dash/contract.ts b/example-apps/dashnote/src/dash/contract.ts index 7e7be7c..a36638c 100644 --- a/example-apps/dashnote/src/dash/contract.ts +++ b/example-apps/dashnote/src/dash/contract.ts @@ -5,11 +5,26 @@ * sdk.contracts.publish({ dataContract, identityKey, signer }) * sdk.identities.nonce(identityId) */ -import { DataContract, Identifier } from "@dashevo/evo-sdk"; - import type { Logger } from "../lib/logger"; import type { DashKeyManager, DashSdk } from "./types"; +// Defer the @dashevo/evo-sdk value import so it doesn't anchor the SDK chunk +// to the entry graph via SessionContext + LoginModal's static imports of this +// file. Synchronous exports below (NOTE_SCHEMAS, loadStoredContractId, etc.) +// don't touch the SDK and stay sync because SessionContext calls them during +// initial render. Cached after first call; cleared on failure for retry. +type SdkModule = typeof import("@dashevo/evo-sdk"); +let sdkModulePromise: Promise | null = null; +function loadSdkModule(): Promise { + if (!sdkModulePromise) { + sdkModulePromise = import("@dashevo/evo-sdk").catch((err) => { + sdkModulePromise = null; + throw err; + }); + } + return sdkModulePromise; +} + export const NOTE_SCHEMAS = { note: { type: "object", @@ -78,6 +93,7 @@ export async function refreshContractCache({ if (!contractId || typeof sdk.getWasmSdkConnected !== "function") return; const wasm = await sdk.getWasmSdkConnected(); if (!wasm || typeof wasm.removeCachedContract !== "function") return; + const { Identifier } = await loadSdkModule(); const identifier = new Identifier(contractId); try { wasm.removeCachedContract(identifier); @@ -98,6 +114,7 @@ export async function registerContract({ log?.("Registering Dashnote note contract…"); const { identity, identityKey, signer } = await keyManager.getAuth(); const identityNonce = await sdk.identities.nonce(identity.id.toString()); + const { DataContract } = await loadSdkModule(); const dataContract = new DataContract({ ownerId: identity.id, identityNonce: (identityNonce || 0n) + 1n, diff --git a/example-apps/dashnote/src/dash/createNote.ts b/example-apps/dashnote/src/dash/createNote.ts index fb2af0e..621d506 100644 --- a/example-apps/dashnote/src/dash/createNote.ts +++ b/example-apps/dashnote/src/dash/createNote.ts @@ -3,11 +3,24 @@ * * SDK method: sdk.documents.create({ document, identityKey, signer }) */ -import { Document } from "@dashevo/evo-sdk"; - import type { Logger } from "../lib/logger"; import type { DashKeyManager, DashSdk } from "./types"; +// Defer the @dashevo/evo-sdk value import so it doesn't anchor the SDK chunk +// to the entry graph via NotesWorkspace's static import of this file. Cached +// after first call; cleared on failure so a transient chunk fetch can retry. +type SdkModule = typeof import("@dashevo/evo-sdk"); +let sdkModulePromise: Promise | null = null; +function loadSdkModule(): Promise { + if (!sdkModulePromise) { + sdkModulePromise = import("@dashevo/evo-sdk").catch((err) => { + sdkModulePromise = null; + throw err; + }); + } + return sdkModulePromise; +} + export interface CreateNoteParams { sdk: DashSdk; keyManager: DashKeyManager; @@ -27,6 +40,7 @@ export async function createNote({ }: CreateNoteParams): Promise { log?.("Creating note…"); const { identity, identityKey, signer } = await keyManager.getAuth(); + const { Document } = await loadSdkModule(); const trimmedTitle = title?.trim(); const document = new Document({ properties: { diff --git a/example-apps/dashnote/src/dash/updateNote.ts b/example-apps/dashnote/src/dash/updateNote.ts index 7a227e0..53f7203 100644 --- a/example-apps/dashnote/src/dash/updateNote.ts +++ b/example-apps/dashnote/src/dash/updateNote.ts @@ -6,11 +6,24 @@ * sdk.documents.get(contractId, documentTypeName, documentId) * sdk.documents.replace({ document, identityKey, signer }) */ -import { Document } from "@dashevo/evo-sdk"; - import type { Logger } from "../lib/logger"; import type { DashKeyManager, DashSdk } from "./types"; +// Defer the @dashevo/evo-sdk value import so it doesn't anchor the SDK chunk +// to the entry graph via NotesWorkspace's static import of this file. Cached +// after first call; cleared on failure so a transient chunk fetch can retry. +type SdkModule = typeof import("@dashevo/evo-sdk"); +let sdkModulePromise: Promise | null = null; +function loadSdkModule(): Promise { + if (!sdkModulePromise) { + sdkModulePromise = import("@dashevo/evo-sdk").catch((err) => { + sdkModulePromise = null; + throw err; + }); + } + return sdkModulePromise; +} + export interface UpdateNoteParams { sdk: DashSdk; keyManager: DashKeyManager; @@ -37,6 +50,7 @@ export async function updateNote({ throw new Error(`Note ${noteId} not found.`); } + const { Document } = await loadSdkModule(); const revision = BigInt(existingDoc.revision ?? 0) + 1n; const trimmedTitle = title?.trim(); const document = new Document({ From 6eb98a8a5eef3ea1cfb93e01921a7fbfd09d6adf Mon Sep 17 00:00:00 2001 From: thephez Date: Wed, 6 May 2026 13:28:53 -0400 Subject: [PATCH 2/2] refactor(dashnote): extract shared SDK lazy-loader to src/dash/sdkModule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pull the duplicated `loadSdkModule` helper (type alias + cached promise + loader function with retry-on-failure) out of contract.ts, createNote.ts, and updateNote.ts into a single src/dash/sdkModule.ts, and have each consumer import it. No behavior change — ESM module-graph caching makes the previous three private promises functionally equivalent to one shared promise. Tutorial files now show only their Dash SDK recipe without the bundler-deferral plumbing. Co-Authored-By: Claude Opus 4.7 (1M context) --- example-apps/dashnote/src/dash/contract.ts | 18 +----------------- example-apps/dashnote/src/dash/createNote.ts | 16 +--------------- example-apps/dashnote/src/dash/sdkModule.ts | 17 +++++++++++++++++ example-apps/dashnote/src/dash/updateNote.ts | 16 +--------------- 4 files changed, 20 insertions(+), 47 deletions(-) create mode 100644 example-apps/dashnote/src/dash/sdkModule.ts diff --git a/example-apps/dashnote/src/dash/contract.ts b/example-apps/dashnote/src/dash/contract.ts index a36638c..8fba063 100644 --- a/example-apps/dashnote/src/dash/contract.ts +++ b/example-apps/dashnote/src/dash/contract.ts @@ -6,25 +6,9 @@ * sdk.identities.nonce(identityId) */ import type { Logger } from "../lib/logger"; +import { loadSdkModule } from "./sdkModule"; import type { DashKeyManager, DashSdk } from "./types"; -// Defer the @dashevo/evo-sdk value import so it doesn't anchor the SDK chunk -// to the entry graph via SessionContext + LoginModal's static imports of this -// file. Synchronous exports below (NOTE_SCHEMAS, loadStoredContractId, etc.) -// don't touch the SDK and stay sync because SessionContext calls them during -// initial render. Cached after first call; cleared on failure for retry. -type SdkModule = typeof import("@dashevo/evo-sdk"); -let sdkModulePromise: Promise | null = null; -function loadSdkModule(): Promise { - if (!sdkModulePromise) { - sdkModulePromise = import("@dashevo/evo-sdk").catch((err) => { - sdkModulePromise = null; - throw err; - }); - } - return sdkModulePromise; -} - export const NOTE_SCHEMAS = { note: { type: "object", diff --git a/example-apps/dashnote/src/dash/createNote.ts b/example-apps/dashnote/src/dash/createNote.ts index 621d506..e5516f4 100644 --- a/example-apps/dashnote/src/dash/createNote.ts +++ b/example-apps/dashnote/src/dash/createNote.ts @@ -4,23 +4,9 @@ * SDK method: sdk.documents.create({ document, identityKey, signer }) */ import type { Logger } from "../lib/logger"; +import { loadSdkModule } from "./sdkModule"; import type { DashKeyManager, DashSdk } from "./types"; -// Defer the @dashevo/evo-sdk value import so it doesn't anchor the SDK chunk -// to the entry graph via NotesWorkspace's static import of this file. Cached -// after first call; cleared on failure so a transient chunk fetch can retry. -type SdkModule = typeof import("@dashevo/evo-sdk"); -let sdkModulePromise: Promise | null = null; -function loadSdkModule(): Promise { - if (!sdkModulePromise) { - sdkModulePromise = import("@dashevo/evo-sdk").catch((err) => { - sdkModulePromise = null; - throw err; - }); - } - return sdkModulePromise; -} - export interface CreateNoteParams { sdk: DashSdk; keyManager: DashKeyManager; diff --git a/example-apps/dashnote/src/dash/sdkModule.ts b/example-apps/dashnote/src/dash/sdkModule.ts new file mode 100644 index 0000000..5cab25b --- /dev/null +++ b/example-apps/dashnote/src/dash/sdkModule.ts @@ -0,0 +1,17 @@ +// Defer the @dashevo/evo-sdk value import so it doesn't anchor the SDK chunk +// to the entry graph via files statically imported by SessionContext / +// LoginModal / NotesWorkspace. Cached after first call; cleared on failure +// so a transient chunk fetch can retry. +export type SdkModule = typeof import("@dashevo/evo-sdk"); + +let sdkModulePromise: Promise | null = null; + +export function loadSdkModule(): Promise { + if (!sdkModulePromise) { + sdkModulePromise = import("@dashevo/evo-sdk").catch((err) => { + sdkModulePromise = null; + throw err; + }); + } + return sdkModulePromise; +} diff --git a/example-apps/dashnote/src/dash/updateNote.ts b/example-apps/dashnote/src/dash/updateNote.ts index 53f7203..1e4556f 100644 --- a/example-apps/dashnote/src/dash/updateNote.ts +++ b/example-apps/dashnote/src/dash/updateNote.ts @@ -7,23 +7,9 @@ * sdk.documents.replace({ document, identityKey, signer }) */ import type { Logger } from "../lib/logger"; +import { loadSdkModule } from "./sdkModule"; import type { DashKeyManager, DashSdk } from "./types"; -// Defer the @dashevo/evo-sdk value import so it doesn't anchor the SDK chunk -// to the entry graph via NotesWorkspace's static import of this file. Cached -// after first call; cleared on failure so a transient chunk fetch can retry. -type SdkModule = typeof import("@dashevo/evo-sdk"); -let sdkModulePromise: Promise | null = null; -function loadSdkModule(): Promise { - if (!sdkModulePromise) { - sdkModulePromise = import("@dashevo/evo-sdk").catch((err) => { - sdkModulePromise = null; - throw err; - }); - } - return sdkModulePromise; -} - export interface UpdateNoteParams { sdk: DashSdk; keyManager: DashKeyManager;