Skip to content

perf(dashnote): defer SDK value imports in note + contract helpers#77

Merged
thephez merged 2 commits intomainfrom
feat/remove-static-import
May 6, 2026
Merged

perf(dashnote): defer SDK value imports in note + contract helpers#77
thephez merged 2 commits intomainfrom
feat/remove-static-import

Conversation

@thephez
Copy link
Copy Markdown
Collaborator

@thephez thephez commented May 6, 2026

Summary

  • contract.ts, createNote.ts, and updateNote.ts had top-level import { Document, DataContract, Identifier } from "@dashevo/evo-sdk". Reachable from the entry graph via SessionContext, LoginModal, and NotesWorkspace, this anchored the 8 MB SDK chunk to the boot critical path with a hoisted static import — the modulePreload filter in vite.config.ts only suppressed the <link> tag, not the dependency edge.
  • Move each value import behind a per-module lazy cache (matching the loginWithPrivateKey pattern from feat(dashnote): preview identity + key-type fitness on WIF paste #75 / 2b2c5a0). The synchronous exports of contract.ts (NOTE_SCHEMAS, loadStoredContractId, etc.) stay sync since SessionContext calls them during initial render.
  • Verified: post-build, dist/assets/index-*.js no longer contains from "./evo-sdk.module-*.js"; only three dynamic import() calls remain.

Throttled Lighthouse (Slow 4G + 4× CPU, simulated mobile)

Metric Before After
First Contentful Paint 16.1 s 1.7 s
Largest Contentful Paint 30.8 s 2.0 s
Speed Index 16.1 s 1.7 s
Performance score 0.55 0.98

Total bytes transferred is unchanged — the win isn't fewer bytes, it's that those bytes no longer block first paint. The SDK now downloads only when an authenticated path (login, save note, register contract, switch contract ID) actually needs it.

image

Summary by CodeRabbit

  • Refactor
    • Improved internal module loading strategy to enhance startup performance across note management features while preserving all existing functionality and behavior.

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) <[email protected]>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

Warning

Rate limit exceeded

@thephez has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 20 minutes and 28 seconds before requesting another review.

To continue reviewing without waiting, purchase usage credits in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2cabfc78-fe88-40b9-9e99-6b709c2c5755

📥 Commits

Reviewing files that changed from the base of the PR and between 90cfd96 and 6eb98a8.

📒 Files selected for processing (4)
  • example-apps/dashnote/src/dash/contract.ts
  • example-apps/dashnote/src/dash/createNote.ts
  • example-apps/dashnote/src/dash/sdkModule.ts
  • example-apps/dashnote/src/dash/updateNote.ts
📝 Walkthrough

Walkthrough

Three files in the dashnote example app convert from static imports of evo-sdk symbols (Identifier, DataContract, Document) to lazy dynamic imports via a shared loader pattern. Each file introduces a typed module loader with caching and uses it to defer SDK symbol acquisition until runtime.

Changes

Lazy SDK Module Loading

Layer / File(s) Summary
Dynamic Load Infrastructure
example-apps/dashnote/src/dash/contract.ts (lines 11–26), example-apps/dashnote/src/dash/createNote.ts (lines 6–23), example-apps/dashnote/src/dash/updateNote.ts (lines 9–26)
All three files introduce identical scaffolding: SdkModule type alias, cached sdkModulePromise, and loadSdkModule() helper for error-tolerant dynamic import with retry logic.
Contract Registration Flow
example-apps/dashnote/src/dash/contract.ts (lines 96–126)
refreshContractCache fetches Identifier dynamically; registerContract loads DataContract via the loader before constructing and publishing contracts. Behavior unchanged, only import strategy deferred.
Note Operations
example-apps/dashnote/src/dash/createNote.ts (line 43), example-apps/dashnote/src/dash/updateNote.ts (line 53)
Both files retrieve Document from the dynamic loader immediately before instantiation, replacing prior static imports. Function signatures and outcomes preserved.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes


A note, a contract, all light and free,
No eager bundles—they load lazily!
The SDK defers its call divine,
While code still flows and notes align. 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly reflects the main change: deferring SDK imports in note and contract helper files for performance optimization.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/remove-static-import

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@thephez
Copy link
Copy Markdown
Collaborator Author

thephez commented May 6, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
example-apps/dashnote/src/dash/createNote.ts (1)

9-22: ⚡ Quick win

Extract the lazy loadSdkModule loader into a shared module to avoid triplicated code.

This 12-line loader (type alias + cached promise + loadSdkModule()) is duplicated verbatim in updateNote.ts (lines 12–25) and contract.ts (lines 11–26). Beyond the DRY hit, each file maintains its own sdkModulePromise cache; since ES import() is module-graph-cached at runtime they still resolve to the same module instance, but a single shared promise is cleaner and gives one place to evolve retry/log/telemetry behavior as more src/dash/* helpers adopt the pattern.

♻️ Proposed extraction

Create example-apps/dashnote/src/dash/sdkModule.ts:

// 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<SdkModule> | null = null;

export function loadSdkModule(): Promise<SdkModule> {
  if (!sdkModulePromise) {
    sdkModulePromise = import("@dashevo/evo-sdk").catch((err) => {
      sdkModulePromise = null;
      throw err;
    });
  }
  return sdkModulePromise;
}

Then in each consumer:

-// 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<SdkModule> | null = null;
-function loadSdkModule(): Promise<SdkModule> {
-  if (!sdkModulePromise) {
-    sdkModulePromise = import("@dashevo/evo-sdk").catch((err) => {
-      sdkModulePromise = null;
-      throw err;
-    });
-  }
-  return sdkModulePromise;
-}
+import { loadSdkModule } from "./sdkModule";

As per coding guidelines: "Files in src/dash/ must reference @dashevo/evo-sdk or shared core re-exports/SDK-shape types" — a shared sdkModule.ts neighbor in src/dash/ fits cleanly within that boundary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@example-apps/dashnote/src/dash/createNote.ts` around lines 9 - 22, Extract
the duplicated loader into a single shared module by creating a new
src/dash/sdkModule.ts that exports the SdkModule type, the cached
sdkModulePromise, and the loadSdkModule() function (which sets sdkModulePromise,
imports "@dashevo/evo-sdk", clears the cache on failure and rethrows); then
remove the local declarations of SdkModule, sdkModulePromise and loadSdkModule
from createNote.ts, updateNote.ts and contract.ts and replace them with imports
from './sdkModule' (e.g. import { loadSdkModule, SdkModule } from './sdkModule')
so all three consumers use the single shared loader and cache.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@example-apps/dashnote/src/dash/createNote.ts`:
- Around line 9-22: Extract the duplicated loader into a single shared module by
creating a new src/dash/sdkModule.ts that exports the SdkModule type, the cached
sdkModulePromise, and the loadSdkModule() function (which sets sdkModulePromise,
imports "@dashevo/evo-sdk", clears the cache on failure and rethrows); then
remove the local declarations of SdkModule, sdkModulePromise and loadSdkModule
from createNote.ts, updateNote.ts and contract.ts and replace them with imports
from './sdkModule' (e.g. import { loadSdkModule, SdkModule } from './sdkModule')
so all three consumers use the single shared loader and cache.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: df9f4fac-9766-4181-9c34-8a2f5e6c9e0b

📥 Commits

Reviewing files that changed from the base of the PR and between e251265 and 90cfd96.

📒 Files selected for processing (3)
  • example-apps/dashnote/src/dash/contract.ts
  • example-apps/dashnote/src/dash/createNote.ts
  • example-apps/dashnote/src/dash/updateNote.ts

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) <[email protected]>
@thephez thephez merged commit 26e631e into main May 6, 2026
3 checks passed
@thephez thephez deleted the feat/remove-static-import branch May 6, 2026 17:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant