Skip to content

Commit

Permalink
support typescript
Browse files Browse the repository at this point in the history
closes #79
  • Loading branch information
Fil committed Nov 9, 2023
1 parent bb96a2c commit 920f6c6
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 2 deletions.
51 changes: 51 additions & 0 deletions docs/typescript.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# TypeScript ([#79](https://github.com/observablehq/cli/issues/79))

[Observable Markdown](./markdown) also supports TypeScript, by simply stripping type information from code enclosed in TypeScript fenced code blocks, as well as from imported ts files.

````md
```ts
TypeScript code here…
```
````

```ts show
function add(a: number, b: number): number {
return a + b;
}
```

The resulting function can be called from js cells as well as other ts cells:

```js show
add(1, 3)
```

```ts show
add(1 as number, 3)
```

Inline expressions are also converted when they appear to be written in TypeScript:

```md
1 + 2 = ${add(1 as number, 2)}.
```

1 + 2 = ${add(1 as number, 2)}.


Errors show up as expected:

```ts show
function bad() ::: {
return a + b;
}
```

TODO. Imports are transpiled too:

```ts show
import {sum} from "./sum.ts"
display(sum);
display(sum(1, 2));
```

5 changes: 5 additions & 0 deletions public/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ pre {

pre .language-md::after,
pre .language-js::after,
pre .language-ts::after,
pre .language-sh::after,
pre .language-sql::after {
position: absolute;
Expand All @@ -329,6 +330,10 @@ pre .language-js::after {
content: "js";
}

pre .language-ts::after {
content: "ts";
}

pre .language-sh::after {
content: "sh";
}
Expand Down
15 changes: 13 additions & 2 deletions src/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {readFile} from "node:fs/promises";
import {pathFromRoot} from "./files.js";
import {computeHash} from "./hash.js";
import {transpileJavaScript, type FileReference, type ImportReference, type Transpile} from "./javascript.js";
import {transpileTag} from "./tag.js";
import {transpileTag, transpileTypeScript} from "./tag.js";

export interface ReadMarkdownResult {
contents: string;
Expand Down Expand Up @@ -83,6 +83,8 @@ function getLiveSource(content, language, option) {
? undefined
: language === "js"
? content
: language === "ts"
? transpileTypeScript(content)
: language === "tex"
? transpileTag(content, "tex.block", true)
: language === "dot"
Expand Down Expand Up @@ -257,13 +259,22 @@ function makePlaceholderRenderer(root: string, sourcePath: string): RenderRule {
return (tokens, idx, options, context: ParseContext) => {
const id = uniqueCodeId(context, tokens[idx].content);
const token = tokens[idx];
const transpile = transpileJavaScript(token.content, {
let transpile = transpileJavaScript(token.content, {
id,
root,
sourcePath,
inline: true,
sourceLine: context.startLine + context.currentLine
});
// Detect that we need TypeScript.
if (transpile.body === '() => { throw new SyntaxError("invalid expression"); }')
transpile = transpileJavaScript(transpileTypeScript(token.content), {
id,
root,
sourcePath,
inline: true,
sourceLine: context.startLine + context.currentLine
});
extendPiece(context, {code: [transpile]});
if (transpile.files) context.files.push(...transpile.files);
return `<span id="cell-${id}"></span>`;
Expand Down
11 changes: 11 additions & 0 deletions src/tag.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Parser, TokContext, tokTypes as tt} from "acorn";
import {Sourcemap} from "./sourcemap.js";
import {transformSync} from "esbuild";

const CODE_DOLLAR = 36;
const CODE_BACKSLASH = 92;
Expand All @@ -16,6 +17,16 @@ export function transpileTag(input, tag = "", raw = false) {
return String(source);
}

export function transpileTypeScript(input) {
try {
const js = transformSync(input, {loader: "ts"}).code;
// preserve the absence of a trailing semi-colon, to display
return input.trim().at(-1) !== ";" ? js.replace(/;[\s\n]*$/, "") : js;
} catch {
return input;
}
}

class TemplateParser extends Parser {
constructor(...args) {
super(...args);
Expand Down

0 comments on commit 920f6c6

Please sign in to comment.