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

@ -1423,6 +1423,11 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"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": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.5.2.tgz",

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

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

@ -84,7 +84,7 @@
let previous_error_line;
$: if (editor) {
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)) {

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

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

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

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

@ -8,7 +8,7 @@
export let bundle;
export let dom;
export let ssr;
export let values;
export let values_store;
export let props;
export let sourceError;
export let error;
@ -157,7 +157,7 @@
const createHtml = () => {
try {
evalInIframe(`${ssr.code}
var rendered = SvelteComponent.render(${JSON.stringify(values)});
var rendered = SvelteComponent.render(${JSON.stringify($values_store)});
if (rendered.css.code) {
var style = document.createElement('style');
@ -190,7 +190,7 @@
var component = new SvelteComponent({
target: document.body,
props: ${JSON.stringify(values)}
props: ${JSON.stringify($values_store)}
});`);
component = window.app = window.component = iframe.contentWindow.component;
@ -256,31 +256,7 @@
toDestroy = component;
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();
}
} 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;
}
init();
};
props_handler = props => {
@ -296,6 +272,9 @@
// (answer: probably)
component.$$.bound[prop] = value => {
dispatch('binding', { prop, value });
values_store.update(values => Object.assign({}, values, {
[prop]: value
}));
};
});
};
@ -305,12 +284,15 @@
function noop(){}
let run = noop;
let bundle_handler = noop;
let values_handler = noop;
let props_handler = noop;
$: bundle_handler(bundle);
$: values_handler(values);
$: props_handler(props);
// pending https://github.com/sveltejs/svelte/issues/1889
$: {
$values_store;
}
</script>
<style>

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

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

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

@ -1,59 +1,39 @@
self.window = self; // egregious hack to get magic-string to work in a worker
let version;
let fulfil_ready;
const ready = new Promise(f => {
fulfil_ready = f;
});
let fulfil;
let ready = new Promise(f => fulfil = f);
self.addEventListener('message', async event => {
switch (event.data.type) {
case 'init':
version = await init(event.data.version);
postMessage({
type: 'version',
version
});
version = event.data.version;
importScripts(
`https://unpkg.com/svelte@${version}/compiler.js`,
`https://unpkg.com/rollup/dist/rollup.browser.js`
);
fulfil();
break;
case 'bundle':
await ready;
postMessage({
type: 'bundled',
result: await bundle(event.data.components)
});
break;
if (event.data.components.length === 0) return;
case 'compile':
await ready;
postMessage({
type: 'compiled',
result: compile(event.data.component, event.data.entry)
});
break;
postMessage(
await bundle(event.data.components)
);
break;
}
});
const commonCompilerOptions = {
cascade: false,
store: true,
skipIntroByDefault: true,
nestedTransitions: 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 = {
dom: null,
ssr: null
@ -107,7 +87,6 @@ async function getBundle(mode, cache, lookup) {
},
load(id) {
if (id.startsWith(`https://`)) return fetch_if_uncached(id);
if (id in lookup) return lookup[id].source;
},
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