npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

lexical-pdf-wasm

v0.1.0

Published

Generate PDFs from [Lexical](https://lexical.dev) editor state entirely in the browser. Powered by a Rust PDF engine compiled to WebAssembly.

Readme

lexical-pdf-wasm

Generate PDFs from Lexical editor state entirely in the browser. Powered by a Rust PDF engine compiled to WebAssembly.

Install

npm install lexical-pdf-wasm

ESM-only. Works with any bundler that supports import.meta.url (Vite, Webpack 5, Rollup, esbuild, etc.).

Quick start

import { generatePdf } from "lexical-pdf-wasm";

const pdfBytes = await generatePdf(editor.getEditorState().toJSON());

That's it. WASM loads automatically on first call.

Download the PDF

import { generatePdf } from "lexical-pdf-wasm";

async function downloadPdf(editor) {
  const pdfBytes = await generatePdf(editor.getEditorState().toJSON());
  const blob = new Blob([pdfBytes], { 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);
}

Open in a new tab

const pdfBytes = await generatePdf(editor.getEditorState().toJSON());
const blob = new Blob([pdfBytes], { type: "application/pdf" });
window.open(URL.createObjectURL(blob));

Configuration

All options are optional.

const pdfBytes = await generatePdf(doc, {
  title: "Quarterly Report",
  author: "Jane Doe",
  page: {
    size: "A4",                // "A4" | "Letter" | "Legal"
    orientation: "Portrait",   // "Portrait" | "Landscape"
    margins: {                 // in points (72pt = 1 inch)
      top: 72,
      right: 72,
      bottom: 72,
      left: 72,
    },
  },
});

PdfConfig

| Field | Type | Description | |-------|------|-------------| | title | string | PDF document title (metadata) | | author | string | PDF document author (metadata) | | page | PageConfig | Page layout options |

PageConfig

| Field | Type | Default | Description | |-------|------|---------|-------------| | size | "A4" \| "Letter" \| "Legal" | "A4" | Page size | | orientation | "Portrait" \| "Landscape" | "Portrait" | Page orientation | | margins | Margins | 72 all sides | Page margins in points |

Worker API

For large documents, run PDF generation off the main thread:

import { createPdfWorker } from "lexical-pdf-wasm";

const worker = createPdfWorker();

// Generate as many PDFs as needed
const pdf1 = await worker.generatePdf(doc1);
const pdf2 = await worker.generatePdf(doc2, { page: { size: "Letter" } });

// Clean up when done
worker.terminate();

The worker loads WASM independently — no contention with the main thread.

PdfWorker

| Method | Returns | Description | |--------|---------|-------------| | generatePdf(doc, config?, images?) | Promise<Uint8Array> | Generate PDF bytes | | version() | Promise<string> | Library version | | terminate() | void | Kill the worker and reject pending calls |

Custom worker URL

If your bundler places the worker file at a non-standard path:

const worker = createPdfWorker(new URL("./custom-path/worker.mjs", import.meta.url));

React example

import { useState } from "react";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { generatePdf } from "lexical-pdf-wasm";

function ExportPdfButton() {
  const [editor] = useLexicalComposerContext();
  const [exporting, setExporting] = useState(false);

  async function handleClick() {
    setExporting(true);
    try {
      const state = editor.getEditorState().toJSON();
      const pdfBytes = await generatePdf(state, {
        title: "Exported Document",
      });
      const blob = new Blob([pdfBytes], { 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);
    } finally {
      setExporting(false);
    }
  }

  return (
    <button onClick={handleClick} disabled={exporting}>
      {exporting ? "Exporting..." : "Export PDF"}
    </button>
  );
}

Svelte 5 example

<script>
  import { generatePdf } from "lexical-pdf-wasm";

  let { editor } = $props();
  let exporting = $state(false);

  async function handleExport() {
    exporting = true;
    try {
      const state = editor.getEditorState().toJSON();
      const pdfBytes = await generatePdf(state, {
        title: "Exported Document",
      });
      const blob = new Blob([pdfBytes], { 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);
    } finally {
      exporting = false;
    }
  }
</script>

<button onclick={handleExport} disabled={exporting}>
  {exporting ? "Exporting..." : "Export PDF"}
</button>

Pre-loading WASM

By default, WASM loads on the first generatePdf() or version() call. To load it earlier (e.g., while the user is editing):

import { init } from "lexical-pdf-wasm";

// Fire and forget — subsequent calls won't reload
init();

Other functions

import { version } from "lexical-pdf-wasm";

const v = await version(); // e.g. "0.1.0"

Supported Lexical nodes

  • Paragraphs (with alignment, indentation)
  • Headings (h1–h6)
  • Text (bold, italic, underline, strikethrough, code, superscript, subscript)
  • Links
  • Blockquotes
  • Lists (ordered, unordered, checklists)
  • Code blocks
  • Tables
  • Horizontal rules
  • Images
  • Line breaks

Bundler notes

The package ships as ESM with a .wasm file alongside the JS bundles. Most modern bundlers handle this automatically. The WASM file is resolved at runtime relative to import.meta.url.

Vite: Works out of the box.

Webpack 5: Works out of the box. Ensure experiments.asyncWebAssembly is not interfering — this package loads WASM manually via fetch(), not as a Webpack WASM module.

Next.js: Works in client components ("use client"). The Worker API requires a browser environment and won't work during SSR.

License

MIT