Merge pull request #2272 from sveltejs/gh-2216

[WIP] Don't use iframes for homepage REPLs
pull/2286/head
Rich Harris 6 years ago committed by GitHub
commit 29b4615d9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,3 +1,3 @@
{
"title": "Adding data"
"title": "Hello world"
}

@ -1,7 +1,7 @@
<script>
import { quintOut } from 'svelte/easing';
import { fade, draw, fly } from 'svelte/transition';
import { expand, blur } from './custom-transitions.js';
import { expand } from './custom-transitions.js';
import { inner, outer } from './shape.js';
let visible = true;
@ -60,7 +60,7 @@
<div class="centered" out:fly="{{y: -20, duration: 800}}">
{#each 'SVELTE' as char, i}
<span
in:blur="{{delay: 1000 + i * 150, duration: 800}}"
in:fade="{{delay: 1000 + i * 150, duration: 800}}"
>{char}</span>
{/each}
</div>

@ -15,20 +15,4 @@ export function expand(node, params) {
easing,
css: t => `opacity: ${t}; stroke-width: ${t * w}`
};
}
export function blur(node, params) {
const {
b = 10,
delay = 0,
duration = 400,
easing = cubicOut
} = params;
return {
delay,
duration,
easing,
css: (t, u) => `opacity: ${t}; filter: blur(${u * b}px);`
};
}

@ -1941,7 +1941,7 @@
"dev": true
},
"eslint-plugin-svelte3": {
"version": "git+https://github.com/sveltejs/eslint-plugin-svelte3.git#a9e7c167484ff7ea5da775ae21b133e0ab5ddc85",
"version": "git+https://github.com/sveltejs/eslint-plugin-svelte3.git#651d7e3695b1731251ab3a501d1067b561ede09f",
"from": "git+https://github.com/sveltejs/eslint-plugin-svelte3.git#semver:*",
"dev": true
},
@ -4811,9 +4811,9 @@
}
},
"sapper": {
"version": "0.26.0-alpha.10",
"resolved": "https://registry.npmjs.org/sapper/-/sapper-0.26.0-alpha.10.tgz",
"integrity": "sha512-S1XdAA0gxEPT3Ikh3jsLKAAbV3EnDd80sppCeUJ5wHCfXTRiP6STxe5PLYZ4Ym8uYU7Iez+6cGLTkfP0ZLPRQw==",
"version": "0.26.0-alpha.12",
"resolved": "https://registry.npmjs.org/sapper/-/sapper-0.26.0-alpha.12.tgz",
"integrity": "sha512-NEXr6Eu5jawY76N5IEQhKMKhcZW6+42E2alH4J8DxFMmOI7Gi2nlwCQ2jcZv7q/S+zMP+OSuqE44c94A5u1H8Q==",
"dev": true,
"requires": {
"html-minifier": "^3.5.21",
@ -5296,9 +5296,9 @@
}
},
"svelte": {
"version": "3.0.0-beta.14",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.0.0-beta.14.tgz",
"integrity": "sha512-naFCFs5JRYe5PGz3+Vg0LiFMqkQUc4bdiH4e7sd6jyDyia0fWd4CJrLxWiC5kHv5Qo5Iv+y26hBHnDoIGAw3zw==",
"version": "3.0.0-beta.20",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.0.0-beta.20.tgz",
"integrity": "sha512-IEZrUseN2Hzpo1KFdyZf3Neqw7abbXLU7oRRkyBVm0iT1PQKHj8G75hR0wISvz7pOegYisiVFdi3C5Asz4ps9Q==",
"dev": true
},
"tar": {
@ -5426,15 +5426,21 @@
}
},
"uglify-js": {
"version": "3.4.9",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz",
"integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==",
"version": "3.4.10",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
"integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==",
"dev": true,
"requires": {
"commander": "~2.17.1",
"commander": "~2.19.0",
"source-map": "~0.6.1"
},
"dependencies": {
"commander": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
"dev": true
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",

@ -53,7 +53,7 @@
"rollup-plugin-replace": "^2.1.0",
"rollup-plugin-svelte": "^5.0.3",
"rollup-plugin-terser": "^4.0.4",
"sapper": "^0.26.0-alpha.10",
"svelte": "^3.0.0-beta.14"
"sapper": "^0.26.0-alpha.12",
"svelte": "^3.0.0-beta.20"
}
}

@ -0,0 +1,59 @@
<script>
import { onMount } from 'svelte';
export let once = false;
export let top = 0;
export let bottom = 0;
export let left = 0;
export let right = 0;
let intersecting = false;
let container;
onMount(() => {
if (typeof IntersectionObserver !== 'undefined') {
const rootMargin = `${bottom}px ${left}px ${top}px ${right}px`;
const observer = new IntersectionObserver(entries => {
intersecting = entries[0].isIntersecting;
if (intersecting && once) {
observer.unobserve(container);
}
}, {
rootMargin
});
observer.observe(container);
return () => observer.unobserve(container);
}
function handler() {
const bcr = container.getBoundingClientRect();
intersecting = (
(bcr.bottom + bottom) > 0 &&
(bcr.right + right) > 0 &&
(bcr.top - top) < window.innerHeight &&
(bcr.left - left) < window.innerWidth
);
if (intersecting && once) {
window.removeEventListener('scroll', handler);
}
}
window.addEventListener('scroll', handler);
return () => window.removeEventListener('scroll', handler);
});
</script>
<style>
div {
width: 100%;
height: 100%;
}
</style>
<div bind:this={container}>
<slot {intersecting}></slot>
</div>

@ -0,0 +1,11 @@
<script>
import { onMount } from 'svelte';
let constructor;
onMount(async () => {
constructor = await $$props.this();
});
</script>
<svelte:component this={constructor} {...$$props}/>

@ -0,0 +1,44 @@
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();
}
}

@ -33,9 +33,9 @@
// than making this state-driven through props,
// because it's difficult to update an editor
// without resetting scroll otherwise
export function set(new_code, new_mode) {
export async function set(new_code, new_mode) {
if (new_mode !== mode) {
createEditor(mode = new_mode);
await createEditor(mode = new_mode);
}
code = new_code;
@ -121,12 +121,13 @@
onMount(() => {
if (_CodeMirror) {
CodeMirror = _CodeMirror;
createEditor(mode || 'svelte');
editor.setValue(code || '');
createEditor(mode || 'svelte').then(() => {
editor.setValue(code || '');
});
} else {
codemirror_promise.then(mod => {
codemirror_promise.then(async mod => {
CodeMirror = mod.default;
createEditor(mode || 'svelte');
await createEditor(mode || 'svelte');
editor.setValue(code || '');
});
}
@ -137,12 +138,10 @@
}
});
function createEditor(mode) {
async function createEditor(mode) {
if (destroyed || !CodeMirror) return;
if (editor) {
editor.toTextArea();
}
if (editor) editor.toTextArea();
const opts = {
lineNumbers,
@ -162,6 +161,12 @@
'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 => {
@ -171,8 +176,13 @@
}
});
await sleep(50);
editor.refresh();
}
function sleep(ms) {
return new Promise(fulfil => setTimeout(fulfil, ms));
}
</script>
<style>

@ -1,3 +1,7 @@
<script>
export let checked;
</script>
<style>
.input-output-toggle {
display: grid;

@ -1,21 +1,32 @@
const workers = new Map();
let uid = 1;
export default class Compiler {
constructor(version) {
this.worker = new Worker('/workers/compiler.js');
this.worker.postMessage({ type: 'init', 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.uid = 1;
this.handlers = new Map();
this.worker.onmessage = event => {
this.worker.addEventListener('message', event => {
const handler = this.handlers.get(event.data.id);
handler(event.data.result);
this.handlers.delete(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 = this.uid++;
const id = uid++;
this.handlers.set(id, fulfil);

@ -1,12 +1,13 @@
let uid = 1;
export default class ReplProxy {
constructor(iframe, handlers) {
this.iframe = iframe;
this.handlers = handlers;
this.cmdId = 1;
this.pendingCmds = new Map();
this.pending_cmds = new Map();
this.handle_event = e => this.handleReplMessage(e);
this.handle_event = e => this.handle_repl_message(e);
window.addEventListener('message', this.handle_event, false);
}
@ -14,63 +15,61 @@ export default class ReplProxy {
window.removeEventListener('message', this.handle_event);
}
iframeCommand(command, args) {
iframe_command(action, args) {
return new Promise((resolve, reject) => {
this.cmdId += 1;
this.pendingCmds.set(this.cmdId, { resolve, reject });
this.iframe.contentWindow.postMessage({
action: command,
cmdId: this.cmdId,
args
}, '*');
const cmd_id = uid++;
this.pending_cmds.set(cmd_id, { resolve, reject });
this.iframe.contentWindow.postMessage({ action, cmd_id, args }, '*');
});
}
handleCommandMessage(cmdData) {
let action = cmdData.action;
let id = cmdData.cmdId;
let handler = this.pendingCmds.get(id);
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.pendingCmds.delete(id);
if (action === 'cmdError') {
let { message, stack } = cmdData;
this.pending_cmds.delete(id);
if (action === 'cmd_error') {
let { message, stack } = cmd_data;
let e = new Error(message);
e.stack = stack;
console.log('repl cmd fail');
handler.reject(e)
}
if (action === 'cmdOk') {
handler.resolve(cmdData.args)
if (action === 'cmd_ok') {
handler.resolve(cmd_data.args)
}
} else {
console.error('command not found', id, cmdData, [...this.pendingCmds.keys()]);
console.error('command not found', id, cmd_data, [...this.pending_cmds.keys()]);
}
}
handleReplMessage(event) {
handle_repl_message(event) {
if (event.source !== this.iframe.contentWindow) return;
const { action, args } = event.data;
if (action === 'cmdError' || action === 'cmdOk') {
this.handleCommandMessage(event.data);
if (action === 'cmd_error' || action === 'cmd_ok') {
this.handle_command_message(event.data);
}
if (action === 'fetch_progress') {
this.handlers.onFetchProgress(args.remaining)
this.handlers.on_fetch_progress(args.remaining)
}
}
eval(script) {
return this.iframeCommand('eval', { script });
return this.iframe_command('eval', { script });
}
handleLinks() {
return this.iframeCommand('catch_clicks', {});
handle_links() {
return this.iframe_command('catch_clicks', {});
}
fetchImports(imports, import_map) {
return this.iframeCommand('fetch_imports', { imports, import_map })
fetch_imports(imports, import_map) {
return this.iframe_command('fetch_imports', { imports, import_map })
}
}

@ -26,13 +26,13 @@
onMount(() => {
proxy = new ReplProxy(iframe, {
onFetchProgress: progress => {
on_fetch_progress: progress => {
pending_imports = progress;
}
});
iframe.addEventListener('load', () => {
proxy.handleLinks();
proxy.handle_links();
ready = true;
});
@ -49,7 +49,7 @@
const token = current_token = {};
try {
await proxy.fetchImports($bundle.imports, $bundle.import_map);
await proxy.fetch_imports($bundle.imports, $bundle.import_map);
if (token !== current_token) return;
await proxy.eval(`

@ -9,9 +9,9 @@
const { register_output } = getContext('REPL');
export let version;
export let sourceErrorLoc;
export let runtimeError;
export let embedded;
export let sourceErrorLoc = null;
export let runtimeError = null;
export let embedded = false;
let foo; // TODO workaround for https://github.com/sveltejs/svelte/issues/2122
@ -39,12 +39,7 @@
}
});
let compiler;
onMount(() => {
compiler = new Compiler(version);
return () => compiler.destroy();
});
const compiler = process.browser && new Compiler(version);
// refs
let viewer;

@ -0,0 +1,88 @@
<script>
import { onMount } from 'svelte';
import { process_example } from './process_example.js';
import Repl from '../../components/Repl/index.svelte';
export let version = 'beta';
export let gist = null;
export let example = null;
export let embedded = false;
let repl;
let name = 'loading...';
onMount(() => {
if (version !== 'local') {
fetch(`https://unpkg.com/svelte@${version}/package.json`)
.then(r => r.json())
.then(pkg => {
version = pkg.version;
});
}
if (gist) {
fetch(`gist/${gist}`).then(r => r.json()).then(data => {
const { id, description, files } = data;
name = description;
const components = Object.keys(files)
.map(file => {
const dot = file.lastIndexOf('.');
if (!~dot) return;
const source = files[file].content;
return {
name: file.slice(0, dot),
type: file.slice(dot + 1),
source
};
})
.filter(x => x.type === 'svelte' || x.type === 'js')
.sort((a, b) => {
if (a.name === 'App' && a.type === 'svelte') return -1;
if (b.name === 'App' && b.type === 'svelte') return 1;
if (a.type !== b.type) return a.type === 'svelte' ? -1 : 1;
return a.name < b.name ? -1 : 1;
});
repl.set({ components });
});
} else if (example) {
fetch(`examples/${example}.json`).then(async response => {
if (response.ok) {
const data = await response.json();
repl.set({
components: process_example(data.files)
});
}
});
}
});
$: if (embedded) document.title = `${name} • Svelte REPL`;
</script>
<style>
.repl-outer {
position: relative;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--back);
overflow: hidden;
box-sizing: border-box;
--pane-controls-h: 4.2rem;
}
</style>
<div class="repl-outer">
{#if process.browser}
<Repl bind:this={repl} {version} embedded={true}/>
{/if}
</div>

@ -7,6 +7,7 @@
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;
@ -31,6 +32,8 @@
module_editor.set($selected.source, $selected.type);
output.set($selected, $compile_options);
rebundle();
}
export function update(data) {
@ -69,8 +72,11 @@
let module_editor;
let output;
function rebundle() {
workers.bundler.postMessage({ type: 'bundle', components: $components });
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', {
@ -143,30 +149,7 @@
let width = typeof window !== 'undefined' ? window.innerWidth : 300;
let show_output = false;
onMount(async () => {
workers = {
bundler: new Worker('/workers/bundler.js')
};
workers.bundler.postMessage({ type: 'init', version });
workers.bundler.onmessage = event => {
bundle.set(event.data);
};
return () => {
workers.bundler.terminate();
};
});
$: if ($bundle && $bundle.error && $selected) {
sourceErrorLoc = $bundle.error.filename === `${$selected.name}.${$selected.type}`
? $bundle.error.start
: null;
}
$: if (workers && $components) {
workers.bundler.postMessage({ type: 'bundle', components: $components });
}
const bundler = process.browser && new Bundler(version);
$: if (output && $selected) {
output.update($selected, $compile_options);

@ -52,7 +52,7 @@
background-color: white;
box-shadow: 0 -0.4rem 0.9rem 0.2rem rgba(0,0,0,.5);
font-family: var(--font);
z-index: 10;
z-index: 100;
user-select: none;
transform: translate(0,calc(-100% - 1rem));
transition: transform 0.2s;

@ -4,7 +4,6 @@
import Nav from '../components/TopNav.svelte';
export let segment;
export let path;
</script>
<InlineSvg />
@ -20,7 +19,8 @@
main {
position: relative;
margin: 0 auto;
padding: var(--nav-h) var(--side-nav) 0 var(--side-nav);
/* padding: var(--nav-h) var(--side-nav) 0 var(--side-nav); */
padding: var(--nav-h) 0 0 0;
overflow-x: hidden;
}
</style>

@ -30,7 +30,7 @@
grid-template-columns: 1fr 1fr;
grid-gap: 1em;
min-height: calc(100vh - var(--nav-h));
padding: var(--top-offset) 0;
padding: var(--top-offset) var(--side-nav) 0 var(--side-nav);
max-width: var(--main-width);
margin: 0 auto;
}

@ -15,6 +15,7 @@
<style>
.content {
max-width: 120rem;
padding: 0 var(--side-nav);
margin: 0 auto;
}

@ -1,15 +1,25 @@
<script>
import Icon from '../components/Icon.svelte';
import Logo from '../components/Logo.svelte';
import IntersectionObserver from '../components/IntersectionObserver.svelte';
// import Lazy from '../components/Lazy.svelte';
import ReplWidget from '../components/Repl/ReplWidget.svelte';
import contributors from './_contributors.js';
let sy = 0;
// TODO this causes a Sapper CSS bug...
// function loadReplWidget() {
// console.log('lazy loading');
// return import('../components/Repl/ReplWidget.svelte').then(mod => mod.default);
// }
</script>
<style>
.container {
position: relative;
margin: 10rem auto;
padding: 0 var(--side-nav);
max-width: 120rem;
}
@ -107,15 +117,31 @@
top: 0.6em;
}
.example {
.examples {
background: var(--second);
color: white;
/* padding: 2em 0; */
overflow: hidden;
}
.example {
/* background: var(--second);
color: white;
padding: 0.8rem;
border-radius: var(--border-r); */
width: 100%;
height: 420px;
}
.repl-container {
width: 100%;
height: 100%;
border-radius: var(--border-r);
overflow: hidden;
}
.example > div:first-child {
padding: 0.8rem;
/* padding: 0.8rem; */
}
iframe {
@ -226,53 +252,67 @@ npm run dev & open http://localhost:5000
</div>
</section>
<section class="container example linkify">
<div>
<p>Svelte components are written in HTML files. Just add data.</p>
</div>
<iframe
title="Hello world example"
src="/repl/embed?example=hello-world"
scrolling="no"
></iframe>
</section>
<section class="container example linkify">
<div>
<p>CSS is component-scoped by default — no more style collisions or specificity wars. Or you can <a href="TODO-blog-post-on-css-in-js">use your favourite CSS-in-JS library</a>.</p>
</div>
<iframe
title="Scope styles example"
src="/repl/embed?example=nested-components"
scrolling="no"
></iframe>
</section>
<section class="container example linkify">
<div>
<p>Trigger efficient, granular updates by assigning to local variables. The compiler does the rest.</p>
</div>
<iframe
title="Reactivity example"
src="/repl/embed?example=reactive-assignments"
scrolling="no"
></iframe>
</section>
<section class="container example linkify">
<div>
<p>Build beautiful UIs with a powerful, performant transition engine built right into the framework.</p>
</div>
<iframe
title="Transitions example"
src="/repl/embed?example=svg-transitions"
scrolling="no"
></iframe>
</section>
<div class="examples">
<section class="container example linkify">
<div>
<p>Svelte components are written in HTML files. Just add data.</p>
</div>
<div class="repl-container">
<IntersectionObserver once let:intersecting top={400}>
{#if intersecting}
<!-- <Lazy this={loadReplWidget} example="hello-world"/> -->
<ReplWidget example="hello-world"/>
{/if}
</IntersectionObserver>
</div>
</section>
<section class="container example linkify">
<div>
<p>CSS is component-scoped by default — no more style collisions or specificity wars. Or you can <a href="TODO-blog-post-on-css-in-js">use your favourite CSS-in-JS library</a>.</p>
</div>
<div class="repl-container">
<IntersectionObserver once let:intersecting top={400}>
{#if intersecting}
<!-- <Lazy this={loadReplWidget} example="nested-components"/> -->
<ReplWidget example="nested-components"/>
{/if}
</IntersectionObserver>
</div>
</section>
<section class="container example linkify">
<div>
<p>Trigger efficient, granular updates by assigning to local variables. The compiler does the rest.</p>
</div>
<div class="repl-container">
<IntersectionObserver once let:intersecting top={400}>
{#if intersecting}
<!-- <Lazy this={loadReplWidget} example="reactive-assignments"/> -->
<ReplWidget example="reactive-assignments"/>
{/if}
</IntersectionObserver>
</div>
</section>
<section class="container example linkify">
<div>
<p>Build beautiful UIs with a powerful, performant transition engine built right into the framework.</p>
</div>
<div class="repl-container">
<IntersectionObserver once let:intersecting top={400}>
{#if intersecting}
<!-- <Lazy this={loadReplWidget} example="svg-transitions"/> -->
<ReplWidget example="svg-transitions"/>
{/if}
</IntersectionObserver>
</div>
</section>
</div>
<section class="container linkify">
<h3>Who's using Svelte?</h3>

@ -1,7 +1,7 @@
<script context="module">
export function preload({ query }) {
return {
version: query.version || 'beta',
version: query.version,
gist: query.gist,
example: query.example
};
@ -9,67 +9,11 @@
</script>
<script>
import { onMount } from 'svelte';
import { process_example } from './_utils/process_example.js';
import Repl from '../../components/Repl/index.svelte';
import ReplWidget from '../../components/Repl/ReplWidget.svelte';
export let version, gist, example;
let repl;
let name = 'loading...';
onMount(() => {
if (version !== 'local') {
fetch(`https://unpkg.com/svelte@${version}/package.json`)
.then(r => r.json())
.then(pkg => {
version = pkg.version;
});
}
if (gist) {
fetch(`gist/${gist}`).then(r => r.json()).then(data => {
const { id, description, files } = data;
name = description;
const components = Object.keys(files)
.map(file => {
const dot = file.lastIndexOf('.');
if (!~dot) return;
const source = files[file].content;
return {
name: file.slice(0, dot),
type: file.slice(dot + 1),
source
};
})
.filter(x => x.type === 'svelte' || x.type === 'js')
.sort((a, b) => {
if (a.name === 'App' && a.type === 'svelte') return -1;
if (b.name === 'App' && b.type === 'svelte') return 1;
if (a.type !== b.type) return a.type === 'svelte' ? -1 : 1;
return a.name < b.name ? -1 : 1;
});
repl.set({ components });
});
} else if (example) {
fetch(`examples/${example}.json`).then(async response => {
if (response.ok) {
const data = await response.json();
repl.set({
components: process_example(data.files)
});
}
});
}
});
export let version = 'beta';
export let gist;
export let example;
</script>
<style>
@ -86,12 +30,8 @@
}
</style>
<svelte:head>
<title>{name} • Svelte REPL</title>
</svelte:head>
<div class="repl-outer">
{#if process.browser}
<Repl bind:this={repl} {version} embedded={true}/>
<ReplWidget {version} {gist} {example} embedded={true}/>
{/if}
</div>

@ -11,7 +11,7 @@
<script>
import { onMount } from 'svelte';
import { locate } from 'locate-character';
import { process_example } from './_utils/process_example.js';
import { process_example } from '../../components/Repl/process_example.js';
import AppControls from './_components/AppControls/index.svelte';
import Repl from '../../components/Repl/index.svelte';
@ -122,7 +122,7 @@
overflow: hidden;
background-color: var(--back);
padding: var(--app-controls-h) 0 0 0;
margin: 0 calc(var(--side-nav) * -1);
/* margin: 0 calc(var(--side-nav) * -1); */
box-sizing: border-box;
}

@ -102,7 +102,7 @@
height: calc(100vh - var(--nav-h));
overflow: hidden;
padding: 0;
margin: 0 calc(var(--side-nav) * -1);
/* margin: 0 calc(var(--side-nav) * -1); */
box-sizing: border-box;
display: grid;
grid-template-columns: minmax(33.333%, 480px) auto;
@ -118,14 +118,6 @@
color: white;
}
.tutorial-repl {
}
.table-of-contents {
}
.chapter-markup {
padding: 1em;
overflow: auto;
@ -153,11 +145,6 @@
color: white;
}
/* .chapter-markup::-webkit-scrollbar-track {
background-color: var(--second);
width: 4px;
} */
.chapter-markup::-webkit-scrollbar {
background-color: var(--second);
width: 8px;
@ -177,10 +164,6 @@
white-space: nowrap;
}
.chapter-markup :global(pre) :global(code) {
/* color: var(--text); */
}
.controls {
border-top: 1px solid rgba(255,255,255,0.1);
padding: 1em 0 0 0;

@ -1,149 +1,105 @@
(function (){
const importCache = {};
function fetchImport(id) {
return new Promise((fulfil, reject) => {
curl([`https://bundle.run/${id}`]).then(module => {
importCache[id] = module;
fulfil(module);
}, err => {
console.error(err.stack);
reject(new Error(`Error loading ${id} from bundle.run`));
(function() {
const import_cache = {};
function fetch_import(id) {
return new Promise((fulfil, reject) => {
curl([`https://bundle.run/${id}`]).then(module => {
import_cache[id] = module;
fulfil(module);
}, err => {
console.error(err.stack);
reject(new Error(`Error loading ${id} from bundle.run`));
});
});
});
}
function fetchImports(imports, progressFunc) {
const missingImports = imports.filter(x => !importCache[x]);
let pendingImports = missingImports.length;
if (missingImports.length) {
let promise = Promise.all(
missingImports.map(id => fetchImport(id).then(() => {
pendingImports -= 1;
if (progressFunc) progressFunc(pendingImports);
}))
);
return promise
} else {
return Promise.resolve();
}
}
function handleMessage(ev) {
let { action, cmdId } = ev.data;
const sendMessage = (payload) => parent.postMessage( { ...payload }, ev.origin);
const sendReply = (payload) => sendMessage({ ...payload, cmdId })
const sendOk = () => sendReply({ action: "cmdOk" });
const sendError = (message, stack) => sendReply({ action: "cmdError", message, stack })
if (action == "eval") {
let { script } = ev.data.args;
try {
eval(script);
sendOk();
} catch (e) {
sendError(e.message, e.stack);
function fetch_imports(imports, progress_func) {
const missing_imports = imports.filter(x => !import_cache[x]);
let pending_imports = missing_imports.length;
if (missing_imports.length) {
let promise = Promise.all(
missing_imports.map(id => fetch_import(id).then(() => {
pending_imports -= 1;
if (progress_func) progress_func(pending_imports);
}))
);
return promise;
} else {
return Promise.resolve();
}
}
if (action == "bind_props") {
let { props } = ev.data.args
if (!window.component) {
// TODO can this happen?
console.warn('no component to bind to');
sendOk();
return;
}
try {
props.forEach(prop => {
// TODO should there be a public API for binding?
// e.g. `component.$watch(prop, handler)`?
// (answer: probably)
window.component.$$.bound[prop] = value => {
sendMessage({ action:"prop_update", args: { prop, value } })
};
});
sendOk();
} catch (e) {
sendError(e.message, e.stack);
}
}
if (action == "set_prop") {
try {
if (!window.component) {
return;
function handle_message(ev) {
let { action, cmd_id } = ev.data;
const send_message = (payload) => parent.postMessage( { ...payload }, ev.origin);
const send_reply = (payload) => send_message({ ...payload, cmd_id });
const send_ok = () => send_reply({ action: 'cmd_ok' });
const send_error = (message, stack) => send_reply({ action: 'cmd_error', message, stack });
if (action === 'eval') {
try {
const { script } = ev.data.args;
eval(script);
send_ok();
} catch (e) {
send_error(e.message, e.stack);
}
let { prop, value } = ev.data.args;
component[prop] = value;
sendOk();
} catch (e) {
sendError(e.message, e.stack);
}
}
if (action == "catch_clicks") {
try {
let topOrigin = ev.origin;
document.body.addEventListener('click', event => {
if (event.which !== 1) return;
if (event.metaKey || event.ctrlKey || event.shiftKey) return;
if (event.defaultPrevented) return;
// ensure target is a link
let el = event.target;
while (el && el.nodeName !== 'A') el = el.parentNode;
if (!el || el.nodeName !== 'A') return;
if (el.hasAttribute('download') || el.getAttribute('rel') === 'external' || el.target) return;
event.preventDefault();
if (el.href.startsWith(topOrigin)) {
const url = new URL(el.href);
if (url.hash[0] === '#') {
window.location.hash = url.hash;
return;
if (action === 'catch_clicks') {
try {
const top_origin = ev.origin;
document.body.addEventListener('click', event => {
if (event.which !== 1) return;
if (event.metaKey || event.ctrlKey || event.shiftKey) return;
if (event.defaultPrevented) return;
// ensure target is a link
let el = event.target;
while (el && el.nodeName !== 'A') el = el.parentNode;
if (!el || el.nodeName !== 'A') return;
if (el.hasAttribute('download') || el.getAttribute('rel') === 'external' || el.target) return;
event.preventDefault();
if (el.href.startsWith(top_origin)) {
const url = new URL(el.href);
if (url.hash[0] === '#') {
window.location.hash = url.hash;
return;
}
}
}
window.open(el.href, '_blank');
});
sendOk();
} catch(e) {
sendError(e.message, e.stack);
window.open(el.href, '_blank');
});
send_ok();
} catch(e) {
send_error(e.message, e.stack);
}
}
}
if (action == "fetch_imports") {
let { imports, import_map } = ev.data.args;
fetchImports(imports, (remaining) => {
sendMessage({action: "fetch_progress", args: { remaining }});
})
.then(() => {
imports.forEach(x=> {
const module = importCache[x];
const name = import_map.get(x);
window[name] = module;
if (action === 'fetch_imports') {
const { imports, import_map } = ev.data.args;
fetch_imports(imports, (remaining) => {
send_message({action: 'fetch_progress', args: { remaining }});
})
.then(() => {
imports.forEach(x=> {
const module = import_cache[x];
const name = import_map.get(x);
window[name] = module;
});
send_ok();
})
.catch(e => {
send_error(e.message, e.stack);
});
sendOk();
})
.catch(e => {
sendError(e.message, e.stack);
})
}
}
}
window.addEventListener("message", handleMessage, false)
console.log("repl-runner initialized");
window.addEventListener('message', handle_message, false);
})();

@ -24,7 +24,7 @@ self.addEventListener('message', async event => {
if (event.data.components.length === 0) return;
await ready;
const result = await bundle(event.data.components);
const result = await bundle(event.data);
if (result) {
postMessage(result);
}
@ -33,7 +33,7 @@ self.addEventListener('message', async event => {
}
});
const commonCompilerOptions = {
const common_options = {
dev: true,
};
@ -42,8 +42,6 @@ let cached = {
ssr: {}
};
let currentToken;
const is_svelte_module = id => id === 'svelte' || id.startsWith('svelte/');
const cache = new Map();
@ -60,7 +58,7 @@ function fetch_if_uncached(url) {
return cache.get(url);
}
async function getBundle(mode, cache, lookup) {
async function get_bundle(mode, cache, lookup) {
let bundle;
const all_warnings = [];
@ -107,7 +105,7 @@ async function getBundle(mode, cache, lookup) {
format: 'esm',
name,
filename: name + '.svelte'
}, commonCompilerOptions));
}, common_options));
new_cache[id] = { code, result };
@ -137,12 +135,10 @@ async function getBundle(mode, cache, lookup) {
return { bundle, cache: new_cache, error: null, warnings: all_warnings };
}
async function bundle(components) {
async function bundle({ id, components }) {
// console.clear();
console.log(`running Svelte compiler version %c${svelte.VERSION}`, 'font-weight: bold');
const token = currentToken = {};
const lookup = {};
components.forEach(component => {
const path = `./${component.name}.${component.type}`;
@ -154,16 +150,11 @@ async function bundle(components) {
let error;
try {
dom = await getBundle('dom', cached.dom, lookup);
dom = await get_bundle('dom', cached.dom, lookup);
if (dom.error) {
throw dom.error;
}
if (token !== currentToken) {
console.error(`aborted`);
return;
}
cached.dom = dom.cache;
let uid = 1;
@ -180,10 +171,8 @@ async function bundle(components) {
sourcemap: true
})).output[0];
if (token !== currentToken) return;
const ssr = false // TODO how can we do SSR?
? await getBundle('ssr', cached.ssr, lookup)
? await get_bundle('ssr', cached.ssr, lookup)
: null;
if (ssr) {
@ -193,8 +182,6 @@ async function bundle(components) {
}
}
if (token !== currentToken) return;
const ssr_result = ssr
? (await ssr.bundle.generate({
format: 'iife',
@ -206,6 +193,7 @@ async function bundle(components) {
: null;
return {
id,
imports: dom_result.imports,
import_map,
dom: dom_result,
@ -218,6 +206,7 @@ async function bundle(components) {
delete e.toString;
return {
id,
imports: [],
import_map,
dom: null,

@ -20,20 +20,19 @@ self.addEventListener('message', async event => {
await ready;
postMessage(compile(event.data));
break;
}
});
const commonCompilerOptions = {
const common_options = {
dev: false,
css: false
};
function compile({ id, source, options, entry }) {
function compile({ id, source, options }) {
try {
const { js, css, stats, vars } = svelte.compile(
const { js, css } = svelte.compile(
source,
Object.assign({}, commonCompilerOptions, options)
Object.assign({}, common_options, options)
);
return {

Loading…
Cancel
Save