pull/1890/head
Rich Harris 7 years ago
parent e03ba9051c
commit 3fd1e3c75c

@ -0,0 +1,45 @@
{
"root": true,
"rules": {
"indent": [2, "tab", { "SwitchCase": 1 }],
"semi": [2, "always"],
"keyword-spacing": [2, { "before": true, "after": true }],
"space-before-blocks": [2, "always"],
"no-mixed-spaces-and-tabs": [2, "smart-tabs"],
"no-cond-assign": 0,
"no-unused-vars": 2,
"object-shorthand": [2, "always"],
"no-const-assign": 2,
"no-class-assign": 2,
"no-this-before-super": 2,
"no-var": 2,
"no-unreachable": 2,
"valid-typeof": 2,
"quote-props": [2, "as-needed"],
"one-var": [2, "never"],
"prefer-arrow-callback": 2,
"prefer-const": [2, { "destructuring": "all" }],
"arrow-spacing": 2,
"no-inner-declarations": 0
},
"env": {
"es6": true,
"browser": true,
"node": true,
"mocha": true
},
"extends": [
"eslint:recommended",
"plugin:import/errors",
"plugin:import/warnings"
],
"plugins": ["svelte3"],
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"settings": {
"import/core-modules": ["svelte"],
"svelte3/extensions": [".html"]
}
}

@ -23,7 +23,7 @@
.domain([minX, maxX]) .domain([minX, maxX])
.range([padding.left, width - padding.right]); .range([padding.left, width - padding.right]);
$: yScale = scaleLinear $: yScale = scaleLinear()
.domain([Math.min.apply(null, yTicks), Math.max.apply(null, yTicks)]) .domain([Math.min.apply(null, yTicks), Math.max.apply(null, yTicks)])
.range([height - padding.bottom, padding.top]); .range([height - padding.bottom, padding.top]);
@ -50,7 +50,7 @@
<div class="chart"> <div class="chart">
<h2>Arctic sea ice minimum</h2> <h2>Arctic sea ice minimum</h2>
<svg ref:svg> <svg bind:this={svg}>
<!-- y axis --> <!-- y axis -->
<g class="axis y-axis" transform="translate(0, {padding.top})"> <g class="axis y-axis" transform="translate(0, {padding.top})">
{#each yTicks as tick} {#each yTicks as tick}

@ -1423,6 +1423,11 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true "dev": true
}, },
"eslint-plugin-svelte3": {
"version": "git+https://github.com/sveltejs/eslint-plugin-svelte3.git#d4da8a7d98f2efa70266313ab798cd4d400ca86c",
"from": "git+https://github.com/sveltejs/eslint-plugin-svelte3.git",
"dev": true
},
"estree-walker": { "estree-walker": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.5.2.tgz", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.5.2.tgz",

@ -29,6 +29,7 @@
"@babel/preset-env": "^7.0.0", "@babel/preset-env": "^7.0.0",
"@babel/runtime": "^7.0.0", "@babel/runtime": "^7.0.0",
"chokidar": "^2.0.4", "chokidar": "^2.0.4",
"eslint-plugin-svelte3": "git+https://github.com/sveltejs/eslint-plugin-svelte3.git",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"rollup": "^0.68.0", "rollup": "^0.68.0",
"rollup-plugin-babel": "^4.0.2", "rollup-plugin-babel": "^4.0.2",

@ -9,11 +9,10 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let repl;
export let examples; export let examples;
export let gist; export let gist;
export let name; export let name;
export let components;
export let json5;
export let zen_mode; export let zen_mode;
export let bundle; export let bundle;

@ -84,7 +84,7 @@
let previous_error_line; let previous_error_line;
$: if (editor) { $: if (editor) {
if (previous_error_line != null) { if (previous_error_line != null) {
editor.removeLineClass(previousLine, 'wrap', 'error-line') editor.removeLineClass(previous_error_line, 'wrap', 'error-line')
} }
if (error_line && (error_line !== previous_error_line)) { if (error_line && (error_line !== previous_error_line)) {

@ -5,39 +5,29 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let components = []; export let component_store;
export let selectedComponent; export let selected_store;
let editing = null; let editing = null;
// let previous_components; function selectComponent(component) {
if ($selected_store != component) {
// $: {
// // bit of a hack...
// if (components !== previous_components) {
// selectedComponent = components[0];
// previous_components = components;
// }
// }
function selectComponent(component, selectedComponent) {
if (selectedComponent != component) {
editing = null; editing = null;
} }
dispatch('select', { component }); selected_store.set(component);
} }
function editTab(component, selectedComponent) { function editTab(component) {
if (selectedComponent === component) { if ($selected_store === component) {
editing = selectedComponent; editing = $selected_store;
} }
} }
function closeEdit(selectedComponent) { function closeEdit() {
const match = /(.+)\.(html|js)$/.exec(selectedComponent.name); const match = /(.+)\.(html|js)$/.exec($selected_store.name);
selectedComponent.name = match ? match[1] : selectedComponent.name; $selected_store.name = match ? match[1] : $selected_store.name;
if (match && match[2]) selectedComponent.type = match[2]; if (match && match[2]) $selected_store.type = match[2];
editing = null; editing = null;
components = components; // TODO necessary? components = components; // TODO necessary?
@ -68,11 +58,10 @@
setTimeout(() => { setTimeout(() => {
// TODO we can do this without IDs // TODO we can do this without IDs
document.getElementById(component.name).scrollIntoView(false); document.getElementById(component.name).scrollIntoView(false);
// selectedComponent = component;
}); });
dispatch('create', { component }); component_store.update(components => components.concat(component));
dispatch('select', { component }); selected_store.set(component);
} }
</script> </script>
@ -180,12 +169,12 @@
<div class="component-selector"> <div class="component-selector">
<div class="file-tabs" on:dblclick="{() => dispatch('create')}"> <div class="file-tabs" on:dblclick="{() => dispatch('create')}">
{#each components as component} {#each $component_store as component}
<button <button
id={component.name} id={component.name}
class:active="{component === selectedComponent}" class:active="{component === $selected_store}"
data-name={component.name} data-name={component.name}
on:click="{() => selectComponent(component, selectedComponent)}" on:click="{() => selectComponent(component)}"
on:dblclick="{e => e.stopPropagation()}" on:dblclick="{e => e.stopPropagation()}"
> >
{#if component.name == 'App'} {#if component.name == 'App'}
@ -200,14 +189,14 @@
autofocus autofocus
bind:value={component.name} bind:value={component.name}
on:focus={selectInput} on:focus={selectInput}
on:blur="{() => closeEdit(selectedComponent)}" on:blur="{() => closeEdit()}"
use:enter="{e => e.target.blur()}" use:enter="{e => e.target.blur()}"
> >
{:else} {:else}
<div <div
class="editable" class="editable"
title="edit component name" title="edit component name"
on:click="{() => editTab(component, selectedComponent)}" on:click="{() => editTab(component)}"
> >
{component.name}.{component.type} {component.name}.{component.type}
</div> </div>

@ -27,7 +27,7 @@
{#if component} {#if component}
<CodeMirror <CodeMirror
mode="{component.type === 'js' ? 'javascript' : 'handlebars'}" mode="{component.type === 'js' ? 'javascript' : 'handlebars'}"
bind:code={component.source} code={component.source}
{error} {error}
{errorLoc} {errorLoc}
{warningCount} {warningCount}

@ -2,27 +2,27 @@
import ComponentSelector from './ComponentSelector.html'; import ComponentSelector from './ComponentSelector.html';
import ModuleEditor from './ModuleEditor.html'; import ModuleEditor from './ModuleEditor.html';
export let components; export let component_store;
export let selectedComponent; export let selected_store;
export let sourceError;
export let sourceErrorLoc; export let error;
export let runtimeErrorLoc; export let errorLoc;
export let warningCount; export let warningCount;
</script> </script>
<!-- TODO would be nice if events bubbled --> <!-- TODO would be nice if events bubbled -->
<ComponentSelector <ComponentSelector
{components} {component_store}
bind:selectedComponent {selected_store}
on:create on:create
on:remove on:remove
on:select on:select
/> />
<ModuleEditor <ModuleEditor
component={selectedComponent} component={$selected_store}
error={sourceError} {error}
errorLoc="{sourceErrorLoc || runtimeErrorLoc}" {errorLoc}
{warningCount} {warningCount}
on:change on:change
on:navigate on:navigate

@ -6,27 +6,29 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let value; export let value;
let json5;
let error;
let code;
if (value === undefined) { if (value === undefined) {
json5 = 'undefined'; code = 'undefined';
} else { } else {
json5 = fleece.stringify(value); code = fleece.stringify(value);
} }
$: try { $: try {
value = fleece.evaluate(json5); value = fleece.evaluate(code);
} catch (err) { } catch (e) {
console.error(Object.keys(err)); error = e;
// TODO show in UI
} }
function handleChange(event) { function handleChange(event) {
try { try {
const value = fleece.evaluate(event.detail.value); const value = fleece.evaluate(event.detail.value);
error = null;
dispatch('change', { value }); dispatch('change', { value });
} catch (err) { } catch (e) {
// TODO indicate error error = e;
} }
} }
</script> </script>
@ -35,21 +37,19 @@
.prop-editor { .prop-editor {
border: 1px solid #eee; border: 1px solid #eee;
} }
.error {
background-color: rgba(218, 16, 96, 0.1);
border: 1px solid #da106e;
}
</style> </style>
<div class="prop-editor"> <div class="prop-editor" class:error title="{error && error.message}">
<CodeMirror <CodeMirror
mode="json" mode="json"
bind:code={json5} bind:code
lineNumbers={false} lineNumbers={false}
on:change={handleChange} on:change={handleChange}
flex flex
/> />
</div> </div>
<!-- <CodeMirror
mode="json"
bind:code={json5}
error={dataError}
errorLoc={dataErrorLoc}
/> -->

@ -8,7 +8,7 @@
export let bundle; export let bundle;
export let dom; export let dom;
export let ssr; export let ssr;
export let values; export let values_store;
export let props; export let props;
export let sourceError; export let sourceError;
export let error; export let error;
@ -157,7 +157,7 @@
const createHtml = () => { const createHtml = () => {
try { try {
evalInIframe(`${ssr.code} evalInIframe(`${ssr.code}
var rendered = SvelteComponent.render(${JSON.stringify(values)}); var rendered = SvelteComponent.render(${JSON.stringify($values_store)});
if (rendered.css.code) { if (rendered.css.code) {
var style = document.createElement('style'); var style = document.createElement('style');
@ -190,7 +190,7 @@
var component = new SvelteComponent({ var component = new SvelteComponent({
target: document.body, target: document.body,
props: ${JSON.stringify(values)} props: ${JSON.stringify($values_store)}
});`); });`);
component = window.app = window.component = iframe.contentWindow.component; component = window.app = window.component = iframe.contentWindow.component;
@ -256,31 +256,7 @@
toDestroy = component; toDestroy = component;
component = null; component = null;
if (values !== undefined) init();
};
values_handler = values => {
console.log({ values });
if (updating) return;
try {
if (component) {
error = null;
updating = true;
component.$set(values);
updating = false;
} else {
init(); init();
}
} catch (e) {
const loc = getLocationFromStack(e.stack, bundle.map);
if (loc) {
e.filename = loc.source;
e.loc = { line: loc.line, column: loc.column };
}
error = e;
}
}; };
props_handler = props => { props_handler = props => {
@ -296,6 +272,9 @@
// (answer: probably) // (answer: probably)
component.$$.bound[prop] = value => { component.$$.bound[prop] = value => {
dispatch('binding', { prop, value }); dispatch('binding', { prop, value });
values_store.update(values => Object.assign({}, values, {
[prop]: value
}));
}; };
}); });
}; };
@ -305,12 +284,15 @@
function noop(){} function noop(){}
let run = noop; let run = noop;
let bundle_handler = noop; let bundle_handler = noop;
let values_handler = noop;
let props_handler = noop; let props_handler = noop;
$: bundle_handler(bundle); $: bundle_handler(bundle);
$: values_handler(values);
$: props_handler(props); $: props_handler(props);
// pending https://github.com/sveltejs/svelte/issues/1889
$: {
$values_store;
}
</script> </script>
<style> <style>

@ -5,16 +5,14 @@
import CodeMirror from '../CodeMirror.html'; import CodeMirror from '../CodeMirror.html';
export let bundle export let bundle
export let compiled; export let code;
export let dom; export let dom;
export let ssr; export let ssr;
export let props; export let props;
export let values; export let values_store;
export let json5; export let json5;
export let sourceError; export let sourceError;
export let runtimeError; export let runtimeError;
export let dataError;
export let dataErrorLoc;
// refs // refs
let viewer; let viewer;
@ -22,14 +20,20 @@
let view = 'result'; let view = 'result';
function updateValues(prop, value) {
values_store.update(v => Object.assign({}, v, {
[prop]: value
}));
}
function setPropFromViewer(prop, value) { function setPropFromViewer(prop, value) {
// console.log(`setting prop from component`, prop, value);
// propEditors[prop].setValue(value); // propEditors[prop].setValue(value);
updateValues(prop, value);
} }
function setPropFromEditor(prop, value) { function setPropFromEditor(prop, value) {
console.log(`setting prop from editor`, prop, value);
viewer.setProp(prop, value); viewer.setProp(prop, value);
updateValues(prop, value);
} }
</script> </script>
@ -75,8 +79,8 @@
display: grid; display: grid;
padding: 0.5em; padding: 0.5em;
grid-template-columns: auto 1fr; grid-template-columns: auto 1fr;
grid-template-rows: min-content; grid-auto-rows: min-content;
grid-column-gap: 0.5em; grid-gap: 0.5em;
overflow-y: auto; overflow-y: auto;
} }
@ -107,8 +111,7 @@
{bundle} {bundle}
{dom} {dom}
{ssr} {ssr}
{data} {values_store}
bind:values
{props} {props}
{sourceError} {sourceError}
bind:error={runtimeError} bind:error={runtimeError}
@ -125,17 +128,27 @@
<section slot="b"> <section slot="b">
<h3>Props editor</h3> <h3>Props editor</h3>
{#if props}
{#if props.length > 0}
<div class="props"> <div class="props">
{#each props.sort() as prop (prop)} {#each props.sort() as prop (prop)}
<code style="display: block; whitespace: pre;">{prop}</code> <code style="display: block; whitespace: pre;">{prop}</code>
<!-- TODO `bind:this={propEditors[prop]}` — currently fails --> <!-- TODO `bind:this={propEditors[prop]}` — currently fails -->
<PropEditor <PropEditor
value={values[prop]} value={$values_store[prop]}
on:change="{e => setPropFromEditor(prop, e.detail.value)}" on:change="{e => setPropFromEditor(prop, e.detail.value)}"
/> />
{/each} {/each}
</div> </div>
{:else}
<div style="padding: 0.5em">
<!-- TODO explain distincion between logic-less and logic-ful components? -->
<!-- TODO style the <a> so it looks like a link -->
<p>This component has no props — <a href="guide#external-properties">declare props with the export keyword</a></p>
</div>
{/if}
{/if}
</section> </section>
</SplitPane> </SplitPane>
{:else} {:else}
@ -143,7 +156,7 @@
<div slot="a"> <div slot="a">
<CodeMirror <CodeMirror
mode="javascript" mode="javascript"
code={compiled} {code}
error={sourceError} error={sourceError}
errorLoc={sourceErrorLoc} errorLoc={sourceErrorLoc}
readonly readonly

@ -1,5 +1,6 @@
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { writable } from 'svelte/store';
import * as fleece from 'golden-fleece'; import * as fleece from 'golden-fleece';
import SplitPane from './SplitPane.html'; import SplitPane from './SplitPane.html';
import CodeMirror from './CodeMirror.html'; import CodeMirror from './CodeMirror.html';
@ -7,151 +8,146 @@
import Output from './Output/index.html'; import Output from './Output/index.html';
export let version; export let version;
export let components; export let app;
export let selectedComponent;
export let values; let component_store = writable([]);
let values_store = writable({});
let selected_store = writable(null);
$: {
component_store.set(app.components);
values_store.set(app.values);
selected_store.set(app.components[0]);
}
$: {
// necessary pending https://github.com/sveltejs/svelte/issues/1889
$component_store;
}
let workers;
let bundle = null; let bundle = null;
let dom; let dom;
let ssr; let ssr;
let sourceError = null; let sourceError = null;
let runtimeError = null; let runtimeError = null;
let dataError = null;
let dataErrorLoc = null;
let warningCount = 0; let warningCount = 0;
let code = ''; let code = '';
let uid = 0; let uid = 0;
let props = []; let props = null;
let sourceErrorLoc; let sourceErrorLoc;
let runtimeErrorLoc; let runtimeErrorLoc;
let worker;
onMount(async () => { onMount(async () => {
worker = new Worker('/repl-worker.js'); workers = {
compiler: new Worker('/workers/compiler.js'),
bundler: new Worker('/workers/bundler.js')
};
function listener(event) { workers.compiler.postMessage({ type: 'init', version });
switch (event.data.type) { workers.compiler.onmessage = event => {
case 'version': code = event.data.code;
version = event.data.version; if (event.data.props) props = event.data.props;
break; };
case 'bundled': workers.bundler.postMessage({ type: 'init', version });
({ bundle, dom, ssr, warningCount, error: sourceError } = event.data.result); workers.bundler.onmessage = event => {
console.log(dom, sourceError); ({ bundle, dom, ssr, warningCount, error: sourceError } = event.data);
if (sourceError) console.error(sourceError);
runtimeError = null; runtimeError = null;
break; };
case 'compiled':
code = event.data.result.code;
if (event.data.result.props) props = event.data.result.props;
break;
}
}
worker.addEventListener('message', listener);
worker.postMessage({ type: 'init', version });
return () => { return () => {
worker.removeEventListener('message', listener); workers.compiler.terminate();
worker.terminate(); workers.bundler.terminate();
}; };
}); });
function removeComponent() { function removeComponent() {
const component = selectedComponent; const selected = $selected_store;
if (component.name === 'App') { if (selected.name === 'App') {
// App.html can't be removed // App.html can't be removed
component.source = ''; selected.source = '';
selectedComponent = component; // $selected_store.set(selected);
} else { } else {
const index = components.indexOf(component); const components = $component_store;
const index = components.indexOf(selected);
if (~index) { if (~index) {
components = components.slice(0, index).concat(components.slice(index + 1)); component_store.set(components.slice(0, index).concat(components.slice(index + 1)));
} else { } else {
console.error(`Could not find component! That's... odd`); console.error(`Could not find component! That's... odd`);
} }
selectedComponent = components[index] || components[components.length - 1]; selected_store.set(components[index] || components[components.length - 1]);
} }
} }
function compile(component) { function compile(component) {
if (component.type === 'html') { if (component.type === 'html') {
worker.postMessage({ workers.compiler.postMessage({
type: 'compile', type: 'compile',
component, source: component.source,
entry: component === components[0] options: {
name: component.name,
filename: `${component.name}.html`
},
entry: component === $component_store[0]
}); });
} else { } else {
code = component.source; code = component.source;
} }
} }
function handleSelect(event) { function handleChange(event) {
console.log(`handleSelect`); selected_store.update(component => {
// TODO this is a bit hacky — we're relying on mutability
selectedComponent = event.detail.component; // so that updating component_store works... might be better
// compile(selectedComponent); // if a) components had unique IDs, b) we tracked selected
} // *index* rather than component, and c) `selected` was
// derived from `components` and `index`
component.source = event.detail.value;
return component;
});
function handleChange() { component_store.update(c => c);
console.log(`handleChange`);
// recompile selected component // recompile selected component
compile(selectedComponent); compile($selected_store);
// regenerate bundle (TODO do this in a separate worker?) // regenerate bundle (TODO do this in a separate worker?)
worker.postMessage({ type: 'bundle', components }); workers.bundler.postMessage({ type: 'bundle', components: $component_store });
}
function updateData(current) {
const data = fleece.evaluate(json5);
for (const key in data) {
data[key] = current[key];
}
json5 = fleece.patch(json5, data);
} }
function navigate(filename) { function navigate(filename) {
const name = filename.replace(/\.html$/, ''); const name = filename.replace(/\.html$/, '');
if (selectedComponent.name === name) return; console.error(`TODO navigate`);
selectedComponent = components.find(c => c.name === name);
// if (selected.name === name) return;
// selected = components.find(c => c.name === name);
} }
$: if (sourceError && selectedComponent) { $: if (sourceError && $selected_store) {
sourceErrorLoc = sourceError.filename === `${selectedComponent.name}.${selectedComponent.type}` sourceErrorLoc = sourceError.filename === `${$selected_store.name}.${$selected_store.type}`
? sourceError.start ? sourceError.start
: null; : null;
} }
$: if (runtimeError && selectedComponent) { $: if (runtimeError && $selected_store) {
runtimeErrorLoc = runtimeError.filename === `${selectedComponent.name}.${selectedComponent.type}` runtimeErrorLoc = runtimeError.filename === `${$selected_store.name}.${$selected_store.type}`
? runtimeError.start ? runtimeError.start
: null; : null;
} }
$: if (worker && components) { $: if (workers && app.components) {
console.log(`posting`, worker, components); workers.bundler.postMessage({ type: 'bundle', components: app.components });
worker.postMessage({ type: 'bundle', components });
}
$: if (worker && selectedComponent) {
compile(selectedComponent);
} }
$: try { $: if (workers && $selected_store) {
data= fleece.evaluate(json5); compile($selected_store);
dataError = null;
dataErrorLoc = null;
} catch (err) {
dataError = err;
dataErrorLoc = err && err.loc;
} }
</script> </script>
@ -239,13 +235,12 @@
<SplitPane type="horizontal"> <SplitPane type="horizontal">
<section slot=a> <section slot=a>
<Input <Input
bind:components {component_store}
{selectedComponent} {selected_store}
{values_store}
error={sourceError} error={sourceError}
errorLoc="{sourceErrorLoc || runtimeErrorLoc}" errorLoc="{sourceErrorLoc || runtimeErrorLoc}"
{warningCount} {warningCount}
on:create="{e => components = components.concat(e.detail.component)}"
on:select="{e => selectedComponent = e.detail.component}"
on:remove={removeComponent} on:remove={removeComponent}
on:change="{handleChange}" on:change="{handleChange}"
/> />
@ -253,16 +248,16 @@
<section slot=b style='height: 100%;'> <section slot=b style='height: 100%;'>
<Output <Output
{version}
{selected_store}
{code} {code}
{bundle} {bundle}
{ssr} {ssr}
{dom} {dom}
{props} {props}
{values} {values_store}
{sourceError} {sourceError}
{runtimeError} {runtimeError}
{dataError}
{dataErrorLoc}
/> />
</section> </section>
</SplitPane> </SplitPane>

@ -8,19 +8,26 @@
export let query; export let query;
let version = query.version || 'alpha'; let version = query.version;
let components; let demo = query.demo || 'hello-world';
let selectedComponent; let gist = query.gist;
let values = {};
let app = {
components: [],
values: {}
};
let name = 'loading...'; let name = 'loading...';
let zen_mode = false; let zen_mode = false;
let repl;
$: if (typeof history !== 'undefined') { $: if (typeof history !== 'undefined') {
const params = []; const params = [];
if (version !== 'latest') params.push(`version=${version}`); if (version !== 'latest') params.push(`version=${version}`);
if (query.gist) params.push(`gist=${query.gist}`); if (gist) params.push(`gist=${gist}`);
else if (query.demo) params.push(`demo=${query.demo}`); else if (demo) params.push(`demo=${demo}`);
const url = params.length > 0 const url = params.length > 0
? `repl?${params.join('&')}` ? `repl?${params.join('&')}`
@ -37,17 +44,24 @@
}); });
onMount(() => { onMount(() => {
if (query.gist) { fetch(`https://unpkg.com/svelte@${version || 'alpha'}/package.json`)
fetch(`gist/${query.gist}`).then(r => r.json()).then(gist => { .then(r => r.json())
name = gist.description; .then(pkg => {
values = {}; version = pkg.version;
});
components = Object.keys(gist.files) if (gist) {
fetch(`gist/${gist}`).then(r => r.json()).then(({ description, files }) => {
name = description;
const values = {};
const components = Object.keys(files)
.map(file => { .map(file => {
const dot = file.lastIndexOf('.'); const dot = file.lastIndexOf('.');
if (!~dot) return; if (!~dot) return;
const source = gist.files[file].content; const source = files[file].content;
// while we're here... // while we're here...
if (file === 'data.json' || file === 'data.json5') { if (file === 'data.json' || file === 'data.json5') {
@ -70,27 +84,26 @@
return a.name < b.name ? -1 : 1; return a.name < b.name ? -1 : 1;
}); });
selectedComponent = components[0]; app = { components, values };
}); });
} else if (!query.demo) {
query.demo = 'hello-world';
} }
}); });
function load_demo(slug) { function load_demo(slug) {
const url = slugs.has(query.demo) const url = slugs.has(slug)
? `api/examples/${query.demo}` ? `api/examples/${slug}`
: `guide/demo/${query.demo}.json`; : `guide/demo/${slug}.json`;
fetch(url).then(async response => { fetch(url).then(async response => {
if (response.ok) { if (response.ok) {
const demo = await response.json(); const data = await response.json();
name = demo.title; name = data.title;
values = tryParseData(demo.json5) || {}; // TODO make this more error-resistant
components = demo.components;
selectedComponent = components[0]; app = {
values: tryParseData(data.json5) || {}, // TODO make this more error-resistant
components: data.components
};
} }
}); });
} }
@ -103,8 +116,8 @@
} }
} }
$: if (process.browser && query.demo) { $: if (process.browser && demo) {
load_demo(query.demo); load_demo(demo);
} }
</script> </script>
@ -171,19 +184,16 @@
<div class="repl-outer {zen_mode ? 'zen-mode' : ''}"> <div class="repl-outer {zen_mode ? 'zen-mode' : ''}">
<AppControls <AppControls
{examples} {examples}
{components} {app}
{values}
{name} {name}
gist_id={query.gist} {gist}
{repl}
bind:zen_mode bind:zen_mode
on:select="{e => query = Object.assign({}, query, { demo: e.detail.slug, gist: null })}" on:select="{e => (demo = e.detail.slug, gist = null)}"
on:forked="{e => query = Object.assign({}, query, { demo: null, gist: e.detail.gist })}" on:forked="{e => (demo = null, gist = e.detail.gist)}"
/> />
<Repl {#if process.browser}
bind:version <Repl bind:this={repl} {version} {app}/>
{components} {/if}
{selectedComponent}
{values}
/>
</div> </div>

@ -1,59 +1,39 @@
self.window = self; // egregious hack to get magic-string to work in a worker self.window = self; // egregious hack to get magic-string to work in a worker
let version; let version;
let fulfil_ready;
const ready = new Promise(f => { let fulfil;
fulfil_ready = f; let ready = new Promise(f => fulfil = f);
});
self.addEventListener('message', async event => { self.addEventListener('message', async event => {
switch (event.data.type) { switch (event.data.type) {
case 'init': case 'init':
version = await init(event.data.version); version = event.data.version;
postMessage({
type: 'version', importScripts(
version `https://unpkg.com/svelte@${version}/compiler.js`,
}); `https://unpkg.com/rollup/dist/rollup.browser.js`
);
fulfil();
break; break;
case 'bundle': case 'bundle':
await ready; if (event.data.components.length === 0) return;
postMessage({
type: 'bundled',
result: await bundle(event.data.components)
});
break;
case 'compile':
await ready; await ready;
postMessage({ postMessage(
type: 'compiled', await bundle(event.data.components)
result: compile(event.data.component, event.data.entry) );
});
break;
break;
} }
}); });
const commonCompilerOptions = { const commonCompilerOptions = {
cascade: false,
store: true,
skipIntroByDefault: true,
nestedTransitions: true,
dev: true, dev: true,
}; };
async function init(version) {
// TODO use local versions
importScripts(
`https://unpkg.com/svelte@${version}/compiler.js`,
`https://unpkg.com/rollup/dist/rollup.browser.js`
);
fulfil_ready();
return version === 'local' ? version : svelte.VERSION;
}
let cached = { let cached = {
dom: null, dom: null,
ssr: null ssr: null
@ -107,7 +87,6 @@ async function getBundle(mode, cache, lookup) {
}, },
load(id) { load(id) {
if (id.startsWith(`https://`)) return fetch_if_uncached(id); if (id.startsWith(`https://`)) return fetch_if_uncached(id);
if (id in lookup) return lookup[id].source; if (id in lookup) return lookup[id].source;
}, },
transform(code, id) { transform(code, id) {
@ -232,20 +211,3 @@ async function bundle(components) {
}; };
} }
} }
function compile(component, entry) {
try {
const { js, stats } = svelte.compile(component.source, Object.assign({
// TODO make options configurable
name: component.name,
filename: component.name + '.html',
}, commonCompilerOptions));
return { code: js.code, props: entry ? stats.props : null };
} catch (err) {
let result = `/* Error compiling component\n\n${err.message}`;
if (err.frame) result += `\n${err.frame}`;
result += `\n\n*/`;
return { code: result, props: null };
}
}

@ -0,0 +1,41 @@
self.window = self; // egregious hack to get magic-string to work in a worker
let fulfil_ready;
const ready = new Promise(f => {
fulfil_ready = f;
});
self.addEventListener('message', async event => {
switch (event.data.type) {
case 'init':
importScripts(`https://unpkg.com/svelte@${event.data.version}/compiler.js`);
fulfil_ready();
break;
case 'compile':
await ready;
postMessage(compile(event.data));
break;
}
});
const commonCompilerOptions = {
dev: false
};
function compile({ source, options, entry }) {
try {
const { js, stats } = svelte.compile(
source,
Object.assign({}, commonCompilerOptions, options)
);
return { code: js.code, props: entry ? stats.props : null };
} catch (err) {
let result = `/* Error compiling component\n\n${err.message}`;
if (err.frame) result += `\n${err.frame}`;
result += `\n\n*/`;
return { code: result, props: null };
}
}
Loading…
Cancel
Save