Skip to content

Commit 4b4737a

Browse files
committed
Merge branch 'gh-1147' into gh-1082-script-style-test
2 parents 7193109 + 8f91648 commit 4b4737a

File tree

110 files changed

+1207
-282
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+1207
-282
lines changed

CHANGELOG.md

+32
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,37 @@
11
# Svelte changelog
22

3+
## 1.54.1
4+
5+
* Hoist destructured references ([#1139](https://github.com/sveltejs/svelte/issues/1139))
6+
* Add `bind:volume` for media elements ([#1143](https://github.com/sveltejs/svelte/issues/1143))
7+
8+
## 1.54.0
9+
10+
* Run `oncreate` hooks depth-first, top-to-bottom ([#1135](https://github.com/sveltejs/svelte/issues/1135))
11+
* Render boolean attributes correctly in SSR mode ([#1109](https://github.com/sveltejs/svelte/issues/1109))
12+
* Add `feed` aria role to expected roles when doing a11y checks ([#1124](https://github.com/sveltejs/svelte/pull/1124))
13+
* More complete fix for case sensitive attributes ([#1062](https://github.com/sveltejs/svelte/issues/1062))
14+
* Handle CLRF line endings in await block comments ([#1132](https://github.com/sveltejs/svelte/issues/1132))
15+
16+
## 1.53.0
17+
18+
* Base scoping selectors on `<style>` contents alone ([#1091](https://github.com/sveltejs/svelte/issues/1091))
19+
20+
## 1.52.0
21+
22+
* Deconflict referenced globals ([#1079](https://github.com/sveltejs/svelte/issues/1079))
23+
* Validate contents of `await` blocks ([#1061](https://github.com/sveltejs/svelte/issues/1061))
24+
* Fire `oncreate` for components in `await` blocks ([#1061](https://github.com/sveltejs/svelte/issues/1061))
25+
* Automatically fix attribute casing ([#1062](https://github.com/sveltejs/svelte/issues/1062))
26+
* Fix escaping in `<script>` and `<style>` ([#1082](https://github.com/sveltejs/svelte/issues/1082))
27+
* Error if invalid characters are used in computed properties, and allow any valid identifier in props ([#1083](https://github.com/sveltejs/svelte/issues/1083))
28+
* Don't run a11y tests on components ([#1110](https://github.com/sveltejs/svelte/issues/1110))
29+
* Respect `store` option in SSR mode ([#1107](https://github.com/sveltejs/svelte/issues/1107))
30+
31+
## 1.51.1
32+
33+
* Only escape <, > and & characters ([#1082](https://github.com/sveltejs/svelte/issues/1082))
34+
335
## 1.51.0
436

537
* Lock `scroll` bindings ([#1071](https://github.com/sveltejs/svelte/issues/1071))

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "svelte",
3-
"version": "1.51.0",
3+
"version": "1.54.1",
44
"description": "The magical disappearing UI framework",
55
"main": "compiler/svelte.js",
66
"files": [
@@ -56,7 +56,8 @@
5656
"eslint-plugin-import": "^2.2.0",
5757
"estree-walker": "^0.5.1",
5858
"glob": "^7.1.1",
59-
"jsdom": "^11.1.0",
59+
"is-reference": "^1.1.0",
60+
"jsdom": "^11.6.1",
6061
"locate-character": "^2.0.0",
6162
"magic-string": "^0.22.3",
6263
"mocha": "^3.2.0",

src/css/Stylesheet.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { walk } from 'estree-walker';
33
import { getLocator } from 'locate-character';
44
import Selector from './Selector';
55
import getCodeFrame from '../utils/getCodeFrame';
6+
import hash from '../utils/hash';
67
import Element from '../generators/nodes/Element';
78
import { Validator } from '../validate/index';
89
import { Node, Parsed, Warning } from '../interfaces';
@@ -269,12 +270,12 @@ export default class Stylesheet {
269270
this.cascade = cascade;
270271
this.filename = filename;
271272

272-
this.id = `svelte-${parsed.hash}`;
273-
274273
this.children = [];
275274
this.keyframes = new Map();
276275

277276
if (parsed.css && parsed.css.children.length) {
277+
this.id = `svelte-${hash(parsed.css.content.styles)}`;
278+
278279
this.hasStyles = true;
279280

280281
const stack: (Rule | Atrule)[] = [];

src/generators/Generator.ts

+17-8
Original file line numberDiff line numberDiff line change
@@ -244,13 +244,20 @@ export default class Generator {
244244
} else if (contexts.has(name)) {
245245
const contextName = contexts.get(name);
246246
if (contextName !== name) {
247-
// this is true for 'reserved' names like `state` and `component`
247+
// this is true for 'reserved' names like `state` and `component`,
248+
// also destructured contexts
248249
code.overwrite(
249250
node.start,
250251
node.start + name.length,
251252
contextName,
252253
{ storeName: true, contentOnly: false }
253254
);
255+
256+
const destructuredName = contextName.replace(/\[\d+\]/, '');
257+
if (destructuredName !== contextName) {
258+
// so that hoisting the context works correctly
259+
usedContexts.add(destructuredName);
260+
}
254261
}
255262

256263
usedContexts.add(name);
@@ -410,11 +417,16 @@ export default class Generator {
410417
const indentationLevel = getIndentationLevel(source, js.content.body[0].start);
411418
const indentExclusionRanges = getIndentExclusionRanges(js.content);
412419

413-
const scope = annotateWithScopes(js.content);
420+
const { scope, globals } = annotateWithScopes(js.content);
421+
414422
scope.declarations.forEach(name => {
415423
this.userVars.add(name);
416424
});
417425

426+
globals.forEach(name => {
427+
this.userVars.add(name);
428+
});
429+
418430
const body = js.content.body.slice(); // slice, because we're going to be mutating the original
419431

420432
// imports need to be hoisted out of the IIFE
@@ -666,7 +678,7 @@ export default class Generator {
666678
isEventHandler: boolean
667679
) => {
668680
this.addSourcemapLocations(node); // TODO this involves an additional walk — can we roll it in somewhere else?
669-
let scope = annotateWithScopes(node);
681+
let { scope } = annotateWithScopes(node);
670682

671683
const dependencies: Set<string> = new Set();
672684

@@ -767,12 +779,9 @@ export default class Generator {
767779
contextDependencies.set(node.context, node.metadata.dependencies);
768780

769781
if (node.destructuredContexts) {
770-
for (let i = 0; i < node.destructuredContexts.length; i += 1) {
771-
const name = node.destructuredContexts[i];
772-
const value = `${node.context}[${i}]`;
773-
782+
node.destructuredContexts.forEach((name: string) => {
774783
contextDependencies.set(name, node.metadata.dependencies);
775-
}
784+
});
776785
}
777786

778787
contextDependenciesStack.push(contextDependencies);

src/generators/dom/index.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -245,20 +245,20 @@ export default function dom(
245245
246246
${(templateProperties.oncreate || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent`
247247
if (!options.root) {
248-
this._oncreate = [${templateProperties.oncreate && `_oncreate`}];
248+
this._oncreate = [];
249249
${(generator.hasComponents || generator.hasComplexBindings) && `this._beforecreate = [];`}
250250
${(generator.hasComponents || generator.hasIntroTransitions) && `this._aftercreate = [];`}
251-
} ${templateProperties.oncreate && deindent`
252-
else {
253-
this.root._oncreate.push(_oncreate);
254-
}
255-
`}
251+
}
256252
`}
257253
258254
${generator.slots.size && `this.slots = {};`}
259255
260256
this._fragment = @create_main_fragment(this._state, this);
261257
258+
${(templateProperties.oncreate) && deindent`
259+
this.root._oncreate.push(_oncreate);
260+
`}
261+
262262
${generator.customElement ? deindent`
263263
this._fragment.c();
264264
this._fragment.${block.hasIntroMethod ? 'i' : 'm'}(this.shadowRoot, null);
@@ -407,7 +407,7 @@ export default function dom(
407407
const code = new MagicString(str);
408408
const expression = parseExpressionAt(str, 0);
409409

410-
let scope = annotateWithScopes(expression);
410+
let { scope } = annotateWithScopes(expression);
411411

412412
walk(expression, {
413413
enter(node: Node, parent: Node) {

src/generators/nodes/Attribute.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import deindent from '../../utils/deindent';
22
import { stringify } from '../../utils/stringify';
3+
import fixAttributeCasing from '../../utils/fixAttributeCasing';
34
import getExpressionPrecedence from '../../utils/getExpressionPrecedence';
45
import { DomGenerator } from '../dom/index';
56
import Node from './shared/Node';
@@ -43,7 +44,7 @@ export default class Attribute {
4344

4445
render(block: Block) {
4546
const node = this.parent;
46-
const name = this.name;
47+
const name = fixAttributeCasing(this.name);
4748

4849
if (name === 'style') {
4950
const styleProps = optimizeStyle(this.value);
@@ -539,6 +540,7 @@ const attributeLookup = {
539540
'textarea',
540541
],
541542
},
543+
volume: { appliesTo: ['audio', 'video'] },
542544
width: {
543545
appliesTo: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'],
544546
},
@@ -662,4 +664,4 @@ function getStyleValue(chunks: Node[]) {
662664

663665
function isDynamic(value: Node[]) {
664666
return value.length > 1 || value[0].type !== 'Text';
665-
}
667+
}

src/generators/nodes/AwaitBlock.ts

+8
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ export default class AwaitBlock extends Node {
101101
block.addVariable(promise);
102102
block.addVariable(resolved);
103103

104+
// the `#component.root.set({})` below is just a cheap way to flush
105+
// any oncreate handlers. We could have a dedicated `flush()` method
106+
// but it's probably not worth it
107+
104108
block.builders.init.addBlock(deindent`
105109
function ${replace_await_block}(${token}, type, ${value}, ${params}) {
106110
if (${token} !== ${await_token}) return;
@@ -113,6 +117,8 @@ export default class AwaitBlock extends Node {
113117
${old_block}.d();
114118
${await_block}.c();
115119
${await_block}.m(${updateMountNode}, ${anchor});
120+
121+
#component.root.set({});
116122
}
117123
}
118124
@@ -121,8 +127,10 @@ export default class AwaitBlock extends Node {
121127
122128
if (@isPromise(${promise})) {
123129
${promise}.then(function(${value}) {
130+
var state = #component.get();
124131
${replace_await_block}(${token}, ${create_then_block}, ${value}, ${params});
125132
}, function (${error}) {
133+
var state = #component.get();
126134
${replace_await_block}(${token}, ${create_catch_block}, ${error}, ${params});
127135
});
128136

src/generators/nodes/Binding.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,11 @@ export default class Binding extends Node {
7373
);
7474
}
7575

76-
if (this.name === 'currentTime') {
76+
if (this.name === 'currentTime' || this.name === 'volume') {
7777
updateCondition = `!isNaN(${snippet})`;
78-
initialUpdate = null;
78+
79+
if (this.name === 'currentTime')
80+
initialUpdate = null;
7981
}
8082

8183
if (this.name === 'paused') {
@@ -267,4 +269,4 @@ function isComputed(node: Node) {
267269
}
268270

269271
return false;
270-
}
272+
}

src/generators/nodes/Element.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import flattenReference from '../../utils/flattenReference';
44
import isVoidElementName from '../../utils/isVoidElementName';
55
import validCalleeObjects from '../../utils/validCalleeObjects';
66
import reservedNames from '../../utils/reservedNames';
7+
import fixAttributeCasing from '../../utils/fixAttributeCasing';
78
import Node from './shared/Node';
89
import Block from '../dom/Block';
910
import Attribute from './Attribute';
@@ -427,12 +428,12 @@ export default class Element extends Node {
427428
}
428429

429430
node.attributes.forEach((attr: Node) => {
430-
open += ` ${attr.name}${stringifyAttributeValue(attr.value)}`
431+
open += ` ${fixAttributeCasing(attr.name)}${stringifyAttributeValue(attr.value)}`
431432
});
432433

433434
if (isVoidElementName(node.name)) return open + '>';
434435

435-
if (node.name === 'script' || node.name === 'style') {
436+
if (node.name === 'script') {
436437
return `${open}>${node.data}</${node.name}>`;
437438
}
438439

@@ -759,5 +760,11 @@ const events = [
759760
filter: (node: Element, name: string) =>
760761
node.isMediaNode() &&
761762
(name === 'buffered' || name === 'seekable')
763+
},
764+
{
765+
eventNames: ['volumechange'],
766+
filter: (node: Element, name: string) =>
767+
node.isMediaNode() &&
768+
name === 'volume'
762769
}
763770
];

src/generators/server-side-rendering/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ export default function ssr(
114114
}
115115
116116
var result = { head: '', addComponent };
117-
${templateProperties.store && `options.store = %store();`}
118117
var html = ${name}._render(result, state, options);
119118
120119
var cssCode = Array.from(components).map(c => c.css && c.css.code).filter(Boolean).join('\\n');
@@ -130,6 +129,7 @@ export default function ssr(
130129
}
131130
132131
${name}._render = function(__result, state, options) {
132+
${templateProperties.store && `options.store = %store();`}
133133
__result.addComponent(${name});
134134
135135
state = Object.assign(${initialState.join(', ')});

src/generators/server-side-rendering/visitors/Element.ts

+17-9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import Element from '../../nodes/Element';
77
import Block from '../Block';
88
import { Node } from '../../../interfaces';
99
import stringifyAttributeValue from './shared/stringifyAttributeValue';
10+
import { escape } from '../../../utils/stringify';
11+
12+
// source: https://gist.github.com/ArjanSchouten/0b8574a6ad7f5065a5e7
13+
const booleanAttributes = new Set('async autocomplete autofocus autoplay border challenge checked compact contenteditable controls default defer disabled formnovalidate frameborder hidden indeterminate ismap loop multiple muted nohref noresize noshade novalidate nowrap open readonly required reversed scoped scrolling seamless selected sortable spellcheck translate'.split(' '));
1014

1115
export default function visitElement(
1216
generator: SsrGenerator,
@@ -35,14 +39,18 @@ export default function visitElement(
3539

3640
if (attribute.name === 'value' && node.name === 'textarea') {
3741
textareaContents = stringifyAttributeValue(block, attribute.value);
42+
} else if (attribute.value === true) {
43+
openingTag += ` ${attribute.name}`;
44+
} else if (
45+
booleanAttributes.has(attribute.name) &&
46+
attribute.value.length === 1 &&
47+
attribute.value[0].type !== 'Text'
48+
) {
49+
// a boolean attribute with one non-Text chunk
50+
block.contextualise(attribute.value[0].expression);
51+
openingTag += '${' + attribute.value[0].metadata.snippet + ' ? " ' + attribute.name + '" : "" }';
3852
} else {
39-
let str = ` ${attribute.name}`;
40-
41-
if (attribute.value !== true) {
42-
str += `="${stringifyAttributeValue(block, attribute.value)}"`;
43-
}
44-
45-
openingTag += str;
53+
openingTag += ` ${attribute.name}="${stringifyAttributeValue(block, attribute.value)}"`;
4654
}
4755
});
4856

@@ -60,8 +68,8 @@ export default function visitElement(
6068

6169
if (node.name === 'textarea' && textareaContents !== undefined) {
6270
generator.append(textareaContents);
63-
} else if (node.name === 'script' || node.name === 'style') {
64-
generator.append(node.data);
71+
} else if (node.name === 'script') {
72+
generator.append(escape(node.data));
6573
} else {
6674
node.children.forEach((child: Node) => {
6775
visit(generator, block, child);

0 commit comments

Comments
 (0)