Skip to content

Worker Pool ​

Learn how to use the MarkdownWorkerPoll component for better performance with multiple markdown documents or frequent updates.

Why Use Worker Pool? ​

Worker pools provide several benefits:

  • Non-blocking parsing: Markdown parsing runs in Web Workers, keeping the main thread responsive
  • Parallel processing: Multiple workers can parse different documents simultaneously
  • Better performance: Ideal for applications with frequent markdown updates or multiple documents

Basic Worker Pool Usage ​

Use MarkdownWorkerPoll to wrap multiple MarkdownRenderer components:

vue
<script setup lang="ts">
import { ref } from 'vue';
import { MarkdownRenderer, MarkdownWorkerPoll } from '@markdown-next/vue';
import type { ParserOptions } from '@markdown-next/parser';

const doc1 = ref('# Document 1\n\nFirst document content');
const doc2 = ref('# Document 2\n\nSecond document content');
const doc3 = ref('# Document 3\n\nThird document content');

const parserOptions: ParserOptions = {
  extendedGrammar: ['gfm', 'mathjax'],
  supportsLaTeX: true,
};
</script>

<template>
  <MarkdownWorkerPoll :worker-count="2" :parserOptions="parserOptions">
    <div class="documents">
      <div class="document">
        <h2>Document 1</h2>
        <MarkdownRenderer :markdown="doc1" :dynamic="true" />
      </div>

      <div class="document">
        <h2>Document 2</h2>
        <MarkdownRenderer :markdown="doc2" :dynamic="true" />
      </div>

      <div class="document">
        <h2>Document 3</h2>
        <MarkdownRenderer :markdown="doc3" :dynamic="true" />
      </div>
    </div>
  </MarkdownWorkerPoll>
</template>

<style scoped>
.documents {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1.5rem;
}

.document {
  padding: 1rem;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  background: white;
}
</style>

Live Editor with Worker Pool ​

Create a markdown editor that doesn't block the UI:

vue
<script setup lang="ts">
import { ref } from 'vue';
import { MarkdownRenderer, MarkdownWorkerPoll } from '@markdown-next/vue';

const markdown = ref(`
# Live Editor Example

This editor uses a worker pool, so parsing happens in the background without blocking the UI.

Try typing something complex:

\`\`\`javascript
// Type a lot of code
for (let i = 0; i < 1000; i++) {
  console.log(i);
}
\`\`\`

$$
\\sum_{i=1}^{100} i^2 = \\frac{100 \\cdot 101 \\cdot 201}{6}
$$
`);

function handleInput(e: Event) {
  markdown.value = (e.target as HTMLTextAreaElement).value;
}

const parserOptions = {
  supportsLaTeX: true,
  extendedGrammar: ['gfm', 'mathjax'],
};
</script>

<template>
  <div class="editor-layout">
    <MarkdownWorkerPoll :worker-count="1" :parserOptions="parserOptions">
      <div class="editor-panel">
        <h3>Editor (Type here)</h3>
        <textarea
          :value="markdown"
          @input="handleInput"
          class="editor"
          placeholder="Start typing markdown..."
        />
      </div>

      <div class="preview-panel">
        <h3>Live Preview (Non-blocking)</h3>
        <MarkdownRenderer :markdown="markdown" :dynamic="true" :debounceMs="200" class="preview" />
      </div>
    </MarkdownWorkerPoll>
  </div>
</template>

<style scoped>
.editor-layout {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2rem;
  height: 700px;
  padding: 1rem;
}

.editor-panel,
.preview-panel {
  display: flex;
  flex-direction: column;
  border: 1px solid #ddd;
  border-radius: 12px;
  padding: 1.5rem;
  background: white;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  overflow: hidden;
}

h3 {
  margin: 0 0 1rem 0;
  color: #2c3e50;
  font-size: 1.1rem;
}

.editor {
  flex: 1;
  width: 100%;
  padding: 1rem;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  font-family: 'Monaco', 'Courier New', monospace;
  font-size: 14px;
  line-height: 1.6;
  resize: none;
  background: #fafafa;
}

.editor:focus {
  outline: 2px solid #e67e22;
  border-color: #e67e22;
  background: white;
}

.preview {
  flex: 1;
  overflow-y: auto;
  padding: 1rem;
  background: #fafafa;
  border-radius: 8px;
}

@media (max-width: 768px) {
  .editor-layout {
    grid-template-columns: 1fr;
    height: auto;
  }

  .editor-panel,
  .preview-panel {
    height: 400px;
  }
}
</style>

Multiple Documents Editor ​

Manage multiple markdown documents with shared worker pool:

vue
<script setup lang="ts">
import { ref, computed } from 'vue';
import { MarkdownRenderer, MarkdownWorkerPoll } from '@markdown-next/vue';

interface Document {
  id: number;
  title: string;
  content: string;
}

const documents = ref<Document[]>([
  { id: 1, title: 'README', content: '# README\n\nProject documentation' },
  { id: 2, title: 'CHANGELOG', content: '# CHANGELOG\n\n## v1.0.0\n- Initial release' },
  { id: 3, title: 'TODO', content: '# TODO\n\n- [ ] Task 1\n- [ ] Task 2' },
]);

const activeDocId = ref(1);

const activeDoc = computed(() => documents.value.find((d) => d.id === activeDocId.value));

function updateContent(content: string) {
  const doc = documents.value.find((d) => d.id === activeDocId.value);
  if (doc) {
    doc.content = content;
  }
}

function addDocument() {
  const newId = Math.max(...documents.value.map((d) => d.id)) + 1;
  documents.value.push({
    id: newId,
    title: `Document ${newId}`,
    content: `# Document ${newId}\n\nNew document content`,
  });
  activeDocId.value = newId;
}

function removeDocument(id: number) {
  if (documents.value.length <= 1) return;
  documents.value = documents.value.filter((d) => d.id !== id);
  if (activeDocId.value === id) {
    activeDocId.value = documents.value[0].id;
  }
}

const parserOptions = {
  extendedGrammar: ['gfm'],
};
</script>

<template>
  <div class="app">
    <MarkdownWorkerPoll :worker-count="2" :parserOptions="parserOptions">
      <div class="tabs">
        <button
          v-for="doc in documents"
          :key="doc.id"
          @click="activeDocId = doc.id"
          :class="['tab', { active: doc.id === activeDocId }]"
        >
          {{ doc.title }}
          <span v-if="documents.length > 1" @click.stop="removeDocument(doc.id)" class="close">
            ×
          </span>
        </button>
        <button @click="addDocument" class="tab add-tab">+</button>
      </div>

      <div v-if="activeDoc" class="content">
        <div class="editor-section">
          <textarea
            :value="activeDoc.content"
            @input="updateContent(($event.target as HTMLTextAreaElement).value)"
            class="editor"
          />
        </div>

        <div class="preview-section">
          <MarkdownRenderer
            :markdown="activeDoc.content"
            :dynamic="true"
            :debounceMs="300"
            class="preview"
          />
        </div>
      </div>
    </MarkdownWorkerPoll>
  </div>
</template>

<style scoped>
.app {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: #f5f5f5;
}

.tabs {
  display: flex;
  gap: 0.5rem;
  padding: 1rem;
  background: white;
  border-bottom: 2px solid #e0e0e0;
  overflow-x: auto;
}

.tab {
  padding: 0.5rem 1rem;
  border: 1px solid #ddd;
  border-radius: 6px 6px 0 0;
  background: #f5f5f5;
  color: #666;
  cursor: pointer;
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  transition: all 0.2s;
}

.tab:hover {
  background: #e8e8e8;
}

.tab.active {
  background: white;
  color: #e67e22;
  border-bottom-color: white;
  font-weight: bold;
}

.add-tab {
  background: #e67e22;
  color: white;
  border-color: #e67e22;
}

.add-tab:hover {
  background: #d67118;
}

.close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.1);
  font-size: 18px;
  line-height: 1;
}

.close:hover {
  background: rgba(231, 76, 60, 0.8);
  color: white;
}

.content {
  flex: 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  padding: 1rem;
  overflow: hidden;
}

.editor-section,
.preview-section {
  background: white;
  border-radius: 8px;
  padding: 1rem;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.editor {
  flex: 1;
  width: 100%;
  padding: 1rem;
  border: 1px solid #e0e0e0;
  border-radius: 6px;
  font-family: 'Monaco', monospace;
  font-size: 14px;
  line-height: 1.6;
  resize: none;
}

.editor:focus {
  outline: 2px solid #e67e22;
  border-color: #e67e22;
}

.preview {
  flex: 1;
  overflow-y: auto;
  padding: 1rem;
}

@media (max-width: 768px) {
  .content {
    grid-template-columns: 1fr;
  }
}
</style>

Performance Monitoring ​

Monitor worker pool performance:

vue
<script setup lang="ts">
import { ref, onUnmounted } from 'vue';
import { MarkdownRenderer, MarkdownWorkerPoll } from '@markdown-next/vue';
import { MarkdownWorkerPool } from '@markdown-next/parser';

const markdown = ref('# Performance Test\n\nType to see worker stats...');
const pool = ref<MarkdownWorkerPool | null>(null);
const stats = ref({
  activeWorkers: 0,
  idleWorkers: 0,
  queuedTasks: 0,
  completedTasks: 0,
});

// Update stats periodically
const statsInterval = setInterval(() => {
  if (pool.value) {
    stats.value = pool.value.getStats();
  }
}, 100);

onUnmounted(() => {
  clearInterval(statsInterval);
  if (pool.value) {
    pool.value.terminate();
  }
});

const parserOptions = {
  extendedGrammar: ['gfm'],
};
</script>

<template>
  <div class="performance-demo">
    <div class="stats-panel">
      <h3>Worker Pool Statistics</h3>
      <div class="stats-grid">
        <div class="stat">
          <span class="stat-label">Active Workers</span>
          <span class="stat-value">{{ stats.activeWorkers }}</span>
        </div>
        <div class="stat">
          <span class="stat-label">Idle Workers</span>
          <span class="stat-value">{{ stats.idleWorkers }}</span>
        </div>
        <div class="stat">
          <span class="stat-label">Queued Tasks</span>
          <span class="stat-value">{{ stats.queuedTasks }}</span>
        </div>
        <div class="stat">
          <span class="stat-label">Completed</span>
          <span class="stat-value">{{ stats.completedTasks }}</span>
        </div>
      </div>
    </div>

    <MarkdownWorkerPoll :worker-count="2" :parserOptions="parserOptions" @ready="pool = $event">
      <div class="editor-area">
        <textarea
          v-model="markdown"
          class="editor"
          placeholder="Type markdown to see worker activity..."
        />
      </div>

      <div class="preview-area">
        <MarkdownRenderer :markdown="markdown" :dynamic="true" :debounceMs="100" />
      </div>
    </MarkdownWorkerPoll>
  </div>
</template>

<style scoped>
.performance-demo {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  height: 100vh;
  padding: 1rem;
}

.stats-panel {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 1.5rem;
  border-radius: 12px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.stats-panel h3 {
  margin: 0 0 1rem 0;
  font-size: 1.2rem;
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1rem;
}

.stat {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1rem;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  backdrop-filter: blur(10px);
}

.stat-label {
  font-size: 0.85rem;
  opacity: 0.9;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.stat-value {
  font-size: 2rem;
  font-weight: bold;
}

.editor-area,
.preview-area {
  flex: 1;
  background: white;
  border-radius: 8px;
  padding: 1rem;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  overflow: auto;
}

.editor {
  width: 100%;
  height: 100%;
  padding: 1rem;
  border: 1px solid #e0e0e0;
  border-radius: 6px;
  font-family: monospace;
  font-size: 14px;
  line-height: 1.6;
  resize: none;
}

.editor:focus {
  outline: 2px solid #667eea;
  border-color: #667eea;
}
</style>

Best Practices ​

Worker Count ​

Choose the right number of workers based on your use case:

ts
// Single document, frequent updates
const workerCount = 1;

// Multiple documents, moderate updates
const workerCount = 2;

// Many documents or heavy parsing
const workerCount = navigator.hardwareConcurrency || 4;

Memory Management ​

Always clean up worker pools when components unmount:

ts
import { onUnmounted } from 'vue';
import { MarkdownWorkerPool } from '@markdown-next/parser';

const pool = new MarkdownWorkerPool({ workerCount: 2 });

onUnmounted(() => {
  pool.terminate(); // Important!
});

Debouncing ​

Use appropriate debounce times for dynamic content:

vue
<!-- Fast typing -->
<MarkdownRenderer :dynamic="true" :debounceMs="300" />

<!-- Slower updates -->
<MarkdownRenderer :dynamic="true" :debounceMs="500" />

Released under the MIT License.