Best Practices
15. Best Practices & Performance
Do
- Resolve services in
main.ts, pass as props. Never callgetService()inside Svelte components. - Create exactly one
ExtensionContextper iframe. Creating more than one attaches duplicate event listeners. - Always unregister actions in
onDestroy. Leftover actions pollute the ⌘K drawer for other views. - Set
viewPathon search results. Theactionclosure is ignored for installed extensions;viewPathis what actually controls navigation. - Bundle everything. Svelte's runtime, component libraries, utility packages — all of it must be in
dist/. - Use
logger.debug()aggressively during development. Strip or convert tologger.info()before publishing. - Use
var(--bg-primary)and friends for all background and text colors to support light/dark theming. - Validate before publishing.
asyar validatecatches manifest errors before they reach reviewers.
Don't
- Don't use
window.fetch()orXMLHttpRequest. The iframe CSP blocks all external requests. UseNetworkService. - Don't use
<script src="https://...">CDN tags. Blocked by CSP. Bundle all dependencies locally. - Don't request permissions you don't use. Reviewers will reject extensions with unnecessary permissions.
- Don't create a second
ExtensionContext. One per iframe — period. - Don't call
getService()inside reactive blocks or component constructors. Always resolve inmain.ts. - Don't rely on the
actionfunction for result navigation (Tier 2). UseviewPathinstead.
Performance tips
Small bundles: Avoid heavy dependencies. Prefer lightweight libraries. Use vite-bundle-visualizer to inspect what is contributing to bundle size.
In-view search: Use SearchEngine from the SDK for filtering lists within your view. It handles subsequence matching and typo tolerance out of the box — no need to implement your own fuzzy search or pull in a separate library. It runs synchronously and is fast enough for keystroke-by-keystroke filtering without debounce.
Background iframes: Every searchable: true extension always has a background iframe running. If your search() method does expensive work, cache aggressively and debounce internally.
Lazy loading views: For multi-view extensions, main.ts is loaded once. Only mount the component for the current ?view= parameter. Avoid importing all views at the top of main.ts if they are large:
// main.ts — conditional import for large views
const viewName = new URLSearchParams(window.location.search).get('view');
if (viewName === 'LargeView') {
const { default: LargeView } = await import('./LargeView.svelte');
mount(LargeView, { target: document.getElementById('app')!, props: { ... } });
} else {
const { default: DefaultView } = await import('./DefaultView.svelte');
mount(DefaultView, { target: document.getElementById('app')!, props: { ... } });
}
StatusBar updates: The updateItem() method is safe to call on every timer tick. Updates are debounced internally.