Merge branch 'master' into resize-observer

pull/5963/head
Jacob Wright 6 years ago committed by GitHub
commit 5aba3ee535
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -13,4 +13,3 @@ insert_final_newline = false
[{package.json,.travis.yml,.eslintrc.json}]
indent_style = space
indent_size = 2

@ -1,5 +1,25 @@
# Svelte changelog
## 3.34.0
* Add a `cssHash` option for controlling the classname used for CSS scoping ([#570](https://github.com/sveltejs/svelte/issues/570))
## 3.33.0
* In custom elements, call `onMount` functions when connecting and clean up when disconnecting ([#1152](https://github.com/sveltejs/svelte/issues/1152), [#2227](https://github.com/sveltejs/svelte/issues/2227), [#4522](https://github.com/sveltejs/svelte/pull/4522))
* Allow destructured defaults to refer to other variables ([#5066](https://github.com/sveltejs/svelte/issues/5066))
* Do not emit `contextual-store` warnings for function parameters or declared variables ([#6008](https://github.com/sveltejs/svelte/pull/6008))
## 3.32.3
* Fix removal of lone `:host` selectors ([#5982](https://github.com/sveltejs/svelte/issues/5982))
## 3.32.2
* Fix unnecessary additional invalidation with `<Component bind:prop={obj.foo}/>` ([#3075](https://github.com/sveltejs/svelte/issues/3075), [#4447](https://github.com/sveltejs/svelte/issues/4447), [#5555](https://github.com/sveltejs/svelte/issues/5555))
* Fix scoping of selectors with `:global()` and `~` sibling combinators ([#5499](https://github.com/sveltejs/svelte/issues/5499))
* Fix removal of `:host` selectors as unused when compiling to a custom element ([#5946](https://github.com/sveltejs/svelte/issues/5946))
## 3.32.1
* Warn when using `module` variables reactively, and close weird reactivity loophole ([#5847](https://github.com/sveltejs/svelte/pull/5847))

@ -0,0 +1,4 @@
if (!process.env.PUBLISH) {
console.error('npm publish must be run with the PUBLISH environment variable set');
process.exit(1);
}

26
package-lock.json generated

@ -1,7 +1,7 @@
{
"name": "svelte",
"version": "3.32.1",
"lockfileVersion": 2,
"version": "3.34.0",
"lockfileVersion": 1,
"requires": true,
"packages": {
"": {
@ -6990,19 +6990,19 @@
}
},
"css-tree": {
"version": "1.0.0-alpha22",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha22.tgz",
"integrity": "sha1-M4oAbjMce0+dq3tq9Tns5W/3ivI=",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz",
"integrity": "sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==",
"dev": true,
"requires": {
"mdn-data": "^1.0.0",
"source-map": "^0.5.3"
"mdn-data": "2.0.14",
"source-map": "^0.6.1"
},
"dependencies": {
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
}
@ -8843,9 +8843,9 @@
}
},
"mdn-data": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.2.0.tgz",
"integrity": "sha512-esDqNvsJB2q5V28+u7NdtdMg6Rmg4khQmAVSjUiX7BY/7haIv0K2yWM43hYp0or+3nvG7+UaTF1JHz31hgU1TA==",
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
"dev": true
},
"mem": {

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.32.1",
"version": "3.34.0",
"description": "Cybernetically enhanced web apps",
"module": "index.mjs",
"main": "index",
@ -73,7 +73,7 @@
"dev": "rollup -cw",
"pretest": "npm run build",
"posttest": "agadoo internal/index.mjs",
"prepublishOnly": "npm run lint && PUBLISH=true npm test",
"prepublishOnly": "node check_publish_env.js && npm run lint && npm test",
"tsd": "tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly",
"lint": "eslint \"{src,test}/**/*.{ts,js}\""
},
@ -112,7 +112,7 @@
"c8": "^5.0.1",
"code-red": "^0.1.4",
"codecov": "^3.5.0",
"css-tree": "1.0.0-alpha22",
"css-tree": "^1.1.2",
"eslint": "^7.15.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-svelte3": "^2.7.3",

@ -74,6 +74,7 @@ The following options can be passed to the compiler. None are required:
| `customElement` | `false` | If `true`, tells the compiler to generate a custom element constructor instead of a regular Svelte component.
| `tag` | `null` | A `string` that tells Svelte what tag name to register the custom element with. It must be a lowercase alphanumeric string with at least one hyphen, e.g. `"my-element"`.
| `css` | `true` | If `true`, styles will be included in the JavaScript class and injected at runtime. It's recommended that you set this to `false` and use the CSS that is statically generated, as it will result in smaller JavaScript bundles and better performance.
| `cssHash` | See right | A function that takes a `{ hash, css, name, filename }` argument and returns the string that is used as a classname for scoped CSS. It defaults to returning `svelte-${hash(css)}`
| `loopGuardTimeout` | 0 | A `number` that tells Svelte to break the loop if it blocks the thread for more than `loopGuardTimeout` ms. This is useful to prevent infinite loops. **Only available when `dev: true`**
| `preserveComments` | `false` | If `true`, your HTML comments will be preserved during server-side rendering. By default, they are stripped out.
| `preserveWhitespace` | `false` | If `true`, whitespace inside and between elements is kept as you typed it, rather than removed or collapsed to a single space where possible.

@ -133,12 +133,14 @@ export default class Component {
this.locate = getLocator(this.source, { offsetLine: 1 });
// styles
this.stylesheet = new Stylesheet(
this.stylesheet = new Stylesheet({
source,
ast,
compile_options.filename,
compile_options.dev
);
filename: compile_options.filename,
component_name: name,
dev: compile_options.dev,
get_css_hash: compile_options.cssHash
});
this.stylesheet.validate(this);
this.component_options = process_component_options(
@ -751,7 +753,7 @@ export default class Component {
return this.skip();
}
component.warn_on_undefined_store_value_references(node, parent, scope);
component.warn_on_undefined_store_value_references(node, parent, prop, scope);
},
leave(node: Node) {
@ -843,7 +845,7 @@ export default class Component {
});
}
warn_on_undefined_store_value_references(node, parent, scope: Scope) {
warn_on_undefined_store_value_references(node: Node, parent: Node, prop: string, scope: Scope) {
if (
node.type === 'LabeledStatement' &&
node.label.name === '$' &&
@ -855,7 +857,7 @@ export default class Component {
});
}
if (is_reference(node as Node, parent as Node)) {
if (is_reference(node, parent)) {
const object = get_object(node);
const { name } = object;
@ -865,10 +867,12 @@ export default class Component {
}
if (name[1] !== '$' && scope.has(name.slice(1)) && scope.find_owner(name.slice(1)) !== this.instance_scope) {
this.error(node, {
code: 'contextual-store',
message: 'Stores must be declared at the top level of the component (this may change in a future version of Svelte)'
});
if (!((/Function/.test(parent.type) && prop === 'params') || (parent.type === 'VariableDeclarator' && prop === 'id'))) {
this.error(node as any, {
code: 'contextual-store',
message: 'Stores must be declared at the top level of the component (this may change in a future version of Svelte)'
});
}
}
}
}

@ -44,7 +44,10 @@ export default class Selector {
}
this.local_blocks = this.blocks.slice(0, i);
this.used = this.local_blocks.length === 0;
const host_only = this.blocks.length === 1 && this.blocks[0].host;
this.used = this.local_blocks.length === 0 || host_only;
}
apply(node: Element) {
@ -84,7 +87,7 @@ export default class Selector {
while (i--) {
const selector = block.selectors[i];
if (selector.type === 'PseudoElementSelector' || selector.type === 'PseudoClassSelector') {
if (selector.name !== 'root') {
if (selector.name !== 'root' && selector.name !== 'host') {
if (i === 0) code.prependRight(selector.start, attr);
}
continue;
@ -162,7 +165,10 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: any[]):
if (!block) return false;
if (!node) {
return block.global && blocks.every(block => block.global);
return (
(block.global && blocks.every(block => block.global)) ||
(block.host && blocks.length === 0)
);
}
switch (block_might_apply_to_node(block, node)) {
@ -182,6 +188,11 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: any[]):
continue;
}
if (ancestor_block.host) {
to_encapsulate.push({ node, block });
return true;
}
let parent = node;
while (parent = get_element_parent(parent)) {
if (block_might_apply_to_node(ancestor_block, parent) !== BlockAppliesToNode.NotPossible) {
@ -211,6 +222,19 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: any[]):
} else if (block.combinator.name === '+' || block.combinator.name === '~') {
const siblings = get_possible_element_siblings(node, block.combinator.name === '+');
let has_match = false;
// NOTE: if we have :global(), we couldn't figure out what is selected within `:global` due to the
// css-tree limitation that does not parse the inner selector of :global
// so unless we are sure there will be no sibling to match, we will consider it as matched
const has_global = blocks.some(block => block.global);
if (has_global) {
if (siblings.size === 0 && get_element_parent(node) !== null) {
return false;
}
to_encapsulate.push({ node, block });
return true;
}
for (const possible_sibling of siblings.keys()) {
if (apply_selector(blocks.slice(), possible_sibling, to_encapsulate)) {
to_encapsulate.push({ node, block });
@ -236,6 +260,10 @@ function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToN
const selector = block.selectors[i];
const name = typeof selector.name === 'string' && selector.name.replace(/\\(.)/g, '$1');
if (selector.type === 'PseudoClassSelector' && name === 'host') {
return BlockAppliesToNode.NotPossible;
}
if (selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector') {
continue;
}
@ -541,6 +569,7 @@ function loop_child(children: INode[], adjacent_only: boolean) {
class Block {
global: boolean;
host: boolean;
combinator: CssNode;
selectors: CssNode[]
start: number;
@ -550,6 +579,7 @@ class Block {
constructor(combinator: CssNode) {
this.combinator = combinator;
this.global = false;
this.host = false;
this.selectors = [];
this.start = null;
@ -562,6 +592,7 @@ class Block {
if (this.selectors.length === 0) {
this.start = selector.start;
this.global = selector.type === 'PseudoClassSelector' && selector.name === 'global';
this.host = selector.type === 'PseudoClassSelector' && selector.name === 'host';
}
this.selectors.push(selector);

@ -2,7 +2,7 @@ import MagicString from 'magic-string';
import { walk } from 'estree-walker';
import Selector from './Selector';
import Element from '../nodes/Element';
import { Ast } from '../../interfaces';
import { Ast, CssHashGetter } from '../../interfaces';
import Component from '../Component';
import { CssNode } from './interfaces';
import hash from '../utils/hash';
@ -47,7 +47,7 @@ class Rule {
constructor(node: CssNode, stylesheet, parent?: Atrule) {
this.node = node;
this.parent = parent;
this.selectors = node.selector.children.map((node: CssNode) => new Selector(node, stylesheet));
this.selectors = node.prelude.children.map((node: CssNode) => new Selector(node, stylesheet));
this.declarations = node.block.children.map((node: CssNode) => new Declaration(node));
}
@ -182,11 +182,11 @@ class Atrule {
minify(code: MagicString, dev: boolean) {
if (this.node.name === 'media') {
const expression_char = code.original[this.node.expression.start];
const expression_char = code.original[this.node.prelude.start];
let c = this.node.start + (expression_char === '(' ? 6 : 7);
if (this.node.expression.start > c) code.remove(c, this.node.expression.start);
if (this.node.prelude.start > c) code.remove(c, this.node.prelude.start);
this.node.expression.children.forEach((query: CssNode) => {
this.node.prelude.children.forEach((query: CssNode) => {
// TODO minify queries
c = query.end;
});
@ -194,17 +194,17 @@ class Atrule {
code.remove(c, this.node.block.start);
} else if (this.node.name === 'supports') {
let c = this.node.start + 9;
if (this.node.expression.start - c > 1) code.overwrite(c, this.node.expression.start, ' ');
this.node.expression.children.forEach((query: CssNode) => {
if (this.node.prelude.start - c > 1) code.overwrite(c, this.node.prelude.start, ' ');
this.node.prelude.children.forEach((query: CssNode) => {
// TODO minify queries
c = query.end;
});
code.remove(c, this.node.block.start);
} else {
let c = this.node.start + this.node.name.length + 1;
if (this.node.expression) {
if (this.node.expression.start - c > 1) code.overwrite(c, this.node.expression.start, ' ');
c = this.node.expression.end;
if (this.node.prelude) {
if (this.node.prelude.start - c > 1) code.overwrite(c, this.node.prelude.start, ' ');
c = this.node.prelude.end;
}
if (this.node.block && this.node.block.start - c > 0) {
code.remove(c, this.node.block.start);
@ -235,7 +235,7 @@ class Atrule {
transform(code: MagicString, id: string, keyframes: Map<string, string>, max_amount_class_specificity_increased: number) {
if (is_keyframes_node(this.node)) {
this.node.expression.children.forEach(({ type, name, start, end }: CssNode) => {
this.node.prelude.children.forEach(({ type, name, start, end }: CssNode) => {
if (type === 'Identifier') {
if (name.startsWith('-global-')) {
code.remove(start, start + 8);
@ -275,6 +275,10 @@ class Atrule {
}
}
const get_default_css_hash: CssHashGetter = ({ css, hash }) => {
return `svelte-${hash(css)}`;
};
export default class Stylesheet {
source: string;
ast: Ast;
@ -289,14 +293,33 @@ export default class Stylesheet {
nodes_with_css_class: Set<CssNode> = new Set();
constructor(source: string, ast: Ast, filename: string, dev: boolean) {
constructor({
source,
ast,
component_name,
filename,
dev,
get_css_hash = get_default_css_hash
}: {
source: string;
ast: Ast;
filename: string | undefined;
component_name: string | undefined;
dev: boolean;
get_css_hash: CssHashGetter;
}) {
this.source = source;
this.ast = ast;
this.filename = filename;
this.dev = dev;
if (ast.css && ast.css.children.length) {
this.id = `svelte-${hash(ast.css.content.styles)}`;
this.id = get_css_hash({
filename,
name: component_name,
css: ast.css.content.styles,
hash
});
this.has_styles = true;
@ -317,7 +340,7 @@ export default class Stylesheet {
}
if (is_keyframes_node(node)) {
node.expression.children.forEach((expression: CssNode) => {
node.prelude.children.forEach((expression: CssNode) => {
if (expression.type === 'Identifier' && !expression.name.startsWith('-global-')) {
this.keyframes.set(expression.name, `${this.id}-${expression.name}`);
}

@ -28,7 +28,8 @@ const valid_options = [
'css',
'loopGuardTimeout',
'preserveComments',
'preserveWhitespace'
'preserveWhitespace',
'cssHash'
];
function validate_options(options: CompileOptions, warnings: Warning[]) {

@ -33,12 +33,12 @@ export default class AwaitBlock extends Node {
if (this.then_node) {
this.then_contexts = [];
unpack_destructuring(this.then_contexts, info.value, node => node);
unpack_destructuring(this.then_contexts, info.value);
}
if (this.catch_node) {
this.catch_contexts = [];
unpack_destructuring(this.catch_contexts, info.error, node => node);
unpack_destructuring(this.catch_contexts, info.error);
}
this.pending = new PendingBlock(component, this, scope, info.pending);

@ -38,7 +38,7 @@ export default class EachBlock extends AbstractBlock {
this.scope = scope.child();
this.contexts = [];
unpack_destructuring(this.contexts, info.context, node => node);
unpack_destructuring(this.contexts, info.context);
this.contexts.forEach(context => {
this.scope.add(context.key.name, this.expression.dependencies, this);

@ -1,33 +1,40 @@
import { x } from 'code-red';
import { Node, Identifier } from 'estree';
import { Node, Identifier, Expression } from 'estree';
import { walk } from 'estree-walker';
import is_reference from 'is-reference';
export interface Context {
key: Identifier;
name?: string;
modifier: (node: Node) => Node;
default_modifier: (node: Node, to_ctx: (name: string) => Node) => Node;
}
export function unpack_destructuring(contexts: Context[], node: Node, modifier: (node: Node) => Node) {
export function unpack_destructuring(contexts: Context[], node: Node, modifier: Context['modifier'] = node => node, default_modifier: Context['default_modifier'] = node => node) {
if (!node) return;
if (node.type === 'Identifier') {
contexts.push({
key: node as Identifier,
modifier
modifier,
default_modifier
});
} else if (node.type === 'RestElement') {
contexts.push({
key: node.argument as Identifier,
modifier
modifier,
default_modifier
});
} else if (node.type === 'ArrayPattern') {
node.elements.forEach((element, i) => {
if (element && element.type === 'RestElement') {
unpack_destructuring(contexts, element, node => x`${modifier(node)}.slice(${i})` as Node);
unpack_destructuring(contexts, element, node => x`${modifier(node)}.slice(${i})` as Node, default_modifier);
} else if (element && element.type === 'AssignmentPattern') {
unpack_destructuring(contexts, element.left, node => x`${modifier(node)}[${i}] !== undefined ? ${modifier(node)}[${i}] : ${element.right}` as Node);
const n = contexts.length;
unpack_destructuring(contexts, element.left, node => x`${modifier(node)}[${i}]`, (node, to_ctx) => x`${node} !== undefined ? ${node} : ${update_reference(contexts, n, element.right, to_ctx)}` as Node);
} else {
unpack_destructuring(contexts, element, node => x`${modifier(node)}[${i}]` as Node);
unpack_destructuring(contexts, element, node => x`${modifier(node)}[${i}]` as Node, default_modifier);
}
});
} else if (node.type === 'ObjectPattern') {
@ -38,19 +45,51 @@ export function unpack_destructuring(contexts: Context[], node: Node, modifier:
unpack_destructuring(
contexts,
property.argument,
node => x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node
node => x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node,
default_modifier
);
} else {
const key = property.key as Identifier;
const value = property.value;
used_properties.push(x`"${(key as Identifier).name}"`);
used_properties.push(x`"${key.name}"`);
if (value.type === 'AssignmentPattern') {
unpack_destructuring(contexts, value.left, node => x`${modifier(node)}.${key.name} !== undefined ? ${modifier(node)}.${key.name} : ${value.right}` as Node);
const n = contexts.length;
unpack_destructuring(contexts, value.left, node => x`${modifier(node)}.${key.name}`, (node, to_ctx) => x`${node} !== undefined ? ${node} : ${update_reference(contexts, n, value.right, to_ctx)}` as Node);
} else {
unpack_destructuring(contexts, value, node => x`${modifier(node)}.${key.name}` as Node);
unpack_destructuring(contexts, value, node => x`${modifier(node)}.${key.name}` as Node, default_modifier);
}
}
});
}
}
function update_reference(contexts: Context[], n: number, expression: Expression, to_ctx: (name: string) => Node): Node {
const find_from_context = (node: Identifier) => {
for (let i = n; i < contexts.length; i++) {
const { key } = contexts[i];
if (node.name === key.name) {
throw new Error(`Cannot access '${node.name}' before initialization`);
}
}
return to_ctx(node.name);
};
if (expression.type === 'Identifier') {
return find_from_context(expression);
}
// NOTE: avoid unnecessary deep clone?
expression = JSON.parse(JSON.stringify(expression)) as Expression;
walk(expression, {
enter(node, parent: Node) {
if (is_reference(node, parent)) {
this.replace(find_from_context(node as Identifier));
this.skip();
}
}
});
return expression;
}

@ -325,7 +325,8 @@ export default class Expression {
// child_ctx[x] = function () { ... }
(template_scope.get_owner(deps[0]) as EachBlock).contexts.push({
key: func_id,
modifier: () => func_expression
modifier: () => func_expression,
default_modifier: node => node
});
this.replace(block.renderer.reference(func_id));
}

@ -9,7 +9,6 @@ export interface Bindings {
property: Identifier;
snippet: Node;
store: string;
tail: Node;
modifier: (node: Node) => Node;
}

@ -485,7 +485,7 @@ export default function dom(
${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
@init(this, { target: this.shadowRoot, props: ${init_props} }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
@init(this, { target: this.shadowRoot, props: ${init_props}, customElement: true }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
${dev_props_check}

@ -87,7 +87,7 @@ class AwaitBlockBranch extends Wrapper {
}
render_destructure() {
const props = this.value_contexts.map(prop => b`#ctx[${this.block.renderer.context_lookup.get(prop.key.name).index}] = ${prop.modifier(x`#ctx[${this.value_index}]`)};`);
const props = this.value_contexts.map(prop => b`#ctx[${this.block.renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x`#ctx[${this.value_index}]`), name => this.renderer.reference(name))};`);
const get_context = this.block.renderer.component.get_unique_name(`get_${this.status}_context`);
this.block.renderer.blocks.push(b`
function ${get_context}(#ctx) {

@ -149,8 +149,7 @@ export default class EachBlockWrapper extends Wrapper {
property: this.index_name,
modifier: prop.modifier,
snippet: prop.modifier(x`${this.vars.each_block_value}[${this.index_name}]` as Node),
store,
tail: prop.modifier(x`[${this.index_name}]` as Node)
store
});
});
@ -347,7 +346,7 @@ export default class EachBlockWrapper extends Wrapper {
this.else.fragment.render(this.else.block, null, x`#nodes` as Identifier);
}
this.context_props = this.node.contexts.map(prop => b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.modifier(x`list[i]`)};`);
this.context_props = this.node.contexts.map(prop => b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x`list[i]`), name => renderer.context_lookup.has(name) ? x`child_ctx[${renderer.context_lookup.get(name).index}]`: { type: 'Identifier', name })};`);
if (this.node.has_binding) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`);
if (this.node.has_binding || this.node.has_index_binding || this.node.index) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`);

@ -347,8 +347,8 @@ export default class InlineComponentWrapper extends Wrapper {
}
const params = [x`#value`];
const args = [x`#value`];
if (contextual_dependencies.length > 0) {
const args = [];
contextual_dependencies.forEach(name => {
params.push({
@ -361,25 +361,30 @@ export default class InlineComponentWrapper extends Wrapper {
});
block.chunks.init.push(b`
function ${id}(#value) {
${callee}.call(null, #value, ${args});
}
`);
block.maintain_context = true; // TODO put this somewhere more logical
} else {
block.chunks.init.push(b`
function ${id}(#value) {
${callee}.call(null, #value);
}
block.chunks.init.push(b`
function ${id}(#value) {
${callee}(${args});
}
`);
let invalidate_binding = b`
${lhs} = #value;
${renderer.invalidate(dependencies[0])};
`;
if (binding.expression.node.type === 'MemberExpression') {
invalidate_binding = b`
if ($$self.$$.not_equal(${lhs}, #value)) {
${invalidate_binding}
}
`);
`;
}
const body = b`
function ${id}(${params}) {
${lhs} = #value;
${renderer.invalidate(dependencies[0])};
${invalidate_binding}
}
`;

@ -104,6 +104,13 @@ export interface Warning {
export type ModuleFormat = 'esm' | 'cjs';
export type CssHashGetter = (args: {
name: string;
filename: string | undefined;
css: string;
hash: (input: string) => string;
}) => string;
export interface CompileOptions {
format?: ModuleFormat;
name?: string;
@ -125,6 +132,7 @@ export interface CompileOptions {
css?: boolean;
loopGuardTimeout?: number;
namespace?: string;
cssHash?: CssHashGetter;
preserveComments?: boolean;
preserveWhitespace?: boolean;
@ -166,7 +174,7 @@ export interface Var {
imported?: boolean;
}
export interface CssResult {
export interface CssResult {
code: string;
map: SourceMap;
}

@ -14,10 +14,13 @@ export default function read_style(parser: Parser, start: number, attributes: No
try {
ast = parse(styles, {
positions: true,
offset: content_start
offset: content_start,
onParseError(error) {
throw error;
}
});
} catch (err) {
if (err.name === 'CssSyntaxError') {
if (err.name === 'SyntaxError') {
parser.error({
code: 'css-syntax-error',
message: err.message

@ -34,6 +34,7 @@ interface T$$ {
on_mount: any[];
on_destroy: any[];
skip_bound: boolean;
on_disconnect: any[];
}
export function bind(component, name, callback) {
@ -52,23 +53,26 @@ export function claim_component(block, parent_nodes) {
block && block.l(parent_nodes);
}
export function mount_component(component, target, anchor) {
export function mount_component(component, target, anchor, customElement) {
const { fragment, on_mount, on_destroy, after_update } = component.$$;
fragment && fragment.m(target, anchor);
// onMount happens before the initial afterUpdate
add_render_callback(() => {
const new_on_destroy = on_mount.map(run).filter(is_function);
if (on_destroy) {
on_destroy.push(...new_on_destroy);
} else {
// Edge case - component was destroyed immediately,
// most likely as a result of a binding initialising
run_all(new_on_destroy);
}
component.$$.on_mount = [];
});
if (!customElement) {
// onMount happens before the initial afterUpdate
add_render_callback(() => {
const new_on_destroy = on_mount.map(run).filter(is_function);
if (on_destroy) {
on_destroy.push(...new_on_destroy);
} else {
// Edge case - component was destroyed immediately,
// most likely as a result of a binding initialising
run_all(new_on_destroy);
}
component.$$.on_mount = [];
});
}
after_update.forEach(add_render_callback);
}
@ -113,6 +117,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
// lifecycle
on_mount: [],
on_destroy: [],
on_disconnect: [],
before_update: [],
after_update: [],
context: new Map(parent_component ? parent_component.$$.context : []),
@ -155,7 +160,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
}
if (options.intro) transition_in(component.$$.fragment);
mount_component(component, options.target, options.anchor);
mount_component(component, options.target, options.anchor, options.customElement);
flush();
}
@ -173,6 +178,9 @@ if (typeof HTMLElement === 'function') {
}
connectedCallback() {
const { on_mount } = this.$$;
this.$$.on_disconnect = on_mount.map(run).filter(is_function);
// @ts-ignore todo: improve typings
for (const key in this.$$.slotted) {
// @ts-ignore todo: improve typings
@ -184,6 +192,10 @@ if (typeof HTMLElement === 'function') {
this[attr] = newValue;
}
disconnectedCallback() {
run_all(this.$$.on_disconnect);
}
$destroy() {
destroy_component(this, 1);
this.$destroy = noop;

@ -0,0 +1,12 @@
export default {
compileOptions: {
filename: 'src/components/FooSwitcher.svelte',
cssHash({ hash, css, name, filename }) {
const minFilename = filename
.split('/')
.map(i => i.charAt(0).toLowerCase())
.join('');
return `sv-${name}-${minFilename}-${hash(css)}`;
}
}
};

@ -0,0 +1 @@
div.sv-FooSwitcher-scf-bzh57p{color:red}

@ -0,0 +1,7 @@
<div>red</div>
<style>
div {
color: red;
}
</style>

@ -0,0 +1,27 @@
export default {
warnings: [
{
code: 'css-unused-selector',
message: 'Unused CSS selector ":host > span"',
pos: 147,
start: {
character: 147,
column: 1,
line: 18
},
end: {
character: 159,
column: 13,
line: 18
},
frame: `
16: }
17:
18: :host > span {
^
19: color: red;
20: }
`
}
]
};

@ -0,0 +1 @@
:host h1.svelte-xyz{color:red}:host>h1.svelte-xyz{color:red}:host>.svelte-xyz{color:red}:host span.svelte-xyz{color:red}:host{color:red}

@ -0,0 +1,31 @@
<style>
:host h1 {
color: red;
}
:host > h1 {
color: red;
}
:host > * {
color: red;
}
:host span {
color: red;
}
:host > span {
color: red;
}
:host {
color: red;
}
</style>
<h1>Hello!</h1>
<div>
<span>World!</span>
</div>

@ -0,0 +1,50 @@
export default {
warnings: [
{
code: 'css-unused-selector',
message: 'Unused CSS selector ":global(input) + span"',
pos: 239,
start: {
character: 239,
column: 2,
line: 9
},
end: {
character: 260,
column: 23,
line: 9
},
frame: `
7: :global(input) ~ p { color: red; }
8:
9: :global(input) + span { color: red; }
^
10: :global(input) ~ span { color: red; }
11: </style>
`
},
{
code: 'css-unused-selector',
message: 'Unused CSS selector ":global(input) ~ span"',
pos: 279,
start: {
character: 279,
column: 2,
line: 10
},
end: {
character: 300,
column: 23,
line: 10
},
frame: `
8:
9: :global(input) + span { color: red; }
10: :global(input) ~ span { color: red; }
^
11: </style>
12:
`
}
]
};

@ -0,0 +1 @@
input+div.svelte-xyz{color:red}input~div.svelte-xyz{color:red}input+h1.svelte-xyz{color:red}input~h1.svelte-xyz{color:red}input+p.svelte-xyz{color:red}input~p.svelte-xyz{color:red}

@ -0,0 +1,21 @@
<style>
:global(input) + div { color: red; }
:global(input) ~ div { color: red; }
:global(input) + h1 { color: red; }
:global(input) ~ h1 { color: red; }
:global(input) + p { color: red; }
:global(input) ~ p { color: red; }
:global(input) + span { color: red; }
:global(input) ~ span { color: red; }
</style>
<h1>Hello!</h1>
<div>
<span>World!</span>
</div>
{#each [] as _}
<p />
{/each}

@ -110,8 +110,8 @@ describe('custom-elements', function() {
const page = await browser.newPage();
page.on('console', (type, ...args) => {
console[type](...args);
page.on('console', (type) => {
console[type._type](type._text);
});
page.on('error', error => {

@ -3,9 +3,12 @@
<script>
import { onMount } from 'svelte';
export let wasCreated;
export let prop = false;
export let propsInitialized;
export let wasCreated;
onMount(() => {
wasCreated = true;
});
onMount(() => {
propsInitialized = prop !== false;
wasCreated = true;
});
</script>

@ -2,7 +2,9 @@ import * as assert from 'assert';
import './main.svelte';
export default function (target) {
target.innerHTML = '<my-app/>';
target.innerHTML = '<my-app prop/>';
const el = target.querySelector('my-app');
assert.ok(el.wasCreated);
assert.ok(el.propsInitialized);
}

@ -0,0 +1,22 @@
<svelte:options tag="my-app"/>
<script>
import { onMount, onDestroy } from 'svelte';
let el;
let parentEl;
onMount(() => {
parentEl = el.parentNode.host.parentElement;
return () => {
parentEl.dataset.onMountDestroyed = true;
}
});
onDestroy(() => {
parentEl.dataset.destroyed = true;
})
</script>
<div bind:this={el}></div>

@ -0,0 +1,11 @@
import * as assert from 'assert';
import './main.svelte';
export default function (target) {
target.innerHTML = '<my-app/>';
const el = target.querySelector('my-app');
target.removeChild(el);
assert.ok(target.dataset.onMountDestroyed);
assert.equal(target.dataset.destroyed, undefined);
}

@ -40,7 +40,8 @@ class Component extends SvelteElement {
this,
{
target: this.shadowRoot,
props: attribute_to_object(this.attributes)
props: attribute_to_object(this.attributes),
customElement: true
},
null,
create_fragment,

@ -37,7 +37,7 @@
"children": [
{
"type": "Rule",
"selector": {
"prelude": {
"type": "SelectorList",
"children": [
{
@ -74,7 +74,7 @@
"end": 43
}
],
"start": 39,
"start": 40,
"end": 43
},
"start": 33,

@ -0,0 +1,6 @@
<script>
export let value;
export let value2;
</script>
{value}{value2}

@ -0,0 +1,5 @@
export default {
async test({ assert, component }) {
assert.equal(component.object_updates, component.primitive_updates);
}
};

@ -0,0 +1,13 @@
<script>
import Component from './Component.svelte';
export let primitive_updates = 0;
export let object_updates = 0;
const obj = { foo: '' };
let foo = 'bar';
$: if (obj) object_updates++;
$: if (foo) primitive_updates++;
</script>
<Component bind:value={obj.foo} bind:value2={foo} />

@ -0,0 +1,5 @@
export default {
error(assert, err) {
assert.ok(err.message === "Cannot access 'c' before initialization" || err.message === 'c is not defined');
}
};

@ -0,0 +1,7 @@
<script>
let array = [{a: 1, c: 2}];
</script>
{#each array as { a, b = c, c }}
{a}{b}{c}
{/each}

@ -0,0 +1,23 @@
export default {
html: `
<input />
<input />
`,
ssrHtml: `
<input />
<input value="hello" />
`,
test({ assert, component, target, window }) {
const [input1, input2] = target.querySelectorAll('input');
assert.equal(input1.value, '');
assert.equal(input2.value, 'hello');
const inputEvent = new window.InputEvent('input');
input2.value = 'world';
input2.dispatchEvent(inputEvent);
assert.equal(input2.value, 'world');
assert.equal(component.array[1].value, 'world');
}
};

@ -0,0 +1,7 @@
<script>
export let array = [{ value: '' }, {}];
</script>
{#each array as { value = "hello" }}
<input bind:value />
{/each}

@ -1,22 +1,26 @@
export default {
props: {
animalEntries: [
{ animal: 'raccoon', class: 'mammal', species: 'P. lotor', kilogram: 25 },
{ animal: 'eagle', class: 'bird', kilogram: 5.4 }
{ animal: 'raccoon', class: 'mammal', species: 'P. lotor', kilogram: 25, bmi: 0.04 },
{ animal: 'eagle', class: 'bird', kilogram: 5.4 },
{ animal: 'tiger', class: 'mammal', kilogram: 10, pound: 30 },
{ animal: 'lion', class: 'mammal', kilogram: 10, height: 50 },
{ animal: 'leopard', class: 'mammal', kilogram: 30, height: 50, bmi: 10 }
]
},
html: `
<p class="mammal">raccoon - P. lotor - 25kg</p>
<p class="bird">eagle - unknown - 5.4kg</p>
<p class="mammal">raccoon - P. lotor - 25kg (55 lb) - 30cm - 0.04</p>
<p class="bird">eagle - unknown - 5.4kg (12 lb) - 30cm - 0.006</p>
<p class="mammal">tiger - unknown - 10kg (30 lb) - 30cm - 0.011111111111111112</p>
<p class="mammal">lion - unknown - 10kg (22 lb) - 50cm - 0.004</p>
<p class="mammal">leopard - unknown - 30kg (66 lb) - 50cm - 10</p>
`,
test({ assert, component, target }) {
component.animalEntries = [{ animal: 'cow', class: 'mammal', species: 'B. taurus' }];
assert.htmlEqual(target.innerHTML, `
<p class="mammal">cow - B. taurus - 50kg</p>
<p class="mammal">cow - B. taurus - 50kg (110 lb) - 30cm - 0.05555555555555555</p>
`);
}
};

@ -1,7 +1,8 @@
<script>
export let animalEntries;
export const defaultHeight = 30;
</script>
{#each animalEntries as { animal, species = 'unknown', kilogram: weight = 50 , ...props } }
<p {...props}>{animal} - {species} - {weight}kg</p>
{#each animalEntries as { animal, species = 'unknown', kilogram: weight = 50, pound = (weight * 2.2).toFixed(0), height = defaultHeight, bmi = weight / (height * height), ...props } }
<p {...props}>{animal} - {species} - {weight}kg ({pound} lb) - {height}cm - {bmi}</p>
{/each}

@ -0,0 +1,25 @@
<script>
function test(store) {
// allow declaring $store as parameter
// it's not referring to the store value of the
// `store` variable in the upper scope
return derived(store, $store => {
});
}
function test2(store) {
// allow declaring the `$store` variable
// it is not referring to the store value of the `store` variable
let $store;
}
</script>
<div
on:test={(store) => {
derived(store, $store => {});
}}
on:test2={(store) => {
let $store;
}}
/>
Loading…
Cancel
Save