custom elements sort of working

pull/1864/head
Rich Harris 7 years ago
parent ae3a7dd369
commit 5030385b1b

@ -133,7 +133,7 @@ export default class Component {
this.walk_module_js(); this.walk_module_js();
this.walk_instance_js(); this.walk_instance_js();
this.name = this.alias(name); this.name = this.getUniqueName(name);
this.meta = process_meta(this, this.ast.html.children); this.meta = process_meta(this, this.ast.html.children);
this.namespace = namespaces[this.meta.namespace] || this.meta.namespace; this.namespace = namespaces[this.meta.namespace] || this.meta.namespace;
@ -209,7 +209,7 @@ export default class Component {
}); });
const importedHelpers = Array.from(helpers) const importedHelpers = Array.from(helpers)
.concat(options.dev ? '$$ComponentDev' : '$$Component') .concat(options.dev ? 'SvelteComponentDev' : 'SvelteComponent')
.sort() .sort()
.map(name => { .map(name => {
const alias = this.alias(name); const alias = this.alias(name);

@ -58,38 +58,9 @@ export default function dom(
const expectedProperties = Array.from(component.expectedProperties); const expectedProperties = Array.from(component.expectedProperties);
const globals = expectedProperties.filter(prop => globalWhitelist.has(prop)); const globals = expectedProperties.filter(prop => globalWhitelist.has(prop));
if (component.customElement) {
// TODO use `export` to determine this
const props = Array.from(component.expectedProperties);
builder.addBlock(deindent`
class ${name} extends HTMLElement {
constructor(options = {}) {
super();
}
static get observedAttributes() {
return ${JSON.stringify(props)};
}
${renderer.slots.size && deindent`
connectedCallback() {
Object.keys(this.$$.slotted).forEach(key => {
this.appendChild(this.$$.slotted[key]);
});
}`}
attributeChangedCallback(attr, oldValue, newValue) {
this[attr] = newValue;
}
}
customElements.define("${component.customElement.tag}", ${name});
`);
} else {
const refs = Array.from(component.refs); const refs = Array.from(component.refs);
const superclass = component.alias(options.dev ? '$$ComponentDev' : '$$Component'); const superclass = component.alias(options.dev ? 'SvelteComponentDev' : 'SvelteComponent');
if (options.dev && !options.hydratable) { if (options.dev && !options.hydratable) {
block.builders.claim.addLine( block.builders.claim.addLine(
@ -135,25 +106,6 @@ export default function dom(
const not_equal = component.options.immutable ? `@not_equal` : `@safe_not_equal`; const not_equal = component.options.immutable ? `@not_equal` : `@safe_not_equal`;
let dev_props_check; let dev_props_check;
if (component.options.dev) {
// TODO check no uunexpected props were passed, as well as
// checking that expected ones were passed
const expected = component.exports
.map(x => x.name)
.filter(name => !component.initialised_declarations.has(name));
if (expected.length) {
dev_props_check = deindent`
const state = this.$$.get();
${expected.map(name => deindent`
if (state.${name} === undefined) {
console.warn("${debug_name} was created without expected data property '${name}'");
}`)}
`;
}
}
component.exports.forEach(x => { component.exports.forEach(x => {
body.push(deindent` body.push(deindent`
get ${x.as}() { get ${x.as}() {
@ -177,6 +129,25 @@ export default function dom(
} }
}); });
if (component.options.dev) {
// TODO check no uunexpected props were passed, as well as
// checking that expected ones were passed
const expected = component.exports
.map(x => x.name)
.filter(name => !component.initialised_declarations.has(name));
if (expected.length) {
dev_props_check = deindent`
const state = this.$$.get();
${expected.map(name => deindent`
if (state.${name} === undefined) {
console.warn("${debug_name} was created without expected data property '${name}'");
}`)}
`;
}
}
builder.addBlock(deindent` builder.addBlock(deindent`
function create_fragment(${component.alias('component')}, ctx) { function create_fragment(${component.alias('component')}, ctx) {
${block.getContents()} ${block.getContents()}
@ -201,7 +172,29 @@ export default function dom(
${inject_refs && `$$self.$$.inject_refs = ${inject_refs};`} ${inject_refs && `$$self.$$.inject_refs = ${inject_refs};`}
} }
`);
if (component.customElement) {
// TODO observedAttributes
builder.addBlock(deindent`
class ${name} extends @SvelteElement {
constructor() {
super();
@init(this, { target: this.shadowRoot }, define, create_fragment, ${not_equal});
}
static get observedAttributes() {
return [];
}
${body.join('\n\n')}
}
customElements.define("${component.customElement.tag}", ${name});
`);
} else {
builder.addBlock(deindent`
class ${name} extends ${superclass} { class ${name} extends ${superclass} {
constructor(options) { constructor(options) {
super(${options.dev && `options`}); super(${options.dev && `options`});

@ -110,7 +110,53 @@ export function init(component, options, define, create_fragment, not_equal) {
set_current_component(previous_component); set_current_component(previous_component);
} }
export class $$Component { export let SvelteElement;
if (typeof HTMLElement !== 'undefined') {
SvelteElement = class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
for (let key in this.$$.slotted) {
this.appendChild(this.$$.slotted[key]);
}
}
attributeChangedCallback(attr, oldValue, newValue) {
this[attr] = newValue;
}
$destroy() {
destroy(this, true);
this.$destroy = noop;
}
$on(type, callback) {
// TODO should this delegate to addEventListener?
const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));
callbacks.push(callback);
return () => {
const index = callbacks.indexOf(callback);
if (index !== -1) callbacks.splice(index, 1);
};
}
$set(values) {
if (this.$$) {
const state = this.$$.get();
this.$$.set(values);
for (const key in values) {
if (this.$$.not_equal(state[key], values[key])) make_dirty(this, key);
}
}
}
}
}
export class SvelteComponent {
$destroy() { $destroy() {
destroy(this, true); destroy(this, true);
this.$destroy = noop; this.$destroy = noop;
@ -137,7 +183,7 @@ export class $$Component {
} }
} }
export class $$ComponentDev extends $$Component { export class SvelteComponentDev extends SvelteComponent {
constructor(options) { constructor(options) {
if (!options || (!options.target && !options.$$inline)) { if (!options || (!options.target && !options.$$inline)) {
throw new Error(`'target' is a required option`); throw new Error(`'target' is a required option`);

@ -15,7 +15,7 @@ const page = `
const assert = fs.readFileSync('test/custom-elements/assert.js', 'utf-8'); const assert = fs.readFileSync('test/custom-elements/assert.js', 'utf-8');
describe('custom-elements', function() { describe.only('custom-elements', function() {
this.timeout(10000); this.timeout(10000);
let svelte; let svelte;
@ -104,12 +104,13 @@ describe('custom-elements', function() {
}) })
.then(result => { .then(result => {
if (result) console.log(result); if (result) console.log(result);
nightmare.end();
}) })
.catch(message => { .catch(message => {
console.log(addLineNumbers(bundle)); console.log(addLineNumbers(bundle));
nightmare.end();
throw new Error(message); throw new Error(message);
}) });
.then(() => nightmare.end());
}); });

@ -1,11 +1,11 @@
import * as assert from 'assert'; import * as assert from 'assert';
import './main.html'; import './main.html';
export default function (target) { export default async function (target) {
target.innerHTML = '<custom-element name="world"></custom-element>'; target.innerHTML = '<custom-element></custom-element>';
const el = target.querySelector('custom-element'); const el = target.querySelector('custom-element');
el.updateFoo(42); await el.updateFoo(42);
const p = el.shadowRoot.querySelector('p'); const p = el.shadowRoot.querySelector('p');
assert.equal(p.textContent, '42'); assert.equal(p.textContent, '42');

@ -25,7 +25,7 @@ function getName(filename) {
return base[0].toUpperCase() + base.slice(1); return base[0].toUpperCase() + base.slice(1);
} }
describe.only("runtime", () => { describe("runtime", () => {
before(() => { before(() => {
svelte = loadSvelte(false); svelte = loadSvelte(false);
svelte$ = loadSvelte(true); svelte$ = loadSvelte(true);

Loading…
Cancel
Save