API WebAssembly

The nova-wasm module allows you to compile NovaType documents directly in the browser via WebAssembly.

Installation

Via npm

npm install @novatype/wasm

Via CDN

<script type="module">
  import { NovaCompiler } from 'https://unpkg.com/@novatype/wasm';
</script>

Basic Usage

import { NovaCompiler } from '@novatype/wasm';

// Initialize the compiler
const compiler = await NovaCompiler.init();

// Typst source code
const source = `
#set document(title: "My Document")
#set page(paper: "a4")

= Introduction

Hello from the browser!
`;

// Compile to SVG
const result = compiler.compile(source);

if (result.success) {
  // result.svg_pages contains an array of SVGs (one per page)
  document.getElementById('preview').innerHTML = result.svg_pages[0];
} else {
  console.error('Errors:', result.errors);
}

API Reference

NovaCompiler

NovaCompiler.init(options?)

Initializes a new compiler instance.

const compiler = await NovaCompiler.init({
  // Options
  fonts: ['./fonts/custom.ttf'],  // Additional fonts
  root: './documents/'           // Virtual root directory
});

compiler.compile(source, options?)

Compiles a document and returns the result.

Parameter Type Description
source string Typst source code
options.format string 'svg' or 'pdf' (default: 'svg')
options.page number Page to render (default: all)

Result

interface CompileResult {
  success: boolean;
  content: string;     // Content without frontmatter
  metadata?: object;   // Extracted metadata
  svg_pages: string[]; // Array of SVGs (one per page)
  errors: string[];    // Compilation errors
}

compiler.validate(source)

Validates a document's metadata.

const validation = await compiler.validate(source);

if (validation.valid) {
  console.log('Valid metadata:', validation.metadata);
} else {
  console.error('Errors:', validation.errors);
}

compiler.addFile(path, content)

Adds a file to the virtual file system.

// Add a BibTeX file
compiler.addFile('references.bib', `
@book{knuth1984,
  author = {Knuth, Donald E.},
  title = {The TeXbook},
  year = {1984}
}
`);

// Add an image (base64 encoded)
compiler.addFile('image.png', imageBase64, { encoding: 'base64' });

Complete Example

<!DOCTYPE html>
<html>
<head>
  <title>NovaType Editor</title>
  <style>
    .container { display: flex; height: 100vh; }
    .editor { flex: 1; }
    .preview { flex: 1; background: #f5f5f5; overflow: auto; }
    textarea { width: 100%; height: 100%; font-family: monospace; }
  </style>
</head>
<body>
  <div class="container">
    <div class="editor">
      <textarea id="source">= Hello World

Welcome to NovaType!

$ e^(i pi) + 1 = 0 $</textarea>
    </div>
    <div class="preview" id="preview"></div>
  </div>

  <script type="module">
    import { NovaCompiler } from '@novatype/wasm';

    const compiler = await NovaCompiler.init();
    const sourceEl = document.getElementById('source');
    const previewEl = document.getElementById('preview');

    let timeout;

    function compile() {
      const result = compiler.compile(sourceEl.value);
      if (result.success) {
        previewEl.innerHTML = result.svg_pages.join('');
      } else {
        previewEl.innerHTML = `<pre style="color:red">${result.errors.join('\n')}</pre>`;
      }
    }

    sourceEl.addEventListener('input', () => {
      clearTimeout(timeout);
      timeout = setTimeout(compile, 300);
    });

    compile();
  </script>
</body>
</html>

Export PDF

async function downloadPDF() {
  const result = await compiler.compile(source, { format: 'pdf' });

  if (result.success) {
    const blob = new Blob([result.pdf], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);

    const a = document.createElement('a');
    a.href = url;
    a.download = 'document.pdf';
    a.click();

    URL.revokeObjectURL(url);
  }
}

Error Handling

try {
  const result = await compiler.compile(source);

  if (!result.success) {
    for (const error of result.errors) {
      console.error(`Line ${error.line}: ${error.message}`);

      // Display in the interface
      highlightError(error.line, error.column);
    }
  }

  // Handle warnings
  for (const warning of result.warnings || []) {
    console.warn(`Warning: ${warning.message}`);
  }
} catch (e) {
  console.error('Fatal error:', e);
}

React Integration

import { useState, useEffect, useCallback } from 'react';
import { NovaCompiler } from '@novatype/wasm';

function useNovaCompiler() {
  const [compiler, setCompiler] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    NovaCompiler.init().then(c => {
      setCompiler(c);
      setLoading(false);
    });
  }, []);

  const compile = useCallback(async (source) => {
    if (!compiler) return null;
    return compiler.compile(source);
  }, [compiler]);

  return { compile, loading };
}

function NovaEditor() {
  const { compile, loading } = useNovaCompiler();
  const [source, setSource] = useState('= Hello\n\nWorld!');
  const [preview, setPreview] = useState('');

  useEffect(() => {
    const timer = setTimeout(async () => {
      const result = await compile(source);
      if (result?.success) {
        setPreview(result.svg);
      }
    }, 300);
    return () => clearTimeout(timer);
  }, [source, compile]);

  if (loading) return <div>Loading...</div>;

  return (
    <div className="editor">
      <textarea
        value={source}
        onChange={e => setSource(e.target.value)}
      />
      <div dangerouslySetInnerHTML={{ __html: preview }} />
    </div>
  );
}
Performance

The WASM module is approximately 37 MB (embedded fonts included). Use a 200-300ms debounce to avoid excessive recompilations while typing.