refactor(load-time): lazy load heavy dependencies and optimize startup performance

- Convert eager imports of multi-megabyte `pdf-lib` (via `ExportConfigModal`) and `dvi2html` (via `TikzRenderer`) to dynamic imports.
- Defer `TikzRenderer` instantiation and loading to `onLayoutReady`.
- Remove blocking synchronous stylesheet checks and reads of `tikzjax.css` from `TikzRenderer.onLoad()`, relying on on-demand lazy loading instead.
- Implement `LazyMathSettingTab` proxy to defer evaluation of the settings UI modals and translations.
- Defer `setupDOMObserver` registration to after layout is ready to avoid MutationObserver overhead during startup layout rendering.
- Dynamically import `processZoteroCleanup` and `TikzEditorModal` inside their respective commands/callbacks.
This commit is contained in:
JK 2026-06-14 11:10:15 +02:00
parent 1642b5cd76
commit 53def164d5
4 changed files with 80 additions and 50 deletions

View file

@ -2,7 +2,6 @@ import { Extension, Prec, EditorState } from '@codemirror/state';
import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
import { showNotice } from 'utils/obsidian';
import LatexReferencer from '../../main';
import { TikzEditorModal } from '../tikz-editor/tikz-editor-modal';
interface CleanableDiv extends HTMLDivElement {
_cleanup?: () => void;
@ -170,21 +169,24 @@ class TikzLivePreviewOverlay {
this.plugin.isTikzEditorOpen = true;
this.destroy();
new TikzEditorModal(
this.plugin.app,
this.plugin,
blockRange.source,
(newSource: string) => {
this.view.dispatch({
changes: {
from: blockRange.from,
to: blockRange.to,
insert: newSource
}
});
showNotice('TikZ block updated.');
}
).open();
void (async () => {
const { TikzEditorModal } = await import('../tikz-editor/tikz-editor-modal');
new TikzEditorModal(
this.plugin.app,
this.plugin,
blockRange.source,
(newSource: string) => {
this.view.dispatch({
changes: {
from: blockRange.from,
to: blockRange.to,
insert: newSource
}
});
showNotice('TikZ block updated.');
}
).open();
})();
} else {
showNotice('Please place your cursor inside a tikz block to edit.');
}

View file

@ -5,7 +5,7 @@ import { TikzJaxLoader } from './tikzjax/loader';
export class TikzRenderer {
private loader: TikzJaxLoader;
private tikzjaxCss: string = '';
private isLoaded: boolean = false;
public isLoaded: boolean = false;
private renderCache = new Map<string, string>();
@ -23,27 +23,6 @@ export class TikzRenderer {
}
try {
const adapter = this.plugin.app.vault.adapter;
const pluginDir = this.plugin.manifest.dir;
if (!pluginDir) {
throw new Error('Plugin manifest directory is not defined.');
}
// Load tikzjax.css (containing base64 math fonts)
const cssPath = `${pluginDir}/tikzjax-assets/tikzjax.css`;
if (await adapter.exists(cssPath)) {
this.tikzjaxCss = await adapter.read(cssPath);
} else {
// Try downloading it via loader
const cssData = await this.loader.loadAssetString('tikzjax.css');
if (cssData) {
this.tikzjaxCss = cssData;
} else {
console.warn('Latex Referencer: tikzjax.css not found and download failed.');
}
}
// Inject styling for main window and pop-outs
this.plugin.app.workspace.onLayoutReady(() => {
this.injectCssAllWindows();

View file

@ -1,6 +1,8 @@
import {
App,
MarkdownView,
Plugin,
PluginSettingTab,
TFile,
parseLinktext,
Menu,
@ -13,7 +15,6 @@ import type { Extension } from '@codemirror/state';
import { around } from 'monkey-around';
import { PluginSettings, DEFAULT_SETTINGS } from './settings/settings';
import { MathSettingTab } from './settings/tab';
import { Provider } from './core/linker/provider-link-render';
import { LatexLinkProvider } from './core/linker/latex-provider';
@ -31,14 +32,11 @@ import { EquationBlock } from 'types';
// ADDED: Import our new internal patcher function
import { patchSuggesterWithQuickPreview } from 'ui/quick-preview/patcher';
import { processActiveNoteEquations } from './core/equations/numbering';
import { ExportConfigModal } from './ui/export-pdf/modal';
import { checkAndFixCalloutMath } from 'utils/fixer';
import { showNotice } from 'utils/obsidian';
import { traverseFolder } from 'features/export-pdf/utils';
import { SnippetManager } from 'features/snippets/manager';
import { processZoteroCleanup } from 'features/zotero-cleanup';
import { CustomNoteManager } from 'features/custom-notes/manager';
import { TikzRenderer } from './features/tikz/renderer';
import type { TikzRenderer } from './features/tikz/renderer';
import {
createRowLayoutProcessor,
createLivePreviewRowLayoutPlugin
@ -85,10 +83,13 @@ export default class LatexReferencer extends Plugin {
this.customNoteManager.onLoad();
// TikZJax Rendering
this.tikzRenderer = new TikzRenderer(this);
await this.tikzRenderer.onLoad();
this.app.workspace.onLayoutReady(async () => {
if (this.settings.enableTikzjax) {
await this.initTikzRenderer();
}
});
this.addSettingTab(new MathSettingTab(this.app, this));
this.addSettingTab(new LazyMathSettingTab(this.app, this));
// Commands
this.registerZoteroCommand();
@ -134,7 +135,10 @@ export default class LatexReferencer extends Plugin {
if (checking) {
return true;
}
new ExportConfigModal(this, file).open();
void (async () => {
const { ExportConfigModal } = await import('./ui/export-pdf/modal');
new ExportConfigModal(this, file).open();
})();
return true;
}
@ -158,6 +162,7 @@ export default class LatexReferencer extends Plugin {
.setIcon('download')
.setSection('action')
.onClick(async () => {
const { ExportConfigModal } = await import('./ui/export-pdf/modal');
new ExportConfigModal(this, file).open();
});
});
@ -183,6 +188,7 @@ export default class LatexReferencer extends Plugin {
.setTitle('Export each file to pdf')
.setIcon('lucide-file-stack')
.onClick(async () => {
const { ExportConfigModal } = await import('./ui/export-pdf/modal');
new ExportConfigModal(this, file, true).open();
})
);
@ -227,11 +233,15 @@ export default class LatexReferencer extends Plugin {
});
this.patchPagePreview();
this.register(setupDOMObserver(this));
this.app.workspace.onLayoutReady(() => {
this.register(setupDOMObserver(this));
});
}
onunload() {
this.tikzRenderer.onUnload();
if (this.tikzRenderer) {
this.tikzRenderer.onUnload();
}
}
async loadSettings() {
@ -362,7 +372,10 @@ export default class LatexReferencer extends Plugin {
name: 'Remove duplicate Zotero annotations',
editorCallback: (editor: Editor, ctx: MarkdownView | MarkdownFileInfo) => {
if (ctx instanceof MarkdownView) {
void processZoteroCleanup(this, ctx);
void (async () => {
const { processZoteroCleanup } = await import('features/zotero-cleanup');
await processZoteroCleanup(this, ctx);
})();
}
}
});
@ -373,6 +386,7 @@ export default class LatexReferencer extends Plugin {
const tocPath = root.path === '/' || root.path === '.' ? '_TOC_.md' : `${root.path}/_TOC_.md`;
let content = `---\ntoc: true\ntitle: ${root.name || 'Root'}\n---\n`;
if (root instanceof TFolder) {
const { traverseFolder } = await import('features/export-pdf/utils');
const files = traverseFolder(root);
for (const file of files) {
if (file.name === '_TOC_.md') {
@ -388,4 +402,36 @@ export default class LatexReferencer extends Plugin {
await this.app.vault.create(tocPath, content);
}
}
async initTikzRenderer() {
if (!this.tikzRenderer) {
const { TikzRenderer } = await import('./features/tikz/renderer');
this.tikzRenderer = new TikzRenderer(this);
await this.tikzRenderer.onLoad();
}
}
}
interface Displayable {
display(): void;
}
class LazyMathSettingTab extends PluginSettingTab {
constructor(
app: App,
public plugin: LatexReferencer
) {
super(app, plugin);
}
display(): void {
import('./settings/tab')
.then(({ MathSettingTab }) => {
const tab = new MathSettingTab(this.app, this.plugin);
tab.containerEl = this.containerEl;
(tab as unknown as Displayable).display();
})
.catch(err => {
console.error('Failed to load settings tab:', err);
});
}
}

View file

@ -581,6 +581,9 @@ export class MathSettingTab extends PluginSettingTab {
toggle.setValue(this.plugin.settings.enableTikzjax).onChange(async value => {
this.plugin.settings.enableTikzjax = value;
await this.plugin.saveSettings();
if (value) {
await this.plugin.initTikzRenderer();
}
})
);