|
|
@ -1,6 +1,7 @@
|
|
|
|
<script>
|
|
|
|
<script>
|
|
|
|
import { onMount, createEventDispatcher } from 'svelte';
|
|
|
|
import { onMount, createEventDispatcher } from 'svelte';
|
|
|
|
import getLocationFromStack from '../../_utils/getLocationFromStack.js';
|
|
|
|
import getLocationFromStack from '../../_utils/getLocationFromStack.js';
|
|
|
|
|
|
|
|
import ReplProxy from '../../_utils/replProxy.js';
|
|
|
|
import { decode } from 'sourcemap-codec';
|
|
|
|
import { decode } from 'sourcemap-codec';
|
|
|
|
|
|
|
|
|
|
|
|
const dispatch = createEventDispatcher();
|
|
|
|
const dispatch = createEventDispatcher();
|
|
|
@ -14,34 +15,18 @@
|
|
|
|
export let error;
|
|
|
|
export let error;
|
|
|
|
|
|
|
|
|
|
|
|
export function setProp(prop, value) {
|
|
|
|
export function setProp(prop, value) {
|
|
|
|
if (!refs.child) return;
|
|
|
|
if (!replProxy) return;
|
|
|
|
refs.child.contentWindow.postMessage({
|
|
|
|
replProxy.setProp(prop, value);
|
|
|
|
action: 'set_prop',
|
|
|
|
|
|
|
|
args: {
|
|
|
|
|
|
|
|
prop: prop,
|
|
|
|
|
|
|
|
value: value
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},'*');
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let component;
|
|
|
|
let hasComponent = false;
|
|
|
|
|
|
|
|
|
|
|
|
const refs = {};
|
|
|
|
const refs = {};
|
|
|
|
const importCache = {};
|
|
|
|
|
|
|
|
let pendingImports = 0;
|
|
|
|
let pendingImports = 0;
|
|
|
|
let pending = false;
|
|
|
|
let pending = false;
|
|
|
|
|
|
|
|
|
|
|
|
function fetchImport(id, curl) {
|
|
|
|
let replProxy = null;
|
|
|
|
return new Promise((fulfil, reject) => {
|
|
|
|
|
|
|
|
curl([`https://bundle.run/${id}`]).then(module => {
|
|
|
|
|
|
|
|
importCache[id] = module;
|
|
|
|
|
|
|
|
fulfil(module);
|
|
|
|
|
|
|
|
}, err => {
|
|
|
|
|
|
|
|
console.error(err.stack);
|
|
|
|
|
|
|
|
reject(new Error(`Error loading ${id} from bundle.run`));
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const namespaceSpecifier = /\*\s+as\s+(\w+)/;
|
|
|
|
const namespaceSpecifier = /\*\s+as\s+(\w+)/;
|
|
|
|
const namedSpecifiers = /\{(.+)\}/;
|
|
|
|
const namedSpecifiers = /\{(.+)\}/;
|
|
|
@ -84,112 +69,21 @@
|
|
|
|
let init;
|
|
|
|
let init;
|
|
|
|
|
|
|
|
|
|
|
|
onMount(() => {
|
|
|
|
onMount(() => {
|
|
|
|
|
|
|
|
replProxy = new ReplProxy(refs.child);
|
|
|
|
refs.child.addEventListener('load', () => {
|
|
|
|
refs.child.addEventListener('load', () => {
|
|
|
|
const iframe = refs.child;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let evalId = 1;
|
|
|
|
|
|
|
|
let fetchId = 1;
|
|
|
|
|
|
|
|
let fetchHandler = null;
|
|
|
|
|
|
|
|
let pendingResults = new Map();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const evalInIframe = function(scriptToEval) {
|
|
|
|
|
|
|
|
let id = evalId++;
|
|
|
|
|
|
|
|
let promise = new Promise((resolve,reject) => {
|
|
|
|
|
|
|
|
iframe.contentWindow.postMessage({
|
|
|
|
|
|
|
|
action: "eval",
|
|
|
|
|
|
|
|
args: {
|
|
|
|
|
|
|
|
evalId: id,
|
|
|
|
|
|
|
|
script: scriptToEval
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}, '*')
|
|
|
|
|
|
|
|
pendingResults.set(id, { resolve: resolve, reject: reject });
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
return promise;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleReplMessage = (ev) => {
|
|
|
|
replProxy.onPropUpdate = (prop, value) => {
|
|
|
|
console.log("got message in parent", ev.data);
|
|
|
|
|
|
|
|
let action = ev.data.action;
|
|
|
|
|
|
|
|
if (action == "evalError") {
|
|
|
|
|
|
|
|
let { message, stack, evalId } = ev.data.args;
|
|
|
|
|
|
|
|
let e = new Error(message);
|
|
|
|
|
|
|
|
e.stack = e.stack;
|
|
|
|
|
|
|
|
let resultHandler = pendingResults.get(evalId);
|
|
|
|
|
|
|
|
if (resultHandler) {
|
|
|
|
|
|
|
|
pendingResults.delete(evalId);
|
|
|
|
|
|
|
|
resultHandler.reject(e);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
console.err("error evaluating script in iframe", e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (action == "evalOk") {
|
|
|
|
|
|
|
|
let { evalId } = ev.data.args;
|
|
|
|
|
|
|
|
let resultHandler = pendingResults.get(evalId);
|
|
|
|
|
|
|
|
if (resultHandler) {
|
|
|
|
|
|
|
|
pendingResults.delete(evalId);
|
|
|
|
|
|
|
|
resultHandler.resolve();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (action == "prop_update") {
|
|
|
|
|
|
|
|
let { prop, value } = ev.data.args;
|
|
|
|
|
|
|
|
dispatch('binding', { prop, value });
|
|
|
|
dispatch('binding', { prop, value });
|
|
|
|
values_store.update(values => Object.assign({}, values, {
|
|
|
|
values_store.update(values => Object.assign({}, values, {
|
|
|
|
[prop]: value
|
|
|
|
[prop]: value
|
|
|
|
}));
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (action == "fetch_complete") {
|
|
|
|
|
|
|
|
console.log(fetchHandler, ev.data.args);
|
|
|
|
|
|
|
|
if (fetchHandler && fetchHandler.fetchId == ev.data.args.fetchId) {
|
|
|
|
|
|
|
|
fetchHandler.resolve()
|
|
|
|
|
|
|
|
fetchHandler = null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (action == "fetch_error") {
|
|
|
|
|
|
|
|
if (fetchHandler && fetchHandler.fetchId == ev.data.args.fetchId) {
|
|
|
|
|
|
|
|
fetchHandler.reject(new Error(ev.data.args.message));
|
|
|
|
|
|
|
|
fetchHandler = null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (action == "fetch_progress") {
|
|
|
|
|
|
|
|
if (fetchHandler && fetchHandler.fetchId == ev.data.args.fetchId) {
|
|
|
|
|
|
|
|
pendingImports = ev.data.args.remaining;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
window.addEventListener("message", handleReplMessage, false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// iframe.contentWindow.eval;
|
|
|
|
|
|
|
|
/* TODO
|
|
|
|
|
|
|
|
// intercept links, so that we can use #hashes inside the iframe
|
|
|
|
|
|
|
|
body.addEventListener('click', event => {
|
|
|
|
|
|
|
|
if (event.which !== 1) return;
|
|
|
|
|
|
|
|
if (event.metaKey || event.ctrlKey || event.shiftKey) return;
|
|
|
|
|
|
|
|
if (event.defaultPrevented) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ensure target is a link
|
|
|
|
|
|
|
|
let el = event.target;
|
|
|
|
|
|
|
|
while (el && el.nodeName !== 'A') el = el.parentNode;
|
|
|
|
|
|
|
|
if (!el || el.nodeName !== 'A') return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (el.hasAttribute('download') || el.getAttribute('rel') === 'external' || el.target) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (el.href.startsWith(top.location.origin)) {
|
|
|
|
replProxy.onFetchProgress = (progress) => {
|
|
|
|
const url = new URL(el.href);
|
|
|
|
pendingImports = progress
|
|
|
|
if (url.hash[0] === '#') {
|
|
|
|
|
|
|
|
iframe.contentWindow.location.hash = url.hash;
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
window.open(el.href, '_blank');
|
|
|
|
replProxy.handleLinks();
|
|
|
|
});
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let promise = null;
|
|
|
|
let promise = null;
|
|
|
|
let updating = false;
|
|
|
|
let updating = false;
|
|
|
@ -199,26 +93,26 @@
|
|
|
|
const init = () => {
|
|
|
|
const init = () => {
|
|
|
|
if (sourceError) return;
|
|
|
|
if (sourceError) return;
|
|
|
|
|
|
|
|
|
|
|
|
const imports = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const missingImports = bundle.imports.filter(x => !importCache[x]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const removeStyles = () => {
|
|
|
|
const removeStyles = () => {
|
|
|
|
evalInIframe(`
|
|
|
|
replProxy.eval(`
|
|
|
|
const styles = document.querySelectorAll('style.svelte');
|
|
|
|
const styles = document.querySelectorAll('style.svelte');
|
|
|
|
let i = styles.length;
|
|
|
|
let i = styles.length;
|
|
|
|
while (i--) styles[i].parentNode.removeChild(styles[i]);
|
|
|
|
while (i--) styles[i].parentNode.removeChild(styles[i]);
|
|
|
|
`)
|
|
|
|
`)
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const destroyComponent = () => {
|
|
|
|
|
|
|
|
replProxy.eval(`if (window.component)
|
|
|
|
|
|
|
|
window.component.\$destroy();
|
|
|
|
|
|
|
|
window.component = null`);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const ready = () => {
|
|
|
|
const ready = () => {
|
|
|
|
error = null;
|
|
|
|
error = null;
|
|
|
|
|
|
|
|
|
|
|
|
if (toDestroy) {
|
|
|
|
if (toDestroy) {
|
|
|
|
removeStyles();
|
|
|
|
removeStyles();
|
|
|
|
|
|
|
|
destroyComponent();
|
|
|
|
toDestroy.$destroy();
|
|
|
|
|
|
|
|
toDestroy = null;
|
|
|
|
toDestroy = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -229,11 +123,10 @@
|
|
|
|
pending = false;
|
|
|
|
pending = false;
|
|
|
|
createComponent();
|
|
|
|
createComponent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const createHtml = () => {
|
|
|
|
const createHtml = () => {
|
|
|
|
|
|
|
|
replProxy.eval(`${ssr.code}
|
|
|
|
evalInIframe(`${ssr.code}
|
|
|
|
|
|
|
|
var rendered = SvelteComponent.render(${JSON.stringify($values_store)});
|
|
|
|
var rendered = SvelteComponent.render(${JSON.stringify($values_store)});
|
|
|
|
|
|
|
|
|
|
|
|
if (rendered.css.code) {
|
|
|
|
if (rendered.css.code) {
|
|
|
@ -244,8 +137,8 @@
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
document.body.innerHTML = rendered.html;
|
|
|
|
document.body.innerHTML = rendered.html;
|
|
|
|
`).catch( e => {
|
|
|
|
`)
|
|
|
|
|
|
|
|
.catch( e => {
|
|
|
|
const loc = getLocationFromStack(e.stack, ssr.map);
|
|
|
|
const loc = getLocationFromStack(e.stack, ssr.map);
|
|
|
|
if (loc) {
|
|
|
|
if (loc) {
|
|
|
|
e.filename = loc.source;
|
|
|
|
e.filename = loc.source;
|
|
|
@ -260,7 +153,7 @@
|
|
|
|
// remove leftover styles from SSR renderer
|
|
|
|
// remove leftover styles from SSR renderer
|
|
|
|
if (ssr) removeStyles();
|
|
|
|
if (ssr) removeStyles();
|
|
|
|
|
|
|
|
|
|
|
|
evalInIframe(`${dom.code}
|
|
|
|
replProxy.eval(`${dom.code}
|
|
|
|
document.body.innerHTML = '';
|
|
|
|
document.body.innerHTML = '';
|
|
|
|
window.location.hash = '';
|
|
|
|
window.location.hash = '';
|
|
|
|
window._svelteTransitionManager = null;
|
|
|
|
window._svelteTransitionManager = null;
|
|
|
@ -268,11 +161,11 @@
|
|
|
|
window.component = new SvelteComponent({
|
|
|
|
window.component = new SvelteComponent({
|
|
|
|
target: document.body,
|
|
|
|
target: document.body,
|
|
|
|
props: ${JSON.stringify($values_store)}
|
|
|
|
props: ${JSON.stringify($values_store)}
|
|
|
|
});`)
|
|
|
|
});
|
|
|
|
|
|
|
|
`)
|
|
|
|
.catch(e => {
|
|
|
|
.catch(e => {
|
|
|
|
|
|
|
|
|
|
|
|
// TODO show in UI
|
|
|
|
// TODO show in UI
|
|
|
|
component = null;
|
|
|
|
hasComponent = false;
|
|
|
|
|
|
|
|
|
|
|
|
const loc = getLocationFromStack(e.stack, dom.map);
|
|
|
|
const loc = getLocationFromStack(e.stack, dom.map);
|
|
|
|
if (loc) {
|
|
|
|
if (loc) {
|
|
|
@ -284,27 +177,19 @@
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Download the imports (sets them on iframe window when complete)
|
|
|
|
new Promise((resolve, reject)=> {
|
|
|
|
{
|
|
|
|
fetchHandler = {
|
|
|
|
let cancelled = false;
|
|
|
|
fetchId: fetchId++,
|
|
|
|
promise = replProxy.fetchImports(bundle);
|
|
|
|
resolve: resolve,
|
|
|
|
promise.cancel = () => { cancelled = true };
|
|
|
|
reject: reject
|
|
|
|
promise.then(() => {
|
|
|
|
}
|
|
|
|
if (cancelled) return;
|
|
|
|
iframe.contentWindow.postMessage({
|
|
|
|
ready()
|
|
|
|
action: "fetch_imports",
|
|
|
|
}).catch(e => {
|
|
|
|
args: {
|
|
|
|
if (cancelled) return;
|
|
|
|
bundle: bundle,
|
|
|
|
|
|
|
|
fetchId: fetchHandler.fetchId
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}, '*');
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.then(() => {
|
|
|
|
|
|
|
|
ready();
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.catch(e => {
|
|
|
|
|
|
|
|
error = e;
|
|
|
|
error = e;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
run = () => {
|
|
|
|
run = () => {
|
|
|
|
pending = false;
|
|
|
|
pending = false;
|
|
|
@ -316,21 +201,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
bundle_handler = bundle => {
|
|
|
|
bundle_handler = bundle => {
|
|
|
|
if (!bundle) return; // TODO can this ever happen?
|
|
|
|
if (!bundle) return; // TODO can this ever happen?
|
|
|
|
if (fetchHandler) fetchHandler = null;
|
|
|
|
if (promise) promise.cancel();
|
|
|
|
|
|
|
|
|
|
|
|
toDestroy = component;
|
|
|
|
toDestroy = hasComponent;
|
|
|
|
component = null;
|
|
|
|
hasComponent = false;
|
|
|
|
|
|
|
|
|
|
|
|
init();
|
|
|
|
init();
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
props_handler = props => {
|
|
|
|
props_handler = props => {
|
|
|
|
iframe.contentWindow.postMessage({
|
|
|
|
replProxy.bindProps(props)
|
|
|
|
action:"bind_props",
|
|
|
|
|
|
|
|
args: {
|
|
|
|
|
|
|
|
props: [...props]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},'*')
|
|
|
|
|
|
|
|
};
|
|
|
|
};
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
@ -399,7 +279,7 @@
|
|
|
|
</style>
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="iframe-container">
|
|
|
|
<div class="iframe-container">
|
|
|
|
<iframe title="Result" bind:this={refs.child} sandbox="allow-scripts" class="{error || pending || pendingImports ? 'greyed-out' : ''}" srcdoc='
|
|
|
|
<iframe title="Result" bind:this={refs.child} sandbox="allow-scripts allow-popups" class="{error || pending || pendingImports ? 'greyed-out' : ''}" srcdoc='
|
|
|
|
<!doctype html>
|
|
|
|
<!doctype html>
|
|
|
|
<html>
|
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<head>
|
|
|
|