@hpcc-js/observablehq-compiler 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -0
- package/bin/ojscc.mjs +75 -0
- package/dist/index.css +1 -0
- package/dist/index.esm.css +1 -0
- package/dist/index.esm.js +7168 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +7176 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -0
- package/src/__package__.ts +3 -0
- package/src/__tests__/File Attachments.ts +895 -0
- package/src/__tests__/Introduction to Imports.ts +749 -0
- package/src/__tests__/Observable TimeChart.ts +772 -0
- package/src/__tests__/index.ts +13 -0
- package/src/__tests__/node.ts +177 -0
- package/src/__tests__/tsconfig.json +21 -0
- package/src/compiler.md +234 -0
- package/src/compiler.ts +264 -0
- package/src/cst.ts +172 -0
- package/src/index.css +460 -0
- package/src/index.ts +7 -0
- package/src/util.md +113 -0
- package/src/util.ts +154 -0
- package/src/writer.ts +80 -0
- package/types/__package__.d.ts +4 -0
- package/types/__package__.d.ts.map +1 -0
- package/types/__tests__/File Attachments.d.ts +110 -0
- package/types/__tests__/File Attachments.d.ts.map +1 -0
- package/types/__tests__/Introduction to Imports.d.ts +120 -0
- package/types/__tests__/Introduction to Imports.d.ts.map +1 -0
- package/types/__tests__/Observable TimeChart.d.ts +111 -0
- package/types/__tests__/Observable TimeChart.d.ts.map +1 -0
- package/types/__tests__/index.d.ts +2 -0
- package/types/__tests__/index.d.ts.map +1 -0
- package/types/__tests__/node.d.ts +2 -0
- package/types/__tests__/node.d.ts.map +1 -0
- package/types/compiler.d.ts +88 -0
- package/types/compiler.d.ts.map +1 -0
- package/types/cst.d.ts +42 -0
- package/types/cst.d.ts.map +1 -0
- package/types/index.d.ts +6 -0
- package/types/index.d.ts.map +1 -0
- package/types/util.d.ts +26 -0
- package/types/util.d.ts.map +1 -0
- package/types/writer.d.ts +19 -0
- package/types/writer.d.ts.map +1 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import fetch, { Blob, blobFrom, blobFromSync, File, fileFrom, fileFromSync, FormData, Headers, Request, Response } from "node-fetch";
|
|
3
|
+
if (!globalThis.fetch) {
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
globalThis.fetch = fetch;
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
globalThis.Headers = Headers;
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
globalThis.Request = Request;
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
globalThis.Response = Response;
|
|
12
|
+
}
|
|
13
|
+
export * from "./node";
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { Library, Runtime, Inspector } from "@observablehq/runtime";
|
|
2
|
+
import { expect } from "chai";
|
|
3
|
+
import { compile, download, ohq, ojs2notebook } from "../index";
|
|
4
|
+
import { Writer } from "../writer";
|
|
5
|
+
import { fa } from "./File Attachments";
|
|
6
|
+
import { imports } from "./Introduction to Imports";
|
|
7
|
+
import { timechart } from "./Observable TimeChart";
|
|
8
|
+
|
|
9
|
+
const placeholder = globalThis?.document?.getElementById("placeholder");
|
|
10
|
+
|
|
11
|
+
const ojs = `
|
|
12
|
+
42;
|
|
13
|
+
a = 1
|
|
14
|
+
b = 2
|
|
15
|
+
c = a + b
|
|
16
|
+
d = {
|
|
17
|
+
yield 1;
|
|
18
|
+
yield 2;
|
|
19
|
+
yield 3;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
viewof e = {
|
|
23
|
+
let output = {};
|
|
24
|
+
let listeners = [];
|
|
25
|
+
output.value = 10;
|
|
26
|
+
output.addEventListener = (listener) => listeners.push(listener);;
|
|
27
|
+
output.removeEventListener = (listener) => {
|
|
28
|
+
listeners = listeners.filter(l => l !== listener);
|
|
29
|
+
};
|
|
30
|
+
return output;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
tenTimes = {
|
|
34
|
+
for (let i = 0; i < 10; i++) {
|
|
35
|
+
yield Promises.delay(100);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
f = {
|
|
40
|
+
tenTimes;
|
|
41
|
+
return (this || 0) + 1;
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
describe("ojs", function () {
|
|
46
|
+
it("quickstart", async function () {
|
|
47
|
+
this.timeout(10000);
|
|
48
|
+
|
|
49
|
+
const notebook = await download("https://observablehq.com/@observablehq/summary-table");
|
|
50
|
+
const compiledNB = await compile(notebook);
|
|
51
|
+
|
|
52
|
+
const library = new Library();
|
|
53
|
+
const runtime = new Runtime(library);
|
|
54
|
+
|
|
55
|
+
compiledNB(runtime, name => {
|
|
56
|
+
const div = globalThis?.document.createElement("div");
|
|
57
|
+
placeholder.appendChild(div);
|
|
58
|
+
return new Inspector(div);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it.only("simple", async function () {
|
|
63
|
+
this.timeout(10000);
|
|
64
|
+
|
|
65
|
+
const notebook = ojs2notebook(ojs);
|
|
66
|
+
const define = await compile(notebook);
|
|
67
|
+
|
|
68
|
+
const library = new Library();
|
|
69
|
+
const runtime = new Runtime(library);
|
|
70
|
+
const main: ohq.Module = define(runtime, name => {
|
|
71
|
+
if (placeholder) {
|
|
72
|
+
const div = globalThis?.document.createElement("div");
|
|
73
|
+
placeholder.appendChild(div);
|
|
74
|
+
return new Inspector(div);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
pending() { },
|
|
78
|
+
fulfilled(value) { console.info("fulfilled", name, value); },
|
|
79
|
+
rejected(error) { console.error("rejected", name, error); },
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(await main.value("d")).to.equal(1);
|
|
84
|
+
expect(await main.value("d")).to.equal(2);
|
|
85
|
+
expect(await main.value("d")).to.equal(3);
|
|
86
|
+
expect(await main.value("d")).to.equal(3);
|
|
87
|
+
expect(await main.value("d")).to.equal(3);
|
|
88
|
+
|
|
89
|
+
expect(await main.value("c")).to.equal(3);
|
|
90
|
+
expect(await main.value("a")).to.equal(1);
|
|
91
|
+
expect(await main.value("b")).to.equal(2);
|
|
92
|
+
|
|
93
|
+
expect(await main.value("e")).to.equal(10);
|
|
94
|
+
const viewOfE = await main.value("viewof e");
|
|
95
|
+
expect(viewOfE).to.have.property("value");
|
|
96
|
+
expect(viewOfE).to.have.property("addEventListener");
|
|
97
|
+
expect(viewOfE).to.have.property("removeEventListener");
|
|
98
|
+
|
|
99
|
+
await main.value("tenTimes");
|
|
100
|
+
|
|
101
|
+
for (const cell of define.cells) {
|
|
102
|
+
cell.dispose();
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("compiler", async function () {
|
|
108
|
+
const writer = new Writer();
|
|
109
|
+
const define = await compile(imports as any);
|
|
110
|
+
define.write(writer);
|
|
111
|
+
console.info(writer.toString());
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("File Attachements", async function () {
|
|
115
|
+
|
|
116
|
+
const define = await compile(fa as any);
|
|
117
|
+
|
|
118
|
+
const library = new Library();
|
|
119
|
+
const runtime = new Runtime(library);
|
|
120
|
+
/*const main: ohq.Module =*/ define(runtime, name => {
|
|
121
|
+
if (placeholder) {
|
|
122
|
+
const div = globalThis?.document.createElement("div");
|
|
123
|
+
placeholder.appendChild(div);
|
|
124
|
+
return new Inspector(div);
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
pending() { console.info("pending", name); },
|
|
128
|
+
fulfilled(value) { console.info("fulfilled", name, value); },
|
|
129
|
+
rejected(error) { console.error("rejected", name, error); },
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("Introduction to Imports", async function () {
|
|
136
|
+
|
|
137
|
+
const define = await compile(imports as any);
|
|
138
|
+
|
|
139
|
+
const library = new Library();
|
|
140
|
+
const runtime = new Runtime(library);
|
|
141
|
+
/*const main: ohq.Module =*/ define(runtime, name => {
|
|
142
|
+
if (placeholder) {
|
|
143
|
+
const div = globalThis?.document.createElement("div");
|
|
144
|
+
placeholder.appendChild(div);
|
|
145
|
+
return new Inspector(div);
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
pending() { console.info("pending", name); },
|
|
149
|
+
fulfilled(value) { console.info("fulfilled", name, value); },
|
|
150
|
+
rejected(error) { console.error("rejected", name, error); },
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("Observable TimeChart", async function () {
|
|
157
|
+
|
|
158
|
+
const define2 = await compile(timechart as any);
|
|
159
|
+
|
|
160
|
+
const library = new Library();
|
|
161
|
+
const runtime = new Runtime(library);
|
|
162
|
+
/*const main: ohq.Module =*/ define2(runtime, name => {
|
|
163
|
+
if (placeholder) {
|
|
164
|
+
const div = globalThis?.document.createElement("div");
|
|
165
|
+
placeholder.appendChild(div);
|
|
166
|
+
return new Inspector(div);
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
pending() { console.info("pending", name); },
|
|
170
|
+
fulfilled(value) { console.info("fulfilled", name, value); },
|
|
171
|
+
rejected(error) { console.error("rejected", name, error); },
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../tsconfig.settings.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "./src",
|
|
5
|
+
"outDir": "./lib-umd",
|
|
6
|
+
"declarationDir": "./types",
|
|
7
|
+
"target": "ES2018",
|
|
8
|
+
"noImplicitThis": true,
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"lib": [
|
|
11
|
+
"dom",
|
|
12
|
+
"ES2018"
|
|
13
|
+
],
|
|
14
|
+
"types": [
|
|
15
|
+
"mocha"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"include": [
|
|
19
|
+
"./src/**/*"
|
|
20
|
+
]
|
|
21
|
+
}
|
package/src/compiler.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Compiler
|
|
2
|
+
|
|
3
|
+
Observable HQ Notebook Compiler and Interpreter
|
|
4
|
+
|
|
5
|
+
## Minimal JSON Notebook
|
|
6
|
+
|
|
7
|
+
While the compiler supports and persists the entire Observable HQ Notebook (v3), it only needs a minimal subset to actually function:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
interface Notebook {
|
|
11
|
+
files: File[];
|
|
12
|
+
nodes: Node[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface Node {
|
|
16
|
+
id: number | string;
|
|
17
|
+
mode: "js" | "md";
|
|
18
|
+
value: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface File {
|
|
22
|
+
name: string;
|
|
23
|
+
url: string;
|
|
24
|
+
mime_type?: string;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
<a name="compile" href="#compile">#</a> **compile**(_notebook_) => Promise\<function\> · [<>](https://github.com/hpcc-systems/Visualization/blob/trunk/packages/observablehq/compiler/src/compiler.ts "Source")
|
|
31
|
+
|
|
32
|
+
* _notebook_: JSON Notebook, see [utilities](./util) for examples on how to fetch or create a notebook.
|
|
33
|
+
* _returns_: Returns a Promise to a "define" function that is compatible with the ["@observablehq/runtime"](https://github.com/observablehq/runtime) and ["@observablehq/inspector"](https://github.com/observablehq/inspector)
|
|
34
|
+
|
|
35
|
+
## Hello World Example
|
|
36
|
+
|
|
37
|
+
First take a simple hello world notebook
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
const notebook = {
|
|
41
|
+
files: [],
|
|
42
|
+
nodes: [
|
|
43
|
+
{
|
|
44
|
+
id: 1,
|
|
45
|
+
value: "md`# ${h} ${w}`",
|
|
46
|
+
mode: "js"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 1,
|
|
50
|
+
value: "h = \"Hello\"",
|
|
51
|
+
mode: "js"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 2,
|
|
55
|
+
value: "w = \"World\"",
|
|
56
|
+
mode: "js"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Then import the compiler and invoke it:
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
import { compile } from "@hpcc-js/observablehq-compiler";
|
|
66
|
+
|
|
67
|
+
const compiledNotebook = await compile(notebook);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
To render it to a web page, simply follow the same steps as if you had downloaded a compiled version from ObservableHQ site:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
|
|
74
|
+
import { Library, Runtime, Inspector } from "https://cdn.skypack.dev/@observablehq/runtime";
|
|
75
|
+
|
|
76
|
+
const placeholder = document.getElementById("placeholder");
|
|
77
|
+
|
|
78
|
+
const library = new Library();
|
|
79
|
+
const runtime = new Runtime(library);
|
|
80
|
+
|
|
81
|
+
compiledNotebook(runtime, name => {
|
|
82
|
+
const div = document.createElement("div");
|
|
83
|
+
placeholder.appendChild(div);
|
|
84
|
+
return new Inspector(div);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Putting it all together:
|
|
90
|
+
|
|
91
|
+
<ClientOnly>
|
|
92
|
+
<hpcc-vitepress preview_height_ratio=0.4 style="width:100%;height:400px">
|
|
93
|
+
<div id="placeholder" style="height:100%;overflow-y:scroll">
|
|
94
|
+
</div>
|
|
95
|
+
<script type="module">
|
|
96
|
+
import { Library, Runtime, Inspector } from "https://cdn.skypack.dev/@observablehq/runtime";
|
|
97
|
+
import { compile } from "@hpcc-js/observablehq-compiler";
|
|
98
|
+
|
|
99
|
+
const placeholder = document.getElementById("placeholder");
|
|
100
|
+
|
|
101
|
+
const notebook = {
|
|
102
|
+
files: [],
|
|
103
|
+
nodes: [
|
|
104
|
+
{
|
|
105
|
+
id: 1,
|
|
106
|
+
value: "md`# ${h} ${w}`",
|
|
107
|
+
mode: "js"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 1,
|
|
111
|
+
value: "h = \"Hello\"",
|
|
112
|
+
mode: "js"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: 2,
|
|
116
|
+
value: "w = \"World\"",
|
|
117
|
+
mode: "js"
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const compiledNotebook = await compile(notebook);
|
|
123
|
+
|
|
124
|
+
const library = new Library();
|
|
125
|
+
const runtime = new Runtime(library);
|
|
126
|
+
compiledNotebook(runtime, name => {
|
|
127
|
+
const div = document.createElement("div");
|
|
128
|
+
placeholder.appendChild(div);
|
|
129
|
+
return new Inspector(div);
|
|
130
|
+
});
|
|
131
|
+
</script>
|
|
132
|
+
</hpcc-vitepress>
|
|
133
|
+
</ClientOnly>
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
To output the generated code simply call `toString` on the compiled function:
|
|
138
|
+
|
|
139
|
+
<ClientOnly>
|
|
140
|
+
<hpcc-vitepress style="width:100%;height:600px">
|
|
141
|
+
<hpcc-codemirror id="placeholder" mode="javascript" theme="dark" style="width:100%;height:100%">
|
|
142
|
+
</hpcc-codemirror>
|
|
143
|
+
<script type="module">
|
|
144
|
+
import "@hpcc-js/wc-editor";
|
|
145
|
+
import { compile } from "@hpcc-js/observablehq-compiler";
|
|
146
|
+
|
|
147
|
+
const notebook = {
|
|
148
|
+
files: [],
|
|
149
|
+
nodes: [
|
|
150
|
+
{
|
|
151
|
+
id: 1,
|
|
152
|
+
value: "md`# ${h} ${w}`",
|
|
153
|
+
mode: "js"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: 1,
|
|
157
|
+
value: "h = \"Hello\"",
|
|
158
|
+
mode: "js"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: 2,
|
|
162
|
+
value: "w = \"World\"",
|
|
163
|
+
mode: "js"
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const compiledNotebook = await compile(notebook);
|
|
169
|
+
const placeholder = document.getElementById("placeholder");
|
|
170
|
+
placeholder.text = compiledNotebook.toString();
|
|
171
|
+
</script>
|
|
172
|
+
</hpcc-vitepress>
|
|
173
|
+
</ClientOnly>
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## MoreExamples
|
|
178
|
+
|
|
179
|
+
* [@observablehq/plot](https://observablehq.com/@observablehq/plot)
|
|
180
|
+
|
|
181
|
+
<ClientOnly>
|
|
182
|
+
<hpcc-vitepress style="width:100%;height:600px">
|
|
183
|
+
<div id="placeholder" style="height:400px;overflow-y:scroll">
|
|
184
|
+
</div>
|
|
185
|
+
<script type="module">
|
|
186
|
+
import { Library, Runtime, Inspector } from "https://cdn.skypack.dev/@observablehq/runtime";
|
|
187
|
+
import { download, compile } from "@hpcc-js/observablehq-compiler";
|
|
188
|
+
|
|
189
|
+
const notebookUrl = "https://observablehq.com/@observablehq/plot";
|
|
190
|
+
const placeholder = document.getElementById("placeholder");
|
|
191
|
+
|
|
192
|
+
const notebook = await download(notebookUrl);
|
|
193
|
+
const compiledNB = await compile(notebook);
|
|
194
|
+
|
|
195
|
+
const library = new Library();
|
|
196
|
+
const runtime = new Runtime(library);
|
|
197
|
+
compiledNB(runtime, name => {
|
|
198
|
+
const div = document.createElement("div");
|
|
199
|
+
placeholder.appendChild(div);
|
|
200
|
+
return new Inspector(div);
|
|
201
|
+
});
|
|
202
|
+
</script>
|
|
203
|
+
</hpcc-vitepress>
|
|
204
|
+
</ClientOnly>
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
* [@mbostock/fullscreen-canvas](https://observablehq.com/@mbostock/fullscreen-canvas)
|
|
209
|
+
|
|
210
|
+
<ClientOnly>
|
|
211
|
+
<hpcc-vitepress style="width:100%;height:600px">
|
|
212
|
+
<div id="placeholder" style="height:400px;overflow-y:scroll">
|
|
213
|
+
</div>
|
|
214
|
+
<script type="module">
|
|
215
|
+
import { Library, Runtime, Inspector } from "https://cdn.skypack.dev/@observablehq/runtime";
|
|
216
|
+
import { download, compile } from "@hpcc-js/observablehq-compiler";
|
|
217
|
+
|
|
218
|
+
const notebookUrl = "https://observablehq.com/@mbostock/fullscreen-canvas";
|
|
219
|
+
const placeholder = document.getElementById("placeholder");
|
|
220
|
+
|
|
221
|
+
const notebook = await download(notebookUrl);
|
|
222
|
+
const compiledNB = await compile(notebook);
|
|
223
|
+
|
|
224
|
+
const library = new Library();
|
|
225
|
+
const runtime = new Runtime(library);
|
|
226
|
+
compiledNB(runtime, name => {
|
|
227
|
+
const div = document.createElement("div");
|
|
228
|
+
placeholder.appendChild(div);
|
|
229
|
+
return new Inspector(div);
|
|
230
|
+
});
|
|
231
|
+
</script>
|
|
232
|
+
</hpcc-vitepress>
|
|
233
|
+
</ClientOnly>
|
|
234
|
+
|
package/src/compiler.ts
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import type { ohq } from "@hpcc-js/observable-shim";
|
|
2
|
+
|
|
3
|
+
import { endsWith, join } from "@hpcc-js/util";
|
|
4
|
+
import { parseCell, ParsedImportCell } from "./cst";
|
|
5
|
+
import { Writer } from "./writer";
|
|
6
|
+
import { encodeBacktick, fetchEx, obfuscatedImport, ojs2notebook, omd2notebook } from "./util";
|
|
7
|
+
|
|
8
|
+
interface ImportDefine {
|
|
9
|
+
(runtime: ohq.Runtime, inspector?: ohq.InspectorFactory): ohq.Module;
|
|
10
|
+
dispose: () => void;
|
|
11
|
+
write: (w: Writer) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function importFile(relativePath: string, baseUrl: string) {
|
|
15
|
+
const path = join(baseUrl, relativePath);
|
|
16
|
+
const content = await fetchEx(path).then(r => r.text());
|
|
17
|
+
let notebook: ohq.Notebook;
|
|
18
|
+
if (endsWith(relativePath, ".ojsnb")) {
|
|
19
|
+
notebook = JSON.parse(content);
|
|
20
|
+
} else if (endsWith(relativePath, ".ojs")) {
|
|
21
|
+
notebook = ojs2notebook(content);
|
|
22
|
+
} else if (endsWith(relativePath, ".omd")) {
|
|
23
|
+
notebook = omd2notebook(content);
|
|
24
|
+
}
|
|
25
|
+
const retVal: ImportDefine = compile(notebook, baseUrl) as any;
|
|
26
|
+
retVal.dispose = () => { };
|
|
27
|
+
retVal.write = (w: Writer) => {
|
|
28
|
+
w.import(path);
|
|
29
|
+
};
|
|
30
|
+
return retVal;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// @ts-ignore - use precompiled notebook from observable
|
|
34
|
+
async function importCompiledNotebook(partial: string) {
|
|
35
|
+
const url = `https://api.observablehq.com/${partial[0] === "@" ? partial : `d/${partial}`}.js?v=3`;
|
|
36
|
+
let impMod = {
|
|
37
|
+
default: function (runtime: ohq.Runtime, inspector?: ohq.InspectorFactory): ohq.Module {
|
|
38
|
+
return undefined;
|
|
39
|
+
} as any
|
|
40
|
+
};
|
|
41
|
+
try {
|
|
42
|
+
impMod = await obfuscatedImport(url);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
}
|
|
45
|
+
const retVal: ImportDefine = impMod.default;
|
|
46
|
+
retVal.dispose = () => { };
|
|
47
|
+
retVal.write = (w: Writer) => {
|
|
48
|
+
w.import(url);
|
|
49
|
+
};
|
|
50
|
+
return retVal;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// @ts-ignore - recursive notebook parsing and compiling
|
|
54
|
+
async function importNotebook(partial: string) {
|
|
55
|
+
const url = `https://api.observablehq.com/document/${partial}`;
|
|
56
|
+
const notebook = fetchEx(url)
|
|
57
|
+
.then(r => {
|
|
58
|
+
return r.json();
|
|
59
|
+
}).catch(e => {
|
|
60
|
+
console.error(url);
|
|
61
|
+
console.error(e);
|
|
62
|
+
});
|
|
63
|
+
const retVal: ImportDefine = compile(await notebook) as any;
|
|
64
|
+
retVal.dispose = () => { };
|
|
65
|
+
retVal.write = (w: Writer) => {
|
|
66
|
+
w.import(url);
|
|
67
|
+
};
|
|
68
|
+
return retVal;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function createVariable(inspect: boolean, name?: string, inputs?: string[], definition?: any, inline = false) {
|
|
72
|
+
|
|
73
|
+
let i: ohq.Inspector;
|
|
74
|
+
let v: ohq.Variable;
|
|
75
|
+
|
|
76
|
+
const retVal = (module: ohq.Module, inspector?: ohq.InspectorFactory) => {
|
|
77
|
+
i = inspect ? inspector(name) : undefined;
|
|
78
|
+
v = module.variable(i);
|
|
79
|
+
if (arguments.length > 1) {
|
|
80
|
+
try {
|
|
81
|
+
v.define(name, inputs, definition);
|
|
82
|
+
} catch (e: any) {
|
|
83
|
+
console.error(e?.message);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return v;
|
|
87
|
+
};
|
|
88
|
+
retVal.dispose = () => {
|
|
89
|
+
try {
|
|
90
|
+
i?._node?.remove();
|
|
91
|
+
} catch (e) {
|
|
92
|
+
}
|
|
93
|
+
i = undefined;
|
|
94
|
+
try {
|
|
95
|
+
v?.delete();
|
|
96
|
+
} catch (e) {
|
|
97
|
+
}
|
|
98
|
+
v = undefined;
|
|
99
|
+
};
|
|
100
|
+
retVal.write = (w: Writer) => {
|
|
101
|
+
if (inline) {
|
|
102
|
+
w.define({ id: name, inputs, func: definition }, inspect, true);
|
|
103
|
+
} else {
|
|
104
|
+
const id = w.function({ id: name, func: definition });
|
|
105
|
+
w.define({ id: name, inputs, func: definition }, inspect, false, id);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
return retVal;
|
|
109
|
+
}
|
|
110
|
+
type VariableFunc = ReturnType<typeof createVariable>;
|
|
111
|
+
|
|
112
|
+
function createImportVariable(name?: string, alias?: string) {
|
|
113
|
+
|
|
114
|
+
let v: ohq.Variable;
|
|
115
|
+
|
|
116
|
+
const retVal = (main: ohq.Module, otherModule: ohq.Module) => {
|
|
117
|
+
v = main.variable();
|
|
118
|
+
v.import(name, alias, otherModule);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
retVal.dispose = () => {
|
|
122
|
+
v?.delete();
|
|
123
|
+
};
|
|
124
|
+
return retVal;
|
|
125
|
+
}
|
|
126
|
+
type ImportVariableFunc = ReturnType<typeof createImportVariable>;
|
|
127
|
+
|
|
128
|
+
async function createModule(parsed: ParsedImportCell, text: string, baseUrl: string) {
|
|
129
|
+
const otherModule = [".", "/"].indexOf(parsed.src[0]) === 0 ?
|
|
130
|
+
await importFile(parsed.src, baseUrl) :
|
|
131
|
+
await importCompiledNotebook(parsed.src);
|
|
132
|
+
|
|
133
|
+
const importVariables: ImportVariableFunc[] = [];
|
|
134
|
+
const variables: VariableFunc[] = [];
|
|
135
|
+
parsed.specifiers.forEach(spec => {
|
|
136
|
+
const viewof = spec.view ? "viewof " : "";
|
|
137
|
+
importVariables.push(createImportVariable(viewof + spec.name, viewof + spec.alias));
|
|
138
|
+
if (spec.view) {
|
|
139
|
+
importVariables.push(createImportVariable(spec.name, spec.alias));
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
variables.push(createVariable(true, undefined, ["md"], md => {
|
|
143
|
+
return md`\`\`\`JavaScript
|
|
144
|
+
${text}
|
|
145
|
+
\`\`\``;
|
|
146
|
+
}));
|
|
147
|
+
|
|
148
|
+
const retVal = (runtime: ohq.Runtime, main: ohq.Module, inspector?: ohq.InspectorFactory) => {
|
|
149
|
+
|
|
150
|
+
let mod = runtime.module(otherModule);
|
|
151
|
+
if (parsed.injections.length) {
|
|
152
|
+
mod = mod.derive(parsed.injections, main);
|
|
153
|
+
}
|
|
154
|
+
variables.forEach(v => v(main, inspector));
|
|
155
|
+
importVariables.forEach(v => v(main, mod));
|
|
156
|
+
return mod;
|
|
157
|
+
};
|
|
158
|
+
retVal.importVariables = importVariables;
|
|
159
|
+
retVal.variables = variables;
|
|
160
|
+
retVal.dispose = () => {
|
|
161
|
+
importVariables.forEach(v => v.dispose());
|
|
162
|
+
variables.forEach(v => v.dispose());
|
|
163
|
+
otherModule.dispose();
|
|
164
|
+
};
|
|
165
|
+
retVal.write = (w: Writer) => {
|
|
166
|
+
otherModule.write(w);
|
|
167
|
+
w.importDefine(parsed);
|
|
168
|
+
};
|
|
169
|
+
return retVal;
|
|
170
|
+
}
|
|
171
|
+
type ModuleFunc = Awaited<ReturnType<typeof createModule>>;
|
|
172
|
+
|
|
173
|
+
async function createCell(node: ohq.Node, baseUrl: string) {
|
|
174
|
+
const modules: ModuleFunc[] = [];
|
|
175
|
+
const variables: VariableFunc[] = [];
|
|
176
|
+
try {
|
|
177
|
+
const text = node.mode && node.mode !== "js" ? `${node.mode}\`${encodeBacktick(node.value)}\`` : node.value;
|
|
178
|
+
const parsed = parseCell(text);
|
|
179
|
+
switch (parsed.type) {
|
|
180
|
+
case "import":
|
|
181
|
+
modules.push(await createModule(parsed, text, baseUrl));
|
|
182
|
+
break;
|
|
183
|
+
case "viewof":
|
|
184
|
+
variables.push(createVariable(true, parsed.variable.id, parsed.variable.inputs, parsed.variable.func));
|
|
185
|
+
variables.push(createVariable(false, parsed.variableValue.id, parsed.variableValue.inputs, parsed.variableValue.func, true));
|
|
186
|
+
break;
|
|
187
|
+
case "mutable":
|
|
188
|
+
variables.push(createVariable(false, parsed.initial.id, parsed.initial.inputs, parsed.initial.func));
|
|
189
|
+
variables.push(createVariable(false, parsed.variable.id, parsed.variable.inputs, parsed.variable.func));
|
|
190
|
+
variables.push(createVariable(true, parsed.variableValue.id, parsed.variableValue.inputs, parsed.variableValue.func, true));
|
|
191
|
+
break;
|
|
192
|
+
case "variable":
|
|
193
|
+
variables.push(createVariable(true, parsed.id, parsed.inputs, parsed.func));
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
} catch (e) {
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const retVal = (runtime: ohq.Runtime, main: ohq.Module, inspector?: ohq.InspectorFactory) => {
|
|
200
|
+
modules.forEach(imp => imp(runtime, main, inspector));
|
|
201
|
+
variables.forEach(v => v(main, inspector));
|
|
202
|
+
};
|
|
203
|
+
retVal.modules = modules;
|
|
204
|
+
retVal.variables = variables;
|
|
205
|
+
retVal.dispose = () => {
|
|
206
|
+
variables.forEach(v => v.dispose());
|
|
207
|
+
modules.forEach(mod => mod.dispose());
|
|
208
|
+
};
|
|
209
|
+
retVal.write = (w: Writer) => {
|
|
210
|
+
modules.forEach(imp => imp.write(w));
|
|
211
|
+
variables.forEach(v => v.write(w));
|
|
212
|
+
};
|
|
213
|
+
return retVal;
|
|
214
|
+
}
|
|
215
|
+
export type CellFunc = Awaited<ReturnType<typeof createCell>>;
|
|
216
|
+
|
|
217
|
+
function createFile(file: ohq.File): [string, any] {
|
|
218
|
+
function toString() { return globalThis.url; }
|
|
219
|
+
return [file.name, { url: new URL(file.url), mimeType: file.mime_type, toString }];
|
|
220
|
+
}
|
|
221
|
+
export type FileFunc = ReturnType<typeof createFile>;
|
|
222
|
+
|
|
223
|
+
export async function compile(notebook: ohq.Notebook, baseUrl: string = ".") {
|
|
224
|
+
|
|
225
|
+
const files = notebook.files.map(f => createFile(f));
|
|
226
|
+
const fileAttachments = new Map<string, any>(files);
|
|
227
|
+
let cells: CellFunc[] = await Promise.all(notebook.nodes.map(n => createCell(n, baseUrl)));
|
|
228
|
+
|
|
229
|
+
const retVal = (runtime: ohq.Runtime, inspector?: ohq.InspectorFactory): ohq.Module => {
|
|
230
|
+
const main = runtime.module();
|
|
231
|
+
main.builtin("FileAttachment", runtime.fileAttachments(name => {
|
|
232
|
+
return fileAttachments.get(name) ?? { url: new URL(name), mimeType: null };
|
|
233
|
+
}));
|
|
234
|
+
cells.forEach(cell => {
|
|
235
|
+
cell(runtime, main, inspector);
|
|
236
|
+
});
|
|
237
|
+
return main;
|
|
238
|
+
};
|
|
239
|
+
retVal.fileAttachments = fileAttachments;
|
|
240
|
+
retVal.cells = cells;
|
|
241
|
+
retVal.appendCell = async (n: ohq.Node, baseUrl: string = ".") => {
|
|
242
|
+
const cell = await createCell(n, baseUrl);
|
|
243
|
+
cells.push(cell);
|
|
244
|
+
return cell;
|
|
245
|
+
};
|
|
246
|
+
retVal.disposeCell = async (cell: CellFunc) => {
|
|
247
|
+
cells = cells.filter(c => c !== cell);
|
|
248
|
+
cell.dispose();
|
|
249
|
+
};
|
|
250
|
+
retVal.dispose = () => {
|
|
251
|
+
cells.forEach(cell => cell.dispose());
|
|
252
|
+
cells = [];
|
|
253
|
+
};
|
|
254
|
+
retVal.write = (w: Writer) => {
|
|
255
|
+
w.files(notebook.files);
|
|
256
|
+
cells.forEach(cell => cell.write(w));
|
|
257
|
+
};
|
|
258
|
+
retVal.toString = (w = new Writer()) => {
|
|
259
|
+
retVal.write(w);
|
|
260
|
+
return w.toString().trim();
|
|
261
|
+
};
|
|
262
|
+
return retVal;
|
|
263
|
+
}
|
|
264
|
+
export type compileFunc = Awaited<ReturnType<typeof compile>>;
|