Repository: ankitects/anki

Issue #1764: Improving dynamic TS package ergonomics
Created: 2022-04-01T05:07:02Z
Re #1626 (comment), I've been hunting for a way to make this a bit less cumbersome & more robust than manually declaring the exports. What if instead, we  export the module's interface in the same place as we register it, ensuring that the interface matches what we're exporting? Eg
diff --git a/ts/editor/NoteEditor.svelte b/ts/editor/NoteEditor.svelte
index 957b07616..51747d084 100644
--- a/ts/editor/NoteEditor.svelte
+++ b/ts/editor/NoteEditor.svelte
@@ -17,8 +17,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
     }
 
     import { registerPackage } from "../lib/runtime-require";
-    import contextProperty from "../sveltelib/context-property";
-    import lifecycleHooks from "../sveltelib/lifecycle-hooks";
+    import contextProperty, { ContextProperty } from "../sveltelib/context-property";
+    import lifecycleHooks, { LifecycleHooks } from "../sveltelib/lifecycle-hooks";
 
     const key = Symbol("noteEditor");
     const [context, setContextProperty] = contextProperty<NoteEditorAPI>(key);
@@ -26,11 +26,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
 
     export { context };
 
-    registerPackage("anki/NoteEditor", {
+    /** anki/NoteEditor */
+    export interface NoteEditorPackage {
+        context: ContextProperty<NoteEditorAPI>;
+        lifecycle: LifecycleHooks<NoteEditorAPI>;
+        instances: NoteEditorAPI[];
+    }
+    const noteEditorPackage = {
         context,
         lifecycle,
         instances,
-    });
+    } as NoteEditorPackage;
+    registerPackage("anki/NoteEditor", noteEditorPackage as any);
 </script>
 
 <script lang="ts">
Add-ons could then use require() with a cast to get a typed module:
import type { NoteEditorPackage } from "@anki/editor/NoteEditor.svelte";
const NoteEditor = require("anki/NoteEditor") as NoteEditorPackage;
I spent some time trying to make this work in the context of a 'declare module' so we could keep the standard TS-style import, but couldn't figure out a way to spread the interface into the module definition except by doing so manually:
declare module "anki/NoteEditor" {
    type Package = import("@anki/editor/NoteEditor.svelte").NoteEditorPackage;
    export const context: Package["context"];
    export const lifecycle: Package["lifecycle"];
    export const instances: Package["instances"];
}
At least we're not juggling types that way, but it's still extra work. Maybe there's some way I'm not aware of?
If not, perhaps one other option would be to define a .ts file that does the require/cast for this and other modules and exports the resulting symbols, so add-ons could just do something like the following?
import { NoteEditor } from "@anki/runtime"
One other thing I noticed while looking at hgiesel/anki_new_format_pack#11 is that we're currently exporting components as a global. Could we be stuffing that into a runtime module somewhere instead to make it more discoverable, and typed?

Duplicate issue #1733: Update demo/docs for editor changes
Created: 2022-03-23T10:15:35Z
Aside from making life easier for add-on authors, an updated demo will help us check the API is working as intended.
Related: https://forums.ankiweb.net/t/2-addons-conflicting-with-svelte-components-editortoolbar-toolbar-insertgroup/18019

Closing comment (2022-04-09T03:19:20Z): Further API tweaks/documentation are tracked in #1764
There are also some notes about typechecking on hgiesel/anki_new_format_pack#11