From 3869a23a7d6c9735b7db1d0e3e5e9b0e5eb43180 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 18 Mar 2019 18:27:41 -0400 Subject: [PATCH 1/5] a few tweaks --- .../11-svg/05-svg-transitions/App.svelte | 4 +- .../05-svg-transitions/custom-transitions.js | 16 --- site/src/routes/_layout.svelte | 3 +- site/src/routes/blog/index.svelte | 2 +- site/src/routes/examples/index.svelte | 1 + site/src/routes/index.svelte | 110 ++++++++++-------- site/src/routes/repl/index.svelte | 2 +- site/src/routes/tutorial/[slug]/index.svelte | 19 +-- 8 files changed, 68 insertions(+), 89 deletions(-) diff --git a/site/content/examples/11-svg/05-svg-transitions/App.svelte b/site/content/examples/11-svg/05-svg-transitions/App.svelte index 485bbf2558..6059b3645f 100644 --- a/site/content/examples/11-svg/05-svg-transitions/App.svelte +++ b/site/content/examples/11-svg/05-svg-transitions/App.svelte @@ -1,7 +1,7 @@ + + + + + {name} • Svelte REPL + + +
+ {#if process.browser} + + {/if} +
diff --git a/site/src/routes/repl/_utils/process_example.js b/site/src/components/Repl/process_example.js similarity index 100% rename from site/src/routes/repl/_utils/process_example.js rename to site/src/components/Repl/process_example.js diff --git a/site/src/routes/index.svelte b/site/src/routes/index.svelte index 134afe859b..95b2dff8b6 100644 --- a/site/src/routes/index.svelte +++ b/site/src/routes/index.svelte @@ -1,6 +1,7 @@ diff --git a/site/static/repl-runner.js b/site/static/repl-runner.js index 4e0194e726..513e1d91e9 100644 --- a/site/static/repl-runner.js +++ b/site/static/repl-runner.js @@ -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); })(); diff --git a/site/static/workers/bundler.js b/site/static/workers/bundler.js index 06a2364f93..48aed10ede 100644 --- a/site/static/workers/bundler.js +++ b/site/static/workers/bundler.js @@ -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, diff --git a/site/static/workers/compiler.js b/site/static/workers/compiler.js index e919097310..3d62eaded3 100644 --- a/site/static/workers/compiler.js +++ b/site/static/workers/compiler.js @@ -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 { From 156c1ecccd9fba1ead23eccb8541c3afbe583ce3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 19 Mar 2019 13:38:37 -0400 Subject: [PATCH 4/5] stagger creation of REPL widgets on home page --- site/package-lock.json | 28 +++++---- site/package.json | 4 +- .../components/IntersectionObserver.svelte | 59 +++++++++++++++++++ site/src/components/Lazy.svelte | 11 ++++ site/src/components/Repl/CodeMirror.svelte | 30 ++++++---- .../components/Repl/InputOutputToggle.svelte | 4 ++ site/src/components/Repl/ReplWidget.svelte | 4 -- site/src/components/Repl/index.svelte | 2 + site/src/components/TopNav.svelte | 2 +- site/src/routes/index.svelte | 52 ++++++++++++++-- 10 files changed, 163 insertions(+), 33 deletions(-) create mode 100644 site/src/components/IntersectionObserver.svelte create mode 100644 site/src/components/Lazy.svelte diff --git a/site/package-lock.json b/site/package-lock.json index 3a1fabf102..b527a5feeb 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -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", diff --git a/site/package.json b/site/package.json index de3edb8f64..664dd59e48 100644 --- a/site/package.json +++ b/site/package.json @@ -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" } } diff --git a/site/src/components/IntersectionObserver.svelte b/site/src/components/IntersectionObserver.svelte new file mode 100644 index 0000000000..210fd524fd --- /dev/null +++ b/site/src/components/IntersectionObserver.svelte @@ -0,0 +1,59 @@ + + + + +
+ +
\ No newline at end of file diff --git a/site/src/components/Lazy.svelte b/site/src/components/Lazy.svelte new file mode 100644 index 0000000000..ef81d68f00 --- /dev/null +++ b/site/src/components/Lazy.svelte @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/site/src/components/Repl/CodeMirror.svelte b/site/src/components/Repl/CodeMirror.svelte index e9519e516e..a1a92fe344 100644 --- a/site/src/components/Repl/CodeMirror.svelte +++ b/site/src/components/Repl/CodeMirror.svelte @@ -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)); + } - - {name} • Svelte REPL - -
{#if process.browser} diff --git a/site/src/components/Repl/index.svelte b/site/src/components/Repl/index.svelte index 5b8f798b32..489e0e49d7 100644 --- a/site/src/components/Repl/index.svelte +++ b/site/src/components/Repl/index.svelte @@ -32,6 +32,8 @@ module_editor.set($selected.source, $selected.type); output.set($selected, $compile_options); + + rebundle(); } export function update(data) { diff --git a/site/src/components/TopNav.svelte b/site/src/components/TopNav.svelte index d1e4db6d31..d02ff58cec 100644 --- a/site/src/components/TopNav.svelte +++ b/site/src/components/TopNav.svelte @@ -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; diff --git a/site/src/routes/index.svelte b/site/src/routes/index.svelte index 95b2dff8b6..d2f6a696a4 100644 --- a/site/src/routes/index.svelte +++ b/site/src/routes/index.svelte @@ -1,10 +1,18 @@ - - {name} • Svelte REPL - -
{#if process.browser} - + {/if}