Skip to content

Using Penrose with Vanilla JS

Both the Language API and Optimization API are exported from @penrose/core, currently released as an ECMAScript module (ESM). If you are making a web page without a build tool, you can use one of the CDNs with built-in ESM support. Here's an example of using JSPM, where we use the JSPM Generator to create the HTML boilerplate for importing our dependencies.

<!doctype html>
    <meta charset="utf-8" />
    <title>Penrose Vanilla JS Demo</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    JSPM Generator Import Map
    Edit URL:
    <script type="importmap">
        "imports": {
          "@penrose/core": ""
        "scopes": {
          "": {
            "@datastructures-js/queue": "",
            "@penrose/optimizer": "",
            "consola": "",
            "crypto": "",
            "immutable": "",
            "lodash": "",
            "mathjax-full/js/": "",
            "mhchemparser/dist/mhchemParser.js": "",
            "moo": "",
            "nearley": "",
            "poly-partition": "",
            "seedrandom": "",
            "true-myth": ""

    <!-- ES Module Shims: Import maps polyfill for olrder browsers without import maps support (eg Safari 16.3) -->

    <script type="module">
      import { compile, optimize, toSVG, showError } from "@penrose/core";
      const trio = {
        substance: `
          Set A
          Label A $e=mc^2$
        style: `canvas {
          width = 150
          height = 150
        forall Set A {
          center = (0, 0)
          Circle { 
            center: center
            r: 50
          Equation { 
            center: center
            string: A.label
        domain: `type Set`,
        variation: `test`,
      const compiled = await compile(trio);
      if (compiled.isErr()) console.error(showError(compiled.error));
      const optimized = optimize(compiled.value);
      if (optimized.isErr()) console.error(showError(optimized.error));
        .appendChild(await toSVG(optimized.value));
    <div id="penrose"></div>

To run this example, copy the code above into an HTML file (e.g. index.html) and run an local HTTP server to view the page:

npx http-server .

You can also check out this example live here.

Experimental bundled ESM


This feature is experimental as of v3.2.0 and is subject to changes.

The default ESM module requires a CDN or a bundler to download all the dependencies of core. This experimental release format is an ESM module that includes all dependencies in one ESM module. To import @penrose/core from the bundle:

import { compile, optimize } from "@penrose/core/bundle";


The bundled core module currently uses IIFE to provide a seemingly synchronous API. However, be aware that any function imported from @penrose/core/bundle might be undefined when used immediately after importing. To work around this, do explicit checks on whether the functions are loaded before using them:

import { compile, optimize } from "@penrose/core/bundle";
if (compile && optimize) {
  // actually call `compile` and `optimize`

Released under the MIT License.