Optimize bundle size + chunked builds + JIT loading

* Replaced AJV with other JSON schema library
* Turndown and Vim mode (very large) now only loaded when used
* Chunked builds with ESBuild
This commit is contained in:
Zef Hemel
2026-03-11 10:04:13 +01:00
parent 81408ed5cf
commit a080ee3b15
13 changed files with 352 additions and 400 deletions
+42 -32
View File
@@ -2,12 +2,6 @@ import { syntaxTree } from "@codemirror/language";
import { EditorView, ViewPlugin, type ViewUpdate } from "@codemirror/view";
import type { Client } from "../client.ts";
// We use turndown to convert HTML to Markdown
import TurndownService from "turndown";
// With tables and task notation as well
// @ts-expect-error - No type definitions available for this package
import { tables, taskListItems } from "@joplin/turndown-plugin-gfm";
import { lezerToParseTree } from "../markdown_parser/parse_tree.ts";
import {
addParentPointers,
@@ -21,17 +15,30 @@ import { localDateString } from "@silverbulletmd/silverbullet/lib/dates";
import type { UploadFile } from "@silverbulletmd/silverbullet/type/client";
import { isValidName, isValidPath } from "@silverbulletmd/silverbullet/lib/ref";
const turndownService = new TurndownService({
hr: "---",
codeBlockStyle: "fenced",
headingStyle: "atx",
emDelimiter: "*",
bulletListMarker: "*", // Duh!
strongDelimiter: "**",
linkStyle: "inlined",
});
turndownService.use(taskListItems);
turndownService.use(tables);
// Dynamically load the turndown service (only used for clipboard pasete)
let turndownService: any = null;
async function getTurndownService() {
if (!turndownService) {
const [{ default: TurndownService }, { tables, taskListItems }] =
await Promise.all([
import("turndown"),
// @ts-expect-error - No type definitions available for this package
import("@joplin/turndown-plugin-gfm"),
]);
turndownService = new TurndownService({
hr: "---",
codeBlockStyle: "fenced",
headingStyle: "atx",
emDelimiter: "*",
bulletListMarker: "*", // Duh!
strongDelimiter: "**",
linkStyle: "inlined",
});
turndownService.use(taskListItems);
turndownService.use(tables);
}
return turndownService;
}
function striptHtmlComments(s: string): string {
return s.replace(/<!--[\s\S]*?-->/g, "");
@@ -137,7 +144,7 @@ export function documentExtension(editor: Client) {
// Only do rich text paste if shift is NOT down
if (richText && !shiftDown) {
// Are we in a fencede code block?
// Are we in a fenced code block?
const editorText = editor.editorView.state.sliceDoc();
const tree = lezerToParseTree(
editorText,
@@ -161,23 +168,26 @@ export function documentExtension(editor: Client) {
}
}
const markdown = striptHtmlComments(
turndownService.turndown(richText),
).trim();
// Prevent default immediately, then do async turndown conversion
event.preventDefault();
const view = editor.editorView;
const selection = view.state.selection.main;
view.dispatch({
changes: [
{
from: selection.from,
to: selection.to,
insert: markdown,
safeRun(async () => {
const td = await getTurndownService();
const markdown = striptHtmlComments(td.turndown(richText)).trim();
view.dispatch({
changes: [
{
from: selection.from,
to: selection.to,
insert: markdown,
},
],
selection: {
anchor: selection.from + markdown.length,
},
],
selection: {
anchor: selection.from + markdown.length,
},
scrollIntoView: true,
scrollIntoView: true,
});
});
return true;
}
+23 -10
View File
@@ -34,9 +34,9 @@ import {
ViewPlugin,
type ViewUpdate,
} from "@codemirror/view";
import { vim } from "@replit/codemirror-vim";
import { markdown } from "@codemirror/lang-markdown";
import type { Client } from "../client.ts";
import { loadVim } from "../vim_loader.ts";
import { inlineContentPlugin } from "./inline_content.ts";
import { cleanModePlugins } from "./clean.ts";
import { lineWrapper } from "./line_wrapper.ts";
@@ -63,6 +63,7 @@ export function createEditorState(
// Ugly: keep the commandKeyHandler compartment in the client, to be replaced
// later once more commands are loaded
client.commandKeyHandlerCompartment = new Compartment();
client.vimCompartment = new Compartment();
const commandKeyBindings = client.commandKeyHandlerCompartment.of(
createCommandKeyBindings(client),
);
@@ -76,6 +77,13 @@ export function createEditorState(
client.undoHistoryCompartment = new Compartment();
const undoHistory = client.undoHistoryCompartment.of([history()]);
const vimMode = client.ui.viewState.uiOptions.vimMode;
// If vim mode is requested, load it async and reconfigure the compartment
if (vimMode) {
void enableVimMode(client);
}
return EditorState.create({
doc: text,
extensions: [
@@ -93,15 +101,8 @@ export function createEditorState(
// bindings wont trigger if they have the same keys.
commandKeyBindings,
// Enable vim mode, or not
[
...(client.ui.viewState.uiOptions.vimMode
? [
vim({ status: true }),
EditorState.allowMultipleSelections.of(true),
]
: []),
],
// Vim mode compartment — starts empty, loaded async if needed
client.vimCompartment.of([]),
[
...(readOnly ||
client.ui.viewState.uiOptions.forcedROMode ||
@@ -425,6 +426,18 @@ export function createRegularKeyBindings(client: Client): Extension {
}
}
async function enableVimMode(client: Client) {
const { vim } = await loadVim();
if (client.editorView && client.vimCompartment) {
client.editorView.dispatch({
effects: client.vimCompartment.reconfigure([
vim({ status: true }),
EditorState.allowMultipleSelections.of(true),
]),
});
}
}
/**
* Checks if the current platform is Mac-like (Mac, iPhone, iPod, iPad).
* @returns A boolean indicating if the platform is Mac-like.