From afe3e2e669f0c4d758d02f06795ccaf2c4bee986 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 2 Sep 2017 16:29:02 -0400 Subject: [PATCH 01/10] basic custom element generation (#797) --- package.json | 1 + src/generators/Generator.ts | 29 ++- src/generators/dom/index.ts | 168 ++++++++------ src/interfaces.ts | 6 + src/validate/js/propValidators/index.ts | 2 + src/validate/js/propValidators/tag.ts | 20 ++ .../samples/custom-element-basic/_config.js | 5 + .../custom-element-basic/expected-bundle.js | 218 ++++++++++++++++++ .../samples/custom-element-basic/expected.js | 57 +++++ .../samples/custom-element-basic/input.html | 7 + test/runtime/index.js | 2 - 11 files changed, 439 insertions(+), 76 deletions(-) create mode 100644 src/validate/js/propValidators/tag.ts create mode 100644 test/js/samples/custom-element-basic/_config.js create mode 100644 test/js/samples/custom-element-basic/expected-bundle.js create mode 100644 test/js/samples/custom-element-basic/expected.js create mode 100644 test/js/samples/custom-element-basic/input.html diff --git a/package.json b/package.json index c9846fa508d5..a86cc22e260a 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ ], "scripts": { "test": "mocha --opts mocha.opts", + "quicktest": "mocha --opts mocha.opts", "precoverage": "export COVERAGE=true && nyc mocha --opts mocha.coverage.opts", "coverage": "nyc report --reporter=text-lcov > coverage.lcov", "codecov": "codecov", diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index ef21649a47b3..f6010a6781cc 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -15,7 +15,7 @@ import clone from '../utils/clone'; import DomBlock from './dom/Block'; import SsrBlock from './server-side-rendering/Block'; import Stylesheet from '../css/Stylesheet'; -import { Node, GenerateOptions, Parsed, CompileOptions } from '../interfaces'; +import { Node, GenerateOptions, Parsed, CompileOptions, CustomElementOptions } from '../interfaces'; const test = typeof global !== 'undefined' && global.__svelte_test; @@ -31,6 +31,10 @@ export default class Generator { name: string; options: CompileOptions; + customElement: CustomElementOptions; + tag: string; + props: string[]; + defaultExport: Node[]; imports: Node[]; helpers: Set; @@ -100,6 +104,19 @@ export default class Generator { this.parseJs(); this.name = this.alias(name); + + if (options.customElement === true) { + this.customElement = { + tag: this.tag, + props: this.props // TODO autofill this in + } + } else { + this.customElement = options.customElement; + } + + if (this.customElement && !this.customElement.tag) { + throw new Error(`No tag name specified`); // TODO better error + } } addSourcemapLocations(node: Node) { @@ -554,6 +571,16 @@ export default class Generator { templateProperties.ondestroy = templateProperties.onteardown; } + if (templateProperties.tag) { + this.tag = templateProperties.tag.value.value; + removeObjectKey(this.code, defaultExport.declaration, 'tag'); + } + + if (templateProperties.props) { + // TODO + this.props = templateProperties.props.value; + } + // now that we've analysed the default export, we can determine whether or not we need to keep it let hasDefaultExport = !!defaultExport; if (defaultExport && defaultExport.declaration.properties.length === 0) { diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 6003c57c8fb6..d3adee900aee 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -148,88 +148,110 @@ export default function dom( .join(',\n')} }`; - // TODO deprecate component.teardown() - builder.addBlock(deindent` - function ${name} ( options ) { - ${options.dev && - `if ( !options || (!options.target && !options._root) ) throw new Error( "'target' is a required option" );`} - this.options = options; - ${generator.usesRefs && `this.refs = {};`} - this._state = ${templateProperties.data - ? `@assign( @template.data(), options.data )` - : `options.data || {}`}; - ${generator.metaBindings} - ${computations.length && `this._recompute( {}, this._state, {}, true );`} - ${options.dev && - Array.from(generator.expectedProperties).map( - prop => - `if ( !( '${prop}' in this._state ) ) console.warn( "Component was created without expected data property '${prop}'" );` - )} - ${generator.bindingGroups.length && - `this._bindingGroups = [ ${Array(generator.bindingGroups.length) - .fill('[]') - .join(', ')} ];`} - - this._observers = { - pre: Object.create( null ), - post: Object.create( null ) - }; - - this._handlers = Object.create( null ); - ${templateProperties.ondestroy && `this._handlers.destroy = [@template.ondestroy]`} - - this._root = options._root || this; - this._yield = options._yield; - this._bind = options._bind; - ${generator.slots.size && `this._slotted = options.slots || {};`} + const target = generator.customElement ? `this.attachShadow({ mode: 'open' })` : `options.target`; + const anchor = generator.customElement ? `null` : `options.anchor || null`; + + const constructorBody = deindent` + ${options.dev && + `if ( !options || (!options.target && !options._root) ) throw new Error( "'target' is a required option" );`} + this.options = options; + ${generator.usesRefs && `this.refs = {};`} + this._state = ${templateProperties.data + ? `@assign( @template.data(), options.data )` + : `options.data || {}`}; + ${generator.metaBindings} + ${computations.length && `this._recompute( {}, this._state, {}, true );`} + ${options.dev && + Array.from(generator.expectedProperties).map( + prop => + `if ( !( '${prop}' in this._state ) ) console.warn( "Component was created without expected data property '${prop}'" );` + )} + ${generator.bindingGroups.length && + `this._bindingGroups = [ ${Array(generator.bindingGroups.length) + .fill('[]') + .join(', ')} ];`} + + this._observers = { + pre: Object.create( null ), + post: Object.create( null ) + }; + + this._handlers = Object.create( null ); + ${templateProperties.ondestroy && `this._handlers.destroy = [@template.ondestroy]`} + + this._root = options._root || this; + this._yield = options._yield; + this._bind = options._bind; + ${generator.slots.size && `this._slotted = options.slots || {};`} + + ${generator.stylesheet.hasStyles && + options.css !== false && + `if ( !document.getElementById( '${generator.stylesheet.id}-style' ) ) @add_css();`} + + ${templateProperties.oncreate && `var oncreate = @template.oncreate.bind( this );`} + + ${(templateProperties.oncreate || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent` + if ( !options._root ) { + this._oncreate = [${templateProperties.oncreate && `oncreate`}]; + ${(generator.hasComponents || generator.hasComplexBindings) && `this._beforecreate = [];`} + ${(generator.hasComponents || generator.hasIntroTransitions) && `this._aftercreate = [];`} + } ${templateProperties.oncreate && deindent` + else { + this._root._oncreate.push(oncreate); + } + `} + `} - ${generator.stylesheet.hasStyles && - options.css !== false && - `if ( !document.getElementById( '${generator.stylesheet.id}-style' ) ) @add_css();`} + ${generator.slots.size && `this.slots = {};`} - ${templateProperties.oncreate && `var oncreate = @template.oncreate.bind( this );`} + this._fragment = @create_main_fragment( this._state, this ); - ${(templateProperties.oncreate || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent` - if ( !options._root ) { - this._oncreate = [${templateProperties.oncreate && `oncreate`}]; - ${(generator.hasComponents || generator.hasComplexBindings) && `this._beforecreate = [];`} - ${(generator.hasComponents || generator.hasIntroTransitions) && `this._aftercreate = [];`} - } ${templateProperties.oncreate && deindent` - else { - this._root._oncreate.push(oncreate); - } + if ( !options._root ) { + ${generator.hydratable + ? deindent` + var nodes = @children( options.target ); + options.hydrate ? this._fragment.claim( nodes ) : this._fragment.create(); + nodes.forEach( @detachNode ); + ` : + deindent` + ${options.dev && `if ( options.hydrate ) throw new Error( 'options.hydrate only works if the component was compiled with the \`hydratable: true\` option' );`} + this._fragment.create(); `} - `} + this._fragment.${block.hasIntroMethod ? 'intro' : 'mount'}( ${target}, ${anchor} ); + } - ${generator.slots.size && `this.slots = {};`} - - this._fragment = @create_main_fragment( this._state, this ); - - if ( options.target ) { - ${generator.hydratable - ? deindent` - var nodes = @children( options.target ); - options.hydrate ? this._fragment.claim( nodes ) : this._fragment.create(); - nodes.forEach( @detachNode ); - ` : - deindent` - ${options.dev && `if ( options.hydrate ) throw new Error( 'options.hydrate only works if the component was compiled with the \`hydratable: true\` option' );`} - this._fragment.create(); - `} - this._fragment.${block.hasIntroMethod ? 'intro' : 'mount'}( options.target, options.anchor || null ); + ${(generator.hasComponents || generator.hasComplexBindings || templateProperties.oncreate || generator.hasIntroTransitions) && deindent` + if ( !options._root ) { + ${generator.hasComponents && `this._lock = true;`} + ${(generator.hasComponents || generator.hasComplexBindings) && `@callAll(this._beforecreate);`} + ${(generator.hasComponents || templateProperties.oncreate) && `@callAll(this._oncreate);`} + ${(generator.hasComponents || generator.hasIntroTransitions) && `@callAll(this._aftercreate);`} + ${generator.hasComponents && `this._lock = false;`} } + `} + `; - ${(generator.hasComponents || generator.hasComplexBindings || templateProperties.oncreate || generator.hasIntroTransitions) && deindent` - if ( !options._root ) { - ${generator.hasComponents && `this._lock = true;`} - ${(generator.hasComponents || generator.hasComplexBindings) && `@callAll(this._beforecreate);`} - ${(generator.hasComponents || templateProperties.oncreate) && `@callAll(this._oncreate);`} - ${(generator.hasComponents || generator.hasIntroTransitions) && `@callAll(this._aftercreate);`} - ${generator.hasComponents && `this._lock = false;`} + if (generator.customElement) { + builder.addBlock(deindent` + class ${name} extends HTMLElement { + constructor(options = {}) { + super(); + ${constructorBody} } - `} - } + } + customElements.define('${generator.tag}', ${name}); + `); + } else { + builder.addBlock(deindent` + function ${name} ( options ) { + ${constructorBody} + } + `); + } + + // TODO deprecate component.teardown() + builder.addBlock(deindent` @assign( ${prototypeBase}, ${proto}); ${options.dev && deindent` diff --git a/src/interfaces.ts b/src/interfaces.ts index 05b65375f313..07bca42ca0bd 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -54,6 +54,7 @@ export interface CompileOptions { cascade?: boolean; hydratable?: boolean; legacy?: boolean; + customElement: CustomElementOptions | true; onerror?: (error: Error) => void; onwarn?: (warning: Warning) => void; @@ -67,4 +68,9 @@ export interface GenerateOptions { export interface Visitor { enter: (node: Node) => void; leave?: (node: Node) => void; +} + +export interface CustomElementOptions { + tag?: string; + props?: string[]; } \ No newline at end of file diff --git a/src/validate/js/propValidators/index.ts b/src/validate/js/propValidators/index.ts index aa71fa0aeb4c..1be9f9ffbb66 100644 --- a/src/validate/js/propValidators/index.ts +++ b/src/validate/js/propValidators/index.ts @@ -9,6 +9,7 @@ import methods from './methods'; import components from './components'; import events from './events'; import namespace from './namespace'; +import tag from './tag'; import transitions from './transitions'; import setup from './setup'; @@ -24,6 +25,7 @@ export default { components, events, namespace, + tag, transitions, setup, }; diff --git a/src/validate/js/propValidators/tag.ts b/src/validate/js/propValidators/tag.ts new file mode 100644 index 000000000000..2bd55b894abd --- /dev/null +++ b/src/validate/js/propValidators/tag.ts @@ -0,0 +1,20 @@ +import usesThisOrArguments from '../utils/usesThisOrArguments'; +import { Validator } from '../../'; +import { Node } from '../../../interfaces'; + +export default function tag(validator: Validator, prop: Node) { + if (prop.value.type !== 'Literal' || typeof prop.value.value !== 'string') { + validator.error( + `'tag' must be a string literal`, + prop.value.start + ); + } + + const tag = prop.value.value; + if (!/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) { + validator.error( + `tag name must be two or more words joined by the '-' character`, + prop.value.start + ); + } +} diff --git a/test/js/samples/custom-element-basic/_config.js b/test/js/samples/custom-element-basic/_config.js new file mode 100644 index 000000000000..735dd07e625a --- /dev/null +++ b/test/js/samples/custom-element-basic/_config.js @@ -0,0 +1,5 @@ +export default { + options: { + customElement: true + } +}; \ No newline at end of file diff --git a/test/js/samples/custom-element-basic/expected-bundle.js b/test/js/samples/custom-element-basic/expected-bundle.js new file mode 100644 index 000000000000..aff7954c6035 --- /dev/null +++ b/test/js/samples/custom-element-basic/expected-bundle.js @@ -0,0 +1,218 @@ +function noop() {} + +function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { + source = arguments[i]; + for (k in source) target[k] = source[k]; + } + + return target; +} + +function appendNode(node, target) { + target.appendChild(node); +} + +function insertNode(node, target, anchor) { + target.insertBefore(node, anchor); +} + +function detachNode(node) { + node.parentNode.removeChild(node); +} + +function createElement(name) { + return document.createElement(name); +} + +function createText(data) { + return document.createTextNode(data); +} + +function destroy(detach) { + this.destroy = noop; + this.fire('destroy'); + this.set = this.get = noop; + + if (detach !== false) this._fragment.unmount(); + this._fragment.destroy(); + this._fragment = this._state = null; +} + +function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); +} + +function dispatchObservers(component, group, changed, newState, oldState) { + for (var key in group) { + if (!changed[key]) continue; + + var newValue = newState[key]; + var oldValue = oldState[key]; + + var callbacks = group[key]; + if (!callbacks) continue; + + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; + + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; + } + } +} + +function get(key) { + return key ? this._state[key] : this._state; +} + +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; + + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); + } +} + +function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; + + (group[key] || (group[key] = [])).push(callback); + + if (!options || options.init !== false) { + callback.__calling = true; + callback.call(this, this._state[key]); + callback.__calling = false; + } + + return { + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); + } + }; +} + +function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); + + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); + + return { + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); + } + }; +} + +function set(newState) { + this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); + callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; +} + +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + +function callAll(fns) { + while (fns && fns.length) fns.pop()(); +} + +var proto = { + destroy: destroy, + get: get, + fire: fire, + observe: observe, + on: on, + set: set, + teardown: destroy, + _recompute: noop, + _set: _set +}; + +function create_main_fragment ( state, component ) { + var div, text; + + return { + create: function () { + div = createElement( 'div' ); + text = createText( "I am shadow DOM" ); + }, + + mount: function ( target, anchor ) { + insertNode( div, target, anchor ); + appendNode( text, div ); + }, + + update: noop, + + unmount: function () { + detachNode( div ); + }, + + destroy: noop + }; +} + +class SvelteComponent extends HTMLElement { + constructor(options = {}) { + super(); + this.options = options; + this._state = options.data || {}; + + this._observers = { + pre: Object.create( null ), + post: Object.create( null ) + }; + + this._handlers = Object.create( null ); + + this._root = options._root || this; + this._yield = options._yield; + this._bind = options._bind; + + this._fragment = create_main_fragment( this._state, this ); + + if ( !options._root ) { + this._fragment.create(); + this._fragment.mount( this.attachShadow({ mode: 'open' }), null ); + } + } +} + +customElements.define('custom-element', SvelteComponent); + +assign( SvelteComponent.prototype, proto ); + +export default SvelteComponent; diff --git a/test/js/samples/custom-element-basic/expected.js b/test/js/samples/custom-element-basic/expected.js new file mode 100644 index 000000000000..50023bb4ec4b --- /dev/null +++ b/test/js/samples/custom-element-basic/expected.js @@ -0,0 +1,57 @@ +import { appendNode, assign, createElement, createText, detachNode, insertNode, noop, proto } from "svelte/shared.js"; + +function create_main_fragment ( state, component ) { + var div, text; + + return { + create: function () { + div = createElement( 'div' ); + text = createText( "I am shadow DOM" ); + }, + + mount: function ( target, anchor ) { + insertNode( div, target, anchor ); + appendNode( text, div ); + }, + + update: noop, + + unmount: function () { + detachNode( div ); + }, + + destroy: noop + }; +} + +class SvelteComponent extends HTMLElement { + constructor(options = {}) { + super(); + this.options = options; + this._state = options.data || {}; + + this._observers = { + pre: Object.create( null ), + post: Object.create( null ) + }; + + this._handlers = Object.create( null ); + + this._root = options._root || this; + this._yield = options._yield; + this._bind = options._bind; + + this._fragment = create_main_fragment( this._state, this ); + + if ( !options._root ) { + this._fragment.create(); + this._fragment.mount( this.attachShadow({ mode: 'open' }), null ); + } + } +} + +customElements.define('custom-element', SvelteComponent); + +assign( SvelteComponent.prototype, proto ); + +export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/custom-element-basic/input.html b/test/js/samples/custom-element-basic/input.html new file mode 100644 index 000000000000..3e00e7121f9b --- /dev/null +++ b/test/js/samples/custom-element-basic/input.html @@ -0,0 +1,7 @@ +
I am shadow DOM
+ + \ No newline at end of file diff --git a/test/runtime/index.js b/test/runtime/index.js index 78415ac42881..95950b6668b9 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -22,8 +22,6 @@ function getName(filename) { return base[0].toUpperCase() + base.slice(1); } -const Object_assign = Object.assign; - describe("runtime", () => { before(() => { svelte = loadSvelte(true); From 30e58c4a73a637b9672d70b45bfee735bfe95d54 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 2 Sep 2017 17:22:06 -0400 Subject: [PATCH 02/10] validate props, auto-generate props if unspecified --- src/generators/Generator.ts | 4 ++-- src/generators/dom/index.ts | 20 ++++++++++++++++++++ src/validate/js/propValidators/index.ts | 2 ++ src/validate/js/propValidators/props.ts | 20 ++++++++++++++++++++ src/validate/js/propValidators/tag.ts | 1 - 5 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 src/validate/js/propValidators/props.ts diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index f6010a6781cc..fa7a47060fc6 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -577,8 +577,8 @@ export default class Generator { } if (templateProperties.props) { - // TODO - this.props = templateProperties.props.value; + this.props = templateProperties.props.value.elements.map((element: Node) => element.value); + removeObjectKey(this.code, defaultExport.declaration, 'props'); } // now that we've analysed the default export, we can determine whether or not we need to keep it diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index d3adee900aee..cdb5bc39e999 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -232,12 +232,32 @@ export default function dom( `; if (generator.customElement) { + const props = generator.props || Array.from(generator.expectedProperties); + builder.addBlock(deindent` class ${name} extends HTMLElement { constructor(options = {}) { super(); ${constructorBody} } + + static get observedAttributes() { + return ${JSON.stringify(props)}; + } + + ${props.map(prop => deindent` + get ${prop}() { + return this.get('${prop}'); + } + + set ${prop}(value) { + this.set({ ${prop}: value }); + } + `).join('\n\n')} + + attributeChangedCallback ( attr, oldValue, newValue ) { + this.set({ [attr]: newValue }); + } } customElements.define('${generator.tag}', ${name}); diff --git a/src/validate/js/propValidators/index.ts b/src/validate/js/propValidators/index.ts index 1be9f9ffbb66..89ccd36f1448 100644 --- a/src/validate/js/propValidators/index.ts +++ b/src/validate/js/propValidators/index.ts @@ -9,6 +9,7 @@ import methods from './methods'; import components from './components'; import events from './events'; import namespace from './namespace'; +import props from './props'; import tag from './tag'; import transitions from './transitions'; import setup from './setup'; @@ -25,6 +26,7 @@ export default { components, events, namespace, + props, tag, transitions, setup, diff --git a/src/validate/js/propValidators/props.ts b/src/validate/js/propValidators/props.ts new file mode 100644 index 000000000000..408e72a7f5f5 --- /dev/null +++ b/src/validate/js/propValidators/props.ts @@ -0,0 +1,20 @@ +import { Validator } from '../../'; +import { Node } from '../../../interfaces'; + +export default function props(validator: Validator, prop: Node) { + if (prop.value.type !== 'ArrayExpression') { + validator.error( + `'props' must be an array expression, if specified`, + prop.value.start + ); + } + + prop.value.elements.forEach((element: Node) => { + if (element.type !== 'Literal' || typeof element.value !== 'string') { + validator.error( + `'props' must be an array of string literals`, + element.start + ); + } + }); +} diff --git a/src/validate/js/propValidators/tag.ts b/src/validate/js/propValidators/tag.ts index 2bd55b894abd..c64381fc54ef 100644 --- a/src/validate/js/propValidators/tag.ts +++ b/src/validate/js/propValidators/tag.ts @@ -1,4 +1,3 @@ -import usesThisOrArguments from '../utils/usesThisOrArguments'; import { Validator } from '../../'; import { Node } from '../../../interfaces'; From af618ba93f7dc58df9b63f232d43e8e4a4139bd6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 2 Sep 2017 17:22:16 -0400 Subject: [PATCH 03/10] render elements --- src/generators/dom/visitors/Element/Element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index 42b2ec71ff94..94d1336a57c8 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -44,7 +44,7 @@ export default function visitElement( return meta[node.name](generator, block, node); } - if (node.name === 'slot') { + if (node.name === 'slot' && !generator.customElement) { return visitSlot(generator, block, state, node, elementStack, componentStack); } From 29fc3e51692e0a5043d7cd4a14b4350700121f59 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 2 Sep 2017 17:22:28 -0400 Subject: [PATCH 04/10] update tests --- .../custom-element-basic/expected-bundle.js | 38 ++- .../samples/custom-element-basic/expected.js | 38 ++- .../samples/custom-element-basic/input.html | 2 +- .../js/samples/custom-element-slot/_config.js | 5 + .../custom-element-slot/expected-bundle.js | 249 ++++++++++++++++++ .../samples/custom-element-slot/expected.js | 84 ++++++ .../js/samples/custom-element-slot/input.html | 15 ++ 7 files changed, 416 insertions(+), 15 deletions(-) create mode 100644 test/js/samples/custom-element-slot/_config.js create mode 100644 test/js/samples/custom-element-slot/expected-bundle.js create mode 100644 test/js/samples/custom-element-slot/expected.js create mode 100644 test/js/samples/custom-element-slot/input.html diff --git a/test/js/samples/custom-element-basic/expected-bundle.js b/test/js/samples/custom-element-basic/expected-bundle.js index aff7954c6035..f234f6dca8ee 100644 --- a/test/js/samples/custom-element-basic/expected-bundle.js +++ b/test/js/samples/custom-element-basic/expected-bundle.js @@ -162,23 +162,31 @@ var proto = { }; function create_main_fragment ( state, component ) { - var div, text; + var h1, text, text_1, text_2; return { create: function () { - div = createElement( 'div' ); - text = createText( "I am shadow DOM" ); + h1 = createElement( 'h1' ); + text = createText( "Hello " ); + text_1 = createText( state.name ); + text_2 = createText( "!" ); }, mount: function ( target, anchor ) { - insertNode( div, target, anchor ); - appendNode( text, div ); + insertNode( h1, target, anchor ); + appendNode( text, h1 ); + appendNode( text_1, h1 ); + appendNode( text_2, h1 ); }, - update: noop, + update: function ( changed, state ) { + if ( changed.name ) { + text_1.data = state.name; + } + }, unmount: function () { - detachNode( div ); + detachNode( h1 ); }, destroy: noop @@ -209,6 +217,22 @@ class SvelteComponent extends HTMLElement { this._fragment.mount( this.attachShadow({ mode: 'open' }), null ); } } + + static get observedAttributes() { + return ["name"]; + } + + get name() { + return this.get('name'); + } + + set name(value) { + this.set({ name: value }); + } + + attributeChangedCallback ( attr, oldValue, newValue ) { + this.set({ [attr]: newValue }); + } } customElements.define('custom-element', SvelteComponent); diff --git a/test/js/samples/custom-element-basic/expected.js b/test/js/samples/custom-element-basic/expected.js index 50023bb4ec4b..1e1b8deac0f5 100644 --- a/test/js/samples/custom-element-basic/expected.js +++ b/test/js/samples/custom-element-basic/expected.js @@ -1,23 +1,31 @@ import { appendNode, assign, createElement, createText, detachNode, insertNode, noop, proto } from "svelte/shared.js"; function create_main_fragment ( state, component ) { - var div, text; + var h1, text, text_1, text_2; return { create: function () { - div = createElement( 'div' ); - text = createText( "I am shadow DOM" ); + h1 = createElement( 'h1' ); + text = createText( "Hello " ); + text_1 = createText( state.name ); + text_2 = createText( "!" ); }, mount: function ( target, anchor ) { - insertNode( div, target, anchor ); - appendNode( text, div ); + insertNode( h1, target, anchor ); + appendNode( text, h1 ); + appendNode( text_1, h1 ); + appendNode( text_2, h1 ); }, - update: noop, + update: function ( changed, state ) { + if ( changed.name ) { + text_1.data = state.name; + } + }, unmount: function () { - detachNode( div ); + detachNode( h1 ); }, destroy: noop @@ -48,6 +56,22 @@ class SvelteComponent extends HTMLElement { this._fragment.mount( this.attachShadow({ mode: 'open' }), null ); } } + + static get observedAttributes() { + return ["name"]; + } + + get name() { + return this.get('name'); + } + + set name(value) { + this.set({ name: value }); + } + + attributeChangedCallback ( attr, oldValue, newValue ) { + this.set({ [attr]: newValue }); + } } customElements.define('custom-element', SvelteComponent); diff --git a/test/js/samples/custom-element-basic/input.html b/test/js/samples/custom-element-basic/input.html index 3e00e7121f9b..6cc7a9e7af93 100644 --- a/test/js/samples/custom-element-basic/input.html +++ b/test/js/samples/custom-element-basic/input.html @@ -1,4 +1,4 @@ -
I am shadow DOM
+

Hello {{name}}!

\ No newline at end of file From f5f35b5a50deb34371322dca1970b916cf754472 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 2 Sep 2017 18:41:45 -0400 Subject: [PATCH 05/10] scoped styles --- src/css/Stylesheet.ts | 10 +- src/generators/Generator.ts | 4 +- src/generators/dom/index.ts | 26 +- .../dom/visitors/Element/Element.ts | 2 +- src/generators/server-side-rendering/index.ts | 4 +- .../expected-bundle.js | 2 +- .../expected.js | 2 +- .../computed-collapsed-if/expected-bundle.js | 2 +- .../samples/computed-collapsed-if/expected.js | 2 +- .../css-media-query/expected-bundle.js | 2 +- test/js/samples/css-media-query/expected.js | 2 +- .../custom-element-basic/expected-bundle.js | 4 +- .../samples/custom-element-basic/expected.js | 4 +- .../custom-element-slot/expected-bundle.js | 4 +- .../samples/custom-element-slot/expected.js | 4 +- .../samples/custom-element-styled/_config.js | 5 + .../custom-element-styled/expected-bundle.js | 245 ++++++++++++++++++ .../samples/custom-element-styled/expected.js | 84 ++++++ .../samples/custom-element-styled/input.html | 13 + .../expected-bundle.js | 2 +- .../each-block-changed-check/expected.js | 2 +- .../event-handlers-custom/expected-bundle.js | 2 +- .../samples/event-handlers-custom/expected.js | 2 +- .../if-block-no-update/expected-bundle.js | 2 +- .../js/samples/if-block-no-update/expected.js | 2 +- .../if-block-simple/expected-bundle.js | 2 +- test/js/samples/if-block-simple/expected.js | 2 +- .../legacy-input-type/expected-bundle.js | 2 +- test/js/samples/legacy-input-type/expected.js | 2 +- .../non-imported-component/expected-bundle.js | 2 +- .../non-imported-component/expected.js | 2 +- .../expected-bundle.js | 2 +- .../onrender-onteardown-rewritten/expected.js | 2 +- .../samples/setup-method/expected-bundle.js | 2 +- test/js/samples/setup-method/expected.js | 2 +- .../expected-bundle.js | 2 +- .../use-elements-as-anchors/expected.js | 2 +- 37 files changed, 411 insertions(+), 46 deletions(-) create mode 100644 test/js/samples/custom-element-styled/_config.js create mode 100644 test/js/samples/custom-element-styled/expected-bundle.js create mode 100644 test/js/samples/custom-element-styled/expected.js create mode 100644 test/js/samples/custom-element-styled/input.html diff --git a/src/css/Stylesheet.ts b/src/css/Stylesheet.ts index 03c711044f07..f79abc1d7561 100644 --- a/src/css/Stylesheet.ts +++ b/src/css/Stylesheet.ts @@ -337,7 +337,7 @@ export default class Stylesheet { } } - render(cssOutputFilename: string) { + render(cssOutputFilename: string, shouldTransformSelectors: boolean) { if (!this.hasStyles) { return { css: null, cssMap: null }; } @@ -351,9 +351,11 @@ export default class Stylesheet { } }); - this.children.forEach((child: (Atrule|Rule)) => { - child.transform(code, this.id, this.keyframes, this.cascade); - }); + if (shouldTransformSelectors) { + this.children.forEach((child: (Atrule|Rule)) => { + child.transform(code, this.id, this.keyframes, this.cascade); + }); + } let c = 0; this.children.forEach(child => { diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index fa7a47060fc6..d912f151e67d 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -389,7 +389,9 @@ export default class Generator { addString(finalChunk); addString('\n\n' + getOutro(format, name, options, this.imports)); - const { css, cssMap } = this.stylesheet.render(options.cssOutputFilename); + const { css, cssMap } = this.customElement ? + { css: null, cssMap: null } : + this.stylesheet.render(options.cssOutputFilename, true); return { ast: this.ast, diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index cdb5bc39e999..1d1e8b260842 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -111,18 +111,17 @@ export default function dom( `); } - if (generator.stylesheet.hasStyles && options.css !== false) { - const { css, cssMap } = generator.stylesheet.render(options.filename); - - const textContent = stringify(options.dev ? - `${css}\n/*# sourceMappingURL=${cssMap.toUrl()} */` : - css, { onlyEscapeAtSymbol: true }); + const { css, cssMap } = generator.stylesheet.render(options.filename, !generator.customElement); + const styles = generator.stylesheet.hasStyles && stringify(options.dev ? + `${css}\n/*# sourceMappingURL=${cssMap.toUrl()} */` : + css, { onlyEscapeAtSymbol: true }); + if (styles && generator.options.css !== false && !generator.customElement) { builder.addBlock(deindent` function @add_css () { var style = @createElement( 'style' ); style.id = '${generator.stylesheet.id}-style'; - style.textContent = ${textContent}; + style.textContent = ${styles}; @appendNode( style, document.head ); } `); @@ -148,7 +147,7 @@ export default function dom( .join(',\n')} }`; - const target = generator.customElement ? `this.attachShadow({ mode: 'open' })` : `options.target`; + const target = generator.customElement ? `this.shadowRoot` : `options.target`; const anchor = generator.customElement ? `null` : `options.anchor || null`; const constructorBody = deindent` @@ -184,9 +183,14 @@ export default function dom( this._bind = options._bind; ${generator.slots.size && `this._slotted = options.slots || {};`} - ${generator.stylesheet.hasStyles && - options.css !== false && - `if ( !document.getElementById( '${generator.stylesheet.id}-style' ) ) @add_css();`} + ${generator.customElement ? + deindent` + this.attachShadow({ mode: 'open' }); + ${css && `this.shadowRoot.innerHTML = '';`} + ` : + (generator.stylesheet.hasStyles && options.css !== false && + `if ( !document.getElementById( '${generator.stylesheet.id}-style' ) ) @add_css();`) + } ${templateProperties.oncreate && `var oncreate = @template.oncreate.bind( this );`} diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index 94d1336a57c8..a230b52cd3e3 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -88,7 +88,7 @@ export default function visitElement( // add CSS encapsulation attribute // TODO add a helper for this, rather than repeating it - if (node._needsCssAttribute) { + if (node._needsCssAttribute && !generator.customElement) { generator.needsEncapsulateHelper = true; block.builders.hydrate.addLine( `@encapsulateStyles( ${name} );` diff --git a/src/generators/server-side-rendering/index.ts b/src/generators/server-side-rendering/index.ts index a7cc04bedc1b..a56e53dc8d7f 100644 --- a/src/generators/server-side-rendering/index.ts +++ b/src/generators/server-side-rendering/index.ts @@ -94,7 +94,9 @@ export default function ssr( visit(generator, mainBlock, node); }); - const { css, cssMap } = generator.stylesheet.render(options.filename); + const { css, cssMap } = generator.customElement ? + { css: null, cssMap: null } : + generator.stylesheet.render(options.filename, true); const result = deindent` ${hasJs && `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]`} diff --git a/test/js/samples/collapses-text-around-comments/expected-bundle.js b/test/js/samples/collapses-text-around-comments/expected-bundle.js index b485e8ae409b..d2dda757efb4 100644 --- a/test/js/samples/collapses-text-around-comments/expected-bundle.js +++ b/test/js/samples/collapses-text-around-comments/expected-bundle.js @@ -236,7 +236,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index 88dd6ab34f95..deb4d4d30340 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -71,7 +71,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/computed-collapsed-if/expected-bundle.js b/test/js/samples/computed-collapsed-if/expected-bundle.js index 8f13660642fa..2ebbbcb13dbf 100644 --- a/test/js/samples/computed-collapsed-if/expected-bundle.js +++ b/test/js/samples/computed-collapsed-if/expected-bundle.js @@ -183,7 +183,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/computed-collapsed-if/expected.js b/test/js/samples/computed-collapsed-if/expected.js index 340622cd28ae..6bb4ebaf9e8d 100644 --- a/test/js/samples/computed-collapsed-if/expected.js +++ b/test/js/samples/computed-collapsed-if/expected.js @@ -42,7 +42,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/css-media-query/expected-bundle.js b/test/js/samples/css-media-query/expected-bundle.js index 9b5f69f529d0..afcf12bb1931 100644 --- a/test/js/samples/css-media-query/expected-bundle.js +++ b/test/js/samples/css-media-query/expected-bundle.js @@ -218,7 +218,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/css-media-query/expected.js b/test/js/samples/css-media-query/expected.js index 86167afeb1bb..23839e6c0d02 100644 --- a/test/js/samples/css-media-query/expected.js +++ b/test/js/samples/css-media-query/expected.js @@ -57,7 +57,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/custom-element-basic/expected-bundle.js b/test/js/samples/custom-element-basic/expected-bundle.js index f234f6dca8ee..77a7b3bc0f93 100644 --- a/test/js/samples/custom-element-basic/expected-bundle.js +++ b/test/js/samples/custom-element-basic/expected-bundle.js @@ -210,11 +210,13 @@ class SvelteComponent extends HTMLElement { this._yield = options._yield; this._bind = options._bind; + this.attachShadow({ mode: 'open' }); + this._fragment = create_main_fragment( this._state, this ); if ( !options._root ) { this._fragment.create(); - this._fragment.mount( this.attachShadow({ mode: 'open' }), null ); + this._fragment.mount( this.shadowRoot, null ); } } diff --git a/test/js/samples/custom-element-basic/expected.js b/test/js/samples/custom-element-basic/expected.js index 1e1b8deac0f5..21437ee29367 100644 --- a/test/js/samples/custom-element-basic/expected.js +++ b/test/js/samples/custom-element-basic/expected.js @@ -49,11 +49,13 @@ class SvelteComponent extends HTMLElement { this._yield = options._yield; this._bind = options._bind; + this.attachShadow({ mode: 'open' }); + this._fragment = create_main_fragment( this._state, this ); if ( !options._root ) { this._fragment.create(); - this._fragment.mount( this.attachShadow({ mode: 'open' }), null ); + this._fragment.mount( this.shadowRoot, null ); } } diff --git a/test/js/samples/custom-element-slot/expected-bundle.js b/test/js/samples/custom-element-slot/expected-bundle.js index 1250dbb624ba..78011c2cb6bf 100644 --- a/test/js/samples/custom-element-slot/expected-bundle.js +++ b/test/js/samples/custom-element-slot/expected-bundle.js @@ -223,11 +223,13 @@ class SvelteComponent extends HTMLElement { this._yield = options._yield; this._bind = options._bind; + this.attachShadow({ mode: 'open' }); + this._fragment = create_main_fragment( this._state, this ); if ( !options._root ) { this._fragment.create(); - this._fragment.mount( this.attachShadow({ mode: 'open' }), null ); + this._fragment.mount( this.shadowRoot, null ); } } diff --git a/test/js/samples/custom-element-slot/expected.js b/test/js/samples/custom-element-slot/expected.js index 831cfce1d097..8b162fa50d63 100644 --- a/test/js/samples/custom-element-slot/expected.js +++ b/test/js/samples/custom-element-slot/expected.js @@ -58,11 +58,13 @@ class SvelteComponent extends HTMLElement { this._yield = options._yield; this._bind = options._bind; + this.attachShadow({ mode: 'open' }); + this._fragment = create_main_fragment( this._state, this ); if ( !options._root ) { this._fragment.create(); - this._fragment.mount( this.attachShadow({ mode: 'open' }), null ); + this._fragment.mount( this.shadowRoot, null ); } } diff --git a/test/js/samples/custom-element-styled/_config.js b/test/js/samples/custom-element-styled/_config.js new file mode 100644 index 000000000000..735dd07e625a --- /dev/null +++ b/test/js/samples/custom-element-styled/_config.js @@ -0,0 +1,5 @@ +export default { + options: { + customElement: true + } +}; \ No newline at end of file diff --git a/test/js/samples/custom-element-styled/expected-bundle.js b/test/js/samples/custom-element-styled/expected-bundle.js new file mode 100644 index 000000000000..5d2d44f38eee --- /dev/null +++ b/test/js/samples/custom-element-styled/expected-bundle.js @@ -0,0 +1,245 @@ +function noop() {} + +function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { + source = arguments[i]; + for (k in source) target[k] = source[k]; + } + + return target; +} + +function appendNode(node, target) { + target.appendChild(node); +} + +function insertNode(node, target, anchor) { + target.insertBefore(node, anchor); +} + +function detachNode(node) { + node.parentNode.removeChild(node); +} + +function createElement(name) { + return document.createElement(name); +} + +function createText(data) { + return document.createTextNode(data); +} + +function destroy(detach) { + this.destroy = noop; + this.fire('destroy'); + this.set = this.get = noop; + + if (detach !== false) this._fragment.unmount(); + this._fragment.destroy(); + this._fragment = this._state = null; +} + +function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); +} + +function dispatchObservers(component, group, changed, newState, oldState) { + for (var key in group) { + if (!changed[key]) continue; + + var newValue = newState[key]; + var oldValue = oldState[key]; + + var callbacks = group[key]; + if (!callbacks) continue; + + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; + + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; + } + } +} + +function get(key) { + return key ? this._state[key] : this._state; +} + +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; + + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); + } +} + +function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; + + (group[key] || (group[key] = [])).push(callback); + + if (!options || options.init !== false) { + callback.__calling = true; + callback.call(this, this._state[key]); + callback.__calling = false; + } + + return { + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); + } + }; +} + +function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); + + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); + + return { + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); + } + }; +} + +function set(newState) { + this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); + callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; +} + +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + +function callAll(fns) { + while (fns && fns.length) fns.pop()(); +} + +var proto = { + destroy: destroy, + get: get, + fire: fire, + observe: observe, + on: on, + set: set, + teardown: destroy, + _recompute: noop, + _set: _set +}; + +function create_main_fragment ( state, component ) { + var h1, text, text_1, text_2; + + return { + create: function () { + h1 = createElement( 'h1' ); + text = createText( "Hello " ); + text_1 = createText( state.name ); + text_2 = createText( "!" ); + }, + + mount: function ( target, anchor ) { + insertNode( h1, target, anchor ); + appendNode( text, h1 ); + appendNode( text_1, h1 ); + appendNode( text_2, h1 ); + }, + + update: function ( changed, state ) { + if ( changed.name ) { + text_1.data = state.name; + } + }, + + unmount: function () { + detachNode( h1 ); + }, + + destroy: noop + }; +} + +class SvelteComponent extends HTMLElement { + constructor(options = {}) { + super(); + this.options = options; + this._state = options.data || {}; + + this._observers = { + pre: Object.create( null ), + post: Object.create( null ) + }; + + this._handlers = Object.create( null ); + + this._root = options._root || this; + this._yield = options._yield; + this._bind = options._bind; + + this.attachShadow({ mode: 'open' }); + this.shadowRoot.innerHTML = ''; + + this._fragment = create_main_fragment( this._state, this ); + + if ( !options._root ) { + this._fragment.create(); + this._fragment.mount( this.shadowRoot, null ); + } + } + + static get observedAttributes() { + return ["name"]; + } + + get name() { + return this.get('name'); + } + + set name(value) { + this.set({ name: value }); + } + + attributeChangedCallback ( attr, oldValue, newValue ) { + this.set({ [attr]: newValue }); + } +} + +customElements.define('custom-element', SvelteComponent); + +assign( SvelteComponent.prototype, proto ); + +export default SvelteComponent; diff --git a/test/js/samples/custom-element-styled/expected.js b/test/js/samples/custom-element-styled/expected.js new file mode 100644 index 000000000000..ef6a614f001e --- /dev/null +++ b/test/js/samples/custom-element-styled/expected.js @@ -0,0 +1,84 @@ +import { appendNode, assign, createElement, createText, detachNode, insertNode, noop, proto } from "svelte/shared.js"; + +function create_main_fragment ( state, component ) { + var h1, text, text_1, text_2; + + return { + create: function () { + h1 = createElement( 'h1' ); + text = createText( "Hello " ); + text_1 = createText( state.name ); + text_2 = createText( "!" ); + }, + + mount: function ( target, anchor ) { + insertNode( h1, target, anchor ); + appendNode( text, h1 ); + appendNode( text_1, h1 ); + appendNode( text_2, h1 ); + }, + + update: function ( changed, state ) { + if ( changed.name ) { + text_1.data = state.name; + } + }, + + unmount: function () { + detachNode( h1 ); + }, + + destroy: noop + }; +} + +class SvelteComponent extends HTMLElement { + constructor(options = {}) { + super(); + this.options = options; + this._state = options.data || {}; + + this._observers = { + pre: Object.create( null ), + post: Object.create( null ) + }; + + this._handlers = Object.create( null ); + + this._root = options._root || this; + this._yield = options._yield; + this._bind = options._bind; + + this.attachShadow({ mode: 'open' }); + this.shadowRoot.innerHTML = ''; + + this._fragment = create_main_fragment( this._state, this ); + + if ( !options._root ) { + this._fragment.create(); + this._fragment.mount( this.shadowRoot, null ); + } + } + + static get observedAttributes() { + return ["name"]; + } + + get name() { + return this.get('name'); + } + + set name(value) { + this.set({ name: value }); + } + + attributeChangedCallback ( attr, oldValue, newValue ) { + this.set({ [attr]: newValue }); + } +} + +customElements.define('custom-element', SvelteComponent); + +assign( SvelteComponent.prototype, proto ); + +export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/custom-element-styled/input.html b/test/js/samples/custom-element-styled/input.html new file mode 100644 index 000000000000..78c7ad685ba9 --- /dev/null +++ b/test/js/samples/custom-element-styled/input.html @@ -0,0 +1,13 @@ +

Hello {{name}}!

+ + + + \ No newline at end of file diff --git a/test/js/samples/each-block-changed-check/expected-bundle.js b/test/js/samples/each-block-changed-check/expected-bundle.js index 6353bf3ee15b..65e244108a1d 100644 --- a/test/js/samples/each-block-changed-check/expected-bundle.js +++ b/test/js/samples/each-block-changed-check/expected-bundle.js @@ -328,7 +328,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index 7dcdb66696db..c4cde082d85b 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -154,7 +154,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/event-handlers-custom/expected-bundle.js b/test/js/samples/event-handlers-custom/expected-bundle.js index 159e569519ef..3edd91e778d4 100644 --- a/test/js/samples/event-handlers-custom/expected-bundle.js +++ b/test/js/samples/event-handlers-custom/expected-bundle.js @@ -227,7 +227,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/event-handlers-custom/expected.js b/test/js/samples/event-handlers-custom/expected.js index e713056e73b8..1af3570e3b7f 100644 --- a/test/js/samples/event-handlers-custom/expected.js +++ b/test/js/samples/event-handlers-custom/expected.js @@ -66,7 +66,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/if-block-no-update/expected-bundle.js b/test/js/samples/if-block-no-update/expected-bundle.js index 3f5fd5ceab7c..141fb94d9645 100644 --- a/test/js/samples/if-block-no-update/expected-bundle.js +++ b/test/js/samples/if-block-no-update/expected-bundle.js @@ -269,7 +269,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/if-block-no-update/expected.js b/test/js/samples/if-block-no-update/expected.js index ca082058f4be..f36c614c9587 100644 --- a/test/js/samples/if-block-no-update/expected.js +++ b/test/js/samples/if-block-no-update/expected.js @@ -104,7 +104,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/if-block-simple/expected-bundle.js b/test/js/samples/if-block-simple/expected-bundle.js index 5de96e35cc3a..704717031166 100644 --- a/test/js/samples/if-block-simple/expected-bundle.js +++ b/test/js/samples/if-block-simple/expected-bundle.js @@ -245,7 +245,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/if-block-simple/expected.js b/test/js/samples/if-block-simple/expected.js index 1eedecd1477a..eaf04155020d 100644 --- a/test/js/samples/if-block-simple/expected.js +++ b/test/js/samples/if-block-simple/expected.js @@ -80,7 +80,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/legacy-input-type/expected-bundle.js b/test/js/samples/legacy-input-type/expected-bundle.js index 9730de31f9b3..92bd8c1608db 100644 --- a/test/js/samples/legacy-input-type/expected-bundle.js +++ b/test/js/samples/legacy-input-type/expected-bundle.js @@ -203,7 +203,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/legacy-input-type/expected.js b/test/js/samples/legacy-input-type/expected.js index 33a277bb4a8b..46277e3c98cb 100644 --- a/test/js/samples/legacy-input-type/expected.js +++ b/test/js/samples/legacy-input-type/expected.js @@ -44,7 +44,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/non-imported-component/expected-bundle.js b/test/js/samples/non-imported-component/expected-bundle.js index 37f5543dc290..a56d55b6d3a7 100644 --- a/test/js/samples/non-imported-component/expected-bundle.js +++ b/test/js/samples/non-imported-component/expected-bundle.js @@ -225,7 +225,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/non-imported-component/expected.js b/test/js/samples/non-imported-component/expected.js index af524aa1aa33..1d23ee8f9ec7 100644 --- a/test/js/samples/non-imported-component/expected.js +++ b/test/js/samples/non-imported-component/expected.js @@ -72,7 +72,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js index e18be3c8d0e4..6d4a4814aa55 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js @@ -190,7 +190,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/onrender-onteardown-rewritten/expected.js b/test/js/samples/onrender-onteardown-rewritten/expected.js index ca473e83c7d1..d19bed152908 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected.js @@ -49,7 +49,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/setup-method/expected-bundle.js b/test/js/samples/setup-method/expected-bundle.js index 14af614e6716..8f162ec7b588 100644 --- a/test/js/samples/setup-method/expected-bundle.js +++ b/test/js/samples/setup-method/expected-bundle.js @@ -192,7 +192,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/setup-method/expected.js b/test/js/samples/setup-method/expected.js index 9e33fb392148..6b554eb78107 100644 --- a/test/js/samples/setup-method/expected.js +++ b/test/js/samples/setup-method/expected.js @@ -51,7 +51,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/use-elements-as-anchors/expected-bundle.js b/test/js/samples/use-elements-as-anchors/expected-bundle.js index cda8dbedbb13..35eec6f9658b 100644 --- a/test/js/samples/use-elements-as-anchors/expected-bundle.js +++ b/test/js/samples/use-elements-as-anchors/expected-bundle.js @@ -429,7 +429,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/use-elements-as-anchors/expected.js b/test/js/samples/use-elements-as-anchors/expected.js index f65d1fe2881b..7bc5165be00b 100644 --- a/test/js/samples/use-elements-as-anchors/expected.js +++ b/test/js/samples/use-elements-as-anchors/expected.js @@ -264,7 +264,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) { + if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } From 71bd8e3008d55b94b1a32347b47ad5754c54ba72 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 2 Sep 2017 18:56:03 -0400 Subject: [PATCH 06/10] use template strings to prevent sourcemaps breaking the code --- src/generators/dom/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 1d1e8b260842..7fff508e6ae0 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -151,7 +151,7 @@ export default function dom( const anchor = generator.customElement ? `null` : `options.anchor || null`; const constructorBody = deindent` - ${options.dev && + ${options.dev && !generator.customElement && `if ( !options || (!options.target && !options._root) ) throw new Error( "'target' is a required option" );`} this.options = options; ${generator.usesRefs && `this.refs = {};`} @@ -186,7 +186,7 @@ export default function dom( ${generator.customElement ? deindent` this.attachShadow({ mode: 'open' }); - ${css && `this.shadowRoot.innerHTML = '';`} + ${css && `this.shadowRoot.innerHTML = \`\`;`} ` : (generator.stylesheet.hasStyles && options.css !== false && `if ( !document.getElementById( '${generator.stylesheet.id}-style' ) ) @add_css();`) From 118988c16b7dd3463b5faa13b092dbf616559b87 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 2 Sep 2017 20:06:35 -0400 Subject: [PATCH 07/10] append host element, not contents, when component is nested --- src/generators/dom/index.ts | 24 ++++++++++++++++-------- src/generators/dom/preprocess.ts | 3 +-- src/generators/dom/visitors/Component.ts | 4 ++-- src/shared/index.js | 16 ++++++++++++++-- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 7fff508e6ae0..f131b840b913 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -142,7 +142,7 @@ export default function dom( ? `@proto ` : deindent` { - ${['destroy', 'get', 'fire', 'observe', 'on', 'set', '_set', 'teardown'] + ${['destroy', 'get', 'fire', 'observe', 'on', 'set', 'teardown', '_set', '_mount', '_unmount'] .map(n => `${n}: @${n === 'teardown' ? 'destroy' : n}`) .join(',\n')} }`; @@ -222,17 +222,15 @@ export default function dom( this._fragment.create(); `} this._fragment.${block.hasIntroMethod ? 'intro' : 'mount'}( ${target}, ${anchor} ); - } - ${(generator.hasComponents || generator.hasComplexBindings || templateProperties.oncreate || generator.hasIntroTransitions) && deindent` - if ( !options._root ) { + ${(generator.hasComponents || generator.hasComplexBindings || templateProperties.oncreate || generator.hasIntroTransitions) && deindent` ${generator.hasComponents && `this._lock = true;`} ${(generator.hasComponents || generator.hasComplexBindings) && `@callAll(this._beforecreate);`} ${(generator.hasComponents || templateProperties.oncreate) && `@callAll(this._oncreate);`} ${(generator.hasComponents || generator.hasIntroTransitions) && `@callAll(this._aftercreate);`} ${generator.hasComponents && `this._lock = false;`} - } - `} + `} + } `; if (generator.customElement) { @@ -265,19 +263,29 @@ export default function dom( } customElements.define('${generator.tag}', ${name}); + @assign( ${prototypeBase}, ${proto}, { + _mount(target, anchor) { + this._fragment.mount(this.shadowRoot, null); + target.insertBefore(this, anchor); + }, + + _unmount() { + this.parentNode.removeChild(this); + } + }); `); } else { builder.addBlock(deindent` function ${name} ( options ) { ${constructorBody} } + + @assign( ${prototypeBase}, ${proto}); `); } // TODO deprecate component.teardown() builder.addBlock(deindent` - @assign( ${prototypeBase}, ${proto}); - ${options.dev && deindent` ${name}.prototype._checkReadOnly = function _checkReadOnly ( newState ) { ${Array.from(generator.readonly).map( diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts index 3aa78249c398..e602af60aee1 100644 --- a/src/generators/dom/preprocess.ts +++ b/src/generators/dom/preprocess.ts @@ -466,8 +466,7 @@ export default function preprocess( const state: State = { namespace, parentNode: null, - parentNodes: 'nodes', - isTopLevel: true, + parentNodes: 'nodes' }; generator.blocks.push(block); diff --git a/src/generators/dom/visitors/Component.ts b/src/generators/dom/visitors/Component.ts index edbf10a637c7..4c42a7884114 100644 --- a/src/generators/dom/visitors/Component.ts +++ b/src/generators/dom/visitors/Component.ts @@ -234,10 +234,10 @@ export default function visitComponent( ); block.builders.mount.addLine( - `${name}._fragment.mount( ${state.parentNode || '#target'}, ${state.parentNode ? 'null' : 'anchor'} );` + `${name}._mount( ${state.parentNode || '#target'}, ${state.parentNode ? 'null' : 'anchor'} );` ); - if (!state.parentNode) block.builders.unmount.addLine(`${name}._fragment.unmount();`); + if (!state.parentNode) block.builders.unmount.addLine(`${name}._unmount();`); block.builders.destroy.addLine(`${name}.destroy( false );`); diff --git a/src/shared/index.js b/src/shared/index.js index fdd267d902d1..0b4ccb85c572 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -163,6 +163,14 @@ export function callAll(fns) { while (fns && fns.length) fns.pop()(); } +export function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +export function _unmount() { + this._fragment.unmount(); +} + export var proto = { destroy: destroy, get: get, @@ -172,7 +180,9 @@ export var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; export var protoDev = { @@ -184,5 +194,7 @@ export var protoDev = { set: set, teardown: destroyDev, _recompute: noop, - _set: _setDev + _set: _setDev, + _mount: _mount, + _unmount: _unmount }; From 824998fdaa9bb29d0ba60b69794d059cfa98ac1f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 2 Sep 2017 20:12:14 -0400 Subject: [PATCH 08/10] update tests --- .../expected-bundle.js | 12 ++++++++- .../computed-collapsed-if/expected-bundle.js | 12 ++++++++- .../css-media-query/expected-bundle.js | 12 ++++++++- .../custom-element-basic/expected-bundle.js | 24 ++++++++++++++--- .../samples/custom-element-basic/expected.js | 12 +++++++-- .../custom-element-slot/expected-bundle.js | 24 ++++++++++++++--- .../samples/custom-element-slot/expected.js | 12 +++++++-- .../custom-element-styled/expected-bundle.js | 26 ++++++++++++++++--- .../samples/custom-element-styled/expected.js | 14 +++++++--- .../expected-bundle.js | 12 ++++++++- .../event-handlers-custom/expected-bundle.js | 12 ++++++++- .../if-block-no-update/expected-bundle.js | 12 ++++++++- .../if-block-simple/expected-bundle.js | 12 ++++++++- .../legacy-input-type/expected-bundle.js | 12 ++++++++- .../non-imported-component/expected-bundle.js | 22 +++++++++++----- .../non-imported-component/expected.js | 10 +++---- .../expected-bundle.js | 14 +++++++--- .../onrender-onteardown-rewritten/expected.js | 2 -- .../samples/setup-method/expected-bundle.js | 12 ++++++++- .../expected-bundle.js | 12 ++++++++- 20 files changed, 235 insertions(+), 45 deletions(-) diff --git a/test/js/samples/collapses-text-around-comments/expected-bundle.js b/test/js/samples/collapses-text-around-comments/expected-bundle.js index d2dda757efb4..ae148c67ccb7 100644 --- a/test/js/samples/collapses-text-around-comments/expected-bundle.js +++ b/test/js/samples/collapses-text-around-comments/expected-bundle.js @@ -153,6 +153,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -162,7 +170,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; var template = (function () { diff --git a/test/js/samples/computed-collapsed-if/expected-bundle.js b/test/js/samples/computed-collapsed-if/expected-bundle.js index 2ebbbcb13dbf..af71c37d15a1 100644 --- a/test/js/samples/computed-collapsed-if/expected-bundle.js +++ b/test/js/samples/computed-collapsed-if/expected-bundle.js @@ -129,6 +129,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -138,7 +146,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; var template = (function () { diff --git a/test/js/samples/css-media-query/expected-bundle.js b/test/js/samples/css-media-query/expected-bundle.js index afcf12bb1931..d743041461bb 100644 --- a/test/js/samples/css-media-query/expected-bundle.js +++ b/test/js/samples/css-media-query/expected-bundle.js @@ -149,6 +149,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -158,7 +166,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; function encapsulateStyles ( node ) { diff --git a/test/js/samples/custom-element-basic/expected-bundle.js b/test/js/samples/custom-element-basic/expected-bundle.js index 77a7b3bc0f93..876bff4ece54 100644 --- a/test/js/samples/custom-element-basic/expected-bundle.js +++ b/test/js/samples/custom-element-basic/expected-bundle.js @@ -149,6 +149,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -158,7 +166,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; function create_main_fragment ( state, component ) { @@ -238,7 +248,15 @@ class SvelteComponent extends HTMLElement { } customElements.define('custom-element', SvelteComponent); - -assign( SvelteComponent.prototype, proto ); +assign( SvelteComponent.prototype, proto , { + _mount(target, anchor) { + this._fragment.mount(this.shadowRoot, null); + target.insertBefore(this, anchor); + }, + + _unmount() { + this.parentNode.removeChild(this); + } +}); export default SvelteComponent; diff --git a/test/js/samples/custom-element-basic/expected.js b/test/js/samples/custom-element-basic/expected.js index 21437ee29367..82dc7f2bb5d9 100644 --- a/test/js/samples/custom-element-basic/expected.js +++ b/test/js/samples/custom-element-basic/expected.js @@ -77,7 +77,15 @@ class SvelteComponent extends HTMLElement { } customElements.define('custom-element', SvelteComponent); - -assign( SvelteComponent.prototype, proto ); +assign( SvelteComponent.prototype, proto , { + _mount(target, anchor) { + this._fragment.mount(this.shadowRoot, null); + target.insertBefore(this, anchor); + }, + + _unmount() { + this.parentNode.removeChild(this); + } +}); export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/custom-element-slot/expected-bundle.js b/test/js/samples/custom-element-slot/expected-bundle.js index 78011c2cb6bf..8ec67565ce84 100644 --- a/test/js/samples/custom-element-slot/expected-bundle.js +++ b/test/js/samples/custom-element-slot/expected-bundle.js @@ -153,6 +153,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -162,7 +170,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; function create_main_fragment ( state, component ) { @@ -245,7 +255,15 @@ class SvelteComponent extends HTMLElement { } customElements.define('custom-element', SvelteComponent); - -assign( SvelteComponent.prototype, proto ); +assign( SvelteComponent.prototype, proto , { + _mount(target, anchor) { + this._fragment.mount(this.shadowRoot, null); + target.insertBefore(this, anchor); + }, + + _unmount() { + this.parentNode.removeChild(this); + } +}); export default SvelteComponent; diff --git a/test/js/samples/custom-element-slot/expected.js b/test/js/samples/custom-element-slot/expected.js index 8b162fa50d63..c0e2ef136b02 100644 --- a/test/js/samples/custom-element-slot/expected.js +++ b/test/js/samples/custom-element-slot/expected.js @@ -80,7 +80,15 @@ class SvelteComponent extends HTMLElement { } customElements.define('custom-element', SvelteComponent); - -assign( SvelteComponent.prototype, proto ); +assign( SvelteComponent.prototype, proto , { + _mount(target, anchor) { + this._fragment.mount(this.shadowRoot, null); + target.insertBefore(this, anchor); + }, + + _unmount() { + this.parentNode.removeChild(this); + } +}); export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/custom-element-styled/expected-bundle.js b/test/js/samples/custom-element-styled/expected-bundle.js index 5d2d44f38eee..ae41cfcccbf8 100644 --- a/test/js/samples/custom-element-styled/expected-bundle.js +++ b/test/js/samples/custom-element-styled/expected-bundle.js @@ -149,6 +149,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -158,7 +166,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; function create_main_fragment ( state, component ) { @@ -211,7 +221,7 @@ class SvelteComponent extends HTMLElement { this._bind = options._bind; this.attachShadow({ mode: 'open' }); - this.shadowRoot.innerHTML = ''; + this.shadowRoot.innerHTML = ``; this._fragment = create_main_fragment( this._state, this ); @@ -239,7 +249,15 @@ class SvelteComponent extends HTMLElement { } customElements.define('custom-element', SvelteComponent); - -assign( SvelteComponent.prototype, proto ); +assign( SvelteComponent.prototype, proto , { + _mount(target, anchor) { + this._fragment.mount(this.shadowRoot, null); + target.insertBefore(this, anchor); + }, + + _unmount() { + this.parentNode.removeChild(this); + } +}); export default SvelteComponent; diff --git a/test/js/samples/custom-element-styled/expected.js b/test/js/samples/custom-element-styled/expected.js index ef6a614f001e..f0b0e38020dd 100644 --- a/test/js/samples/custom-element-styled/expected.js +++ b/test/js/samples/custom-element-styled/expected.js @@ -50,7 +50,7 @@ class SvelteComponent extends HTMLElement { this._bind = options._bind; this.attachShadow({ mode: 'open' }); - this.shadowRoot.innerHTML = ''; + this.shadowRoot.innerHTML = ``; this._fragment = create_main_fragment( this._state, this ); @@ -78,7 +78,15 @@ class SvelteComponent extends HTMLElement { } customElements.define('custom-element', SvelteComponent); - -assign( SvelteComponent.prototype, proto ); +assign( SvelteComponent.prototype, proto , { + _mount(target, anchor) { + this._fragment.mount(this.shadowRoot, null); + target.insertBefore(this, anchor); + }, + + _unmount() { + this.parentNode.removeChild(this); + } +}); export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/each-block-changed-check/expected-bundle.js b/test/js/samples/each-block-changed-check/expected-bundle.js index 65e244108a1d..9f3048a816ad 100644 --- a/test/js/samples/each-block-changed-check/expected-bundle.js +++ b/test/js/samples/each-block-changed-check/expected-bundle.js @@ -162,6 +162,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -171,7 +179,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; function create_main_fragment ( state, component ) { diff --git a/test/js/samples/event-handlers-custom/expected-bundle.js b/test/js/samples/event-handlers-custom/expected-bundle.js index 3edd91e778d4..472e46c8ac5b 100644 --- a/test/js/samples/event-handlers-custom/expected-bundle.js +++ b/test/js/samples/event-handlers-custom/expected-bundle.js @@ -149,6 +149,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -158,7 +166,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; var template = (function () { diff --git a/test/js/samples/if-block-no-update/expected-bundle.js b/test/js/samples/if-block-no-update/expected-bundle.js index 141fb94d9645..30fcae061a0f 100644 --- a/test/js/samples/if-block-no-update/expected-bundle.js +++ b/test/js/samples/if-block-no-update/expected-bundle.js @@ -153,6 +153,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -162,7 +170,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; function create_main_fragment ( state, component ) { diff --git a/test/js/samples/if-block-simple/expected-bundle.js b/test/js/samples/if-block-simple/expected-bundle.js index 704717031166..9ac38ae5febf 100644 --- a/test/js/samples/if-block-simple/expected-bundle.js +++ b/test/js/samples/if-block-simple/expected-bundle.js @@ -153,6 +153,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -162,7 +170,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; function create_main_fragment ( state, component ) { diff --git a/test/js/samples/legacy-input-type/expected-bundle.js b/test/js/samples/legacy-input-type/expected-bundle.js index 92bd8c1608db..0a2fa851feb1 100644 --- a/test/js/samples/legacy-input-type/expected-bundle.js +++ b/test/js/samples/legacy-input-type/expected-bundle.js @@ -147,6 +147,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -156,7 +164,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; function create_main_fragment ( state, component ) { diff --git a/test/js/samples/non-imported-component/expected-bundle.js b/test/js/samples/non-imported-component/expected-bundle.js index a56d55b6d3a7..7d90b2cf538f 100644 --- a/test/js/samples/non-imported-component/expected-bundle.js +++ b/test/js/samples/non-imported-component/expected-bundle.js @@ -143,6 +143,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -152,7 +160,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; var template = (function () { @@ -182,17 +192,17 @@ function create_main_fragment ( state, component ) { }, mount: function ( target, anchor ) { - imported._fragment.mount( target, anchor ); + imported._mount( target, anchor ); insertNode( text, target, anchor ); - nonimported._fragment.mount( target, anchor ); + nonimported._mount( target, anchor ); }, update: noop, unmount: function () { - imported._fragment.unmount(); + imported._unmount(); detachNode( text ); - nonimported._fragment.unmount(); + nonimported._unmount(); }, destroy: function () { @@ -228,9 +238,7 @@ function SvelteComponent ( options ) { if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); - } - if ( !options._root ) { this._lock = true; callAll(this._beforecreate); callAll(this._oncreate); diff --git a/test/js/samples/non-imported-component/expected.js b/test/js/samples/non-imported-component/expected.js index 1d23ee8f9ec7..3d5c223e7d4a 100644 --- a/test/js/samples/non-imported-component/expected.js +++ b/test/js/samples/non-imported-component/expected.js @@ -29,17 +29,17 @@ function create_main_fragment ( state, component ) { }, mount: function ( target, anchor ) { - imported._fragment.mount( target, anchor ); + imported._mount( target, anchor ); insertNode( text, target, anchor ); - nonimported._fragment.mount( target, anchor ); + nonimported._mount( target, anchor ); }, update: noop, unmount: function () { - imported._fragment.unmount(); + imported._unmount(); detachNode( text ); - nonimported._fragment.unmount(); + nonimported._unmount(); }, destroy: function () { @@ -75,9 +75,7 @@ function SvelteComponent ( options ) { if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); - } - if ( !options._root ) { this._lock = true; callAll(this._beforecreate); callAll(this._oncreate); diff --git a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js index 6d4a4814aa55..400cca8ff5e4 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js @@ -129,6 +129,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -138,7 +146,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; var template = (function () { @@ -193,9 +203,7 @@ function SvelteComponent ( options ) { if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); - } - if ( !options._root ) { callAll(this._oncreate); } } diff --git a/test/js/samples/onrender-onteardown-rewritten/expected.js b/test/js/samples/onrender-onteardown-rewritten/expected.js index d19bed152908..02cef79f50dc 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected.js @@ -52,9 +52,7 @@ function SvelteComponent ( options ) { if ( !options._root ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); - } - if ( !options._root ) { callAll(this._oncreate); } } diff --git a/test/js/samples/setup-method/expected-bundle.js b/test/js/samples/setup-method/expected-bundle.js index 8f162ec7b588..40e4565a9f04 100644 --- a/test/js/samples/setup-method/expected-bundle.js +++ b/test/js/samples/setup-method/expected-bundle.js @@ -129,6 +129,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -138,7 +146,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; var template = (function () { diff --git a/test/js/samples/use-elements-as-anchors/expected-bundle.js b/test/js/samples/use-elements-as-anchors/expected-bundle.js index 35eec6f9658b..d8cf0a2ad3ed 100644 --- a/test/js/samples/use-elements-as-anchors/expected-bundle.js +++ b/test/js/samples/use-elements-as-anchors/expected-bundle.js @@ -153,6 +153,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -162,7 +170,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; function create_main_fragment ( state, component ) { From 2380109f15a02df7b9a4e3587ab6418cb9771e1d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 3 Sep 2017 09:45:10 -0400 Subject: [PATCH 09/10] handly directly instantiated custom elements, and slots --- src/generators/dom/index.ts | 18 +- .../dom/visitors/Element/Element.ts | 10 +- .../expected-bundle.js | 2 +- .../expected.js | 2 +- .../computed-collapsed-if/expected-bundle.js | 2 +- .../samples/computed-collapsed-if/expected.js | 2 +- .../css-media-query/expected-bundle.js | 2 +- test/js/samples/css-media-query/expected.js | 2 +- .../custom-element-basic/expected-bundle.js | 4 +- .../samples/custom-element-basic/expected.js | 4 +- .../custom-element-slot/expected-bundle.js | 13 +- .../samples/custom-element-slot/expected.js | 13 +- .../custom-element-styled/expected-bundle.js | 4 +- .../samples/custom-element-styled/expected.js | 4 +- .../expected-bundle.js | 2 +- .../each-block-changed-check/expected.js | 2 +- .../event-handlers-custom/expected-bundle.js | 2 +- .../samples/event-handlers-custom/expected.js | 2 +- .../if-block-no-update/expected-bundle.js | 2 +- .../js/samples/if-block-no-update/expected.js | 2 +- .../if-block-simple/expected-bundle.js | 2 +- test/js/samples/if-block-simple/expected.js | 2 +- .../expected-bundle.js | 221 +++++++++++++++++ .../expected.js | 64 +++++ .../expected-bundle.js | 216 +++++++++++++++++ .../inline-style-optimized-url/expected.js | 59 +++++ .../inline-style-optimized/expected-bundle.js | 216 +++++++++++++++++ .../inline-style-optimized/expected.js | 59 +++++ .../expected-bundle.js | 227 ++++++++++++++++++ .../inline-style-unoptimized/expected.js | 70 ++++++ .../legacy-input-type/expected-bundle.js | 2 +- test/js/samples/legacy-input-type/expected.js | 2 +- .../non-imported-component/expected-bundle.js | 2 +- .../non-imported-component/expected.js | 2 +- .../expected-bundle.js | 2 +- .../onrender-onteardown-rewritten/expected.js | 2 +- .../samples/setup-method/expected-bundle.js | 2 +- test/js/samples/setup-method/expected.js | 2 +- .../expected-bundle.js | 2 +- .../use-elements-as-anchors/expected.js | 2 +- 40 files changed, 1206 insertions(+), 44 deletions(-) create mode 100644 test/js/samples/inline-style-optimized-multiple/expected-bundle.js create mode 100644 test/js/samples/inline-style-optimized-multiple/expected.js create mode 100644 test/js/samples/inline-style-optimized-url/expected-bundle.js create mode 100644 test/js/samples/inline-style-optimized-url/expected.js create mode 100644 test/js/samples/inline-style-optimized/expected-bundle.js create mode 100644 test/js/samples/inline-style-optimized/expected.js create mode 100644 test/js/samples/inline-style-unoptimized/expected-bundle.js create mode 100644 test/js/samples/inline-style-unoptimized/expected.js diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index f131b840b913..1c810ddeb7b3 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -147,9 +147,6 @@ export default function dom( .join(',\n')} }`; - const target = generator.customElement ? `this.shadowRoot` : `options.target`; - const anchor = generator.customElement ? `null` : `options.anchor || null`; - const constructorBody = deindent` ${options.dev && !generator.customElement && `if ( !options || (!options.target && !options._root) ) throw new Error( "'target' is a required option" );`} @@ -210,7 +207,7 @@ export default function dom( this._fragment = @create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { ${generator.hydratable ? deindent` var nodes = @children( options.target ); @@ -221,7 +218,9 @@ export default function dom( ${options.dev && `if ( options.hydrate ) throw new Error( 'options.hydrate only works if the component was compiled with the \`hydratable: true\` option' );`} this._fragment.create(); `} - this._fragment.${block.hasIntroMethod ? 'intro' : 'mount'}( ${target}, ${anchor} ); + ${generator.customElement ? + `this._mount( options.target, options.anchor || null );` : + `this._fragment.${block.hasIntroMethod ? 'intro' : 'mount'}( options.target, options.anchor || null );`} ${(generator.hasComponents || generator.hasComplexBindings || templateProperties.oncreate || generator.hasIntroTransitions) && deindent` ${generator.hasComponents && `this._lock = true;`} @@ -257,6 +256,13 @@ export default function dom( } `).join('\n\n')} + ${generator.slots.size && deindent` + connectedCallback() { + Object.keys(this._slotted).forEach(key => { + this.appendChild(this._slotted[key]); + }); + }`} + attributeChangedCallback ( attr, oldValue, newValue ) { this.set({ [attr]: newValue }); } @@ -265,7 +271,7 @@ export default function dom( customElements.define('${generator.tag}', ${name}); @assign( ${prototypeBase}, ${proto}, { _mount(target, anchor) { - this._fragment.mount(this.shadowRoot, null); + this._fragment.${block.hasIntroMethod ? 'intro' : 'mount'}(this.shadowRoot, null); target.insertBefore(this, anchor); }, diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index a230b52cd3e3..1b72dd3a85ce 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -8,6 +8,7 @@ import visitEventHandler from './EventHandler'; import visitBinding from './Binding'; import visitRef from './Ref'; import * as namespaces from '../../../../utils/namespaces'; +import getStaticAttributeValue from '../../../shared/getStaticAttributeValue'; import addTransitions from './addTransitions'; import { DomGenerator } from '../../index'; import Block from '../../Block'; @@ -44,8 +45,13 @@ export default function visitElement( return meta[node.name](generator, block, node); } - if (node.name === 'slot' && !generator.customElement) { - return visitSlot(generator, block, state, node, elementStack, componentStack); + if (node.name === 'slot') { + if (generator.customElement) { + const slotName = getStaticAttributeValue(node, 'name') || 'default'; + generator.slots.add(slotName); + } else { + return visitSlot(generator, block, state, node, elementStack, componentStack); + } } if (generator.components.has(node.name) || node.name === ':Self') { diff --git a/test/js/samples/collapses-text-around-comments/expected-bundle.js b/test/js/samples/collapses-text-around-comments/expected-bundle.js index ae148c67ccb7..58ee113019b0 100644 --- a/test/js/samples/collapses-text-around-comments/expected-bundle.js +++ b/test/js/samples/collapses-text-around-comments/expected-bundle.js @@ -246,7 +246,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index deb4d4d30340..88dd6ab34f95 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -71,7 +71,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/computed-collapsed-if/expected-bundle.js b/test/js/samples/computed-collapsed-if/expected-bundle.js index af71c37d15a1..2d23af3ba830 100644 --- a/test/js/samples/computed-collapsed-if/expected-bundle.js +++ b/test/js/samples/computed-collapsed-if/expected-bundle.js @@ -193,7 +193,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/computed-collapsed-if/expected.js b/test/js/samples/computed-collapsed-if/expected.js index 6bb4ebaf9e8d..340622cd28ae 100644 --- a/test/js/samples/computed-collapsed-if/expected.js +++ b/test/js/samples/computed-collapsed-if/expected.js @@ -42,7 +42,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/css-media-query/expected-bundle.js b/test/js/samples/css-media-query/expected-bundle.js index d743041461bb..a62ee67ef96f 100644 --- a/test/js/samples/css-media-query/expected-bundle.js +++ b/test/js/samples/css-media-query/expected-bundle.js @@ -228,7 +228,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/css-media-query/expected.js b/test/js/samples/css-media-query/expected.js index 23839e6c0d02..86167afeb1bb 100644 --- a/test/js/samples/css-media-query/expected.js +++ b/test/js/samples/css-media-query/expected.js @@ -57,7 +57,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/custom-element-basic/expected-bundle.js b/test/js/samples/custom-element-basic/expected-bundle.js index 876bff4ece54..abbc95a50f1f 100644 --- a/test/js/samples/custom-element-basic/expected-bundle.js +++ b/test/js/samples/custom-element-basic/expected-bundle.js @@ -224,9 +224,9 @@ class SvelteComponent extends HTMLElement { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); - this._fragment.mount( this.shadowRoot, null ); + this._mount( options.target, options.anchor || null ); } } diff --git a/test/js/samples/custom-element-basic/expected.js b/test/js/samples/custom-element-basic/expected.js index 82dc7f2bb5d9..71db8c0cc86f 100644 --- a/test/js/samples/custom-element-basic/expected.js +++ b/test/js/samples/custom-element-basic/expected.js @@ -53,9 +53,9 @@ class SvelteComponent extends HTMLElement { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); - this._fragment.mount( this.shadowRoot, null ); + this._mount( options.target, options.anchor || null ); } } diff --git a/test/js/samples/custom-element-slot/expected-bundle.js b/test/js/samples/custom-element-slot/expected-bundle.js index 8ec67565ce84..6a23c19f6225 100644 --- a/test/js/samples/custom-element-slot/expected-bundle.js +++ b/test/js/samples/custom-element-slot/expected-bundle.js @@ -232,14 +232,17 @@ class SvelteComponent extends HTMLElement { this._root = options._root || this; this._yield = options._yield; this._bind = options._bind; + this._slotted = options.slots || {}; this.attachShadow({ mode: 'open' }); + this.slots = {}; + this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); - this._fragment.mount( this.shadowRoot, null ); + this._mount( options.target, options.anchor || null ); } } @@ -249,6 +252,12 @@ class SvelteComponent extends HTMLElement { + connectedCallback() { + Object.keys(this._slotted).forEach(key => { + this.appendChild(this._slotted[key]); + }); + } + attributeChangedCallback ( attr, oldValue, newValue ) { this.set({ [attr]: newValue }); } diff --git a/test/js/samples/custom-element-slot/expected.js b/test/js/samples/custom-element-slot/expected.js index c0e2ef136b02..4e40cc559f6c 100644 --- a/test/js/samples/custom-element-slot/expected.js +++ b/test/js/samples/custom-element-slot/expected.js @@ -57,14 +57,17 @@ class SvelteComponent extends HTMLElement { this._root = options._root || this; this._yield = options._yield; this._bind = options._bind; + this._slotted = options.slots || {}; this.attachShadow({ mode: 'open' }); + this.slots = {}; + this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); - this._fragment.mount( this.shadowRoot, null ); + this._mount( options.target, options.anchor || null ); } } @@ -74,6 +77,12 @@ class SvelteComponent extends HTMLElement { + connectedCallback() { + Object.keys(this._slotted).forEach(key => { + this.appendChild(this._slotted[key]); + }); + } + attributeChangedCallback ( attr, oldValue, newValue ) { this.set({ [attr]: newValue }); } diff --git a/test/js/samples/custom-element-styled/expected-bundle.js b/test/js/samples/custom-element-styled/expected-bundle.js index ae41cfcccbf8..95ce6cdbac42 100644 --- a/test/js/samples/custom-element-styled/expected-bundle.js +++ b/test/js/samples/custom-element-styled/expected-bundle.js @@ -225,9 +225,9 @@ class SvelteComponent extends HTMLElement { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); - this._fragment.mount( this.shadowRoot, null ); + this._mount( options.target, options.anchor || null ); } } diff --git a/test/js/samples/custom-element-styled/expected.js b/test/js/samples/custom-element-styled/expected.js index f0b0e38020dd..e3b4769a034c 100644 --- a/test/js/samples/custom-element-styled/expected.js +++ b/test/js/samples/custom-element-styled/expected.js @@ -54,9 +54,9 @@ class SvelteComponent extends HTMLElement { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); - this._fragment.mount( this.shadowRoot, null ); + this._mount( options.target, options.anchor || null ); } } diff --git a/test/js/samples/each-block-changed-check/expected-bundle.js b/test/js/samples/each-block-changed-check/expected-bundle.js index 9f3048a816ad..b4b5d6f3548b 100644 --- a/test/js/samples/each-block-changed-check/expected-bundle.js +++ b/test/js/samples/each-block-changed-check/expected-bundle.js @@ -338,7 +338,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index c4cde082d85b..7dcdb66696db 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -154,7 +154,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/event-handlers-custom/expected-bundle.js b/test/js/samples/event-handlers-custom/expected-bundle.js index 472e46c8ac5b..7489f51cba5c 100644 --- a/test/js/samples/event-handlers-custom/expected-bundle.js +++ b/test/js/samples/event-handlers-custom/expected-bundle.js @@ -237,7 +237,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/event-handlers-custom/expected.js b/test/js/samples/event-handlers-custom/expected.js index 1af3570e3b7f..e713056e73b8 100644 --- a/test/js/samples/event-handlers-custom/expected.js +++ b/test/js/samples/event-handlers-custom/expected.js @@ -66,7 +66,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/if-block-no-update/expected-bundle.js b/test/js/samples/if-block-no-update/expected-bundle.js index 30fcae061a0f..e390e11fefe5 100644 --- a/test/js/samples/if-block-no-update/expected-bundle.js +++ b/test/js/samples/if-block-no-update/expected-bundle.js @@ -279,7 +279,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/if-block-no-update/expected.js b/test/js/samples/if-block-no-update/expected.js index f36c614c9587..ca082058f4be 100644 --- a/test/js/samples/if-block-no-update/expected.js +++ b/test/js/samples/if-block-no-update/expected.js @@ -104,7 +104,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/if-block-simple/expected-bundle.js b/test/js/samples/if-block-simple/expected-bundle.js index 9ac38ae5febf..efca4b86738d 100644 --- a/test/js/samples/if-block-simple/expected-bundle.js +++ b/test/js/samples/if-block-simple/expected-bundle.js @@ -255,7 +255,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/if-block-simple/expected.js b/test/js/samples/if-block-simple/expected.js index eaf04155020d..1eedecd1477a 100644 --- a/test/js/samples/if-block-simple/expected.js +++ b/test/js/samples/if-block-simple/expected.js @@ -80,7 +80,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/inline-style-optimized-multiple/expected-bundle.js b/test/js/samples/inline-style-optimized-multiple/expected-bundle.js new file mode 100644 index 000000000000..c9f7b3729a21 --- /dev/null +++ b/test/js/samples/inline-style-optimized-multiple/expected-bundle.js @@ -0,0 +1,221 @@ +function noop() {} + +function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { + source = arguments[i]; + for (k in source) target[k] = source[k]; + } + + return target; +} + +function insertNode(node, target, anchor) { + target.insertBefore(node, anchor); +} + +function detachNode(node) { + node.parentNode.removeChild(node); +} + +function createElement(name) { + return document.createElement(name); +} + +function setStyle(node, key, value) { + node.style.setProperty(key, value); +} + +function destroy(detach) { + this.destroy = noop; + this.fire('destroy'); + this.set = this.get = noop; + + if (detach !== false) this._fragment.unmount(); + this._fragment.destroy(); + this._fragment = this._state = null; +} + +function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); +} + +function dispatchObservers(component, group, changed, newState, oldState) { + for (var key in group) { + if (!changed[key]) continue; + + var newValue = newState[key]; + var oldValue = oldState[key]; + + var callbacks = group[key]; + if (!callbacks) continue; + + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; + + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; + } + } +} + +function get(key) { + return key ? this._state[key] : this._state; +} + +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; + + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); + } +} + +function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; + + (group[key] || (group[key] = [])).push(callback); + + if (!options || options.init !== false) { + callback.__calling = true; + callback.call(this, this._state[key]); + callback.__calling = false; + } + + return { + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); + } + }; +} + +function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); + + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); + + return { + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); + } + }; +} + +function set(newState) { + this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); + callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; +} + +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + +function callAll(fns) { + while (fns && fns.length) fns.pop()(); +} + +var proto = { + destroy: destroy, + get: get, + fire: fire, + observe: observe, + on: on, + set: set, + teardown: destroy, + _recompute: noop, + _set: _set +}; + +function create_main_fragment ( state, component ) { + var div; + + return { + create: function () { + div = createElement( 'div' ); + this.hydrate(); + }, + + hydrate: function ( nodes ) { + setStyle(div, 'color', state.color); + setStyle(div, 'transform', "translate(" + state.x + "px," + state.y + "px)"); + }, + + mount: function ( target, anchor ) { + insertNode( div, target, anchor ); + }, + + update: function ( changed, state ) { + if ( changed.color ) { + setStyle(div, 'color', state.color); + } + + if ( changed.x || changed.y ) { + setStyle(div, 'transform', "translate(" + state.x + "px," + state.y + "px)"); + } + }, + + unmount: function () { + detachNode( div ); + }, + + destroy: noop + }; +} + +function SvelteComponent ( options ) { + this.options = options; + this._state = options.data || {}; + + this._observers = { + pre: Object.create( null ), + post: Object.create( null ) + }; + + this._handlers = Object.create( null ); + + this._root = options._root || this; + this._yield = options._yield; + this._bind = options._bind; + + this._fragment = create_main_fragment( this._state, this ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, options.anchor || null ); + } +} + +assign( SvelteComponent.prototype, proto ); + +export default SvelteComponent; diff --git a/test/js/samples/inline-style-optimized-multiple/expected.js b/test/js/samples/inline-style-optimized-multiple/expected.js new file mode 100644 index 000000000000..3d48ec09a80a --- /dev/null +++ b/test/js/samples/inline-style-optimized-multiple/expected.js @@ -0,0 +1,64 @@ +import { assign, createElement, detachNode, insertNode, noop, proto, setStyle } from "svelte/shared.js"; + +function create_main_fragment ( state, component ) { + var div; + + return { + create: function () { + div = createElement( 'div' ); + this.hydrate(); + }, + + hydrate: function ( nodes ) { + setStyle(div, 'color', state.color); + setStyle(div, 'transform', "translate(" + state.x + "px," + state.y + "px)"); + }, + + mount: function ( target, anchor ) { + insertNode( div, target, anchor ); + }, + + update: function ( changed, state ) { + if ( changed.color ) { + setStyle(div, 'color', state.color); + } + + if ( changed.x || changed.y ) { + setStyle(div, 'transform', "translate(" + state.x + "px," + state.y + "px)"); + } + }, + + unmount: function () { + detachNode( div ); + }, + + destroy: noop + }; +} + +function SvelteComponent ( options ) { + this.options = options; + this._state = options.data || {}; + + this._observers = { + pre: Object.create( null ), + post: Object.create( null ) + }; + + this._handlers = Object.create( null ); + + this._root = options._root || this; + this._yield = options._yield; + this._bind = options._bind; + + this._fragment = create_main_fragment( this._state, this ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, options.anchor || null ); + } +} + +assign( SvelteComponent.prototype, proto ); + +export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/inline-style-optimized-url/expected-bundle.js b/test/js/samples/inline-style-optimized-url/expected-bundle.js new file mode 100644 index 000000000000..e3793b4fe1d7 --- /dev/null +++ b/test/js/samples/inline-style-optimized-url/expected-bundle.js @@ -0,0 +1,216 @@ +function noop() {} + +function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { + source = arguments[i]; + for (k in source) target[k] = source[k]; + } + + return target; +} + +function insertNode(node, target, anchor) { + target.insertBefore(node, anchor); +} + +function detachNode(node) { + node.parentNode.removeChild(node); +} + +function createElement(name) { + return document.createElement(name); +} + +function setStyle(node, key, value) { + node.style.setProperty(key, value); +} + +function destroy(detach) { + this.destroy = noop; + this.fire('destroy'); + this.set = this.get = noop; + + if (detach !== false) this._fragment.unmount(); + this._fragment.destroy(); + this._fragment = this._state = null; +} + +function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); +} + +function dispatchObservers(component, group, changed, newState, oldState) { + for (var key in group) { + if (!changed[key]) continue; + + var newValue = newState[key]; + var oldValue = oldState[key]; + + var callbacks = group[key]; + if (!callbacks) continue; + + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; + + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; + } + } +} + +function get(key) { + return key ? this._state[key] : this._state; +} + +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; + + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); + } +} + +function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; + + (group[key] || (group[key] = [])).push(callback); + + if (!options || options.init !== false) { + callback.__calling = true; + callback.call(this, this._state[key]); + callback.__calling = false; + } + + return { + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); + } + }; +} + +function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); + + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); + + return { + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); + } + }; +} + +function set(newState) { + this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); + callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; +} + +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + +function callAll(fns) { + while (fns && fns.length) fns.pop()(); +} + +var proto = { + destroy: destroy, + get: get, + fire: fire, + observe: observe, + on: on, + set: set, + teardown: destroy, + _recompute: noop, + _set: _set +}; + +function create_main_fragment ( state, component ) { + var div; + + return { + create: function () { + div = createElement( 'div' ); + this.hydrate(); + }, + + hydrate: function ( nodes ) { + setStyle(div, 'background', "url(data:image/png;base64," + state.data + ")"); + }, + + mount: function ( target, anchor ) { + insertNode( div, target, anchor ); + }, + + update: function ( changed, state ) { + if ( changed.data ) { + setStyle(div, 'background', "url(data:image/png;base64," + state.data + ")"); + } + }, + + unmount: function () { + detachNode( div ); + }, + + destroy: noop + }; +} + +function SvelteComponent ( options ) { + this.options = options; + this._state = options.data || {}; + + this._observers = { + pre: Object.create( null ), + post: Object.create( null ) + }; + + this._handlers = Object.create( null ); + + this._root = options._root || this; + this._yield = options._yield; + this._bind = options._bind; + + this._fragment = create_main_fragment( this._state, this ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, options.anchor || null ); + } +} + +assign( SvelteComponent.prototype, proto ); + +export default SvelteComponent; diff --git a/test/js/samples/inline-style-optimized-url/expected.js b/test/js/samples/inline-style-optimized-url/expected.js new file mode 100644 index 000000000000..b02e794bb859 --- /dev/null +++ b/test/js/samples/inline-style-optimized-url/expected.js @@ -0,0 +1,59 @@ +import { assign, createElement, detachNode, insertNode, noop, proto, setStyle } from "svelte/shared.js"; + +function create_main_fragment ( state, component ) { + var div; + + return { + create: function () { + div = createElement( 'div' ); + this.hydrate(); + }, + + hydrate: function ( nodes ) { + setStyle(div, 'background', "url(data:image/png;base64," + state.data + ")"); + }, + + mount: function ( target, anchor ) { + insertNode( div, target, anchor ); + }, + + update: function ( changed, state ) { + if ( changed.data ) { + setStyle(div, 'background', "url(data:image/png;base64," + state.data + ")"); + } + }, + + unmount: function () { + detachNode( div ); + }, + + destroy: noop + }; +} + +function SvelteComponent ( options ) { + this.options = options; + this._state = options.data || {}; + + this._observers = { + pre: Object.create( null ), + post: Object.create( null ) + }; + + this._handlers = Object.create( null ); + + this._root = options._root || this; + this._yield = options._yield; + this._bind = options._bind; + + this._fragment = create_main_fragment( this._state, this ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, options.anchor || null ); + } +} + +assign( SvelteComponent.prototype, proto ); + +export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/inline-style-optimized/expected-bundle.js b/test/js/samples/inline-style-optimized/expected-bundle.js new file mode 100644 index 000000000000..98713a9e1b0c --- /dev/null +++ b/test/js/samples/inline-style-optimized/expected-bundle.js @@ -0,0 +1,216 @@ +function noop() {} + +function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { + source = arguments[i]; + for (k in source) target[k] = source[k]; + } + + return target; +} + +function insertNode(node, target, anchor) { + target.insertBefore(node, anchor); +} + +function detachNode(node) { + node.parentNode.removeChild(node); +} + +function createElement(name) { + return document.createElement(name); +} + +function setStyle(node, key, value) { + node.style.setProperty(key, value); +} + +function destroy(detach) { + this.destroy = noop; + this.fire('destroy'); + this.set = this.get = noop; + + if (detach !== false) this._fragment.unmount(); + this._fragment.destroy(); + this._fragment = this._state = null; +} + +function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); +} + +function dispatchObservers(component, group, changed, newState, oldState) { + for (var key in group) { + if (!changed[key]) continue; + + var newValue = newState[key]; + var oldValue = oldState[key]; + + var callbacks = group[key]; + if (!callbacks) continue; + + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; + + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; + } + } +} + +function get(key) { + return key ? this._state[key] : this._state; +} + +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; + + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); + } +} + +function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; + + (group[key] || (group[key] = [])).push(callback); + + if (!options || options.init !== false) { + callback.__calling = true; + callback.call(this, this._state[key]); + callback.__calling = false; + } + + return { + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); + } + }; +} + +function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); + + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); + + return { + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); + } + }; +} + +function set(newState) { + this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); + callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; +} + +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + +function callAll(fns) { + while (fns && fns.length) fns.pop()(); +} + +var proto = { + destroy: destroy, + get: get, + fire: fire, + observe: observe, + on: on, + set: set, + teardown: destroy, + _recompute: noop, + _set: _set +}; + +function create_main_fragment ( state, component ) { + var div; + + return { + create: function () { + div = createElement( 'div' ); + this.hydrate(); + }, + + hydrate: function ( nodes ) { + setStyle(div, 'color', state.color); + }, + + mount: function ( target, anchor ) { + insertNode( div, target, anchor ); + }, + + update: function ( changed, state ) { + if ( changed.color ) { + setStyle(div, 'color', state.color); + } + }, + + unmount: function () { + detachNode( div ); + }, + + destroy: noop + }; +} + +function SvelteComponent ( options ) { + this.options = options; + this._state = options.data || {}; + + this._observers = { + pre: Object.create( null ), + post: Object.create( null ) + }; + + this._handlers = Object.create( null ); + + this._root = options._root || this; + this._yield = options._yield; + this._bind = options._bind; + + this._fragment = create_main_fragment( this._state, this ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, options.anchor || null ); + } +} + +assign( SvelteComponent.prototype, proto ); + +export default SvelteComponent; diff --git a/test/js/samples/inline-style-optimized/expected.js b/test/js/samples/inline-style-optimized/expected.js new file mode 100644 index 000000000000..2de1d2b0846a --- /dev/null +++ b/test/js/samples/inline-style-optimized/expected.js @@ -0,0 +1,59 @@ +import { assign, createElement, detachNode, insertNode, noop, proto, setStyle } from "svelte/shared.js"; + +function create_main_fragment ( state, component ) { + var div; + + return { + create: function () { + div = createElement( 'div' ); + this.hydrate(); + }, + + hydrate: function ( nodes ) { + setStyle(div, 'color', state.color); + }, + + mount: function ( target, anchor ) { + insertNode( div, target, anchor ); + }, + + update: function ( changed, state ) { + if ( changed.color ) { + setStyle(div, 'color', state.color); + } + }, + + unmount: function () { + detachNode( div ); + }, + + destroy: noop + }; +} + +function SvelteComponent ( options ) { + this.options = options; + this._state = options.data || {}; + + this._observers = { + pre: Object.create( null ), + post: Object.create( null ) + }; + + this._handlers = Object.create( null ); + + this._root = options._root || this; + this._yield = options._yield; + this._bind = options._bind; + + this._fragment = create_main_fragment( this._state, this ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, options.anchor || null ); + } +} + +assign( SvelteComponent.prototype, proto ); + +export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/inline-style-unoptimized/expected-bundle.js b/test/js/samples/inline-style-unoptimized/expected-bundle.js new file mode 100644 index 000000000000..f463764157dc --- /dev/null +++ b/test/js/samples/inline-style-unoptimized/expected-bundle.js @@ -0,0 +1,227 @@ +function noop() {} + +function assign(target) { + var k, + source, + i = 1, + len = arguments.length; + for (; i < len; i++) { + source = arguments[i]; + for (k in source) target[k] = source[k]; + } + + return target; +} + +function insertNode(node, target, anchor) { + target.insertBefore(node, anchor); +} + +function detachNode(node) { + node.parentNode.removeChild(node); +} + +function createElement(name) { + return document.createElement(name); +} + +function createText(data) { + return document.createTextNode(data); +} + +function destroy(detach) { + this.destroy = noop; + this.fire('destroy'); + this.set = this.get = noop; + + if (detach !== false) this._fragment.unmount(); + this._fragment.destroy(); + this._fragment = this._state = null; +} + +function differs(a, b) { + return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); +} + +function dispatchObservers(component, group, changed, newState, oldState) { + for (var key in group) { + if (!changed[key]) continue; + + var newValue = newState[key]; + var oldValue = oldState[key]; + + var callbacks = group[key]; + if (!callbacks) continue; + + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; + + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; + } + } +} + +function get(key) { + return key ? this._state[key] : this._state; +} + +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; + + for (var i = 0; i < handlers.length; i += 1) { + handlers[i].call(this, data); + } +} + +function observe(key, callback, options) { + var group = options && options.defer + ? this._observers.post + : this._observers.pre; + + (group[key] || (group[key] = [])).push(callback); + + if (!options || options.init !== false) { + callback.__calling = true; + callback.call(this, this._state[key]); + callback.__calling = false; + } + + return { + cancel: function() { + var index = group[key].indexOf(callback); + if (~index) group[key].splice(index, 1); + } + }; +} + +function on(eventName, handler) { + if (eventName === 'teardown') return this.on('destroy', handler); + + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); + + return { + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); + } + }; +} + +function set(newState) { + this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); + callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; +} + +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + +function callAll(fns) { + while (fns && fns.length) fns.pop()(); +} + +var proto = { + destroy: destroy, + get: get, + fire: fire, + observe: observe, + on: on, + set: set, + teardown: destroy, + _recompute: noop, + _set: _set +}; + +function create_main_fragment ( state, component ) { + var div, text, div_1, div_1_style_value; + + return { + create: function () { + div = createElement( 'div' ); + text = createText( "\n" ); + div_1 = createElement( 'div' ); + this.hydrate(); + }, + + hydrate: function ( nodes ) { + div.style.cssText = state.style; + div_1.style.cssText = div_1_style_value = "" + ( state.key ) + ": " + ( state.value ); + }, + + mount: function ( target, anchor ) { + insertNode( div, target, anchor ); + insertNode( text, target, anchor ); + insertNode( div_1, target, anchor ); + }, + + update: function ( changed, state ) { + if ( changed.style ) { + div.style.cssText = state.style; + } + + if ( ( changed.key || changed.value ) && div_1_style_value !== ( div_1_style_value = "" + ( state.key ) + ": " + ( state.value ) ) ) { + div_1.style.cssText = div_1_style_value; + } + }, + + unmount: function () { + detachNode( div ); + detachNode( text ); + detachNode( div_1 ); + }, + + destroy: noop + }; +} + +function SvelteComponent ( options ) { + this.options = options; + this._state = options.data || {}; + + this._observers = { + pre: Object.create( null ), + post: Object.create( null ) + }; + + this._handlers = Object.create( null ); + + this._root = options._root || this; + this._yield = options._yield; + this._bind = options._bind; + + this._fragment = create_main_fragment( this._state, this ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, options.anchor || null ); + } +} + +assign( SvelteComponent.prototype, proto ); + +export default SvelteComponent; diff --git a/test/js/samples/inline-style-unoptimized/expected.js b/test/js/samples/inline-style-unoptimized/expected.js new file mode 100644 index 000000000000..74f9ddca2a34 --- /dev/null +++ b/test/js/samples/inline-style-unoptimized/expected.js @@ -0,0 +1,70 @@ +import { assign, createElement, createText, detachNode, insertNode, noop, proto } from "svelte/shared.js"; + +function create_main_fragment ( state, component ) { + var div, text, div_1, div_1_style_value; + + return { + create: function () { + div = createElement( 'div' ); + text = createText( "\n" ); + div_1 = createElement( 'div' ); + this.hydrate(); + }, + + hydrate: function ( nodes ) { + div.style.cssText = state.style; + div_1.style.cssText = div_1_style_value = "" + ( state.key ) + ": " + ( state.value ); + }, + + mount: function ( target, anchor ) { + insertNode( div, target, anchor ); + insertNode( text, target, anchor ); + insertNode( div_1, target, anchor ); + }, + + update: function ( changed, state ) { + if ( changed.style ) { + div.style.cssText = state.style; + } + + if ( ( changed.key || changed.value ) && div_1_style_value !== ( div_1_style_value = "" + ( state.key ) + ": " + ( state.value ) ) ) { + div_1.style.cssText = div_1_style_value; + } + }, + + unmount: function () { + detachNode( div ); + detachNode( text ); + detachNode( div_1 ); + }, + + destroy: noop + }; +} + +function SvelteComponent ( options ) { + this.options = options; + this._state = options.data || {}; + + this._observers = { + pre: Object.create( null ), + post: Object.create( null ) + }; + + this._handlers = Object.create( null ); + + this._root = options._root || this; + this._yield = options._yield; + this._bind = options._bind; + + this._fragment = create_main_fragment( this._state, this ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, options.anchor || null ); + } +} + +assign( SvelteComponent.prototype, proto ); + +export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/legacy-input-type/expected-bundle.js b/test/js/samples/legacy-input-type/expected-bundle.js index 0a2fa851feb1..189bfddb89bc 100644 --- a/test/js/samples/legacy-input-type/expected-bundle.js +++ b/test/js/samples/legacy-input-type/expected-bundle.js @@ -213,7 +213,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/legacy-input-type/expected.js b/test/js/samples/legacy-input-type/expected.js index 46277e3c98cb..33a277bb4a8b 100644 --- a/test/js/samples/legacy-input-type/expected.js +++ b/test/js/samples/legacy-input-type/expected.js @@ -44,7 +44,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/non-imported-component/expected-bundle.js b/test/js/samples/non-imported-component/expected-bundle.js index 7d90b2cf538f..1bdf45a773d2 100644 --- a/test/js/samples/non-imported-component/expected-bundle.js +++ b/test/js/samples/non-imported-component/expected-bundle.js @@ -235,7 +235,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); diff --git a/test/js/samples/non-imported-component/expected.js b/test/js/samples/non-imported-component/expected.js index 3d5c223e7d4a..71d4e0b35573 100644 --- a/test/js/samples/non-imported-component/expected.js +++ b/test/js/samples/non-imported-component/expected.js @@ -72,7 +72,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); diff --git a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js index 400cca8ff5e4..4002cb83948e 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js @@ -200,7 +200,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); diff --git a/test/js/samples/onrender-onteardown-rewritten/expected.js b/test/js/samples/onrender-onteardown-rewritten/expected.js index 02cef79f50dc..a50372f91b88 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected.js @@ -49,7 +49,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); diff --git a/test/js/samples/setup-method/expected-bundle.js b/test/js/samples/setup-method/expected-bundle.js index 40e4565a9f04..4c3b5db7aaf0 100644 --- a/test/js/samples/setup-method/expected-bundle.js +++ b/test/js/samples/setup-method/expected-bundle.js @@ -202,7 +202,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/setup-method/expected.js b/test/js/samples/setup-method/expected.js index 6b554eb78107..9e33fb392148 100644 --- a/test/js/samples/setup-method/expected.js +++ b/test/js/samples/setup-method/expected.js @@ -51,7 +51,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/use-elements-as-anchors/expected-bundle.js b/test/js/samples/use-elements-as-anchors/expected-bundle.js index d8cf0a2ad3ed..c93fbe1a4731 100644 --- a/test/js/samples/use-elements-as-anchors/expected-bundle.js +++ b/test/js/samples/use-elements-as-anchors/expected-bundle.js @@ -439,7 +439,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } diff --git a/test/js/samples/use-elements-as-anchors/expected.js b/test/js/samples/use-elements-as-anchors/expected.js index 7bc5165be00b..f65d1fe2881b 100644 --- a/test/js/samples/use-elements-as-anchors/expected.js +++ b/test/js/samples/use-elements-as-anchors/expected.js @@ -264,7 +264,7 @@ function SvelteComponent ( options ) { this._fragment = create_main_fragment( this._state, this ); - if ( !options._root ) { + if ( options.target ) { this._fragment.create(); this._fragment.mount( options.target, options.anchor || null ); } From 81a04ad86c1a345e7d1009a8702504d4127f7d21 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 3 Sep 2017 10:02:41 -0400 Subject: [PATCH 10/10] merge master -> gh-797 --- .../expected-bundle.js | 12 +++++++++++- .../inline-style-optimized-url/expected-bundle.js | 12 +++++++++++- .../inline-style-optimized/expected-bundle.js | 12 +++++++++++- .../inline-style-unoptimized/expected-bundle.js | 12 +++++++++++- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/test/js/samples/inline-style-optimized-multiple/expected-bundle.js b/test/js/samples/inline-style-optimized-multiple/expected-bundle.js index c9f7b3729a21..4f9f0c5949ee 100644 --- a/test/js/samples/inline-style-optimized-multiple/expected-bundle.js +++ b/test/js/samples/inline-style-optimized-multiple/expected-bundle.js @@ -145,6 +145,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -154,7 +162,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; function create_main_fragment ( state, component ) { diff --git a/test/js/samples/inline-style-optimized-url/expected-bundle.js b/test/js/samples/inline-style-optimized-url/expected-bundle.js index e3793b4fe1d7..3b55e1678f56 100644 --- a/test/js/samples/inline-style-optimized-url/expected-bundle.js +++ b/test/js/samples/inline-style-optimized-url/expected-bundle.js @@ -145,6 +145,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -154,7 +162,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; function create_main_fragment ( state, component ) { diff --git a/test/js/samples/inline-style-optimized/expected-bundle.js b/test/js/samples/inline-style-optimized/expected-bundle.js index 98713a9e1b0c..0c5ce75b2a20 100644 --- a/test/js/samples/inline-style-optimized/expected-bundle.js +++ b/test/js/samples/inline-style-optimized/expected-bundle.js @@ -145,6 +145,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -154,7 +162,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; function create_main_fragment ( state, component ) { diff --git a/test/js/samples/inline-style-unoptimized/expected-bundle.js b/test/js/samples/inline-style-unoptimized/expected-bundle.js index f463764157dc..4848e382a34c 100644 --- a/test/js/samples/inline-style-unoptimized/expected-bundle.js +++ b/test/js/samples/inline-style-unoptimized/expected-bundle.js @@ -145,6 +145,14 @@ function callAll(fns) { while (fns && fns.length) fns.pop()(); } +function _mount(target, anchor) { + this._fragment.mount(target, anchor); +} + +function _unmount() { + this._fragment.unmount(); +} + var proto = { destroy: destroy, get: get, @@ -154,7 +162,9 @@ var proto = { set: set, teardown: destroy, _recompute: noop, - _set: _set + _set: _set, + _mount: _mount, + _unmount: _unmount }; function create_main_fragment ( state, component ) {