Experimenting using RunKit↗ as a custom repl for nodejs. Warning, it starts off messy, but in the end we have a reusable nodejs
custom REPL.
Table of Contents
Exploration
Embed
Lets start with just the simple demo embed,↗ adding the embed script and a container below.
A container to hold the embed.
Run the #init cell below 👇 to populate it With some demo source.
return RunKit.createNotebook({
// the parent element for the new notebook
element: document.getElementById("my-element"),
onEvaluate: (...args) => alert(JSON.stringify([...args])),
// specify the source of the notebook
source: "// GeoJSON!\nvar getJSON = require(\"async-get-json\");\n\nawait getJSON(\"https://storage.googleapis.com/maps-devrel/google.json\");"
})
Endpoint
react bindings for simple embed: https://github.com/runkitdev/react-runkit↗ and trying out https://runkit.com/docs/endpoint↗
return (async fn => {
const React = lit.utils.React
const Embed = (await import('https://cdn.skypack.dev/runkit-embed-react')).default
const createdAt = new Date()
const helloSource = `exports.endpoint = function(request, response) {
response.end("Hello world! From .lit and Nodejs thanks to RunKit. Created at " + createdAt);
}`
const onLoad = (...args) => console.log(JSON.stringify([...args]))
return <Embed
mode='endpoint'
readOnly={true}
evaluateOnLoad={true}
// hidesActionButton={true}
source={ helloSource }
// ref='embed'
onLoad={ onLoad }
/>
})()
https://2mkz0r1anfrl.runkit.sh/
Viewer plug-in
import Embed from 'https://cdn.skypack.dev/runkit-embed-react'
export const viewer = ({node,React}) => {
const {useState} = React
const [url, setUrl] = useState(false)
const meta = node?.properties?.meta || {}
const endpoint = meta.attrs && meta.attrs.mode === 'endpoint'
const onLoad = async (rk) => {
if (endpoint)
setUrl(await rk.getEndpointURL())
}
return url || <Embed
mode={endpoint ? 'endpoint' : 'default'}
readOnly={endpoint}
evaluateOnLoad={endpoint}
hidesActionButton={endpoint}
source={ node.data.value }
// ref='embed'
onLoad={ onLoad }
/>
}
console.log("Hello world! From .lit and Nodejs thanks to RunKit.")
exports.endpoint = function(req, res) {
res.end("Hello world! From .lit and Nodejs thanks to RunKit.");
}
exports.endpoint = function(req,res) {
res.writeHead(200, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*',
});
res.end('{"ping": "pong!"}')
}
return fetch("https://jzc0q8a4nyog.runkit.sh").then(res => res.text())
REPL Implementation
Split out the endpoint source to its own .lit
cell for legibility instead of an inline string.
On load the following repl
!plugin
creates a RunKit endpoint if it doesn't exist using the above source.
if (typeof lit !== "undefined" && !window.__runkitNodeEnpoint) {
(async (fn) => {
const el = document.createElement("div");
document.body.appendChild(el);
el.setAttribute("style", "height:0;");
RunKit.createNotebook({
element: el,
mode: "endpoint",
onLoad: async (rk) => {
window.__runkitNodeEnpoint = await rk.getEndpointURL();
document.body.removeChild(el);
},
evaluateOnLoad: true,
source: await lit.fs.readFile("/testing/runkit-repl-endpoint.js", {
encoding: "utf8",
}),
});
})();
}
export const repl = async (src, meta, node) => {
if (!window.__runkitNodeEnpoint) {
return "Still setting up repl endpoint";
} else {
try {
return await (
await fetch(window.__runkitNodeEnpoint, {
method: "POST",
body: JSON.stringify({ src, meta }),
})
).text();
} catch (err) {
return err.message;
}
}
};
Usage:
Next steps and improvements
- Don't start the endpoint on every page load, and instead lazily setup just before first execution.
- Don't use require module from string hack, and instead use a proper
vm
and context. - Create a testing/CORS Proxy