Published
Edited
Jul 2, 2021
Importers
13 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
// Prepare DIV container where the results of the executions are shown
const container = html`<div style="border: 3px solid silver; padding: 0.5em;">`;
const define = async (runtime, observer) => {
const id = "main";
// const notebook = buildNotebook(id, code);
// const compiledModules = await compileNotebook({
// runtime,
// observer,
// notebook,
// resolve : resolveModule
// });
// return compiledModules[0];

// Transform the text code to an array of JSON objects with cell definitions.
// All cell methods are serialized as simple text.
const moduleDefinition = buildModule(id, code);
// Transform methods code to executable functions.
return await compileModule({
runtime,
observer,
moduleDefinition,
resolve: resolveModule // This function resolves imports
});
};
// Create a new runtime
const nrt = new rt.Runtime();
// Create a new module
const main = nrt.module(define, (name) => {
// if (name && name.indexOf('mutable ') === 0) return ;
const div = html`<div>`;
container.appendChild(div);
return new rt.Inspector(div);
});
invalidation.then(() => nrt.dispose());
return container;
}
Insert cell
Insert cell
async function resolveModule({ source, runtime, observer }) {
const path = extractPath(source);
console.log("RESOLVE MODULE URL", source, path);

if (source[0] === "#") {
console.log("DEFINE MODULE", path);
const main = runtime.module();
main
.variable(observer("totot"))
.define(
"totot",
["html", "name"],
(html, name) => html`<strong>Hello ${name}</strong>`
);
main.variable(observer("name")).define("name", function () {
return "Totot";
});
return main;
}

const moduleUrl = `https://api.observablehq.com/${path}.js?v=3`;
const module = (await new Function(`return import("${moduleUrl}")`)())
.default;
return module(runtime, (name) => {
if (!name) return false;
console.log("IMPORT", path, name);
return true;
});

function extractPath(path) {
let source = path;
let m;

// "https://api.observablehq.com/@jashkenas/inputs.js?v=3" => strip off ".js"
if ((m = /\.js(\?|$)/i.exec(source))) source = source.slice(0, m.index);

// "74f872c4fde62e35" => "d/..."
if ((m = /^[0-9a-f]{16}$/i.test(source))) source = `d/${source}`;

// link of notebook
if ((m = /^https:\/\/(api\.|beta\.|)observablehq\.com\//i.exec(source)))
source = source.slice(m[0].length);
return source;
}
}
Insert cell
Insert cell
Insert cell
async function compileNotebook({ runtime, observer, notebook, resolve }) {
const index = {};
let counter = 0;
notebook.modules.forEach(m => {
m.id = m.id || `id-${Date.now()}-${counter++}`;
index[m.id] = runtime.module();
});
const res = async ({ source, runtime, observer }) => {
const m = index[source] = index[source] || await resolve({ source, runtime, observer });
return m;
}
const result = [];
for (let moduleDefinition of notebook.modules) {
const compiled = await compileModule({
runtime,
module : index[moduleDefinition.id],
observer,
moduleDefinition,
resolve : res
});
result.push(compiled);
}
return result;
}
Insert cell
async function compileModule({
runtime,
observer,
module,
moduleDefinition,
resolve
}) {
module = module || runtime.module();
const { definitions } = moduleDefinition;
for (let i = 0; i < definitions.length; i++) {
const cell = definitions[i];
compileCell({ runtime, observer, module, cell, resolve });
}
return module;
}
Insert cell
async function compileCell({
runtime, observer, module, cell, resolve
}) {
if (cell.type === "import") {
const { source, injections, specifiers } = cell;
let importedModule = await resolve({ source, runtime, observer });
if (injections.length) {
importedModule = importedModule.derive(injections, module);
}
specifiers.map((specifier) => {
module.import(specifier.name, specifier.alias, importedModule);
});
} else {
const { name, references, method } = cell;
const m =
typeof method === "string"
? new Function([], `return (${method})`)()
: method;
module.variable(observer(name)).define(name, references, m);
}
return module;
}
Insert cell
Insert cell
// Example:
buildModule(
"main",
`
{
const myData = FileAttachment("MyData.json");
const mySecret = Secret("current user or me");
const db = DatabaseClient("hello-db");
db.save(myData, mySecret, Secret("AllMyKeys"));
}

import { foo as Foo } with { bar as Bar } from '#toto';


toto=myA + 3
Secret("AllMyKeys")

myA

viewof myA=html\`<input type="number" value="2">\`

`
)
Insert cell
Insert cell
function buildNotebook(id, code) {
const module = buildModule(id, code);
return {
id,
modules: [ module ]
}
}
Insert cell
function buildModule(id, code) {
const ast = parser.parseModule(code);
const definitions = ast.cells.reduce((list, cell) => list.concat(buildCell(cell)), []);
return {
id,
ast,
definitions
};
}
Insert cell
function buildCell(cell) {
return (cell.body.type === "ImportDeclaration")
? getCellImports(cell)
: getCellDefinitions(cell);
}
Insert cell
function getCellImports(cell) {
const addTo = (list, d) => {
if (d.view) {
list.push({
name: 'viewof ' + d.imported.name,
alias: 'viewof ' + d.local.name
});
} else if (d.mutable) {
list.push({
name: 'mutable ' + d.imported.name,
alias: 'mutable ' + d.local.name
});
}
list.push({
name: d.imported.name,
alias: d.local.name
});
return list;
}
const specifiers = (cell.body.specifiers || []).reduce(addTo, []);
const injections = (cell.body.injections || []).reduce(addTo, []);
const source = cell.body.source.value;
const imports = [];
imports.push({
type : 'import',
source,
specifiers,
injections,
});
return imports;
}
Insert cell
function getCellDefinitions(cell) {
const { references, code } = getCellCodeAndReferences(cell);
const TYPE = "cell";

let name = null;
if (cell.id && cell.id.name) name = cell.id.name;
else if (cell.id && cell.id.id && cell.id.id.name) name = cell.id.id.name;
// if (!name) name = `$cell_${getCellDefinitions.id = (getCellDefinitions.id || 0) + 1}`;

const definitions = [];
if (cell.id && cell.id.type === "ViewExpression") {
definitions.push({
type: TYPE,
name: `viewof ${name}`,
references,
method: code
});
definitions.push({
type: TYPE,
name: `${name}`,
references: ["Generators", `viewof ${name}`],
method: `function value_${name}(Generators, $) { return Generators.input($); }`
});
} else if (cell.id && cell.id.type === "MutableExpression") {
definitions.push({
type: TYPE,
name: `mutable initial ${name}`,
references,
method: code
});
definitions.push({
type: TYPE,
name: `mutable ${name}`,
references: ["Mutable", `mutable initial ${name}`],
method: `function mutable_${name}(Mutable, $) { return new Mutable($); }`
});
definitions.push({
type: TYPE,
name: `${name}`,
references: [`mutable ${name}`],
method: `function value_${name}($) { return $.generator; }`
});
} else {
definitions.push({
type: TYPE,
name,
references,
method: code
});
}
return definitions;
}
Insert cell
function getCellCodeAndReferences(cell) {
const { walk } = parser;
const { simple } = acornWalk;

let bodyText = cell.input.substring(cell.body.start, cell.body.end);

const _getRefName = (ref) => {
if (!ref) return null;
if (ref.type === "ViewExpression") {
return "viewof " + ref.id.name;
} else if (ref.type === "MutableExpression") {
return "mutable " + ref.id.name;
} else return ref.name;
};
const references = (cell.references || []).map(_getRefName);
let $count = 0;
let indexShift = 0;

const _updateText = (node, $string) => {
const start = node.start - cell.body.start;
const end = node.end - cell.body.start;
bodyText =
bodyText.slice(0, start + indexShift) +
$string +
bodyText.slice(end + indexShift);
indexShift += $string.length - (end - start);
};

const codeReferences = (cell.references || []).map((ref) => {
if (ref.type === "ViewExpression") {
const $string = "$" + $count++;
// replace "viewof X" in bodyText with "$($count)"
simple(
cell.body,
{
ViewExpression(node) {
_updateText(node, $string);
}
},
walk
);
return $string;
} else if (ref.type === "MutableExpression") {
const $string = "$" + $count++;
// replace "mutable Y" in bodyText with "$($count).value"
simple(
cell.body,
{
MutableExpression(node) {
_updateText(node, $string + ".value");
}
},
walk
);
return $string;
} else return ref.name;
});

let code;
if (cell.body.type !== "BlockStatement") {
if (cell.async) code = `${bodyText}`;
else code = `{\nreturn (${bodyText});\n}`;
} else code = `${bodyText}`;

const refs = codeReferences.join(",");
const name = _getRefName(cell.id);
const functionName = name ? name.replace(/\s/gim, "_") : ``;
if (cell.generator && cell.async)
code = `async function* ${functionName}(${refs}) ${code}`;
else if (cell.async) code = `async function ${functionName}(${refs}) ${code}`;
else if (cell.generator) code = `function* ${functionName}(${refs}) ${code}`;
else code = `function ${functionName}(${refs}) ${code} `;

return { references, code };
}
Insert cell
Insert cell
parser = import("@observablehq/parser")
Insert cell
acornWalk = import("acorn-walk")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more