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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@valtown/codemirror-ts

v2.2.1

Published

codemirror extensions for typescript

Downloads

9,856

Readme

codemirror-ts

Made by val.town, a social website to write and deploy backend services.

On npm as @valtown/codemirror-ts

TypeScript extensions for CodeMirror. This aims to support as much of the basic interactions with TypeScript code as possible in CodeMirror.

Currently provides

  • Hover hints for types
  • Autocomplete
  • Diagnostics (lints, in CodeMirror's terminology)

Peer dependencies

This module does not depend on anything: your project should have direct dependencies to:

  • @codemirror/view
  • @codemirror/lint
  • @codemirror/autocomplete

Setup

Below are recipes for setting up this code - check out the StackBlitz demos above if you want easily copy-paste-able code!

Setup (main thread)

This is the simplest way to use this code: you'll be running the TypeScript server on the same processing as the rest of the web application. To run the TypeScript server in a worker (which can yield performance benefits, at the cost of complexity), see the next section.

This is designed to scale up to more complex scenarios, so there is some assembly required. We could encapsulate more, but that would mean removing important points of control.

  1. Create a TypeScript environment.

We don't create a TypeScript environment for you. This you bring, and it probably is used for other parts of your application. The simplest setup would be something like this, using @typescript/vfs:

import {
  createDefaultMapFromCDN,
  createSystem,
  createVirtualTypeScriptEnvironment,
} from "@typescript/vfs";

const fsMap = await createDefaultMapFromCDN(
  { target: ts.ScriptTarget.ES2022 },
  "3.7.3",
  true,
  ts,
);
const system = createSystem(fsMap);
const compilerOpts = {};
const env = createVirtualTypeScriptEnvironment(system, [], ts, compilerOpts);
  1. Install the facet and the sync extension:

The facet configures the rest of the extensions with the right path and environment.

When you make changes in your editor, the sync extension mirrors them to the TypeScript environment using createFile and updateFile in the TypeScript compiler.

Note, also, that we're supplying a path. These extensions use file paths in order to differentiate between different editor instances and to allow editors to import & export to & from one another. So each extension has a required path parameter, as well as the env parameter which should be your TypeScript environment.

import { tsSync, tsFacet } from "@valtown/codemirror-ts";

let env = "index.ts";

let editor = new EditorView({
  extensions: [
    basicSetup,
    javascript({
      typescript: true,
      jsx: true,
    }),
    tsFacet.of({ env, path }),
    tsSync(),
  ],
  parent: document.querySelector("#editor"),
});

Linting

The tsLinter extension can be initialized like this and added to the extensions array in the setup of your CodeMirror instance.

tsLinter();

This uses the @codemirror/lint package and grabs diagnostics from the TypeScript environment.

If you want to modify how lints are handled, you can use the getLints({ env, path }) method and wire it up with CodeMirror's linter method yourself.

Autocompletion

To make it possible to combine different autocompletion sources, we expose a CompletionSource which you can use with the CodeMirror autocomplete method:

autocompletion({
  override: [tsAutocomplete()],
});

We expose a lower-level interface to autocompletions with the getAutocompletion({ env, path, context }) method that takes a CompletionContext parameter.

Hover

The hover definition can be used like the following:

tsHover();

Which automatically uses a default renderer. However, you can customize this to your heart's content, and use your web framework to render custom UI if you want to, using the renderTooltip option.

tsHover({
  renderTooltip: (info: HoverInfo) => {
    const div = document.createElement("div");
    if (info.quickInfo?.displayParts) {
      for (let part of info.quickInfo.displayParts) {
        const span = div.appendChild(document.createElement("span"));
        span.className = `quick-info-${part.kind}`;
        span.innerText = part.text;
      }
    }
    return { dom: div };
  },
});

Setup (worker)

Using a Worker, you can run TypeScript separately from the rest of your JavaScript, which can make both faster and more reliable. Depending on how you're building applications, you'll need to consult their documentation on setting up web workers:

With that out of the way:

  1. Create your worker

In a file like worker.ts, you'll need something like this:

import {
  createDefaultMapFromCDN,
  createSystem,
  createVirtualTypeScriptEnvironment,
} from "@typescript/vfs";
import ts from "typescript";
import * as Comlink from "comlink";
import { createWorker } from "@valtown/codemirror-ts/worker";

Comlink.expose(
  createWorker(async function () {
    const fsMap = await createDefaultMapFromCDN(
      { target: ts.ScriptTarget.ES2022 },
      "3.7.3",
      false,
      ts,
    );
    const system = createSystem(fsMap);
    const compilerOpts = {};
    return createVirtualTypeScriptEnvironment(system, [], ts, compilerOpts);
  }),
);

This code should look familiar if you read the section about setting this up with the main thread: it's the same setting-up of the TypeScript environment, but this time wrapping it in Comlink.expose, and, importantly, setting the third parameter of createDefaultMapFromCDN to false.

The third option in createDefaultMapFromCDN is whether to cache files: it uses localStorage to power that cache, and Web Workers don't support localStorage. You can implement your own storer instead.

There is an optional fifth option in createDefaultMapFromCDN to pass in lzstring to compress files that as well. You can follow this example and add a lzstring.min.js file to your codebase if you want.

  1. Initialize the worker

Now, on the application side (in the code in which you're initializing CodeMirror), you'll need to import and initialize the worker:

import { type WorkerShape } from "@valtown/codemirror-ts/worker";
import * as Comlink from "comlink";

const innerWorker = new Worker(new URL("./worker.ts", import.meta.url), {
  type: "module",
});
const worker = Comlink.wrap<WorkerShape>(innerWorker);
await worker.initialize();
  1. Add extensions

In short, there are *Worker versions of each of the extension that accept the worker instead of env as an argument.

[
  tsFacetWorker.of({ worker, path }),
  tsSyncWorker(),
  tsLinterWorker(),
  autocompletion({
    override: [tsAutocompleteWorker()],
  }),
  tsHoverWorker(),
];

Conceptual notes: persisted code

There are a few different approaches to building CodeMirror + TypeScript integrations. Each of the things that this does - linting, hovering, autocompleting - they all require TypeScript to know about your source code. It's tempting to send it over all the time: you get your whole source code and call something like

lint(sourceCode);

However, this has an overhead, and it combines poorly: if you're linting, and hovering, and autocompleting, it's inefficient to send the whole code over for each of those. Hence how these extensions start with the sync method, which updates TypeScript's version of the code contents.

This has some drawbacks: maybe the version gets out of sync, especially when the TypeScript environment is in a worker. But we think it's worth it, and it yields some other benefits.

Conceptual notes: file names

These extensions expect your client-side CodeMirror instance to be attached to a filename, like index.ts. By sharing a TypeScript environment, this lets you have two CodeMirror instances, say, editing a.ts and b.ts, and for one to import values from the other and get the correct types - because TypeScript automatically include both.

Note, however: these extensions currently only support creating and updating files - if you support removing or deleting files, they won't be possible to do that. It would be really nice to support those other parts of the lifecycle - PRs gladly accepted!

Conceptual notes: Comlink

This uses Comlink as an abstraction for the WebWorker. There are certainly other ways to build it - we could write our own similar abstraction. But, Comlink is a pretty nifty way to use Web Workers - it allows you to call functions in a web worker from the top page.

Like any other communication across postMessage, there are limits on the kinds of values you can pass. So we can't pass the raw CompletionContext object across the boundary. Right now this module works around that limitation by just passing the properties we need. There may be other solutions in the future.

Comlink is lightweight (4.7kb gzipped).

❤️ Other great CodeMirror plugins