diff --git a/src/compile/Component.ts b/src/compile/Component.ts index ed030a055e..a47063242b 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -506,29 +506,134 @@ export default class Component { this.addSourcemapLocations(script.content); - let { scope, map, globals } = createScopes(script.content); - this.instance_scope = scope; + let { scope: instance_scope, map, globals } = createScopes(script.content); + this.instance_scope = instance_scope; this.instance_scope_map = map; - scope.declarations.forEach((node, name) => { + instance_scope.declarations.forEach((node, name) => { this.userVars.add(name); this.declarations.push(name); this.node_for_declaration.set(name, node); }); - this.writable_declarations = scope.writable_declarations; - this.initialised_declarations = scope.initialised_declarations; + this.writable_declarations = instance_scope.writable_declarations; + this.initialised_declarations = instance_scope.initialised_declarations; globals.forEach(name => { this.userVars.add(name); }); this.extract_imports_and_exports(script.content, this.imports, this.props); - + this.rewrite_props(); this.javascript = this.extract_javascript(script); } + rewrite_props() { + const { instance_scope, instance_scope_map: map } = this; + let scope = instance_scope; + + // TODO we will probably end up wanting to use this elsewhere + const exported = new Set(); + this.props.forEach(prop => { + exported.add(prop.name); + }); + + const coalesced_declarations = []; + let current_group; + + walk(this.instance_script.content, { + enter(node) { + if (/Function/.test(node.type)) { + current_group = null; + return this.skip(); + } + + if (map.has(node)) { + scope = map.get(node); + } + + if (node.type === 'VariableDeclaration') { + if (node.kind === 'var' || scope === instance_scope) { + let has_exports = false; + let has_only_exports = true; + + node.declarations.forEach(declarator => { + extractNames(declarator.id).forEach(name => { + if (exported.has(name)) { + has_exports = true; + } else { + has_only_exports = false; + } + }); + }); + + if (has_only_exports) { + if (current_group && current_group[current_group.length - 1].kind !== node.kind) { + current_group = null; + } + + // rewrite as a group, later + if (!current_group) { + current_group = []; + coalesced_declarations.push(current_group); + } + + current_group.push(node); + } else { + if (has_exports) { + // rewrite in place + throw new Error('TODO rewrite prop declaration in place'); + } + + current_group = null; + } + } + } else { + current_group = null; + } + }, + + leave(node) { + if (map.has(node)) { + scope = scope.parent; + } + } + }); + + coalesced_declarations.forEach(group => { + const kind = group[0].kind; + let replacement = ''; + + let combining = false; + + group.forEach(node => { + node.declarations.forEach(({ id, init }) => { + if (id.type === 'Identifier') { + const value = init + ? this.code.slice(id.start, init.end) + : this.code.slice(id.start, id.end); + + if (combining) { + replacement += `, ${value}`; + } else { + replacement += `${kind} { ${value}`; + combining = true; + } + } else { + throw new Error('TODO destructured declarations'); + } + }); + }); + + if (combining) { + replacement += ' } = $$props;'; + } + + this.code.overwrite(group[0].start, group[group.length - 1].end, replacement); + }); + } + warn_if_undefined(node, template_scope: TemplateScope) { const { name } = node; if (this.module_scope && this.module_scope.declarations.has(name)) return; diff --git a/src/compile/render-dom/index.ts b/src/compile/render-dom/index.ts index 319b3b4b0b..2b266b21b7 100644 --- a/src/compile/render-dom/index.ts +++ b/src/compile/render-dom/index.ts @@ -177,15 +177,14 @@ export default function dom( ${component.fully_hoisted.length > 0 && component.fully_hoisted.join('\n\n')} - function define($$self, $$make_dirty) { - ${should_add_css && - `if (!document.getElementById("${component.stylesheet.id}-style")) @add_css();`} - - ${component.javascript || component.props.map(x => `let ${x.name};`)} + function ${component.alias('define')}($$self, $$props, $$make_dirty) { + ${component.javascript || ( + component.props.length > 0 && + `let { ${component.props.map(x => x.name === x.as ? x.as : `${x.as}: ${x.name}`).join(', ')} } = $$props;` + )} ${component.partly_hoisted.length > 0 && component.partly_hoisted.join('\n\n')} - // TODO only what's needed by the template $$self.$$.get = () => ({ ${component.declarations.join(', ')} }); ${set && `$$self.$$.set = ${set};`} @@ -202,7 +201,7 @@ export default function dom( ${css.code && `this.shadowRoot.innerHTML = \`\`;`} - @init(this, { target: this.shadowRoot }, define, create_fragment, ${not_equal}); + @init(this, { target: this.shadowRoot }, ${component.alias('define')}, create_fragment, ${not_equal}); ${dev_props_check} @@ -232,6 +231,7 @@ export default function dom( class ${name} extends ${superclass} { constructor(options) { super(${options.dev && `options`}); + ${should_add_css && `if (!document.getElementById("${component.stylesheet.id}-style")) @add_css();`} @init(this, options, define, create_fragment, ${not_equal}); ${dev_props_check} diff --git a/src/compile/wrapModule.ts b/src/compile/wrapModule.ts index 90eee257e5..003c4d6815 100644 --- a/src/compile/wrapModule.ts +++ b/src/compile/wrapModule.ts @@ -75,6 +75,7 @@ function esm( ${importBlock} ${code} + export default ${name}; ${module_exports.length > 0 && `export { ${module_exports.map(e => e.name === e.as ? e.name : `${e.name} as ${e.as}`).join(', ')} };`}`; } diff --git a/test/js/index.js b/test/js/index.js index 3ee1a1da2d..71416d8cf3 100644 --- a/test/js/index.js +++ b/test/js/index.js @@ -3,7 +3,7 @@ import * as fs from "fs"; import * as path from "path"; import { loadConfig, svelte } from "../helpers.js"; -describe("js", () => { +describe.only("js", () => { fs.readdirSync("test/js/samples").forEach(dir => { if (dir[0] === ".") return; diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index 2b580f6be6..212bedabff 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -47,8 +47,6 @@ function create_fragment(component, ctx) { } function define($$self, $$props, $$make_dirty) { - if (!document.getElementById("svelte-1a7i8ec-style")) add_css(); - let { foo = 42 } = $$props; $$self.$$.get = () => ({ foo }); @@ -61,6 +59,7 @@ function define($$self, $$props, $$make_dirty) { class SvelteComponent extends SvelteComponent_1 { constructor(options) { super(); + if (!document.getElementById("svelte-1a7i8ec-style")) add_css(); init(this, options, define, create_fragment, safe_not_equal); } @@ -73,4 +72,5 @@ class SvelteComponent extends SvelteComponent_1 { flush(); } } + export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/window-binding-scroll/expected.js b/test/js/samples/window-binding-scroll/expected.js index f43609e6e5..ca7e920b46 100644 --- a/test/js/samples/window-binding-scroll/expected.js +++ b/test/js/samples/window-binding-scroll/expected.js @@ -54,7 +54,7 @@ function create_fragment(component, ctx) { }; } -function define($$self, $$make_dirty) { +function define($$self, $$props, $$make_dirty) { let { y } = $$props; function onwindowscroll() { @@ -64,7 +64,6 @@ function define($$self, $$make_dirty) { window_updating = false; } - // TODO only what's needed by the template $$self.$$.get = () => ({ y, onwindowscroll }); $$self.$$.set = $$props => {