Skip to content

Commit dcfa41d

Browse files
committed
WIP
1 parent d137767 commit dcfa41d

21 files changed

+344
-73
lines changed

packages/observablehq-compiler/.vscode/launch.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@
4343
"!**/node_modules/**"
4444
]
4545
},
46+
{
47+
"name": "index-notebook-kit.html",
48+
"request": "launch",
49+
"type": "msedge",
50+
"url": "http://localhost:5514/index-notebook-kit.html",
51+
"runtimeArgs": [
52+
"--disable-web-security"
53+
],
54+
"webRoot": "${workspaceFolder}",
55+
"outFiles": [
56+
"${workspaceFolder}/**/*.js",
57+
"!**/node_modules/**"
58+
]
59+
},
4660
{
4761
"name": "index-preview.html",
4862
"request": "launch",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<title>Home</title>
6+
<style>
7+
body {
8+
font-family: Arial, sans-serif;
9+
margin: 0;
10+
padding: 0;
11+
background-color: #f0f0f0;
12+
}
13+
14+
h1 {
15+
text-align: center;
16+
margin-top: 50px;
17+
}
18+
19+
#placeholder {
20+
width: 100%;
21+
height: 500px;
22+
background-color: #fff;
23+
margin-top: 20px;
24+
}
25+
</style>
26+
</head>
27+
28+
<body>
29+
<h1>ESM Quick Test</h1>
30+
<div id="placeholder"></div>
31+
<script type="module">
32+
33+
import { test } from "./tests/index-notebook-kit.ts";
34+
test();
35+
36+
</script>
37+
</body>
38+
39+
</html>
Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
export type { ohq } from "./observable-shim.ts";
1+
export type { ohq } from "./ojs/observable-shim.ts";
22

3-
export * from "./compiler.ts";
4-
export { ojs2notebook, omd2notebook, download } from "./util.ts";
5-
export * from "./writer.ts";
3+
export * from "./ojs/compiler.ts";
4+
export { ojs2notebook, omd2notebook } from "./ojs/util.ts";
5+
export { download } from "./util/comms.ts";
6+
export * from "./ojs/writer.ts";
67

78
import "../src/index.css";
89

9-
// Note: ohqnk exports are not re-exported at the root to avoid pulling
10-
// @observablehq/notebook-kit (which uses npm: specifiers and top-level await)
11-
// into Node/test environments by default.
10+
export * from "./ohqnk/index.ts";
Lines changed: 118 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,148 @@
1-
import type { Notebook } from "@observablehq/notebook-kit";
2-
import { define, type DefineState, type Definition } from "@observablehq/notebook-kit/runtime";
1+
import { Identifier } from "acorn";
2+
import { FileAttachments } from "@observablehq/stdlib";
3+
import { type Notebook, type Cell, parseJavaScript, transpile, type TranspiledJavaScript } from "@observablehq/notebook-kit";
4+
import { type DefineState, type Definition } from "@observablehq/notebook-kit/runtime";
35
import type { Inspector } from "@observablehq/inspector";
46
import type { Runtime, Module } from "@observablehq/runtime";
7+
import { define } from "@observablehq/notebook-kit/runtime";
8+
import { fixRelativeUrl } from "../util/paths.ts";
9+
import { fetchEx } from "../util/comms.ts";
10+
import { html2notebook } from "./util.ts";
511

612
export type InspectorFactory = (name: string | undefined, id: string | number) => Inspector;
713

8-
class RuntimeWrapper {
9-
protected stateById = new Map<string, DefineState>();
14+
const FunctionConstructors = {
15+
regular: Object.getPrototypeOf(function () { }).constructor,
16+
async: Object.getPrototypeOf(async function () { }).constructor,
17+
generator: Object.getPrototypeOf(function* () { }).constructor,
18+
asyncGenerator: Object.getPrototypeOf(async function* () { }).constructor,
19+
} as const;
1020

11-
constructor() {
21+
type FunctionConstructor =
22+
| typeof FunctionConstructors.regular
23+
| typeof FunctionConstructors.async
24+
| typeof FunctionConstructors.generator
25+
| typeof FunctionConstructors.asyncGenerator;
1226

27+
type FunctionLikeExpression = {
28+
type: "FunctionExpression" | "ArrowFunctionExpression";
29+
body: { type: "BlockStatement" | "Expression"; start: number; end: number };
30+
async?: boolean;
31+
generator?: boolean;
32+
params?: Identifier[];
33+
};
34+
35+
function constructFunction(input: string) {
36+
const { body } = parseJavaScript(input) as { body: FunctionLikeExpression };
37+
if (body.type !== "FunctionExpression" && body.type !== "ArrowFunctionExpression") {
38+
throw new Error(`Unsupported function type: ${body.type}`);
1339
}
1440

15-
add(vscodeCellID: string, definition: Definition, placeholderDiv: HTMLDivElement): void {
16-
let state = this.stateById.get(vscodeCellID);
41+
const Constructor = (body.async && body.generator) ?
42+
FunctionConstructors.asyncGenerator :
43+
body.async ?
44+
FunctionConstructors.async :
45+
body.generator ?
46+
FunctionConstructors.generator :
47+
FunctionConstructors.regular;
48+
49+
const params = body.params?.map(param => input.substring(param.start, param.end)).join(", ") || "";
50+
const bodyStr = body.body.type === "BlockStatement" ? input.substring(body.body.start, body.body.end).slice(1, -1).trim() : input.substring(body.body.start, body.body.end);
51+
return Constructor(params, bodyStr);
52+
}
53+
interface CellEx extends Cell {
54+
transpiled: TranspiledJavaScript;
55+
}
56+
function transpileCell(cell: Cell): CellEx {
57+
const retVal: CellEx = {
58+
...cell,
59+
transpiled: transpile(cell.value, cell.mode)
60+
};
61+
retVal.transpiled.body = constructFunction(retVal.transpiled.body);
62+
return retVal;
63+
}
64+
export interface CompileOptions {
65+
baseUrl?: string;
66+
importMode?: "recursive" | "precompiled";
67+
}
68+
export function notebook(_cells: CellEx[] = [], { baseUrl = ".", importMode = "precompiled" }: CompileOptions = {}) {
69+
const stateById = new Map<number, DefineState>();
70+
71+
function add(vscodeCellID: number, definition: Definition, inspector: Inspector): void {
72+
let state = stateById.get(vscodeCellID);
1773
if (state) {
1874
state.variables.forEach((v) => v.delete());
1975
state.variables = [];
2076
} else {
21-
state = { root: placeholderDiv, expanded: [], variables: [] };
22-
this.stateById.set(vscodeCellID, state);
77+
state = { root: inspector._node, expanded: [], variables: [] };
78+
stateById.set(vscodeCellID, state);
2379
}
2480
define(state, definition);
2581
}
2682

27-
remove(vscodeCellID: string): void {
28-
const state = this.stateById.get(vscodeCellID)!;
83+
function remove(vscodeCellID: number): void {
84+
const state = stateById.get(vscodeCellID)!;
2985
state.root.remove();
3086
state.variables.forEach((v) => v.delete());
31-
this.stateById.delete(vscodeCellID);
87+
stateById.delete(vscodeCellID);
3288
}
3389

34-
removeAll(): void {
35-
const keys = Array.from(this.stateById.keys());
90+
function removeAll(): void {
91+
const keys = Array.from(stateById.keys());
3692
for (const key of keys) {
37-
this.remove(key);
93+
remove(key);
3894
}
3995
}
4096

41-
}
42-
43-
// File ---
44-
export function compile(notebook: Notebook) {
45-
const retVal = (runtime: Runtime, inspector?: InspectorFactory): Module => {
46-
const main: Module = runtime.module();
97+
const files: any[] = [];
98+
const fileAttachmentsMap = new Map<string, any>(files);
99+
const cells = new Map<number, CellEx>(_cells.map(c => [c.id, c]));
47100

48-
notebook.cells.forEach(cell => {
49-
main.define(cell.id, cell.value);
101+
const retVal = (runtime: Runtime, inspectorFactory: InspectorFactory): Module => {
102+
runtime.main.builtin("fetchEx", fetchEx);
103+
cells.forEach(cell => {
104+
const inspector = inspectorFactory(`cell-${cell.id}`, cell.id);
105+
add(cell.id, { id: cell.id, ...cell.transpiled }, inspector);
50106
});
51-
return main;
107+
return runtime.main;
52108
};
109+
retVal.fileAttachments = fileAttachmentsMap;
110+
retVal.cells = cells;
111+
// retVal.set = async (n: Cell): Promise<JavaScriptCell> => {
112+
// const cell = await transpileCell(n, { baseUrl, importMode });
113+
// retVal.delete(cell.id);
114+
// cells.set(cell.id, cell);
115+
// return cell;
116+
// };
117+
// retVal.get = (id: string | number): JavaScriptCell | undefined => {
118+
// return cells.get(id);
119+
// };
120+
// retVal.delete = (id: string | number): boolean => {
121+
// const cell = cells.get(id);
122+
// if (cell) {
123+
// cell.delete();
124+
// return cells.delete(id);
125+
// }
126+
// return false;
127+
// };
128+
// retVal.clear = () => {
129+
// cells.forEach(cell => cell.delete());
130+
// cells.clear();
131+
// };
132+
// retVal.write = (w: Writer) => {
133+
// w.files(_files);
134+
// cells.forEach(cell => cell.write(w));
135+
// };
136+
// retVal.toString = (w = new Writer()) => {
137+
// retVal.write(w);
138+
// return w.toString().trim();
139+
// };
53140
return retVal;
54141
}
142+
143+
export function compile(notebookOrOjs: Notebook | string, { baseUrl = ".", importMode = "precompiled" }: CompileOptions = {}) {
144+
const htmlNotebook = typeof notebookOrOjs === "string" ? html2notebook(notebookOrOjs) : notebookOrOjs;
145+
const _cells: CellEx[] = htmlNotebook.cells.map(n => transpileCell(n));
146+
return notebook(_cells, { baseUrl, importMode });
147+
}
148+
export type compileFunc = Awaited<ReturnType<typeof compile>>;

packages/observablehq-compiler/src/ohqnk/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Notebook as ohqnkNotebook, Cell as ohqnkCell } from "@observablehq/notebook-kit";
1+
import type { Notebook as ohqnkNotebook, Cell as ohqnkCell } from "@observablehq/notebook-kit";
22
import { compile as ohqnkCompile } from "./compiler.ts";
3+
import { html2notebook as ohqnkHtml2notebook } from "./util.ts";
34

45
export namespace ohqnk {
56
export interface Notebook extends ohqnkNotebook {
@@ -9,4 +10,5 @@ export namespace ohqnk {
910
}
1011

1112
export const compile = ohqnkCompile;
13+
export const html2notebook = ohqnkHtml2notebook;
1214
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { deserialize, type Notebook } from "@observablehq/notebook-kit";
2+
3+
export function html2notebook(html: string): Notebook {
4+
return deserialize(html);
5+
}
File renamed without changes.

packages/observablehq-compiler/src/compiler.ts renamed to packages/observablehq-compiler/src/ojs/compiler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { FileAttachments } from "@observablehq/stdlib";
2+
import { fixRelativeUrl, isRelativePath } from "../util/paths.ts";
3+
import { fetchEx } from "../util/comms.ts";
24
import { ohq, splitModule } from "./observable-shim.ts";
35
import { parseCell, ParsedImportCell } from "./cst.ts";
46
import { Writer } from "./writer.ts";
5-
import { fixRelativeUrl, isRelativePath, encodeBacktick, fetchEx, obfuscatedImport, ojs2notebook, omd2notebook } from "./util.ts";
7+
import { encodeBacktick, obfuscatedImport, ojs2notebook, omd2notebook } from "./util.ts";
68

79
// Inspector Factory ---
810
export type InspectorFactoryEx = (name: string | undefined, id: string | number) => Inspector;

packages/observablehq-compiler/src/cst.ts renamed to packages/observablehq-compiler/src/ojs/cst.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ancestor, parseCell as ohqParseCell, Cell, Node, walk, AncestorVisitors } from "./observable-shim.ts";
22

3-
import { fixRelativeUrl, createFunction, Refs } from "./util.ts";
3+
import { createFunction, Refs } from "./util.ts";
4+
import { fixRelativeUrl } from "../util/paths.ts";
45

56
function calcRefs(cellAst: Cell, cellStr: string): Refs {
67
if (cellAst.references === undefined) return { inputs: [], args: [], patches: [] };
File renamed without changes.

0 commit comments

Comments
 (0)