From 295019f9e4956f7b870bfa7e2500b917e41328c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20S=C3=A1nchez=20Ortega?= Date: Fri, 22 Feb 2019 17:46:25 +0100 Subject: [PATCH 01/71] Check if slotted component has an update function during runtime --- src/compile/render-dom/wrappers/Slot.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compile/render-dom/wrappers/Slot.ts b/src/compile/render-dom/wrappers/Slot.ts index 440590239d..de866ed882 100644 --- a/src/compile/render-dom/wrappers/Slot.ts +++ b/src/compile/render-dom/wrappers/Slot.ts @@ -141,7 +141,7 @@ export default class SlotWrapper extends Wrapper { if (this.dependencies.size > 1) update_conditions = `(${update_conditions})`; block.builders.update.addBlock(deindent` - if (${slot} && ${update_conditions}) { + if (${slot} && ${slot}.p && ${update_conditions}) { ${slot}.p(@assign(@assign({}, ${get_slot_changes}(changed)), ctx.$$scope.changed), @get_slot_context(${slot_definition}, ctx, ${get_slot_context})); } `); @@ -150,4 +150,4 @@ export default class SlotWrapper extends Wrapper { `if (${slot}) ${slot}.d(detach);` ); } -} \ No newline at end of file +} From 1e1784adaf4b8c41acf0ef80853fe0429ff459fe Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 10 Mar 2019 05:38:28 -0400 Subject: [PATCH 02/71] update options.customElement/ handling (#2025) --- src/compile/Component.ts | 15 +++++++-------- src/compile/index.ts | 34 +++++++++++++++++++++++++++++++++- src/interfaces.ts | 3 +-- src/parse/index.ts | 4 ++-- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/compile/Component.ts b/src/compile/Component.ts index 1b843e9ca2..8589ef6af7 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -119,16 +119,15 @@ export default class Component { this.componentOptions = process_component_options(this, this.ast.html.children); this.namespace = namespaces[this.componentOptions.namespace] || this.componentOptions.namespace; - if (compileOptions.customElement === true && !this.componentOptions.tag) { - throw new Error(`No tag name specified`); // TODO better error + if (compileOptions.customElement) { + this.tag = compileOptions.customElement.tag || this.componentOptions.tag; + if (!this.tag) { + throw new Error(`Cannot compile to a custom element without specifying a tag name via options.customElement or `); + } + } else { + this.tag = this.name; } - this.tag = compileOptions.customElement - ? compileOptions.customElement === true - ? this.componentOptions.tag - : compileOptions.customElement as string - : this.name; - this.walk_module_js(); this.walk_instance_js_pre_template(); diff --git a/src/compile/index.ts b/src/compile/index.ts index 704d5bd706..ae9d554035 100644 --- a/src/compile/index.ts +++ b/src/compile/index.ts @@ -3,7 +3,7 @@ import Stats from '../Stats'; import parse from '../parse/index'; import renderDOM from './render-dom/index'; import renderSSR from './render-ssr/index'; -import { CompileOptions, Ast, Warning } from '../interfaces'; +import { CompileOptions, Ast, Warning, CustomElementOptions } from '../interfaces'; import Component from './Component'; import fuzzymatch from '../utils/fuzzymatch'; @@ -41,6 +41,10 @@ function validate_options(options: CompileOptions, warnings: Warning[]) { throw new Error(`options.name must be a valid identifier (got '${name}')`); } + if ('customElement' in options) { + options.customElement = normalize_customElement_option(options.customElement); + } + if (name && /^[a-z]/.test(name)) { const message = `options.name should be capitalised`; warnings.push({ @@ -52,6 +56,34 @@ function validate_options(options: CompileOptions, warnings: Warning[]) { } } +const valid_customElement_options = ['tag']; + +function normalize_customElement_option(customElement: boolean | string | CustomElementOptions) { + if (typeof customElement === 'boolean') { + return customElement ? {} : null; + } else if (typeof customElement === 'string') { + customElement = { tag: customElement }; + } else if (typeof customElement === 'object') { + Object.keys(customElement).forEach(key => { + if (valid_customElement_options.indexOf(key) === -1) { + const match = fuzzymatch(key, valid_customElement_options); + let message = `Unrecognized option 'customElement.${key}'`; + if (match) message += ` (did you mean 'customElement.${match}'?)`; + + throw new Error(message); + } + }); + } else { + throw new Error(`options.customElement must be a boolean, a string or an object`); + } + + if ('tag' in customElement && !/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(customElement.tag)) { + throw new Error(`options.customElement tag name must be two or more words joined by the '-' character`); + } + + return customElement; +} + function get_name(filename) { if (!filename) return null; const parts = filename.split(/[\/\\]/); diff --git a/src/interfaces.ts b/src/interfaces.ts index 4ec258c94a..fc2e6b260e 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -52,7 +52,7 @@ export interface CompileOptions { immutable?: boolean; hydratable?: boolean; legacy?: boolean; - customElement?: CustomElementOptions | true; + customElement?: CustomElementOptions; css?: boolean; preserveComments?: boolean | false; @@ -65,7 +65,6 @@ export interface Visitor { export interface CustomElementOptions { tag?: string; - props?: string[]; } export interface AppendTarget { diff --git a/src/parse/index.ts b/src/parse/index.ts index 4b1e0a225c..1618ee5e6e 100644 --- a/src/parse/index.ts +++ b/src/parse/index.ts @@ -9,7 +9,7 @@ import error from '../utils/error'; interface ParserOptions { filename?: string; bind?: boolean; - customElement?: CustomElementOptions | true; + customElement?: CustomElementOptions; } type ParserState = (parser: Parser) => (ParserState | void); @@ -17,7 +17,7 @@ type ParserState = (parser: Parser) => (ParserState | void); export class Parser { readonly template: string; readonly filename?: string; - readonly customElement: CustomElementOptions | true; + readonly customElement: CustomElementOptions; index = 0; stack: Array = []; From e619de7e089eb37f3e266e80b2ec17cf536862fa Mon Sep 17 00:00:00 2001 From: Thomas Ghysels Date: Sun, 3 Mar 2019 21:58:48 +0100 Subject: [PATCH 03/71] Throw descriptive error when 'subscribable' of undefined Fix #2139 --- src/compile/nodes/shared/Expression.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/compile/nodes/shared/Expression.ts b/src/compile/nodes/shared/Expression.ts index 0d33988fb3..d52a0d80de 100644 --- a/src/compile/nodes/shared/Expression.ts +++ b/src/compile/nodes/shared/Expression.ts @@ -149,6 +149,13 @@ export default class Expression { dependencies.add(name); } + if (name[0] === '$' && !component.var_lookup.get(name.slice(1)) && name !== '$$props') { + component.error(node, { + code: `missing-store`, + message: `Stores must be declared` + }); + } + component.add_reference(name); component.warn_if_undefined(nodes[0], template_scope, true); } From 8c61edf2d5fe8eb72478f9becd4818067485847e Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Fri, 15 Mar 2019 21:34:14 -0400 Subject: [PATCH 04/71] remove support for logic-less components with vars --- src/compile/Component.ts | 12 +----------- src/interfaces.ts | 1 - 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/compile/Component.ts b/src/compile/Component.ts index 004ecb32ca..f2b6d25121 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -169,15 +169,6 @@ export default class Component { const variable = this.var_lookup.get(subscribable_name); variable.subscribable = true; - } else if (!this.ast.instance) { - this.add_var({ - name, - export_name: name, - implicit: true, - mutated: false, - referenced: true, - writable: true - }); } else { this.usedNames.add(name); } @@ -1140,7 +1131,7 @@ export default class Component { return `ctx.${name}`; } - warn_if_undefined(node, template_scope: TemplateScope, allow_implicit?: boolean) { + warn_if_undefined(node, template_scope: TemplateScope) { let { name } = node; if (name[0] === '$') { @@ -1148,7 +1139,6 @@ export default class Component { this.has_reactive_assignments = true; } - if (allow_implicit && !this.ast.instance && !this.ast.module) return; if (this.var_lookup.has(name)) return; if (template_scope && template_scope.names.has(name)) return; if (globalWhitelist.has(name)) return; diff --git a/src/interfaces.ts b/src/interfaces.ts index 4ec258c94a..b3e778a48b 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -85,7 +85,6 @@ export interface Var { // used internally, but not exposed global?: boolean; - implicit?: boolean; // logic-less template references internal?: boolean; // event handlers, bindings initialised?: boolean; hoistable?: boolean; From 0f0f9478652db2cdc42c80c54fd22e920659d352 Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Fri, 15 Mar 2019 21:34:22 -0400 Subject: [PATCH 05/71] update vars tests --- test/vars/index.js | 2 +- test/vars/samples/implicit-action/_config.js | 5 ----- test/vars/samples/implicit-action/input.svelte | 1 - test/vars/samples/implicit/_config.js | 13 +------------ test/vars/samples/template-references/_config.js | 12 ++++++------ test/vars/samples/template-references/input.svelte | 7 +++++++ 6 files changed, 15 insertions(+), 25 deletions(-) delete mode 100644 test/vars/samples/implicit-action/_config.js delete mode 100644 test/vars/samples/implicit-action/input.svelte diff --git a/test/vars/index.js b/test/vars/index.js index 66ffd94c70..71617b8bf9 100644 --- a/test/vars/index.js +++ b/test/vars/index.js @@ -2,7 +2,7 @@ import * as fs from 'fs'; import * as assert from 'assert'; import { svelte, loadConfig, tryToLoadJson } from '../helpers.js'; -describe('vars', () => { +describe.only('vars', () => { fs.readdirSync('test/vars/samples').forEach(dir => { if (dir[0] === '.') return; diff --git a/test/vars/samples/implicit-action/_config.js b/test/vars/samples/implicit-action/_config.js deleted file mode 100644 index 2b84e83f12..0000000000 --- a/test/vars/samples/implicit-action/_config.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - test(assert, vars) { - assert.deepEqual(vars, []); - }, -}; diff --git a/test/vars/samples/implicit-action/input.svelte b/test/vars/samples/implicit-action/input.svelte deleted file mode 100644 index 466495d255..0000000000 --- a/test/vars/samples/implicit-action/input.svelte +++ /dev/null @@ -1 +0,0 @@ -
\ No newline at end of file diff --git a/test/vars/samples/implicit/_config.js b/test/vars/samples/implicit/_config.js index 0c685e7f18..2b84e83f12 100644 --- a/test/vars/samples/implicit/_config.js +++ b/test/vars/samples/implicit/_config.js @@ -1,16 +1,5 @@ export default { test(assert, vars) { - assert.deepEqual(vars, [ - { - export_name: 'foo', - injected: false, - module: false, - mutated: false, - name: 'foo', - reassigned: false, - referenced: true, - writable: true, - }, - ]); + assert.deepEqual(vars, []); }, }; diff --git a/test/vars/samples/template-references/_config.js b/test/vars/samples/template-references/_config.js index 1d78fbdf46..adacc0d2ef 100644 --- a/test/vars/samples/template-references/_config.js +++ b/test/vars/samples/template-references/_config.js @@ -2,27 +2,27 @@ export default { test(assert, vars) { assert.deepEqual(vars, [ { - export_name: 'foo', + export_name: null, injected: false, module: false, mutated: false, - name: 'foo', + name: 'Bar', reassigned: false, referenced: true, - writable: true, + writable: false, }, { - export_name: 'Bar', + export_name: null, injected: false, module: false, mutated: false, - name: 'Bar', + name: 'foo', reassigned: false, referenced: true, writable: true, }, { - export_name: 'baz', + export_name: null, injected: false, module: false, mutated: false, diff --git a/test/vars/samples/template-references/input.svelte b/test/vars/samples/template-references/input.svelte index 938e61b7bd..4e2edad7e6 100644 --- a/test/vars/samples/template-references/input.svelte +++ b/test/vars/samples/template-references/input.svelte @@ -1,2 +1,9 @@ + + {foo} From 73e45ab483296d4282295342a8314baddf78650f Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Fri, 15 Mar 2019 22:44:30 -0400 Subject: [PATCH 06/71] update validate tests --- .../samples/a11y-html-has-lang/input.svelte | 4 ++++ .../samples/a11y-html-has-lang/warnings.json | 10 +++++----- .../a11y-iframe-has-title/input.svelte | 6 +++++- .../a11y-iframe-has-title/warnings.json | 12 +++++------ .../a11y-tabindex-no-positive/input.svelte | 4 ++++ .../a11y-tabindex-no-positive/warnings.json | 10 +++++----- .../samples/animation-missing/input.svelte | 4 ++++ .../samples/animation-missing/warnings.json | 10 +++++----- .../binding-dimensions-svg-child/errors.json | 10 +++++----- .../binding-dimensions-svg-child/input.svelte | 4 ++++ .../binding-dimensions-svg/errors.json | 10 +++++----- .../binding-dimensions-svg/input.svelte | 4 ++++ .../binding-dimensions-void/errors.json | 10 +++++----- .../binding-dimensions-void/input.svelte | 4 ++++ .../samples/binding-input-checked/errors.json | 10 +++++----- .../binding-input-checked/input.svelte | 4 ++++ .../binding-input-type-boolean/errors.json | 10 +++++----- .../binding-input-type-boolean/input.svelte | 4 ++++ .../binding-input-type-dynamic/errors.json | 12 +++++------ .../binding-input-type-dynamic/input.svelte | 7 ++++++- .../binding-invalid-on-element/errors.json | 10 +++++----- .../binding-invalid-on-element/input.svelte | 4 ++++ .../samples/binding-invalid/errors.json | 10 +++++----- .../samples/binding-invalid/input.svelte | 4 ++++ .../errors.json | 10 +++++----- .../input.svelte | 5 +++++ .../each-block-multiple-children/input.svelte | 5 +++++ .../samples/empty-block/input.svelte | 4 ++++ .../samples/empty-block/warnings.json | 20 +++++++++---------- .../samples/non-empty-block-dev/input.svelte | 4 ++++ .../samples/select-multiple/input.svelte | 4 ++++ .../input.svelte | 2 +- .../input.svelte | 9 ++++++++- .../warnings.json | 12 +++++------ .../window-binding-online/input.svelte | 4 ++++ test/vars/index.js | 2 +- 36 files changed, 170 insertions(+), 88 deletions(-) diff --git a/test/validator/samples/a11y-html-has-lang/input.svelte b/test/validator/samples/a11y-html-has-lang/input.svelte index 0fa84d90a6..ccc238e5d4 100644 --- a/test/validator/samples/a11y-html-has-lang/input.svelte +++ b/test/validator/samples/a11y-html-has-lang/input.svelte @@ -1,3 +1,7 @@ + + diff --git a/test/validator/samples/a11y-html-has-lang/warnings.json b/test/validator/samples/a11y-html-has-lang/warnings.json index 963a2570db..2ac8a2e5e0 100644 --- a/test/validator/samples/a11y-html-has-lang/warnings.json +++ b/test/validator/samples/a11y-html-has-lang/warnings.json @@ -4,14 +4,14 @@ "message": "A11y: element should have a lang attribute", "start": { "column": 0, - "line": 5, - "character": 82 + "line": 9, + "character": 124 }, "end": { - "line": 5, + "line": 9, "column": 13, - "character": 95 + "character": 137 }, - "pos": 82 + "pos": 124 } ] diff --git a/test/validator/samples/a11y-iframe-has-title/input.svelte b/test/validator/samples/a11y-iframe-has-title/input.svelte index 5e07ca5a08..4689a2dcf7 100644 --- a/test/validator/samples/a11y-iframe-has-title/input.svelte +++ b/test/validator/samples/a11y-iframe-has-title/input.svelte @@ -1 +1,5 @@ - \ No newline at end of file + + + \ No newline at end of file diff --git a/test/validator/samples/a11y-iframe-has-title/warnings.json b/test/validator/samples/a11y-iframe-has-title/warnings.json index d71bae1dae..cc80cceaa2 100644 --- a/test/validator/samples/a11y-iframe-has-title/warnings.json +++ b/test/validator/samples/a11y-iframe-has-title/warnings.json @@ -3,15 +3,15 @@ "code": "a11y-missing-attribute", "message": "A11y: - - -
-
-

CSS is component-scoped by default — no more style collisions or specificity wars. Or you can use your favourite CSS-in-JS library.

-
- - -
- -
-
-

Trigger efficient, granular updates by assigning to local variables. The compiler does the rest.

-
- - -
- -
-
-

Build beautiful UIs with a powerful, performant transition engine built right into the framework.

-
- - -
+
+
+
+

Svelte components are written in HTML files. Just add data.

+
+ + +
+ +
+
+

CSS is component-scoped by default — no more style collisions or specificity wars. Or you can use your favourite CSS-in-JS library.

+
+ + +
+ +
+
+

Trigger efficient, granular updates by assigning to local variables. The compiler does the rest.

+
+ + +
+ +
+
+

Build beautiful UIs with a powerful, performant transition engine built right into the framework.

+
+ + +
+

Who's using Svelte?

diff --git a/site/src/routes/repl/index.svelte b/site/src/routes/repl/index.svelte index 8671717f5d..56d0532b30 100644 --- a/site/src/routes/repl/index.svelte +++ b/site/src/routes/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; } diff --git a/site/src/routes/tutorial/[slug]/index.svelte b/site/src/routes/tutorial/[slug]/index.svelte index eca5686270..28bcd9b4ff 100644 --- a/site/src/routes/tutorial/[slug]/index.svelte +++ b/site/src/routes/tutorial/[slug]/index.svelte @@ -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; From 9f0630c3fb1c0f3c444052047f328c27540d558e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 18 Mar 2019 18:44:40 -0400 Subject: [PATCH 62/71] start moving REPL widgets out of iframes (#2216) --- site/src/components/Repl/ReplWidget.svelte | 89 +++++++++++++++++++ .../Repl}/process_example.js | 0 site/src/routes/index.svelte | 28 ++---- site/src/routes/repl/embed.svelte | 2 +- site/src/routes/repl/index.svelte | 2 +- 5 files changed, 99 insertions(+), 22 deletions(-) create mode 100644 site/src/components/Repl/ReplWidget.svelte rename site/src/{routes/repl/_utils => components/Repl}/process_example.js (100%) diff --git a/site/src/components/Repl/ReplWidget.svelte b/site/src/components/Repl/ReplWidget.svelte new file mode 100644 index 0000000000..25beed2988 --- /dev/null +++ b/site/src/components/Repl/ReplWidget.svelte @@ -0,0 +1,89 @@ + + + + + + {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 @@ + +

{x} {y} {z}

\ No newline at end of file From d788bb97351fa628da2057f86086ea357492ded6 Mon Sep 17 00:00:00 2001 From: John Chesley Date: Mon, 18 Mar 2019 21:33:42 -0400 Subject: [PATCH 65/71] break declaration grouping on non-declaration nodes (fixes #2022) --- src/compile/Component.ts | 2 +- .../reactive-values-exported/{main.html => main.svelte} | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) rename test/runtime/samples/reactive-values-exported/{main.html => main.svelte} (55%) diff --git a/src/compile/Component.ts b/src/compile/Component.ts index 77c516049e..30a06ca50b 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -865,7 +865,7 @@ export default class Component { } } else { if (node.type !== 'ExportNamedDeclaration') { - if (!parent) current_group = null; + if (!parent || parent.type === 'Program') current_group = null; } } }, diff --git a/test/runtime/samples/reactive-values-exported/main.html b/test/runtime/samples/reactive-values-exported/main.svelte similarity index 55% rename from test/runtime/samples/reactive-values-exported/main.html rename to test/runtime/samples/reactive-values-exported/main.svelte index 2a9e5dc8f5..169fb7fcd8 100644 --- a/test/runtime/samples/reactive-values-exported/main.html +++ b/test/runtime/samples/reactive-values-exported/main.svelte @@ -1,11 +1,10 @@ -

{x} {y} {z}

\ No newline at end of file +

{x} {y} {z}

From 795ca0c2915b84ae635fe33033c7fe90a03d857d Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Tue, 19 Mar 2019 09:21:47 -0400 Subject: [PATCH 66/71] share workers, prevent REPL crosstalk --- site/src/components/Repl/Bundler.js | 44 ++++ site/src/components/Repl/Output/Compiler.js | 27 ++- site/src/components/Repl/Output/ReplProxy.js | 63 +++-- site/src/components/Repl/Output/Viewer.svelte | 6 +- site/src/components/Repl/Output/index.svelte | 13 +- site/src/components/Repl/ReplWidget.svelte | 4 +- site/src/components/Repl/index.svelte | 33 +-- site/src/routes/_layout.svelte | 1 - site/static/repl-runner.js | 218 +++++++----------- site/static/workers/bundler.js | 29 +-- site/static/workers/compiler.js | 9 +- 11 files changed, 210 insertions(+), 237 deletions(-) create mode 100644 site/src/components/Repl/Bundler.js diff --git a/site/src/components/Repl/Bundler.js b/site/src/components/Repl/Bundler.js new file mode 100644 index 0000000000..ddde546e7f --- /dev/null +++ b/site/src/components/Repl/Bundler.js @@ -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(); + } +} \ No newline at end of file diff --git a/site/src/components/Repl/Output/Compiler.js b/site/src/components/Repl/Output/Compiler.js index b6d13e53bd..7ffb8b6bbc 100644 --- a/site/src/components/Repl/Output/Compiler.js +++ b/site/src/components/Repl/Output/Compiler.js @@ -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); diff --git a/site/src/components/Repl/Output/ReplProxy.js b/site/src/components/Repl/Output/ReplProxy.js index 5988c20f19..7ae140a8a7 100644 --- a/site/src/components/Repl/Output/ReplProxy.js +++ b/site/src/components/Repl/Output/ReplProxy.js @@ -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 }) } } \ No newline at end of file diff --git a/site/src/components/Repl/Output/Viewer.svelte b/site/src/components/Repl/Output/Viewer.svelte index 55e2ca9edc..66221d6acb 100644 --- a/site/src/components/Repl/Output/Viewer.svelte +++ b/site/src/components/Repl/Output/Viewer.svelte @@ -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(` diff --git a/site/src/components/Repl/Output/index.svelte b/site/src/components/Repl/Output/index.svelte index 59d67b5fcf..6c312a0b76 100644 --- a/site/src/components/Repl/Output/index.svelte +++ b/site/src/components/Repl/Output/index.svelte @@ -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; diff --git a/site/src/components/Repl/ReplWidget.svelte b/site/src/components/Repl/ReplWidget.svelte index 25beed2988..7211256884 100644 --- a/site/src/components/Repl/ReplWidget.svelte +++ b/site/src/components/Repl/ReplWidget.svelte @@ -4,8 +4,8 @@ import Repl from '../../components/Repl/index.svelte'; export let version = 'beta'; - export let gist; - export let example; + export let gist = null; + export let example = null; let repl; let name = 'loading...'; diff --git a/site/src/components/Repl/index.svelte b/site/src/components/Repl/index.svelte index aa9e423932..5b8f798b32 100644 --- a/site/src/components/Repl/index.svelte +++ b/site/src/components/Repl/index.svelte @@ -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; @@ -69,8 +70,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', { @@ -145,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); diff --git a/site/src/routes/_layout.svelte b/site/src/routes/_layout.svelte index 4cf0bac475..a72065a7c2 100644 --- a/site/src/routes/_layout.svelte +++ b/site/src/routes/_layout.svelte @@ -4,7 +4,6 @@ import Nav from '../components/TopNav.svelte'; export let segment; - export let path; 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 e8b7cd7889e93efc7814a18249830ca596797f96 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 19 Mar 2019 13:38:37 -0400 Subject: [PATCH 67/71] 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}
From 665c3eac790649a33abef8bed972046fb368c7f2 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Wed, 20 Mar 2019 10:52:56 -0400 Subject: [PATCH 69/71] site: run tutorial and widget REPLs in relaxed sandbox (#2147) --- site/src/components/Repl/Output/Viewer.svelte | 4 +++- site/src/components/Repl/Output/index.svelte | 2 ++ site/src/components/Repl/ReplWidget.svelte | 2 +- site/src/components/Repl/index.svelte | 3 ++- site/src/routes/tutorial/[slug]/index.svelte | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/site/src/components/Repl/Output/Viewer.svelte b/site/src/components/Repl/Output/Viewer.svelte index 66221d6acb..9e1997b39c 100644 --- a/site/src/components/Repl/Output/Viewer.svelte +++ b/site/src/components/Repl/Output/Viewer.svelte @@ -15,6 +15,8 @@ proxy.setProp(prop, value); } + export let relaxed = false; + let iframe; let pending_imports = 0; let pending = false; @@ -127,7 +129,7 @@
-