Skip to content

Commit 510dc2c

Browse files
Move cell rendering to render.ts and flatten type
1 parent 2925982 commit 510dc2c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+108
-185
lines changed

src/javascript.ts

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ export interface ImportReference {
2121
}
2222

2323
export interface Transpile {
24-
// TODO: Create a better type for the transpiled result.
25-
cell: Record<string, any>;
26-
// TODO: Replace usages of js with cell and remove js here.
27-
js: string;
28-
files: FileReference[];
29-
imports: ImportReference[];
24+
id: string;
25+
inputs?: string[];
26+
outputs?: string[];
27+
inline?: boolean;
28+
body: string;
29+
files?: FileReference[];
30+
imports?: ImportReference[];
3031
}
3132

3233
export interface TranspileOptions {
@@ -41,7 +42,7 @@ export function transpileJavaScript(input: string, {id, root, ...options}: Trans
4142
.filter((f) => f.type === "FileAttachment")
4243
.filter((f) => canReadSync(join(root, f.name)))
4344
.map((f) => ({name: f.name, mimeType: mime.getType(f.name)}));
44-
const inputs = Array.from(new Set(node.references.map((r) => r.name)));
45+
const inputs = Array.from(new Set<string>(node.references.map((r) => r.name)));
4546
const output = new Sourcemap(input);
4647
trim(output, input);
4748
if (node.expression && !inputs.includes("display")) {
@@ -51,28 +52,16 @@ export function transpileJavaScript(input: string, {id, root, ...options}: Trans
5152
}
5253
rewriteImports(output, node);
5354
rewriteFetches(output, node);
54-
const cell = {
55+
return {
5556
id: `${id}`,
5657
...(inputs.length ? {inputs} : null),
5758
...(options.inline ? {inline: true} : null),
5859
...(node.declarations?.length ? {outputs: node.declarations.map(({name}) => name)} : null),
5960
...(files.length ? {files} : null),
6061
body: `${node.async ? "async " : ""}(${inputs}) => {
6162
${String(output)}${node.declarations?.length ? `\nreturn {${node.declarations.map(({name}) => name)}};` : ""}
62-
}`
63-
};
64-
return {
65-
cell,
66-
js: `define({id: ${id}${inputs.length ? `, inputs: ${JSON.stringify(inputs)}` : ""}${
67-
options.inline ? `, inline: true` : ""
68-
}${node.declarations?.length ? `, outputs: ${JSON.stringify(node.declarations.map(({name}) => name))}` : ""}${
69-
files.length ? `, files: ${JSON.stringify(files)}` : ""
70-
}, body: ${node.async ? "async " : ""}(${inputs}) => {
71-
${String(output)}${node.declarations?.length ? `\nreturn {${node.declarations.map(({name}) => name)}};` : ""}
72-
}});
73-
`,
74-
files,
75-
imports: node.imports
63+
}`,
64+
...(node.imports.length ? {imports: node.imports} : null)
7665
};
7766
} catch (error) {
7867
if (!(error instanceof SyntaxError)) throw error;
@@ -89,12 +78,8 @@ ${String(output)}${node.declarations?.length ? `\nreturn {${node.declarations.ma
8978
// whether we want to show the file name here.
9079
console.error(`${error.name}: ${message}`);
9180
return {
92-
cell: {},
93-
// TODO: Add error details to the response to improve code rendering.
94-
js: `define({id: ${id}, body: () => { throw new SyntaxError(${JSON.stringify(error.message)}); }});
95-
`,
96-
files: [],
97-
imports: []
81+
id: `${id}`,
82+
body: `() => { throw new SyntaxError(${JSON.stringify(error.message)}); }`
9883
};
9984
}
10085
}

src/markdown.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,15 @@ export interface HtmlPiece {
2020
cellIds?: string[];
2121
}
2222

23-
type TranspiledCell = Transpile["cell"];
24-
25-
export interface CellPiece extends TranspiledCell {
23+
export interface CellPiece extends Transpile {
2624
type: "cell";
27-
inline: boolean;
2825
}
2926

3027
export type ParsePiece = HtmlPiece | CellPiece;
3128

3229
export interface ParseResult {
3330
title: string | null;
3431
html: string;
35-
js: string;
3632
data: {[key: string]: any} | null;
3733
files: FileReference[];
3834
imports: ImportReference[];
@@ -42,7 +38,7 @@ export interface ParseResult {
4238

4339
interface RenderPiece {
4440
html: string;
45-
code: (Transpile["cell"] & {inline: boolean})[];
41+
code: Transpile[];
4642
}
4743

4844
interface ParseContext {
@@ -86,10 +82,9 @@ function makeFenceRenderer(root: string, baseRenderer: RenderRule): RenderRule {
8682
root,
8783
sourceLine: context.startLine + context.currentLine
8884
});
89-
extendPiece(context, {code: [{...transpile.cell, inline: false}]});
90-
context.js += `\n${transpile.js}`;
91-
context.files.push(...transpile.files);
92-
context.imports.push(...transpile.imports);
85+
extendPiece(context, {code: [transpile]});
86+
if (transpile.files) context.files.push(...transpile.files);
87+
if (transpile.imports) context.imports.push(...transpile.imports);
9388
result += `<div id="cell-${id}" class="observablehq observablehq--block"></div>\n`;
9489
count++;
9590
}
@@ -242,9 +237,8 @@ function makePlaceholderRenderer(root: string): RenderRule {
242237
inline: true,
243238
sourceLine: context.startLine + context.currentLine
244239
});
245-
context.js += `\n${transpile.js}`;
246-
extendPiece(context, {code: [{...transpile.cell, inline: true}]});
247-
context.files.push(...transpile.files);
240+
extendPiece(context, {code: [transpile]});
241+
if (transpile.files) context.files.push(...transpile.files);
248242
return `<span id="cell-${id}"></span>`;
249243
};
250244
}
@@ -346,7 +340,6 @@ export function parseMarkdown(source: string, root: string): ParseResult {
346340
const html = md.renderer.render(tokens, md.options, context);
347341
return {
348342
html,
349-
js: context.js,
350343
data: isEmpty(parts.data) ? null : parts.data,
351344
title: parts.data?.title ?? findTitle(tokens) ?? null,
352345
files: context.files,
@@ -383,7 +376,14 @@ function findTitle(tokens: ReturnType<MarkdownIt["parse"]>): string | undefined
383376
function diffReducer(patch: PatchItem<ParsePiece>) {
384377
// Remove body from remove updates, we just need the ids.
385378
if (patch.type === "remove") {
386-
return {...patch, items: patch.items.map((item) => ({type: item.type, id: item.id, cellIds: item.cellIds}))};
379+
return {
380+
...patch,
381+
items: patch.items.map((item) => ({
382+
type: item.type,
383+
id: item.id,
384+
...("cellIds" in item ? {cellIds: item.cellIds} : null)
385+
}))
386+
};
387387
}
388388
return patch;
389389
}

src/render.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ export function renderServerless(source: string, options: RenderOptions): Render
3232
};
3333
}
3434

35+
export function renderDefineCell(cell) {
36+
const {id, inline, inputs, outputs, files, body} = cell;
37+
return `define({${Object.entries({id, inline, inputs, outputs, files})
38+
.filter((arg) => arg[1] !== undefined)
39+
.map((arg) => `${arg[0]}: ${JSON.stringify(arg[1])}`)
40+
.join(", ")}, body: ${body}});\n`;
41+
}
42+
3543
type RenderInternalOptions =
3644
| {preview?: false; hash?: never} // serverless
3745
| {preview: true; hash: string}; // preview
@@ -66,7 +74,9 @@ ${JSON.stringify(
6674
6775
import {${preview ? "open, " : ""}define} from "/_observablehq/client.js";
6876
69-
${preview ? `open({hash: ${JSON.stringify(hash)}});\n` : ""}${parseResult.js}
77+
${preview ? `open({hash: ${JSON.stringify(hash)}});\n` : ""}${parseResult.cells
78+
.map((cell) => renderDefineCell(cell))
79+
.join("")}
7080
</script>${
7181
parseResult.data
7282
? `

test/javascript-test.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {readFile, unlink, writeFile} from "node:fs/promises";
44
import {basename, join, resolve} from "node:path";
55
import {isNodeError} from "../src/error.js";
66
import {transpileJavaScript} from "../src/javascript.js";
7+
import {renderDefineCell} from "../src/render.js";
78

89
describe("transpileJavaScript(input)", () => {
910
for (const name of readdirSync("./test/input")) {
@@ -16,22 +17,24 @@ describe("transpileJavaScript(input)", () => {
1617
(only ? it.only : skip ? it.skip : it)(`test/input/${name}`, async () => {
1718
const outfile = resolve("./test/output", `${basename(outname, ".js")}.js`);
1819
const diffile = resolve("./test/output", `${basename(outname, ".js")}-changed.js`);
19-
const actual = await transpileJavaScript(await readFile(path, "utf8"), {id: 0, root: "test/input"});
20+
const actual = renderDefineCell(
21+
await transpileJavaScript(await readFile(path, "utf8"), {id: 0, root: "test/input"})
22+
);
2023
let expected;
2124

2225
try {
2326
expected = await readFile(outfile, "utf8");
2427
} catch (error) {
2528
if (isNodeError(error) && error.code === "ENOENT" && process.env.CI !== "true") {
2629
console.warn(`! generating ${outfile}`);
27-
await writeFile(outfile, actual.js, "utf8");
30+
await writeFile(outfile, actual, "utf8");
2831
return;
2932
} else {
3033
throw error;
3134
}
3235
}
3336

34-
const equal = expected === actual.js;
37+
const equal = expected === actual;
3538

3639
if (equal) {
3740
if (process.env.CI !== "true") {
@@ -46,7 +49,7 @@ describe("transpileJavaScript(input)", () => {
4649
}
4750
} else {
4851
console.warn(`! generating ${diffile}`);
49-
await writeFile(diffile, actual.js, "utf8");
52+
await writeFile(diffile, actual, "utf8");
5053
}
5154

5255
assert.ok(equal, `${name} must match snapshot`);

test/markdown-test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ describe("parseMarkdown(input)", () => {
1717
(only ? it.only : skip ? it.skip : it)(`test/input/${name}`, async () => {
1818
const snapshot = parseMarkdown(await readFile(path, "utf8"), "test/input");
1919
let allequal = true;
20-
for (const ext of ["html", "js", "json"]) {
20+
for (const ext of ["html", "json"]) {
2121
const actual = ext === "json" ? jsonMeta(snapshot) : snapshot[ext];
2222
const outfile = resolve("./test/output", `${basename(outname, ".md")}.${ext}`);
2323
const diffile = resolve("./test/output", `${basename(outname, ".md")}-changed.${ext}`);
@@ -59,7 +59,7 @@ describe("parseMarkdown(input)", () => {
5959
}
6060
});
6161

62-
function jsonMeta({html, js, ...rest}: ParseResult): string {
62+
function jsonMeta({html, ...rest}: ParseResult): string {
6363
return JSON.stringify(rest, null, 2);
6464
}
6565

test/output/anonymous-class.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
define({id: 0, inputs: ["display"], body: (display) => {
1+
define({id: "0", inputs: ["display"], body: (display) => {
22
display((
33
class {}
44
))

test/output/anonymous-function.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
define({id: 0, inputs: ["display"], body: (display) => {
1+
define({id: "0", inputs: ["display"], body: (display) => {
22
display((
33
function() { return 42; }
44
))
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
define({id: 0, outputs: ["addAsync"], body: () => {
1+
define({id: "0", outputs: ["addAsync"], body: () => {
22
const addAsync = async (a, b) => (await a) + (await b);
33
return {addAsync};
44
}});

test/output/async-class.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
define({id: 0, outputs: ["Foo"], body: () => {
1+
define({id: "0", outputs: ["Foo"], body: () => {
22
class Foo {
33
async addAsync(a, b) {
44
return (await a) + (await b);

test/output/async-function.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
define({id: 0, outputs: ["addAsync"], body: () => {
1+
define({id: "0", outputs: ["addAsync"], body: () => {
22
async function addAsync(a, b) {
33
return (await a) + (await b);
44
}

0 commit comments

Comments
 (0)