How can I use a component based approach for my templates?

Problem

My Anki template became a bit unwieldy. I basically have five components, that are all having their own html and css. Some have their own javascript as well. I was hoping to make it easier maintainable and looked into vue.js and svelte.

They are great to create components but the generated output is js. For Anki, I would need the output to be plain html with <script> tags at the bottom and a separate css file for the styling. Also, I couldn’t get some js related things working. Checkout the “sources” component below for an example.

Svelte Examples

I already tried svelte and vue. But due to their output, I do not think they are useful for my goal here. I’ll share a sample of my svelte code here, though, in case it makes things easier to understand:

App.svelte (contains all components)
<script>
	import Question from "./components/Question.svelte";
	import Divider from "./components/Divider.svelte";
	import Answer from "./components/Answer.svelte";
	import Remarks from "./components/Remarks.svelte";
	import Sources from "./components/Sources.svelte";
</script>

<div id="all" lang="de">
	<main id="flashcard">
		<Question />
		<Divider />
		<Answer />
	</main>

	<Remarks />
	<Sources />
</div>

<style>
	#all {
		--max-character-limit-per-line: 65ch; /* Text should not be longer than 65 characters to increase readability.*/

		max-width: var(--max-character-limit-per-line);
		margin: 0 auto;
	}

	#flashcard {
		background-color: var(--global-background-color);
		word-wrap: break-word;
		min-width: 80%;
		margin-top: var(--global-top-margin);
		margin-bottom: 0;
		margin-right: auto;
		margin-left: auto;
		padding: var(--global-padding);
		box-shadow: var(--global-box-shadow);
		border: var(--global-border);
		border-radius: var(--global-border-radius);
	}
</style>
Answer.svelte (contains the parts of code for my answer side)
<div class="hidden_on_front" , id="back_answer">
	{`{{Back_Text}}`}
</div>

<div class="hidden_on_front" , id="back_code">
	{`{{Back_Code_Block}}`}
</div>

<div class="hidden_on_front" , id="back_picture">
	{`
		{{#Back_Image}}
			{{Back_Image}}
		{{/Back_Image}}
	`}
</div>
Sources.svelte (contains the sources of my card. Javascript is used to format the sources appropriatly)
<script>
	import { onMount } from "svelte";

	/**********************************************************************/
	/** [JS] BREAK SOURCES INTO SEPARATE DIVS                            **/
	/**********************************************************************/

	var source_string = `{{Back_Sources}}`;
	var individual_source_string = [];

	onMount(() => {
		var source_container = document.getElementById("back_sources");

		if (source_container) {
			individual_source_string = source_string.split("<br>");

			individual_source_string.forEach((source, index) => {
				const div = document.createElement("div");

				div.innerHTML = "•&nbsp;" + source;
				div.classList.add("back_sources_elements");
				source_container.appendChild(div);
			});
		}
	});
</script>

<footer>
	<section class="hidden_on_front" , id="back_sources">
		<h1>Quellen:</h1>
		<!-- generated by JS -->
	</section>
</footer>

I also tried the more ideomatic {#each ...} • Svelte Docs, but the result is always the same:

  • The code works fine in my browser (chrome and librewolf) via local server
  • In Anki, the js won’t run / update the card
  • I have no html with <script> tags that I could include in ankis template window

Question

How can I use a component based approach for my card templates? It’s totally fine if I have to compile it first (like with e.g. svelte) via my IDE (I use vs codium).

I’m essentially looking for something that makes maintainace much easier, while still being performant on desktop and mobile anki alike.

Thank you for your time and ideas!

Possibly related

2 Likes

I got a (kind of) working Svelte demo in Anki following this:

  1. Create a scaffold Svelte project using npm create vite@latest as explained in the docs.
  2. In the new project, modify src/main.ts so it points to Anki’s #qa element:
import { mount } from 'svelte'
import './app.css'
import App from './App.svelte'

const app = mount(App, {
  target: document.getElementById('qa')!,
})

export default app
  1. Modify vite.config.ts:
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'

// https://vite.dev/config/
export default defineConfig({
  plugins: [svelte()],
  build: {
    minify: false,
    lib: {
      name: "MyLib",
      entry: "src/main.ts",
      fileName: (format, entry) => "lib.js",
      cssFileName: "lib",
      formats: ["umd"]
    }
  }
})

  1. Build the project using npm run build.
  2. Copy JS/CSS files and any assets to your media folder.
  3. Add references to the JS/CSS files to your front/back template and styling section:
<script src="_lib.js"></script>
@import url("_lib.css");

2 Likes

This unfortunately doesn’t work properly, the fields won’t be properly replaced:

(I am also wondering about performance, considering the generated lib.js is 2780 lines long; surely static html is way more performant)


As a second idea:
I could perhaps also write a Makefile or bash script that cuts and concatenates those different js, css and html sections, then paste that into anki.

You can try using a creating specific element other than qa in your template and using that, but I’ve not tested.

Yeah, it’s not very lightweight if you don’t need Svelte features. I’d recommend using a templating language instead, such as https://ejs.co/
The AnKing note types are built using that: GitHub - AnKing-VIP/AnKing-Note-Types

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.