Anki 24.11 - addon using Svelte failing with "Cannot require "svelte/internal""

My addon Language Tools is failing with the following error
JS error /_anki/js/editor.js:531 Uncaught Error: Cannot require "svelte/internal" at runtime.
Here’s the source code for the svelte code: anki-language-tools/web at main · Vocab-Apps/anki-language-tools · GitHub

The reference addon for selve editor integration doesn’t seem to have change in the last two years so I doubt a solution will be present there: GitHub - hgiesel/anki_new_format_pack

Just curious is my “Language Tools” addon the only one attempting an integration with the Anki editor using Svelte ? With HyperTTS, I’ve moved away from that and just use the regular editor buttons (using the python API) and it’s been much more stable. Maybe that’s the recommended way for addons ? I’m not judging in any way, just wondering what the recommended approach is for addon developers looking for more stability.

1 Like

Unfortunately Svelte add-ons are broken again after the migration to Svelte 5: Update to Svelte 5 by abdnh · Pull Request #3292 · ankitects/anki · GitHub

1 Like

Is there a way to fix my addon ?

1 Like

Some background:

The hack we were using to share the svelte code between Anki and add-ons broke in Svelte 5, and I think it’s probably not something we could fix on our end.

I did some experimenting with your code to try bundle your own copy of Svelte, but unfortunately could not get things into a working state - I suspect svelte is making assumptions about the components being created in the same bundle.

diff --git a/web/esbuild.config.js b/web/esbuild.config.js
index c3f1b3a..9979621 100644
--- a/web/esbuild.config.js
+++ b/web/esbuild.config.js
@@ -46,8 +46,8 @@ const options = {
     treeShaking: production,
     sourcemap: !production,
     pure: production ? ["console.log", "console.time", "console.timeEnd"] : [],
-    watch,
-    external: ["svelte", "anki"],
+    // watch,
+    external: ["anki"],
     plugins: [
         sveltePlugin({
             preprocess: sveltePreprocess({
diff --git a/web/package.json b/web/package.json
index 9a33bec..ef9af07 100644
--- a/web/package.json
+++ b/web/package.json
@@ -15,17 +15,17 @@
         "@typescript-eslint/eslint-plugin": "^5.10.1",
         "@typescript-eslint/parser": "^5.10.1",
         "cross-env": "^7.0.3",
-        "esbuild": "^0.13.7",
+        "esbuild": "^0.24",
         "esbuild-sass-plugin": "^2.2.1",
-        "esbuild-svelte": "^0.5.6",
+        "esbuild-svelte": "^0.9",
         "eslint": "^8.7.0",
         "eslint-plugin-compat": "^4.0.1",
         "prettier": "^2.4.1",
         "prettier-plugin-svelte": "^2.4.0",
         "sass": "^1.49.0",
-        "svelte": "^3.43.2",
-        "svelte-check": "^2.2.7",
-        "svelte-preprocess": "^4.9.8",
+        "svelte": "^5",
+        "svelte-check": "^4",
+        "svelte-preprocess": "^6",
         "typescript": "^4.4.4"
     },
     "browserslist": [

Things still crash on the line

    toolbar.toolbar.append({component: LanguageTools, id: "languagetools"});

which as far as I can tell, is when we try to include the component in DynamicallySlottable.svelte

As a workaround, you could perhaps create a HTML fragment that loads and mounts your svelte component, and then pass the HTML to editor_did_init_left_buttons or similar.

2 Likes

Events won’t work this way probably.

I’m also trying to fix the Exam Notifier add-on (A Deck Options add-on). Manually mounting the component using a hack like this partially work (changes to auxData are not recognized):

import { mount } from "svelte";

import DeckOptionsAddon from "./DeckOptionsAddon.svelte";

$deckOptions.then((deckOptions) => {
    deckOptions.addHtmlAddon("", () => {});

    setTimeout(() => {
        let target = null;
        for (const h1 of document.querySelectorAll("h1")) {
            if (h1.textContent === "Add-ons") {
                target = h1.parentElement;
                break;
            }
        }
        mount(DeckOptionsAddon, {
            target: target,
            props: { data: deckOptions.auxData() },
        });
    }, 100);
});
1 Like

I wonder if that may because we’re not making auxData reactive in Addons.svelte? Do you have this hack in a repo branch so I can have a little play with it?

The add-on is not mounted using Addons.svelte.

The issue was that I was passing the deckOptions.auxData() store to the add-on component instead of its value. Adding a get() call did the trick:

import { mount } from "svelte";
import { get } from "svelte/store";

import DeckOptionsAddon from "./DeckOptionsAddon.svelte";

$deckOptions.then((deckOptions) => {
    deckOptions.addHtmlAddon("", () => {});
    setTimeout(() => {
        let target = null;
        for (const h1 of document.querySelectorAll("h1")) {
            if (h1.textContent === "Add-ons") {
                target = h1.parentElement;
                break;
            }
        }
        mount(DeckOptionsAddon, {
            target: target,
            props: { data: get(deckOptions.auxData()) },
        });
    }, 100);
});

Repo: GitHub - AnKing-VIP/exam-notifier at svelte5

2 Likes

@abdo should other addons try this to fix the issue that they’re experiencing with svelte ?

1 Like

That made the add-on functional again, so yes, I suggest trying something similar for now. I only needed an ugly hack to get the mount target. In your editor add-on though, I imagine you can add a dummy button using editor.addButton() then mount your component there.

1 Like