use external repl package everywhere

pull/2322/head
Richard Harris 6 years ago
parent c7f9f514be
commit c3b4e6c792

@ -1139,6 +1139,30 @@
"resolved": "https://registry.npmjs.org/@polka/url/-/url-0.5.0.tgz",
"integrity": "sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw=="
},
"@sveltejs/svelte-repl": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/@sveltejs/svelte-repl/-/svelte-repl-0.0.2.tgz",
"integrity": "sha512-g2vBAZJ3e9Oy6FG5FLUhRQ7muRslNtyzgros/AzgkNTsWUKSuLQMnE8hOFx5w+wSLMHLrNqKPVc7m18Wk746Gw==",
"dev": true,
"requires": {
"codemirror": "^5.45.0",
"yootils": "0.0.15"
},
"dependencies": {
"codemirror": {
"version": "5.45.0",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.45.0.tgz",
"integrity": "sha512-c19j644usCE8gQaXa0jqn2B/HN9MnB2u6qPIrrhrMkB+QAP42y8G4QnTwuwbVSoUS1jEl7JU9HZMGhCDL0nsAw==",
"dev": true
},
"yootils": {
"version": "0.0.15",
"resolved": "https://registry.npmjs.org/yootils/-/yootils-0.0.15.tgz",
"integrity": "sha512-GvGLuJ7XHJPGEUQ52vh8fh+vPjfikuGcu7yBswfrsNsHqnAoytOVuSb69eM0j8wQIjMz0U3kY3YsfwMhJgfG9w==",
"dev": true
}
}
},
"@types/estree": {
"version": "0.0.39",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",

@ -39,6 +39,7 @@
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.3.1",
"@babel/runtime": "^7.3.1",
"@sveltejs/svelte-repl": "0.0.2",
"chokidar": "^2.1.2",
"degit": "^2.1.3",
"eslint-plugin-svelte3": "git+https://github.com/sveltejs/eslint-plugin-svelte3.git#semver:*",

@ -1,44 +0,0 @@
const workers = new Map();
let uid = 1;
export default class Bundler {
constructor(version) {
if (!workers.has(version)) {
const worker = new Worker('/workers/bundler.js');
worker.postMessage({ type: 'init', version });
workers.set(version, worker);
}
this.worker = workers.get(version);
this.handlers = new Map();
this.worker.addEventListener('message', event => {
const handler = this.handlers.get(event.data.id);
if (handler) { // if no handler, was meant for a different REPL
handler(event.data);
this.handlers.delete(event.data.id);
}
});
}
bundle(components) {
return new Promise(fulfil => {
const id = uid++;
this.handlers.set(id, fulfil);
this.worker.postMessage({
id,
type: 'bundle',
components
});
});
}
destroy() {
this.worker.terminate();
}
}

@ -1,273 +0,0 @@
<script context="module">
let codemirror_promise;
let _CodeMirror;
if (process.browser) {
codemirror_promise = import(/* webpackChunkName: "codemirror" */ './_codemirror.js');
codemirror_promise.then(mod => {
_CodeMirror = mod.default;
});
}
</script>
<script>
import { onMount, beforeUpdate, createEventDispatcher, getContext } from 'svelte';
import Message from './Message.svelte';
const dispatch = createEventDispatcher();
const { navigate } = getContext('REPL');
export let readonly = false;
export let errorLoc = null;
export let flex = false;
export let lineNumbers = true;
export let tab = true;
let w;
let h;
let code = '';
let mode;
// We have to expose set and update methods, rather
// than making this state-driven through props,
// because it's difficult to update an editor
// without resetting scroll otherwise
export async function set(new_code, new_mode) {
if (new_mode !== mode) {
await createEditor(mode = new_mode);
}
code = new_code;
updating_externally = true;
if (editor) editor.setValue(code);
updating_externally = false;
}
export function update(new_code) {
code = new_code;
if (editor) {
const { left, top } = editor.getScrollInfo();
editor.setValue(code = new_code);
editor.scrollTo(left, top);
}
}
export function resize() {
editor.refresh();
}
export function focus() {
editor.focus();
}
const modes = {
js: {
name: 'javascript',
json: false
},
json: {
name: 'javascript',
json: true
},
svelte: {
name: 'handlebars',
base: 'text/html'
}
};
const refs = {};
let editor;
let updating_externally = false;
let marker;
let error_line;
let destroyed = false;
let CodeMirror;
$: if (editor && w && h) {
editor.refresh();
}
$: {
if (marker) marker.clear();
if (errorLoc) {
const line = errorLoc.line - 1;
const ch = errorLoc.column;
marker = editor.markText({ line, ch }, { line, ch: ch + 1 }, {
className: 'error-loc'
});
error_line = line;
} else {
error_line = null;
}
}
let previous_error_line;
$: if (editor) {
if (previous_error_line != null) {
editor.removeLineClass(previous_error_line, 'wrap', 'error-line')
}
if (error_line && (error_line !== previous_error_line)) {
editor.addLineClass(error_line, 'wrap', 'error-line');
previous_error_line = error_line;
}
}
onMount(() => {
if (_CodeMirror) {
CodeMirror = _CodeMirror;
createEditor(mode || 'svelte').then(() => {
editor.setValue(code || '');
});
} else {
codemirror_promise.then(async mod => {
CodeMirror = mod.default;
await createEditor(mode || 'svelte');
editor.setValue(code || '');
});
}
return () => {
destroyed = true;
if (editor) editor.toTextArea();
}
});
async function createEditor(mode) {
if (destroyed || !CodeMirror) return;
if (editor) editor.toTextArea();
const opts = {
lineNumbers,
lineWrapping: true,
indentWithTabs: true,
indentUnit: 2,
tabSize: 2,
value: '',
mode: modes[mode] || {
name: mode
},
readOnly: readonly
};
if (!tab) opts.extraKeys = {
Tab: tab,
'Shift-Tab': tab
};
// Creating a text editor is a lot of work, so we yield
// the main thread for a moment. This helps reduce jank
await sleep(50);
if (destroyed) return;
editor = CodeMirror.fromTextArea(refs.editor, opts);
editor.on('change', instance => {
if (!updating_externally) {
const value = instance.getValue();
dispatch('change', { value });
}
});
await sleep(50);
editor.refresh();
}
function sleep(ms) {
return new Promise(fulfil => setTimeout(fulfil, ms));
}
</script>
<style>
.codemirror-container {
position: relative;
width: 100%;
height: 100%;
border: none;
line-height: 1.5;
overflow: hidden;
}
.codemirror-container :global(.CodeMirror) {
height: 100%;
/* background: var(--background); */
background: transparent;
font: 400 var(--code-fs)/1.7 var(--font-mono);
color: var(--base);
}
.codemirror-container.flex :global(.CodeMirror) {
height: auto;
}
.codemirror-container.flex :global(.CodeMirror-lines) {
padding: 0;
}
.codemirror-container :global(.CodeMirror-gutters) {
padding: 0 1.6rem 0 .8rem;
border: none;
}
.codemirror-container :global(.error-loc) {
position: relative;
border-bottom: 2px solid #da106e;
}
.codemirror-container :global(.error-line) {
background-color: rgba(200, 0, 0, .05);
}
textarea {
visibility: hidden;
}
pre {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
border: none;
padding: 4px 4px 4px 60px;
resize: none;
font-family: var(--font-mono);
font-size: 1.3rem;
line-height: 1.7;
user-select: none;
pointer-events: none;
color: #ccc;
tab-size: 2;
-moz-tab-size: 2;
}
.flex pre {
padding: 0 0 0 4px;
height: auto;
}
</style>
<div class='codemirror-container' class:flex bind:offsetWidth={w} bind:offsetHeight={h}>
<textarea
tabindex='2'
bind:this={refs.editor}
readonly
value={code}
></textarea>
{#if !CodeMirror}
<pre style="position: absolute; left: 0; top: 0"
>{code}</pre>
<div style="position: absolute; width: 100%; bottom: 0">
<Message kind='info'>loading editor...</Message>
</div>
{/if}
</div>

@ -1,233 +0,0 @@
<script>
import { getContext, createEventDispatcher } from 'svelte';
import Icon from '../../Icon.svelte';
import { enter } from '../../../utils/events.js';
export let handle_select;
const { components, selected, request_focus, rebundle } = getContext('REPL');
let editing = null;
function selectComponent(component) {
if ($selected !== component) {
editing = null;
handle_select(component);
}
}
function editTab(component) {
if ($selected === component) {
editing = $selected;
}
}
function closeEdit() {
const match = /(.+)\.(svelte|js)$/.exec($selected.name);
$selected.name = match ? match[1] : $selected.name;
if (match && match[2]) $selected.type = match[2];
editing = null;
// re-select, in case the type changed
handle_select($selected);
components = components; // TODO necessary?
// focus the editor, but wait a beat (so key events aren't misdirected)
setTimeout(request_focus);
rebundle();
}
function remove(component) {
let result = confirm(`Are you sure you want to delete ${component.name}.${component.type}?`);
if (result) {
const index = $components.indexOf(component);
if (~index) {
components.set($components.slice(0, index).concat($components.slice(index + 1)));
} else {
console.error(`Could not find component! That's... odd`);
}
handle_select($components[index] || $components[$components.length - 1]);
}
}
function selectInput(event) {
setTimeout(() => {
event.target.select();
});
}
let uid = 1;
function addNew() {
const component = {
name: uid++ ? `Component${uid}` : 'Component1',
type: 'svelte',
source: ''
};
editing = component;
setTimeout(() => {
// TODO we can do this without IDs
document.getElementById(component.name).scrollIntoView(false);
});
components.update(components => components.concat(component));
handle_select(component);
}
</script>
<style>
.component-selector {
position: relative;
border-bottom: 1px solid #eee;
overflow: hidden;
}
.file-tabs {
border: none;
margin: 0;
white-space: nowrap;
overflow-x: auto;
overflow-y: hidden;
height: 10em;
}
.file-tabs .button, .file-tabs button {
position: relative;
display: inline-block;
font: 400 1.2rem/1.5 var(--font);
border-bottom: var(--border-w) solid transparent;
padding: 1.2rem 1.4rem 0.8rem 0.8rem;
margin: 0;
color: #999;
}
.file-tabs .button:first-child {
padding-left: 1.2rem;
}
.file-tabs .button.active {
/* color: var(--second); */
color: #333;
border-bottom: var(--border-w) solid var(--prime);
}
.editable, .uneditable, .input-sizer, input {
display: inline-block;
position: relative;
line-height: 1;
}
.input-sizer {
color: #ccc;
}
input {
position: absolute;
width: 100%;
left: 0.8rem;
top: 1.2rem;
font: 400 1.2rem/1.5 var(--font);
border: none;
color: var(--flash);
outline: none;
background-color: transparent;
}
.remove {
position: absolute;
display: none;
right: .1rem;
top: .4rem;
width: 1.6rem;
text-align: right;
padding: 1.2em 0 1.2em .5em;
font-size: 0.8rem;
cursor: pointer;
}
.remove:hover {
color: var(--flash);
}
.file-tabs .button.active .editable {
cursor: text;
}
.file-tabs .button.active .remove {
display: block;
}
.add-new {
position: absolute;
left: 0;
top: 0;
padding: 1.2rem 1rem 0.8rem 0 !important;
height: 4.2rem;
text-align: center;
background-color: white;
}
.add-new:hover {
color: var(--flash) !important;
}
</style>
<div class="component-selector">
{#if $components.length}
<div class="file-tabs" on:dblclick="{addNew}">
{#each $components as component}
<div
id={component.name}
class="button"
role="button"
class:active="{component === $selected}"
on:click="{() => selectComponent(component)}"
on:dblclick="{e => e.stopPropagation()}"
>
{#if component.name == 'App'}
<div class="uneditable">
App.svelte
</div>
{:else}
{#if component === editing}
<span class="input-sizer">{editing.name + (/\./.test(editing.name) ? '' : `.${editing.type}`)}</span>
<input
autofocus
spellcheck={false}
bind:value={editing.name}
on:focus={selectInput}
on:blur={closeEdit}
use:enter="{e => e.target.blur()}"
>
{:else}
<div
class="editable"
title="edit component name"
on:click="{() => editTab(component)}"
>
{component.name}.{component.type}
</div>
<span class="remove" on:click="{() => remove(component)}">
<Icon name="close" size={12}/>
<!-- &times; -->
</span>
{/if}
{/if}
</div>
{/each}
<button class="add-new" on:click={addNew} title="add new component">
<Icon name="plus" />
</button>
</div>
{/if}
</div>

@ -1,63 +0,0 @@
<script>
import { getContext, onMount } from 'svelte';
import CodeMirror from '../CodeMirror.svelte';
import Message from '../Message.svelte';
const { bundle, selected, handle_change, navigate, register_module_editor } = getContext('REPL');
export let errorLoc;
let editor;
onMount(() => {
register_module_editor(editor);
});
export function focus() {
editor.focus();
}
</script>
<style>
.editor-wrapper {
z-index: 5;
background: var(--back-light);
display: flex;
flex-direction: column;
}
.editor {
height: 0;
flex: 1 1 auto;
}
@media (min-width: 600px) {
:global(.columns) .editor-wrapper {
/* make it easier to interact with scrollbar */
padding-right: 8px;
height: auto;
/* height: 100%; */
}
}
</style>
<div class="editor-wrapper">
<div class="editor">
<CodeMirror
bind:this={editor}
{errorLoc}
on:change={handle_change}
/>
</div>
<div class="info">
{#if $bundle}
{#if $bundle.error}
<Message kind="error" details={$bundle.error} filename="{$selected.name}.{$selected.type}"/>
{:else if $bundle.warnings.length > 0}
{#each $bundle.warnings as warning}
<Message kind="warning" details={warning} filename="{$selected.name}.{$selected.type}"/>
{/each}
{/if}
{/if}
</div>
</div>

@ -1,35 +0,0 @@
<script>
export let checked;
</script>
<style>
.input-output-toggle {
display: grid;
position: absolute;
user-select: none;
grid-template-columns: 1fr 40px 1fr;
grid-gap: 0.5em;
align-items: center;
width: 100%;
height: 4.2rem;
border-top: 1px solid var(--second);
}
input {
display: block;
}
span {
color: #ccc;
}
.active {
color: #555;
}
</style>
<label class="input-output-toggle">
<span class:active={!checked} style="text-align: right">input</span>
<input type="checkbox" bind:checked>
<span class:active={checked}>output</span>
</label>

@ -1,80 +0,0 @@
<script>
import { getContext } from 'svelte';
const { navigate } = getContext('REPL');
export let kind;
export let details = null;
export let filename = null;
function message(details) {
let str = details.message || '[missing message]';
let loc = [];
if (details.filename && details.filename !== filename) {
loc.push(details.filename);
}
if (details.start) loc.push(details.start.line, details.start.column);
return str + (loc.length ? ` (${loc.join(':')})` : ``);
};
</script>
<style>
.message {
position: relative;
color: white;
padding: 1.2rem 1.6rem 1.2rem 4.4rem;
font: 400 1.2rem/1.7 var(--font);
margin: 0;
border-top: 1px solid white;
}
.navigable {
cursor: pointer;
}
.message::before {
content: '!';
position: absolute;
left: 1.2rem;
top: 1.1rem;
width: 1rem;
height: 1rem;
text-align: center;
line-height: 1;
padding: .4rem;
border-radius: 50%;
color: white;
border: .2rem solid white;
}
p {
margin: 0;
}
.info {
background-color: var(--second);
}
.error {
background-color: #da106e;
}
.warning {
background-color: #e47e0a;
}
</style>
<div class="message {kind}">
{#if details}
<p
class:navigable={details.filename}
on:click="{() => navigate(details)}"
>{message(details)}</p>
{:else}
<slot></slot>
{/if}
</div>

@ -1,49 +0,0 @@
const workers = new Map();
let uid = 1;
export default class Compiler {
constructor(version) {
if (!workers.has(version)) {
const worker = new Worker('/workers/compiler.js');
worker.postMessage({ type: 'init', version });
workers.set(version, worker);
}
this.worker = workers.get(version);
this.handlers = new Map();
this.worker.addEventListener('message', event => {
const handler = this.handlers.get(event.data.id);
if (handler) { // if no handler, was meant for a different REPL
handler(event.data.result);
this.handlers.delete(event.data.id);
}
});
}
compile(component, options) {
return new Promise(fulfil => {
const id = uid++;
this.handlers.set(id, fulfil);
this.worker.postMessage({
id,
type: 'compile',
source: component.source,
options: Object.assign({
name: component.name,
filename: `${component.name}.svelte`
}, options),
entry: component.name === 'App'
});
});
}
destroy() {
this.worker.terminate();
}
}

@ -1,146 +0,0 @@
<script>
import { getContext } from 'svelte';
const { compile_options } = getContext('REPL');
</script>
<style>
.options {
padding: 0 1rem;
font-family: var(--font-ui);
font-size: 1.3rem;
color: #999;
}
.option {
display: block;
padding: 0 0 0 1.25em;
white-space: nowrap;
color: var(--text);
user-select: none;
}
.key {
display: inline-block;
width: 9em;
}
.string {
color: hsl(41, 37%, 45%);
}
.boolean {
color: hsl(45, 7%, 45%);
}
label {
display: inline-block;
}
label[for] {
color: var(--string);
}
input[type=checkbox] {
top: -1px;
}
input[type=radio] {
position: absolute;
top: auto;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
width: 1px;
height: 1px;
white-space: nowrap;
}
input[type=radio] + label {
padding: 0 0 0 1.6em;
margin: 0 0.6em 0 0;
opacity: 0.7;
}
input[type=radio]:checked + label {
opacity: 1;
}
/* input[type=radio]:focus + label {
color: #00f;
outline: 1px dotted #00f;
} */
input[type=radio] + label:before {
content: '';
background: #eee;
display: block;
box-sizing: border-box;
float: left;
width: 1.5rem;
height: 1.5rem;
margin-left: -2.1rem;
margin-top: 0.4rem;
vertical-align: top;
cursor: pointer;
text-align: center;
transition: box-shadow 0.1s ease-out;
}
input[type=radio] + label:before {
background-color: var(--second);
border-radius: 100%;
box-shadow: inset 0 0 0 0.5em rgba(255, 255, 255, .95);
border: 1px solid var(--second);
}
input[type=radio]:checked + label:before {
background-color: var(--prime);
box-shadow: inset 0 0 0 .15em rgba(255, 255, 255, .95);
border: 1px solid var(--second);
transition: box-shadow 0.2s ease-out;
}
</style>
<div class="options">
result = svelte.compile(source, &#123;
<div class="option">
<span class="key">generate:</span>
<input id="dom-input" type="radio" bind:group={$compile_options.generate} value="dom">
<label for="dom-input"><span class="string">"dom"</span></label>
<input id="ssr-input" type="radio" bind:group={$compile_options.generate} value="ssr">
<label for="ssr-input"><span class="string">"ssr"</span>,</label>
</div>
<label class="option">
<span class="key">dev:</span>
<input type="checkbox" bind:checked={$compile_options.dev}> <span class="boolean">{$compile_options.dev}</span>,
</label>
<label class="option">
<span class="key">css:</span>
<input type="checkbox" bind:checked={$compile_options.css}> <span class="boolean">{$compile_options.css}</span>,
</label>
<label class="option">
<span class="key">hydratable:</span>
<input type="checkbox" bind:checked={$compile_options.hydratable}> <span class="boolean">{$compile_options.hydratable}</span>,
</label>
<label class="option">
<span class="key">customElement:</span>
<input type="checkbox" bind:checked={$compile_options.customElement}> <span class="boolean">{$compile_options.customElement}</span>,
</label>
<label class="option">
<span class="key">immutable:</span>
<input type="checkbox" bind:checked={$compile_options.immutable}> <span class="boolean">{$compile_options.immutable}</span>,
</label>
<label class="option">
<span class="key">legacy:</span>
<input type="checkbox" bind:checked={$compile_options.legacy}> <span class="boolean">{$compile_options.legacy}</span>
</label>
});
</div>

@ -1,75 +0,0 @@
let uid = 1;
export default class ReplProxy {
constructor(iframe, handlers) {
this.iframe = iframe;
this.handlers = handlers;
this.pending_cmds = new Map();
this.handle_event = e => this.handle_repl_message(e);
window.addEventListener('message', this.handle_event, false);
}
destroy() {
window.removeEventListener('message', this.handle_event);
}
iframe_command(action, args) {
return new Promise((resolve, reject) => {
const cmd_id = uid++;
this.pending_cmds.set(cmd_id, { resolve, reject });
this.iframe.contentWindow.postMessage({ action, cmd_id, args }, '*');
});
}
handle_command_message(cmd_data) {
let action = cmd_data.action;
let id = cmd_data.cmd_id;
let handler = this.pending_cmds.get(id);
if (handler) {
this.pending_cmds.delete(id);
if (action === 'cmd_error') {
let { message, stack } = cmd_data;
let e = new Error(message);
e.stack = stack;
handler.reject(e)
}
if (action === 'cmd_ok') {
handler.resolve(cmd_data.args)
}
} else {
console.error('command not found', id, cmd_data, [...this.pending_cmds.keys()]);
}
}
handle_repl_message(event) {
if (event.source !== this.iframe.contentWindow) return;
const { action, args } = event.data;
if (action === 'cmd_error' || action === 'cmd_ok') {
this.handle_command_message(event.data);
}
if (action === 'fetch_progress') {
this.handlers.on_fetch_progress(args.remaining)
}
}
eval(script) {
return this.iframe_command('eval', { script });
}
handle_links() {
return this.iframe_command('catch_clicks', {});
}
fetch_imports(imports, import_map) {
return this.iframe_command('fetch_imports', { imports, import_map })
}
}

@ -1,156 +0,0 @@
<script>
import { onMount, createEventDispatcher, getContext } from 'svelte';
import getLocationFromStack from './getLocationFromStack.js';
import ReplProxy from './ReplProxy.js';
import Message from '../Message.svelte';
import { decode } from 'sourcemap-codec';
const dispatch = createEventDispatcher();
const { bundle, navigate } = getContext('REPL');
export let error; // TODO should this be exposed as a prop?
export function setProp(prop, value) {
if (!proxy) return;
proxy.setProp(prop, value);
}
export let relaxed = false;
let iframe;
let pending_imports = 0;
let pending = false;
let proxy = null;
let ready = false;
let inited = false;
onMount(() => {
proxy = new ReplProxy(iframe, {
on_fetch_progress: progress => {
pending_imports = progress;
}
});
iframe.addEventListener('load', () => {
proxy.handle_links();
ready = true;
});
return () => {
proxy.destroy();
}
});
let current_token;
async function apply_bundle($bundle) {
if (!$bundle || $bundle.error) return;
const token = current_token = {};
try {
await proxy.fetch_imports($bundle.imports, $bundle.import_map);
if (token !== current_token) return;
await proxy.eval(`
// needed for context API tutorial
window.MAPBOX_ACCESS_TOKEN = process.env.MAPBOX_ACCESS_TOKEN;
const styles = document.querySelectorAll('style[id^=svelte-]');
${$bundle.dom.code}
let i = styles.length;
while (i--) styles[i].parentNode.removeChild(styles[i]);
if (window.component) {
try {
window.component.$destroy();
} catch (err) {
console.error(err);
}
}
document.body.innerHTML = '';
window.location.hash = '';
window._svelteTransitionManager = null;
window.component = new SvelteComponent.default({
target: document.body
});
`);
error = null;
} catch (e) {
const loc = getLocationFromStack(e.stack, $bundle.dom.map);
if (loc) {
e.filename = loc.source;
e.loc = { line: loc.line, column: loc.column };
}
error = e;
}
inited = true;
}
$: if (ready) apply_bundle($bundle);
</script>
<style>
.iframe-container {
position: absolute;
background-color: white;
border: none;
width: 100%;
height: 100%;
}
iframe {
width: 100%;
height: 100%;
/* height: calc(100vh - var(--nav-h)); */
border: none;
display: block;
}
.greyed-out {
filter: grayscale(50%) blur(1px);
opacity: .25;
}
.overlay {
position: absolute;
bottom: 0;
width: 100%;
}
</style>
<div class="iframe-container">
<iframe title="Result" class:inited bind:this={iframe} sandbox="allow-popups-to-escape-sandbox allow-scripts allow-popups allow-forms allow-pointer-lock allow-top-navigation allow-modals {relaxed ? 'allow-scripts allow-same-origin' : ''}" class="{error || pending || pending_imports ? 'greyed-out' : ''}" srcdoc='
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="/repl-viewer.css">
</head>
<body>
<script src="/curl.js"></script>
<script>curl.config(&#123; dontAddFileExt: /./ });</script>
<script src="/repl-runner.js"></script>
</body>
</html>
'></iframe>
<div class="overlay">
{#if error}
<Message kind="error" details={error}/>
{:else if !$bundle}
<Message kind="info">loading Svelte compiler...</Message>
{:else if pending_imports}
<Message kind="info">loading {pending_imports} {pending_imports === 1 ? 'dependency' : 'dependencies'} from
https://bundle.run</Message>
{/if}
</div>
</div>

@ -1,31 +0,0 @@
import { decode } from 'sourcemap-codec';
export default function getLocationFromStack(stack, map) {
if (!stack) return;
const last = stack.split('\n')[1];
const match = /<anonymous>:(\d+):(\d+)\)$/.exec(last);
if (!match) return null;
const line = +match[1];
const column = +match[2];
return trace({ line, column }, map);
}
function trace(loc, map) {
const mappings = decode(map.mappings);
const segments = mappings[loc.line - 1];
for (let i = 0; i < segments.length; i += 1) {
const segment = segments[i];
if (segment[0] === loc.column) {
const [, sourceIndex, line, column] = segment;
const source = map.sources[sourceIndex].slice(2);
return { source, line: line + 1, column };
}
}
return null;
}

@ -1,166 +0,0 @@
<script>
import { getContext, onMount } from 'svelte';
import SplitPane from '../SplitPane.svelte';
import Viewer from './Viewer.svelte';
import CompilerOptions from './CompilerOptions.svelte';
import Compiler from './Compiler.js';
import CodeMirror from '../CodeMirror.svelte';
const { register_output } = getContext('REPL');
export let version;
export let sourceErrorLoc = null;
export let runtimeError = null;
export let embedded = false;
export let relaxed = false;
let foo; // TODO workaround for https://github.com/sveltejs/svelte/issues/2122
register_output({
set: async (selected, options) => {
if (selected.type === 'js') {
js_editor.set(`/* Select a component to see its compiled code */`);
css_editor.set(`/* Select a component to see its compiled code */`);
return;
}
const compiled = await compiler.compile(selected, options);
js_editor.set(compiled.js, 'js');
css_editor.set(compiled.css, 'css');
},
update: async (selected, options) => {
if (selected.type === 'js') return;
const compiled = await compiler.compile(selected, options);
js_editor.update(compiled.js);
css_editor.update(compiled.css);
}
});
const compiler = process.browser && new Compiler(version);
// refs
let viewer;
let js_editor;
let css_editor;
const setters = {};
let view = 'result';
</script>
<style>
.view-toggle {
height: var(--pane-controls-h);
border-bottom: 1px solid #eee;
white-space: nowrap;
}
button {
/* width: 50%;
height: 100%; */
text-align: left;
position: relative;
font: 400 1.2rem/1.5 var(--font);
border-bottom: var(--border-w) solid transparent;
padding: 1.2rem 1.2rem 0.8rem 1.2rem;
color: #999;
}
button.active {
border-bottom: var(--border-w) solid var(--prime);
color: #333;
}
div[slot] {
height: 100%;
}
h3 {
font: 700 1.2rem/1.5 var(--font);
padding: 1.2rem 0 0.8rem 1rem;
color: var(--text);
}
.tab-content {
position: absolute;
width: 100%;
height: calc(100% - 4.2rem);
opacity: 0;
pointer-events: none;
}
.tab-content.visible {
/* can't use visibility due to a weird painting bug in Chrome */
opacity: 1;
pointer-events: all;
}
</style>
<div class="view-toggle">
<button
class:active="{view === 'result'}"
on:click="{() => view = 'result'}"
>Result</button>
<button
class:active="{view === 'js'}"
on:click="{() => view = 'js'}"
>JS output</button>
<button
class:active="{view === 'css'}"
on:click="{() => view = 'css'}"
>CSS output</button>
</div>
<!-- component viewer -->
<div class="tab-content" class:visible="{view === 'result'}">
<Viewer
bind:this={viewer}
bind:error={runtimeError}
{relaxed}
on:binding="{e => setPropFromViewer(e.detail.prop, e.detail.value)}"
/>
</div>
<!-- js output -->
<div class="tab-content" class:visible="{view === 'js'}">
{#if embedded}
<CodeMirror
bind:this={js_editor}
mode="js"
errorLoc={sourceErrorLoc}
readonly
/>
{:else}
<SplitPane type="vertical" pos={67}>
<div slot="a">
<CodeMirror
bind:this={js_editor}
mode="js"
errorLoc={sourceErrorLoc}
readonly
/>
</div>
<section slot="b">
<h3>Compiler options</h3>
<CompilerOptions bind:foo={foo}/>
</section>
</SplitPane>
{/if}
</div>
<!-- css output -->
<div class="tab-content" class:visible="{view === 'css'}">
<CodeMirror
bind:this={css_editor}
mode="css"
errorLoc={sourceErrorLoc}
readonly
/>
</div>

@ -1,7 +1,7 @@
<script>
import { onMount } from 'svelte';
import { process_example } from './process_example.js';
import Repl from '../../components/Repl/index.svelte';
import Repl from '@sveltejs/svelte-repl';
export let version = 'beta';
export let gist = null;
@ -65,6 +65,12 @@
});
$: if (embedded) document.title = `${name} • Svelte REPL`;
$: svelteUrl = version === 'local' ?
'/repl/local' :
`https://unpkg.com/svelte@${version}`;
const rollupUrl = `https://unpkg.com/rollup@1/dist/rollup.browser.js`;
</script>
<style>
@ -83,6 +89,6 @@
<div class="repl-outer">
{#if process.browser}
<Repl bind:this={repl} {version} embedded={true} relaxed/>
<Repl bind:this={repl} {svelteUrl} {rollupUrl} embedded={true} relaxed/>
{/if}
</div>

@ -1,164 +0,0 @@
<script>
import * as yootils from 'yootils';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
export let type;
export let pos = 50;
export let fixed = false;
export let fixed_pos = pos;
export let min = 50;
// export let min1 = min;
// export let min2 = min;
const refs = {};
const side = type === 'horizontal' ? 'left' : 'top';
const dimension = type === 'horizontal' ? 'width' : 'height';
let dragging = false;
function setPos(event) {
const { top, bottom, left, right } = refs.container.getBoundingClientRect();
const extents = type === 'vertical' ? [top, bottom] : [left, right];
const px = yootils.clamp(
type === 'vertical' ? event.clientY : event.clientX,
extents[0] + min,
extents[1] - min
);
pos = 100 * (px - extents[0]) / (extents[1] - extents[0]);
dispatch('change');
}
function drag(node, callback) {
const mousedown = event => {
if (event.which !== 1) return;
event.preventDefault();
dragging = true;
const onmouseup = () => {
dragging = false;
window.removeEventListener('mousemove', callback, false);
window.removeEventListener('mouseup', onmouseup, false);
};
window.addEventListener('mousemove', callback, false);
window.addEventListener('mouseup', onmouseup, false);
}
node.addEventListener('mousedown', mousedown, false);
return {
destroy() {
node.removeEventListener('mousedown', onmousedown, false);
}
};
}
</script>
<style>
.container {
position: relative;
width: 100%;
height: 100%;
}
.pane {
position: relative;
float: left;
width: 100%;
height: 100%;
}
.mousecatcher {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(255,255,255,.01);
}
.divider {
position: absolute;
z-index: 10;
display: none;
}
.divider::after {
content: '';
position: absolute;
/* background-color: #eee; */
background-color: var(--second);
}
.horizontal {
padding: 0 8px;
width: 0;
height: 100%;
cursor: ew-resize;
}
.horizontal::after {
left: 8px;
top: 0;
width: 1px;
height: 100%;
}
.vertical {
padding: 8px 0;
width: 100%;
height: 0;
cursor: ns-resize;
}
.vertical::after {
top: 8px;
left: 0;
width: 100%;
height: 1px;
}
.left, .right, .divider {
display: block;
}
.left, .right {
height: 100%;
float: left;
}
.top, .bottom {
position: absolute;
width: 100%;
}
.top { top: 0; }
.bottom { bottom: 0; }
</style>
<div class="container" bind:this={refs.container}>
<div class="pane" style="{dimension}: {fixed ? fixed_pos : pos}%;">
<slot name="a"></slot>
</div>
<div class="pane" style="{dimension}: {100 - (fixed ? fixed_pos : pos)}%;">
<slot name="b"></slot>
</div>
{#if !fixed}
<div class="{type} divider" style="{side}: calc({pos}% - 8px)" use:drag={setPos}></div>
{/if}
</div>
{#if dragging}
<div class="mousecatcher"></div>
{/if}

@ -1,10 +0,0 @@
const CodeMirror = require('codemirror');
require('./codemirror.css');
require('codemirror/mode/javascript/javascript.js');
require('codemirror/mode/shell/shell.js');
require('codemirror/mode/handlebars/handlebars.js');
require('codemirror/mode/htmlmixed/htmlmixed.js');
require('codemirror/mode/xml/xml.js');
require('codemirror/mode/css/css.js');
module.exports = CodeMirror;

@ -1,350 +0,0 @@
/* BASICS */
.CodeMirror {
/* copied colors over from prism */
--background: var(--back-light);
--base: hsl(45, 7%, 45%);
--comment: hsl(210, 25%, 60%);
--keyword: hsl(204, 58%, 45%);
--function: hsl(19, 67%, 45%);
--string: hsl(41, 37%, 45%);
--number: hsl(102, 27%, 50%);
--tags: var(--function);
--important: var(--string);
/* Set height, width, borders, and global font properties here */
/* see prism.css */
height: 300px;
direction: ltr;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: var(--back-light);
white-space: nowrap;
}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: var(--comment);
white-space: nowrap;
opacity: .6;
}
.CodeMirror-guttermarker { color: black; }
.CodeMirror-guttermarker-subtle { color: #999; }
/* CURSOR */
.CodeMirror-cursor {
border-left: 1px solid black;
border-right: none;
width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
border: 0 !important;
background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-fat-cursor-mark {
background-color: rgba(20, 255, 20, .5);
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
background-color: #7e7;
}
@-moz-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@-webkit-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
.cm-tab { display: inline-block; text-decoration: inherit; }
.CodeMirror-rulers {
position: absolute;
left: 0; right: 0; top: -50px; bottom: -20px;
overflow: hidden;
}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
top: 0; bottom: 0;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-header {color: blue}
.cm-s-default .cm-quote {color: #090}
.cm-negative {color: #d44}
.cm-positive {color: #292}
.cm-header, .cm-strong {font-weight: bold}
.cm-em {font-style: italic}
.cm-link {text-decoration: underline}
.cm-strikethrough {text-decoration: line-through}
.cm-s-default .cm-atom,
.cm-s-default .cm-def,
.cm-s-default .cm-property,
.cm-s-default .cm-variable-2,
.cm-s-default .cm-variable-3,
.cm-s-default .cm-punctuation {color: var(--base)}
.cm-s-default .cm-hr,
.cm-s-default .cm-comment {color: var(--comment)}
.cm-s-default .cm-attribute,
.cm-s-default .cm-keyword {color: var(--keyword)}
.cm-s-default .cm-variable,
.cm-s-default .cm-bracket,
.cm-s-default .cm-tag {color: var(--tags)}
.cm-s-default .cm-number {color: var(--number)}
.cm-s-default .cm-string {color: var(--string)}
.cm-s-default .cm-string-2 {color: #f50}
.cm-s-default .cm-type {color: #085}
.cm-s-default .cm-meta {color: #555}
.cm-s-default .cm-qualifier {color: #555}
.cm-s-default .cm-builtin {color: #30a}
.cm-s-default .cm-link {color: var(--flash)}
.cm-s-default .cm-error {color: #ff008c}
.cm-invalidchar {color: #ff008c}
.CodeMirror-composing { border-bottom: 2px solid; }
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px; margin-right: -30px;
padding-bottom: 30px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
min-height: 100%;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
vertical-align: top;
margin-bottom: -30px;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0; bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
-webkit-font-variant-ligatures: contextual;
font-variant-ligatures: contextual;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
padding: .1px; /* Force widget margins to stay inside of the container */
}
.CodeMirror-rtl pre { direction: rtl; }
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-cursor {
position: absolute;
pointer-events: none;
}
.CodeMirror-measure pre { position: static; }
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
.cm-searching {
background-color: #ffa;
background-color: rgba(255, 255, 0, .4);
}
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after { content: ''; }
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }

@ -1,232 +0,0 @@
<script>
import { onMount, setContext, createEventDispatcher } from 'svelte';
import { writable } from 'svelte/store';
import SplitPane from './SplitPane.svelte';
import CodeMirror from './CodeMirror.svelte';
import ComponentSelector from './Input/ComponentSelector.svelte';
import ModuleEditor from './Input/ModuleEditor.svelte';
import Output from './Output/index.svelte';
import InputOutputToggle from './InputOutputToggle.svelte';
import Bundler from './Bundler.js';
export let version = 'beta'; // TODO change this to latest when the time comes
export let embedded = false;
export let orientation = 'columns';
export let relaxed = false;
export function toJSON() {
// TODO there's a bug here — Svelte hoists this function because
// it wrongly things that $components is global. Needs to
// factor in $ variables when determining hoistability
version; // workaround
return {
imports: $bundle.imports,
components: $components
};
}
export function set(data) {
components.set(data.components);
selected.set(data.components[0]);
module_editor.set($selected.source, $selected.type);
output.set($selected, $compile_options);
rebundle();
}
export function update(data) {
const { name, type } = $selected || {};
components.set(data.components);
const matched_component = data.components.find(file => file.name === name && file.type === type);
selected.set(matched_component || data.components[0]);
if (matched_component) {
module_editor.update(matched_component.source);
output.update(matched_component, $compile_options);
} else {
module_editor.set(matched_component.source, matched_component.type);
output.set(matched_component, $compile_options);
}
}
const dispatch = createEventDispatcher();
const components = writable([]);
const selected = writable(null);
const bundle = writable(null);
const compile_options = writable({
generate: 'dom',
dev: false,
css: false,
hydratable: false,
customElement: false,
immutable: false,
legacy: false
});
let module_editor;
let output;
let current_token;
async function rebundle() {
const token = current_token = {};
const result = await bundler.bundle($components);
if (result && token === current_token) bundle.set(result);
}
setContext('REPL', {
components,
selected,
bundle,
compile_options,
rebundle,
navigate: item => {
const match = /^(.+)\.(\w+)$/.exec(item.filename);
if (!match) return; // ???
const [, name, type] = match;
const component = $components.find(c => c.name === name && c.type === type);
handle_select(component);
// TODO select the line/column in question
},
handle_change: event => {
selected.update(component => {
// TODO this is a bit hacky — we're relying on mutability
// so that updating components 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;
});
components.update(c => c);
// recompile selected component
output.update($selected, $compile_options);
rebundle();
dispatch('change', {
components: $components
});
},
register_module_editor(editor) {
module_editor = editor;
},
register_output(handlers) {
output = handlers;
},
request_focus() {
module_editor.focus();
}
});
function handle_select(component) {
selected.set(component);
module_editor.set(component.source, component.type);
output.set($selected, $compile_options);
}
let workers;
let input;
let sourceErrorLoc;
let runtimeErrorLoc; // TODO refactor this stuff — runtimeErrorLoc is unused
let width = typeof window !== 'undefined' ? window.innerWidth : 300;
let show_output = false;
const bundler = process.browser && new Bundler(version);
$: if (output && $selected) {
output.update($selected, $compile_options);
}
</script>
<style>
.container {
position: relative;
width: 100%;
height: calc(100% - 4.2rem);
}
.repl-inner {
width: 200%;
height: 100%;
transition: transform 0.3s;
}
.repl-inner :global(section) {
position: relative;
padding: 4.2rem 0 0 0;
height: 100%;
}
.repl-inner :global(section) > :global(*):first-child {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4.2rem;
}
.repl-inner :global(section) > :global(*):last-child {
width: 100%;
height: 100%;
}
.offset {
transform: translate(-50%,0);
}
@media (min-width: 600px) {
.container {
height: 100%;
}
.repl-inner {
width: 100%;
}
.offset {
transition: none;
transform: none;
}
}
</style>
<div class="container" class:orientation bind:clientWidth={width}>
<div class="repl-inner" class:offset="{show_output}">
<SplitPane
type="{orientation === 'rows' ? 'vertical' : 'horizontal'}"
fixed="{600 > width}"
pos="{orientation === 'rows' ? 50 : 60}"
fixed_pos={50}
>
<section slot=a>
<ComponentSelector {handle_select}/>
<ModuleEditor bind:this={input} errorLoc="{sourceErrorLoc || runtimeErrorLoc}"/>
</section>
<section slot=b style='height: 100%;'>
<Output {version} {embedded} {relaxed}/>
</section>
</SplitPane>
</div>
</div>
<InputOutputToggle bind:checked={show_output}/>

@ -16,7 +16,7 @@
<script>
import TableOfContents from './_components/TableOfContents.svelte';
import Icon from '../../../components/Icon.svelte';
import Repl from '../../../components/Repl/index.svelte';
import Repl from '@sveltejs/svelte-repl';
import { getContext } from 'svelte';
export let slug;
@ -94,6 +94,9 @@
);
});
}
const svelteUrl = `https://unpkg.com/svelte@beta`;
const rollupUrl = `https://unpkg.com/rollup@1/dist/rollup.browser.js`;
</script>
<style>
@ -239,6 +242,6 @@
</div>
<div class="tutorial-repl">
<Repl bind:this={repl} orientation="rows" on:change={handle_change} relaxed/>
<Repl bind:this={repl} {svelteUrl} {rollupUrl} orientation="rows" on:change={handle_change} relaxed/>
</div>
</div>

Loading…
Cancel
Save