Skip to content

Commit c262bc5

Browse files
committed
fix: improve runtime placeholder handling
Fixes previous issues with update and remove Signed-off-by: Gordon Smith <GordonJSmith@gmail.com>
1 parent f5c0c82 commit c262bc5

8 files changed

Lines changed: 142 additions & 97 deletions

File tree

packages/observablehq-compiler/index-kit.html

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
<title>Home</title>
66
<meta charset="utf-8">
77
<title>ObservableHQ Kit Preview</title>
8-
<!-- Google Fonts: Roboto -->
9-
<!-- <style>
8+
<style>
109
body,
1110
html {
1211
font-family: "Segoe UI", Verdana, Arial, Helvetica, sans-serif;
1312
}
14-
</style> -->
13+
</style>
1514
</head>
1615

1716
<body>

packages/observablehq-compiler/src/compiler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
import { type Notebook, type Definition, compile as compileKit, fixRelativeUrl, isRelativePath, obfuscatedImport } from "./kit/index.ts";
2+
import { type Notebook, type Definition, compileNotebook, fixRelativeUrl, isRelativePath, obfuscatedImport } from "./kit/index.ts";
33
import { ohq, splitModule } from "./observable-shim.ts";
44
import { parseCell, ParsedImportCell } from "./cst.ts";
55
import { Writer } from "./writer.ts";
@@ -330,7 +330,7 @@ export async function compile(notebookOrOjs: ohq.Notebook, options?: CompileOpti
330330
export async function compile(notebookOrOjs: string, options?: CompileOptions): Promise<NotebookFunc>;
331331
export async function compile(notebookOrOjs: Notebook | ohq.Notebook | string, { baseUrl = ".", importMode = "precompiled" }: CompileOptions = {}) {
332332
if (isNotebookKit(notebookOrOjs)) {
333-
return compileKit(notebookOrOjs);
333+
return compileNotebook(notebookOrOjs);
334334
} else if (typeof notebookOrOjs === "string") {
335335
notebookOrOjs = ojs2notebook(notebookOrOjs);
336336
}

packages/observablehq-compiler/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ export type { ohq } from "./observable-shim.ts";
22

33
export * from "./compiler.ts";
44
export { ojs2notebook, omd2notebook, omd2notebookKit, ojs2notebookKit, download } from "./util.ts";
5-
export { compile as compileKit, html2notebook, notebook2html } from "./kit/index.ts";
5+
export * from "./kit/index.ts";
66
export * from "./writer.ts";
Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,59 @@
11

2-
import { type Notebook, transpile } from "@observablehq/notebook-kit";
2+
import { type Notebook, type Cell, transpile } from "@observablehq/notebook-kit";
33
import { type Definition } from "@observablehq/notebook-kit/runtime";
44
import { constructFunction } from "./util.ts";
55

6+
export { Notebook, Cell };
7+
68
export interface CompileKitOptions {
79
inline?: boolean;
10+
resolveLocalImports?: boolean;
811
}
912

10-
export function compile(notebook: Notebook, options: CompileKitOptions = { inline: true }): Definition[] {
13+
export function compileCell(cell: Cell, { inline = true, resolveLocalImports = false }: CompileKitOptions = {}): Definition[] {
1114
const retVal: Definition[] = [];
12-
let id = 1;
13-
for (const cell of notebook.cells) {
14-
try {
15-
const compiled = transpile(cell);
16-
retVal.push({
17-
id: id++,
18-
...compiled,
19-
body: options.inline ? constructFunction(compiled.body, `cell_${id}`) : compiled.body,
20-
});
21-
if (cell.pinned) {
22-
const compiled = transpile({
23-
...cell,
24-
mode: "md",
25-
value: `\
15+
const sourceIDOffset = 1000000;
16+
try {
17+
const compiled = transpile(cell, { resolveLocalImports });
18+
retVal.push({
19+
id: cell.id,
20+
...compiled,
21+
body: inline ? constructFunction(compiled.body, `cell_${cell.id}`) : compiled.body,
22+
});
23+
if (cell.pinned) {
24+
const compiled = transpile({
25+
...cell,
26+
mode: "md",
27+
value: `\
2628
\`\`\`${cell.mode}
2729
${cell.value}
2830
\`\`\``
29-
});
30-
retVal.push({
31-
id: id++,
32-
...compiled,
33-
body: options.inline ? constructFunction(compiled.body, `cell_${id}`) : compiled.body,
34-
});
35-
}
36-
} catch (error) {
37-
console.error(`Error compiling cell ${id}:`, error);
31+
});
32+
retVal.push({
33+
id: sourceIDOffset + cell.id,
34+
...compiled,
35+
body: inline ? constructFunction(compiled.body, `cell_source_${sourceIDOffset + cell.id}`) : compiled.body,
36+
});
3837
}
38+
} catch (error) {
39+
console.error(`Error compiling cell ${cell.id}:`, error);
3940
}
4041
return retVal;
4142
}
43+
44+
export function compileNotebook(notebook: Notebook, { inline = true, resolveLocalImports = false }: CompileKitOptions = {}): Definition[] {
45+
const retVal: Definition[] = [];
46+
for (const cell of notebook.cells) {
47+
const cellDefs = compileCell(cell, { inline, resolveLocalImports });
48+
retVal.push(...cellDefs);
49+
}
50+
return retVal;
51+
}
52+
53+
export function resetCellIDs(notebook: Notebook, start: number = 0, increment: number = 1): Notebook {
54+
for (const cell of notebook.cells) {
55+
cell.id = start;
56+
start += increment;
57+
}
58+
return notebook;
59+
}
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
export * from "./util.ts";
21
export * from "./compiler.ts";
32
export * from "./util.ts";
Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { type DefineState, NotebookRuntime as NotebookRuntimeBase } from "@observablehq/notebook-kit/runtime";
2-
import { type Definition } from "./index.ts";
1+
import { type Definition, type DefineState, NotebookRuntime as NotebookRuntimeBase } from "@observablehq/notebook-kit/runtime";
32

43
import "@observablehq/notebook-kit/index.css";
54
import "@observablehq/notebook-kit/theme-air.css";
65

6+
export { Definition, DefineState };
7+
78
export class NotebookRuntime extends NotebookRuntimeBase {
89

910
stateById = new Map<number, DefineState>();
@@ -16,23 +17,50 @@ export class NotebookRuntime extends NotebookRuntimeBase {
1617
return this.stateById.has(cellId);
1718
}
1819

19-
async add(cellId: number, definition: Definition, placeholderDiv: HTMLDivElement): Promise<void> {
20-
let state: DefineState | undefined = this.stateById.get(cellId);
21-
if (state) {
22-
this.remove(cellId);
20+
async add(definition: Definition): Promise<HTMLDivElement> {
21+
if (this.stateById.has(definition.id)) {
22+
throw new Error(`Cell with id ${definition.id} already exists`);
2323
}
24-
state = { root: placeholderDiv, expanded: [], variables: [] };
25-
this.stateById.set(cellId, state);
24+
const placeholderDiv = document.createElement("div");
25+
placeholderDiv.className = "observablehq observablehq--cell";
26+
const state = { root: placeholderDiv, expanded: [], variables: [] };
27+
this.stateById.set(definition.id, state);
2628
this.define(state, definition);
2729
await this.runtime._computeNow();
30+
return state.root;
2831
}
2932

30-
async remove(cellId: number): Promise<void> {
33+
async update(definition: Definition): Promise<HTMLDivElement> {
34+
const state = this.stateById.get(definition.id);
35+
if (!state) {
36+
throw new Error(`Cell with id ${definition.id} does not exist`);
37+
}
38+
await this.clear(definition.id);
39+
this.define(state, definition);
40+
await this.runtime._computeNow();
41+
return state.root;
42+
}
43+
44+
async clear(cellId: number): Promise<void> {
3145
const state = this.stateById.get(cellId);
32-
if (!state) return;
46+
if (!state) {
47+
throw new Error(`Cell with id ${cellId} does not exist`);
48+
}
3349
[...state.variables].forEach(v => v.delete());
50+
await this.runtime._computeNow();
51+
state.variables = [];
52+
state.expanded = [];
53+
state.root.innerHTML = "";
54+
}
55+
56+
async remove(cellId: number): Promise<void> {
57+
const state = this.stateById.get(cellId);
58+
if (!state) {
59+
throw new Error(`Cell with id ${cellId} does not exist`);
60+
}
61+
void this.clear(cellId);
3462
this.stateById.delete(cellId);
35-
state.root?.remove();
63+
state.root.remove();
3664
await this.runtime._computeNow();
3765
}
3866

@@ -44,14 +72,15 @@ export class NotebookRuntime extends NotebookRuntimeBase {
4472
await this.runtime._computeNow();
4573
}
4674

47-
render(definitions: Definition[], target: HTMLElement) {
48-
definitions.forEach(definition => {
49-
const placeholderDiv = document.createElement("div");
50-
placeholderDiv.id = `cell-${definition.id}`;
51-
placeholderDiv.className = "observablehq observablehq--cell";
52-
this.stateById.set(definition.id, { root: placeholderDiv, expanded: [], variables: [] });
53-
this.define(this.stateById.get(definition.id)!, definition);
54-
target.appendChild(placeholderDiv);
55-
});
75+
async render(definitions: Definition[], target: HTMLElement) {
76+
for (const definition of definitions) {
77+
let observableDiv: HTMLDivElement;
78+
if (this.stateById.has(definition.id)) {
79+
observableDiv = await this.update(definition);
80+
} else {
81+
observableDiv = await this.add(definition);
82+
}
83+
target.appendChild(observableDiv);
84+
}
5685
}
5786
}

packages/observablehq-compiler/tests/index-notebookkit.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { compileKit, html2notebook } from "@hpcc-js/observablehq-compiler";
1+
import { compileNotebook, html2notebook } from "@hpcc-js/observablehq-compiler";
22
import { NotebookRuntime } from "@hpcc-js/observablehq-compiler/runtime";
33

44
// import "@observablehq/notebook-kit/theme-air.css";
@@ -19,13 +19,13 @@ import { NotebookRuntime } from "@hpcc-js/observablehq-compiler/runtime";
1919

2020
export async function testHtml(target) {
2121

22-
const element = document.getElementById(target);
23-
if (!element) {
24-
throw new Error(`Element with id ${target} not found`);
25-
}
22+
const element = document.getElementById(target);
23+
if (!element) {
24+
throw new Error(`Element with id ${target} not found`);
25+
}
2626

27-
// const html = await fetch("../tests/albers-usa-projection.txt");
28-
const html = `\
27+
// const html = await fetch("../tests/albers-usa-projection.txt");
28+
const html = `\
2929
<!doctype html>
3030
<notebook theme="slate">
3131
<title>Observable Notebooks 2.0 System guide</title>
@@ -210,8 +210,8 @@ export async function testHtml(target) {
210210
</script>
211211
</notebook>
212212
`;
213-
const notebook = html2notebook(html);
214-
const definitions = compileKit(notebook);
215-
const runtime = new NotebookRuntime();
216-
runtime.render(definitions, element);
213+
const notebook = html2notebook(html);
214+
const definitions = compileNotebook(notebook);
215+
const runtime = new NotebookRuntime();
216+
runtime.render(definitions, element);
217217
}

packages/observablehq-compiler/tests/index-notebookkit.ts

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { omd2notebookKit, ojs2notebookKit, compileKit, html2notebook } from "../src/index.ts";
1+
import { omd2notebookKit, ojs2notebookKit, compileNotebook, html2notebook } from "../src/index.ts";
22
import { NotebookRuntime } from "../src/kit/runtime.ts";
33

44
// import "@observablehq/notebook-kit/theme-air.css";
55
// import "@observablehq/notebook-kit/theme-coffee.css";
66
// import "@observablehq/notebook-kit/theme-cotton.css";
77
// import "@observablehq/notebook-kit/theme-deep-space.css";
88
// import "@observablehq/notebook-kit/theme-glacier.css";
9-
// import "@observablehq/notebook-kit/theme-ink.css";
9+
import "@observablehq/notebook-kit/theme-ink.css";
1010
// import "@observablehq/notebook-kit/theme-midnight.css";
1111
// import "@observablehq/notebook-kit/theme-near-midnight.css";
1212
// import "@observablehq/notebook-kit/theme-ocean-floor.css";
@@ -19,28 +19,28 @@ import { NotebookRuntime } from "../src/kit/runtime.ts";
1919

2020
export async function testHtml(target: string): Promise<void> {
2121

22-
const element = document.getElementById(target) as HTMLDivElement;
23-
if (!element) {
24-
throw new Error(`Element with id ${target} not found`);
25-
}
26-
27-
// const html = await fetch("../tests/albers-usa-projection.txt");
28-
const html = await fetch("../tests/system-guide.txt")
29-
.then((response) => {
30-
return response.text();
31-
});
32-
const notebook = html2notebook(html);
33-
const definitions = compileKit(notebook);
34-
const runtime = new NotebookRuntime();
35-
runtime.render(definitions, element);
22+
const element = document.getElementById(target) as HTMLDivElement;
23+
if (!element) {
24+
throw new Error(`Element with id ${target} not found`);
25+
}
26+
27+
// const html = await fetch("../tests/albers-usa-projection.txt");
28+
const html = await fetch("../tests/system-guide.txt")
29+
.then((response) => {
30+
return response.text();
31+
});
32+
const notebook = html2notebook(html);
33+
const definitions = compileNotebook(notebook);
34+
const runtime = new NotebookRuntime();
35+
runtime.render(definitions, element);
3636
}
3737

3838
export function testOmd(target: string): void {
39-
const element = document.getElementById(target) as HTMLDivElement;
40-
if (!element) {
41-
throw new Error(`Element with id ${target} not found`);
42-
}
43-
const omd2 = `\
39+
const element = document.getElementById(target) as HTMLDivElement;
40+
if (!element) {
41+
throw new Error(`Element with id ${target} not found`);
42+
}
43+
const omd2 = `\
4444
4545
## Imports (dot)
4646
@@ -331,18 +331,18 @@ value
331331
332332
`;
333333

334-
const notebook = omd2notebookKit(omd2);
335-
const definitions = compileKit(notebook);
336-
const runtime = new NotebookRuntime();
337-
runtime.render(definitions, element);
334+
const notebook = omd2notebookKit(omd2);
335+
const definitions = compileKit(notebook);
336+
const runtime = new NotebookRuntime();
337+
runtime.render(definitions, element);
338338
}
339339

340340
export function testOjs(target: string): void {
341-
const element = document.getElementById(target) as HTMLDivElement;
342-
if (!element) {
343-
throw new Error(`Element with id ${target} not found`);
344-
}
345-
const ojs = `\
341+
const element = document.getElementById(target) as HTMLDivElement;
342+
if (!element) {
343+
throw new Error(`Element with id ${target} not found`);
344+
}
345+
const ojs = `\
346346
2 * 3 * 7
347347
{
348348
let sum = 0;
@@ -357,8 +357,8 @@ md \`# lib.ojs\`;
357357
mo = 38 + 40;
358358
`;
359359

360-
const notebook = ojs2notebookKit(ojs);
361-
const definitions = compileKit(notebook);
362-
const runtime = new NotebookRuntime();
363-
runtime.render(definitions, element);
360+
const notebook = ojs2notebookKit(ojs);
361+
const definitions = compileKit(notebook);
362+
const runtime = new NotebookRuntime();
363+
runtime.render(definitions, element);
364364
}

0 commit comments

Comments
 (0)