Skip to content

Commit

Permalink
Provide ATA example
Browse files Browse the repository at this point in the history
  • Loading branch information
tmcw committed Feb 7, 2025
1 parent 02a9d68 commit 9ae3348
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 27 deletions.
62 changes: 56 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,6 @@ have direct dependencies to:

## Setup

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

## Setup

Using a [Worker](https://developer.mozilla.org/en-US/docs/Web/API/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,
Expand Down Expand Up @@ -67,7 +62,7 @@ Comlink.expose(
createWorker(async function () {
const fsMap = await createDefaultMapFromCDN(
{ target: ts.ScriptTarget.ES2022 },
"3.7.3",
"5.7.3",
false,
ts,
);
Expand Down Expand Up @@ -123,6 +118,61 @@ that accept the `worker` instead of `env` as an argument.
];
```

## Using ATA

The example above will give you a working TypeScript setup, but if you
import a module from NPM, that module will not have types. Usually TypeScript
expects you to be installing modules with npm and expects that they'll be stored
in a `node_modules` directory. `codemirror-ts` is on the internet in a WebWorker,
so obviously it is not running NPM.

You can emulate what you'd get in a local editor by using [ATA](https://www.npmjs.com/package/@typescript/ata), or
'automatic type acquisition'. The setup looks like this example,
and is a change to your WebWorker setup. We use the `onFileUpdated`
callback passed to `createWorker`, trigger ATA to get new types,
and then create those files on path.

```ts
// … import createWorker etc.
// Import setupTypeAcquisition from @typescript/ata

const worker = createWorker({
env: (async () => {
const fsMap = await createDefaultMapFromCDN(
{ target: ts.ScriptTarget.ES2022 },
ts.version,
false,
ts,
);
const system = createSystem(fsMap);
return createVirtualTypeScriptEnvironment(system, [], ts, {
lib: ["ES2022"],
});
})(),
onFileUpdated(_env, _path, code) {
ata(code);
},
});

const ata = setupTypeAcquisition({
projectName: "My ATA Project",
typescript: ts,
logger: console,
delegate: {
receivedFile: (code: string, path: string) => {
worker.getEnv().createFile(path, code);
},
},
});

Comlink.expose(worker);
```

> [!NOTE]
> If you are targeting a _non-Node_ environment, like Deno or
> a web browser, ATA will not help you with your HTTP imports or prefixed
> imports. It narrowly targets the Node & NPM way of doing imports.
## Conceptual notes: persisted code

There are a few different approaches to building CodeMirror + TypeScript
Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
},
"files": {
"ignoreUnknown": false,
"ignore": ["demo", "dist", "coverage", "scripts", "test"]
"ignore": ["dist", "coverage", "scripts", "test"]
},
"formatter": {
"enabled": true,
Expand Down
9 changes: 9 additions & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ <h2>Demo</h2>
<div id="app" class='border'>
<div id="editor-worker"></div>
</div>

<h2>Demo with ATA</h2>
<p>
This demo uses <a href='https://www.npmjs.com/package/@typescript/ata'>ATA</a>
to acquire types of referenced NPM modules.
</p>
<div id="app" class='border'>
<div id="editor-worker-ata"></div>
</div>
<h2>Currently supported</h2>
<ul>
<li>Autocomplete</li>
Expand Down
73 changes: 64 additions & 9 deletions demo/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { EditorView, basicSetup } from "codemirror";
import { javascript } from "@codemirror/lang-javascript";
import { autocompletion } from "@codemirror/autocomplete";
import { javascript } from "@codemirror/lang-javascript";
import {
createDefaultMapFromCDN,
createSystem,
createVirtualTypeScriptEnvironment,
} from "@typescript/vfs";
import ts from "typescript";
import { EditorView, basicSetup } from "codemirror";
import * as Comlink from "comlink";
import type ts from "typescript";
import {
tsLinter,
tsHover,
tsAutocomplete,
tsSync,
tsFacet,
tsGoto,
tsHover,
tsLinter,
tsSync,
tsTwoslash,
} from "../src/index.js";
import * as Comlink from "comlink";
import { WorkerShape } from "../src/worker.js";
import type { WorkerShape } from "../src/worker.js";

function renderDisplayParts(dp: ts.SymbolDisplayPart[]) {
const div = document.createElement("div");
Expand All @@ -39,7 +39,7 @@ function renderDisplayParts(dp: ts.SymbolDisplayPart[]) {
const worker = Comlink.wrap(innerWorker) as WorkerShape;
await worker.initialize();

let editor = new EditorView({
const editor = new EditorView({
doc: `let hasAnError: string = 10;
function increment(num: number) {
Expand Down Expand Up @@ -88,3 +88,58 @@ increment('not a number');`,
parent: document.querySelector("#editor-worker")!,
});
})().catch((e) => console.error(e));

(async () => {
const path = "index.ts";

// TODO: this is the one place where we can't use .js urls
const innerWorker = new Worker(new URL("./worker_ata.ts", import.meta.url), {
type: "module",
});
const worker = Comlink.wrap(innerWorker) as WorkerShape;
await worker.initialize();

const editor = new EditorView({
doc: `import { min } from "simple-statistics";
const minimumValue = min([1, 2, 3]);`,
extensions: [
basicSetup,
javascript({
typescript: true,
jsx: true,
}),
tsFacet.of({ worker, path }),
tsSync(),
tsLinter(),
autocompletion({
override: [
tsAutocomplete({
renderAutocomplete(raw) {
return () => {
const div = document.createElement("div");
if (raw.documentation) {
const description = div.appendChild(
document.createElement("div"),
);
description.appendChild(
renderDisplayParts(raw.documentation),
);
}
if (raw?.displayParts) {
const dp = div.appendChild(document.createElement("div"));
dp.appendChild(renderDisplayParts(raw.displayParts));
}
return { dom: div };
};
},
}),
],
}),
tsHover(),
tsGoto(),
tsTwoslash(),
],
parent: document.querySelector("#editor-worker-ata")!,
});
})().catch((e) => console.error(e));
4 changes: 2 additions & 2 deletions demo/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import {
createSystem,
createVirtualTypeScriptEnvironment,
} from "@typescript/vfs";
import ts from "typescript";
import * as Comlink from "comlink";
import ts from "typescript";
import { createWorker } from "../src/worker/createWorker.js";

Comlink.expose(
createWorker({
env: (async function () {
env: (async () => {
const fsMap = await createDefaultMapFromCDN(
{ target: ts.ScriptTarget.ES2022 },
ts.version,
Expand Down
40 changes: 40 additions & 0 deletions demo/worker_ata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { setupTypeAcquisition } from "@typescript/ata";
import {
createDefaultMapFromCDN,
createSystem,
createVirtualTypeScriptEnvironment,
} from "@typescript/vfs";
import * as Comlink from "comlink";
import ts from "typescript";
import { createWorker } from "../src/worker/createWorker.js";

const worker = createWorker({
env: (async () => {
const fsMap = await createDefaultMapFromCDN(
{ target: ts.ScriptTarget.ES2022 },
ts.version,
false,
ts,
);
const system = createSystem(fsMap);
return createVirtualTypeScriptEnvironment(system, [], ts, {
lib: ["ES2022"],
});
})(),
onFileUpdated(_env, _path, code) {
ata(code);
},
});

const ata = setupTypeAcquisition({
projectName: "My ATA Project",
typescript: ts,
logger: console,
delegate: {
receivedFile: (code: string, path: string) => {
worker.getEnv().createFile(path, code);
},
},
});

Comlink.expose(worker);
11 changes: 3 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@
"test:watch": "vitest",
"build": "vite build"
},
"keywords": [
"codemirror",
"typescript",
"ts"
],
"keywords": ["codemirror", "typescript", "ts"],
"author": "Val Town",
"license": "ISC",
"peerDependencies": {
Expand All @@ -35,6 +31,7 @@
"@biomejs/biome": "^1.9.4",
"@codemirror/lang-javascript": "^6.2.2",
"@types/node": "^22.13.1",
"@typescript/ata": "^0.9.7",
"@typescript/vfs": "^1.6.1",
"@vitest/coverage-v8": "^2.1.9",
"codemirror": "^6.0.1",
Expand All @@ -44,9 +41,7 @@
"vite": "^6.1.0",
"vitest": "^2.1.9"
},
"files": [
"dist"
],
"files": ["dist"],
"exports": {
"./package.json": "./package.json",
".": {
Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/lint/tsLinter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Diagnostic, linter, type LintSource } from "@codemirror/lint";
import { type Diagnostic, type LintSource, linter } from "@codemirror/lint";
import { tsFacet } from "../index.js";

/**
Expand Down
3 changes: 3 additions & 0 deletions src/worker/createWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export function createWorker(_options: Promisable<InitializerOptions>) {
initialized = true;
}
},
/**
* Usually used to hook into ATA
*/
updateFile({ path, code }: { path: string; code: string }) {
if (!env) return;
if (createOrUpdateFile(env, path, code)) {
Expand Down

0 comments on commit 9ae3348

Please sign in to comment.