Skip to content

Commit

Permalink
helper functions for MIME-tagged outputs
Browse files Browse the repository at this point in the history
fixes #35
  • Loading branch information
Jake Donham committed Jun 4, 2024
1 parent fd94d11 commit b466833
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 60 deletions.
42 changes: 32 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,19 +91,41 @@ Since code in cells is transformed by Vite, you need to prefix variables with

## Rendering different MIME types

Object values are returned with MIME type `application/json` and rendered using
`react-json-view`. `HTMLElement` values are returned with MIME type `text/html`
and rendered as HTML. `SVGElement` values are returned with MIME type
`image/svg+xml` and rendered as SVG.

To override the MIME type, return an object of type `{ data: string, mime:
string }` (currently there's no way to return binary data). VS Code has several
built-in renderers (see [Rich
`vitale` inspects the output value and tries to pick an appropriate MIME type to
render it. For most Javascript objects this is `application/json`, which is
rendered by VS Code with syntax highlighting. For complex objects you can render
an expandable view (using
[react-json-view](https://github.com/uiwjs/react-json-view)) by returning the
`application/x-json-view` MIME type. `HTMLElement` and `SVGElement` objects are
rendered as `text/html` and `image/svg+xml` respectively (see below for an
example).

To set the MIME type of the output explicitly, return an object of type `{ data:
string, mime: string }` (currently there's no way to return binary data). VS
Code has several built-in renderers (see [Rich
Output](https://code.visualstudio.com/api/extension-guides/notebook#rich-output))
and you can install others as extensions.

To make `HTMLElement` / `SVGElement` values, you can use `jsdom` or a similar
library; for example, to render an SVG from [Observable
There are helper functions in `@githubnext/vitale` to construct these
MIME-tagged outputs:

```ts
function text(data: string);
function markdown(data: string);
function html(html: string | { outerHTML: string });
function svg(html: string | { outerHTML: string });
function json(obj: object);
function jsonView(obj: object);
```

This package is auto-imported under the name `Vitale` in notebook cells, so you can write e.g.

```ts
Vitale.jsonView({ foo: "bar" });
```

You can construct `HTMLElement` and `SVGElement` values using `jsdom` or a
similar library; for example, to render an SVG from [Observable
Plot](https://observablehq.com/plot/):

```ts
Expand Down
1 change: 1 addition & 0 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"license": "MIT",
"type": "module",
"types": "dist/index.d.ts",
"module": "dist/index.js",
"files": [
"dist/cli.js",
"dist/index.js",
Expand Down
28 changes: 27 additions & 1 deletion packages/server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
export * from "./types";
export * from "./rpc-types";

export function text(data: string) {
return { data, mime: "text/plain" };
}

export function markdown(data: string) {
return { data, mime: "text/markdown" };
}

export function html(html: string | { outerHTML: string }) {
const data = typeof html === "object" ? html.outerHTML : html;
return { data, mime: "text/html" };
}

export function svg(html: string | { outerHTML: string }) {
const data = typeof html === "object" ? html.outerHTML : html;
return { data, mime: "image/svg+xml" };
}

export function json(obj: object) {
return { data: JSON.stringify(obj), mime: "application/json" };
}

export function jsonView(obj: object) {
return { data: JSON.stringify(obj), mime: "application/x-json-view" };
}
7 changes: 6 additions & 1 deletion packages/server/src/rewrite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,12 @@ function findAutoImports(
},
});

const autoImports: babelTypes.ImportDeclaration[] = [];
const autoImports: babelTypes.ImportDeclaration[] = [
babelTypes.importDeclaration(
[babelTypes.importNamespaceSpecifier(babelTypes.identifier("Vitale"))],
babelTypes.stringLiteral("@githubnext/vitale")
),
];
next: for (const name of unbound) {
for (const cell of cells.values()) {
if (cell.sourceDescription) {
Expand Down
45 changes: 45 additions & 0 deletions packages/server/src/rpc-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
export interface CellOutputItem {
data: number[]; // Uint8Array
mime: string;
}

export interface CellOutput {
items: CellOutputItem[];
}

export type ServerFunctions = {
ping: () => Promise<"pong">;

removeCells: (
cells: {
path: string;
cellId: string;
language: string;
}[]
) => void;

executeCells: (
cells: {
path: string;
cellId: string;
language: string;
code?: string;
}[],
force: boolean,
executeDirtyCells: boolean
) => void;
};

export type ClientFunctions = {
markCellsDirty: (cells: { path: string; cellId: string }[]) => void;
startCellExecution: (
path: string,
cellId: string,
force: boolean
) => Promise<boolean>;
endCellExecution: (
path: string,
cellId: string,
cellOutput: CellOutput
) => void;
};
2 changes: 1 addition & 1 deletion packages/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type {
CellOutputItem,
ClientFunctions,
ServerFunctions,
} from "./types";
} from "./rpc-types";
import { Cell, Options } from "./types";
import { handleHMRUpdate } from "./hmr";
import * as Domain from "node:domain";
Expand Down
46 changes: 0 additions & 46 deletions packages/server/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,6 @@
import type { GeneratorResult } from "@babel/generator";
import * as babelTypes from "@babel/types";

export interface CellOutputItem {
data: number[]; // Uint8Array
mime: string;
}

export interface CellOutput {
items: CellOutputItem[];
}

export type ServerFunctions = {
ping: () => Promise<"pong">;

removeCells: (
cells: {
path: string;
cellId: string;
language: string;
}[]
) => void;

executeCells: (
cells: {
path: string;
cellId: string;
language: string;
code?: string;
}[],
force: boolean,
executeDirtyCells: boolean
) => void;
};

export type ClientFunctions = {
markCellsDirty: (cells: { path: string; cellId: string }[]) => void;
startCellExecution: (
path: string,
cellId: string,
force: boolean
) => Promise<boolean>;
endCellExecution: (
path: string,
cellId: string,
cellOutput: CellOutput
) => void;
};

export type SourceDescription = GeneratorResult & {
type: "server" | "client";
autoExports: babelTypes.ImportDeclaration[];
Expand Down
2 changes: 1 addition & 1 deletion packages/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"displayName": "JSON Renderer",
"entrypoint": "./dist/jsonRenderer.mjs",
"mimeTypes": [
"application/json"
"application/x-json-view"
]
},
{
Expand Down

0 comments on commit b466833

Please sign in to comment.