Skip to content

Getting started

This guide embeds the full SparqlStudio app with the qlue-ls language server. If you only need the editor or the result viewer, see Editor and Results.

1. Install

bash
npm install @rdfjs/sparql-studio

To use the qlue-ls language server, also add it and the Vite WASM plugin to your app:

bash
npm install qlue-ls
npm install -D vite-plugin-wasm

The @rdfjs/* packages are self-contained ESM bundles (Monaco and the language client are bundled in), you do not need to install monaco-editor. They are ESM only (Monaco loads its workers via import.meta.url, which UMD can't do), so use a modern bundler (Vite recommended).

Each package ships its own CSS that you must import once:

js
import "@rdfjs/sparql-studio/style.css";
// or for standalone use:
// import "@rdfjs/sparql-editor-monaco/style.css";
// import "@rdfjs/sparql-results/style.css";

2. Bundler setup (Vite)

Because the qlue-ls worker loads WebAssembly, your app's Vite config needs vite-plugin-wasm and ES-module workers:

ts
// vite.config.ts
import { defineConfig } from "vite";
import wasm from "vite-plugin-wasm";

export default defineConfig({
  plugins: [wasm()],
  worker: {
    format: "es",
    plugins: () => [wasm()],
  },
});

No language server

If you don't use a language server at all, none of this is needed, the editor still does syntax highlighting.

3. Set up the language server

The language server runs in a Web Worker. The qlue-ls backend/settings plumbing ships with the package (the qlueLs helpers), so the only file you write is the worker itself, which is also the only file you change to switch to a different SPARQL language server later. You pass that worker straight to the editor: it waits for the worker to signal it is ready, then connects the LSP client for you, so no readiness wrapper is needed. See the Language server page for details; here is the minimal setup:

ts
// @ts-ignore qlue-ls is loaded as a WASM module via vite-plugin-wasm
import init, { init_language_server, listen } from "qlue-ls?init";

init().then(() => {
  const input = new TransformStream();
  const output = new TransformStream();
  const reader = output.readable.getReader();
  const writer = input.writable.getWriter();

  const server = init_language_server(output.writable.getWriter());
  listen(server, input.readable.getReader());

  // Bridge: language client -> server, and server -> language client.
  self.onmessage = (msg) => writer.write(JSON.stringify(msg.data));
  (async () => {
    while (true) {
      const { value, done } = await reader.read();
      if (done) break;
      self.postMessage(JSON.parse(value));
    }
  })();

  // Tell the editor the WASM server is initialized; it waits for this before connecting.
  self.postMessage({ type: "ready" });
});
export {};

4. Mount SparqlStudio

ts
import SparqlStudio from "@rdfjs/sparql-studio";
import SparqlEditor, { qlueLs } from "@rdfjs/sparql-editor-monaco";
import "@rdfjs/sparql-studio/style.css";
import QlueLsWorker from "./qlue-ls.worker?worker";

const yasgui = new SparqlStudio(document.getElementById("yasgui")!, {
  requestConfig: { endpoint: "https://sparql.dblp.org/sparql" },

  // Editor factory: SparqlStudio is editor-independent, so you build the editor yourself.
  // Pass the worker straight in (a Worker instance, or a () => Worker factory for lazy start);
  // the editor waits for its "ready" signal and connects the client. Per-entry hooks
  // (onReady, onEndpointChange) fire only while that server is active.
  editor: (parent, conf) =>
    new SparqlEditor(parent, {
      ...conf,
      languageServers: [
        {
          label: "Qlue-ls",
          worker: () => new QlueLsWorker({ name: "qlue-ls" }),
          onReady: (client) => {
            qlueLs.configureSettings(client);
            qlueLs.configureBackend(client, yasgui?.getTab()?.getEndpoint());
          },
          onEndpointChange: (client, endpoint) => qlueLs.configureBackend(client, endpoint),
        },
      ],
    }),
});

Offering several servers

Add more entries to languageServers to let users switch at runtime; with two or more, a switcher appears (right-click in Monaco, a dropdown in CodeMirror) and the choice is remembered per endpoint.

CodeMirror instead of Monaco

The factory is also where you choose the editor implementation. To use the CodeMirror 6 editor, import SparqlEditor from @rdfjs/sparql-editor-codemirror instead. The languageServers config is identical, both editors take the same worker (Monaco connects a language client to it, CodeMirror builds an LSPClient from it internally). See Language server.