Merge branch 'master' into pr/6032

pull/6032/head
Conduitry 5 years ago
commit 9a0e0313a3

@ -13,6 +13,14 @@ jobs:
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- id: npm-cache-dir
run: echo "::set-output name=dir::$(npm config get cache)"
- uses: actions/cache@v2
id: npm-cache
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-node-
- run: npm install - run: npm install
- run: npm test - run: npm test
env: env:
@ -23,6 +31,14 @@ jobs:
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
- id: npm-cache-dir
run: echo "::set-output name=dir::$(npm config get cache)"
- uses: actions/cache@v2
id: npm-cache
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-node-
- run: 'npm i && npm run lint' - run: 'npm i && npm run lint'
Unit: Unit:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@ -33,4 +49,12 @@ jobs:
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
- id: npm-cache-dir
run: echo "::set-output name=dir::$(npm config get cache)"
- uses: actions/cache@v2
id: npm-cache
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-node-
- run: 'npm i && npm run test:unit' - run: 'npm i && npm run test:unit'

@ -1,11 +1,13 @@
# Svelte changelog # Svelte changelog
## Unreleased ## 3.36.0
* Add `this: void` typing to store functions ([#6094](https://github.com/sveltejs/svelte/pull/6094)) * Add `this: void` typing to store functions ([#6094](https://github.com/sveltejs/svelte/pull/6094))
* Export `Spring`, `Tweened` and `EasingFunction` interfaces ([#6070](https://github.com/sveltejs/svelte/issues/6070), [#6056](https://github.com/sveltejs/svelte/pull/6056)) * Export `Spring`, `Tweened` and `EasingFunction` interfaces ([#6070](https://github.com/sveltejs/svelte/issues/6070), [#6056](https://github.com/sveltejs/svelte/pull/6056))
* Export interfaces for transition parameters ([#5207](https://github.com/sveltejs/svelte/issues/5207)) * Export interfaces for transition parameters ([#5207](https://github.com/sveltejs/svelte/issues/5207))
* Export store's useful TypeScript definitions ([#5864](https://github.com/sveltejs/svelte/issues/5864)) * Export store's useful TypeScript definitions ([#5864](https://github.com/sveltejs/svelte/issues/5864))
* Fix previous breaking change to `svelte/preprocess` types location ([#6100](https://github.com/sveltejs/svelte/pull/6100))
* Fix missing slotted elements in AST ([#6066](https://github.com/sveltejs/svelte/issues/6066))
## 3.35.0 ## 3.35.0

8
package-lock.json generated

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.35.0", "version": "3.36.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -4797,9 +4797,9 @@
"dev": true "dev": true
}, },
"y18n": { "y18n": {
"version": "4.0.0", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
"dev": true "dev": true
}, },
"yallist": { "yallist": {

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.35.0", "version": "3.36.0",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"module": "index.mjs", "module": "index.mjs",
"main": "index", "main": "index",

@ -1328,6 +1328,28 @@ Named slots allow consumers to target specific areas. They can also have fallbac
</Widget> </Widget>
``` ```
Components can be placed in a named slot using the syntax `<Component slot="name" />`.
In order to place content in a slot without using a wrapper element, you can use the special element `<svelte:fragment>`.
```sv
<!-- Widget.svelte -->
<div>
<slot name="header">No header was provided</slot>
<p>Some content between header and footer</p>
<slot name="footer"></slot>
</div>
<!-- App.svelte -->
<Widget>
<HeaderComponent slot="header" />
<svelte:fragment slot="footer">
<p>All rights reserved.</p>
<p>Copyright (c) 2019 Svelte Industries</p>
</svelte:fragment>
</Widget>
```
#### [`$$slots`](slots_object) #### [`$$slots`](slots_object)
--- ---
@ -1537,3 +1559,25 @@ The `<svelte:options>` element provides a place to specify per-component compile
```sv ```sv
<svelte:options tag="my-custom-element"/> <svelte:options tag="my-custom-element"/>
``` ```
### `<svelte:fragment>`
The `<svelte:fragment>` element allows you to place content in a [named slot](docs#slot_name) without wrapping it in a container DOM element. This keeps the flow layout of your document intact.
```sv
<!-- Widget.svelte -->
<div>
<slot name="header">No header was provided</slot>
<p>Some content between header and footer</p>
<slot name="footer"></slot>
</div>
<!-- App.svelte -->
<Widget>
<h1 slot="header">Hello</h1>
<svelte:fragment slot="footer">
<p>All rights reserved.</p>
<p>Copyright (c) 2019 Svelte Industries</p>
</svelte:fragment>
</Widget>
```

@ -2,7 +2,7 @@
question: How do I test Svelte apps? question: How do I test Svelte apps?
--- ---
We recommend trying to seperate your view logic from your business logic. Data transformation or cross component state management is best kept outside of Svelte components. You can test those parts like you would test any JavaScript functionality that way. When it comes to testing the components, it is best to test the logic of the component and remember that the Svelte library has its own tests and you do not need to test implementation details provided by Svelte. We recommend trying to separate your view logic from your business logic. Data transformation or cross component state management is best kept outside of Svelte components. You can test those parts like you would test any JavaScript functionality that way. When it comes to testing the components, it is best to test the logic of the component and remember that the Svelte library has its own tests and you do not need to test implementation details provided by Svelte.
There are a few approaches that people take when testing, but it generally involves compiling the component and mounting it to something and then performing the tests. You essentially need to create a bundle for each component you're testing (since svelte is a compiler and not a normal library) and then mount them. You can mount to a JSDOM instance. Or you can use a real browser powered by a library like Playwright, Puppeteer, or Cypress. There are a few approaches that people take when testing, but it generally involves compiling the component and mounting it to something and then performing the tests. You essentially need to create a bundle for each component you're testing (since svelte is a compiler and not a normal library) and then mount them. You can mount to a JSDOM instance. Or you can use a real browser powered by a library like Playwright, Puppeteer, or Cypress.

@ -2,7 +2,7 @@
title: beforeUpdate and afterUpdate title: beforeUpdate and afterUpdate
--- ---
The `beforeUpdate` function schedules work to happen immediately before the DOM has been updated. `afterUpdate` is its counterpart, used for running code once the DOM is in sync with your data. The `beforeUpdate` function schedules work to happen immediately before the DOM is updated. `afterUpdate` is its counterpart, used for running code once the DOM is in sync with your data.
Together, they're useful for doing things imperatively that are difficult to achieve in a purely state-driven way, like updating the scroll position of an element. Together, they're useful for doing things imperatively that are difficult to achieve in a purely state-driven way, like updating the scroll position of an element.

@ -0,0 +1,10 @@
<script>
import Box from './Box.svelte'
</script>
<Box>
<div slot="footer">
<p>All rights reserved.</p>
<p>Copyright (c) 2019 Svelte Industries</p>
</div>
</Box>

@ -0,0 +1,20 @@
<style>
.box {
width: 300px;
border: 1px solid #aaa;
border-radius: 2px;
box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
padding: 1em;
margin: 0 0 1em 0;
display: flex;
flex-direction: column;
gap: 1em;
}
</style>
<div class="box">
<slot name="header">No header was provided</slot>
<p>Some content between header and footer</p>
<slot name="footer"></slot>
</div>

@ -0,0 +1,10 @@
<script>
import Box from './Box.svelte'
</script>
<Box>
<svelte:fragment slot="footer">
<p>All rights reserved.</p>
<p>Copyright (c) 2019 Svelte Industries</p>
</svelte:fragment>
</Box>

@ -0,0 +1,20 @@
<style>
.box {
width: 300px;
border: 1px solid #aaa;
border-radius: 2px;
box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
padding: 1em;
margin: 0 0 1em 0;
display: flex;
flex-direction: column;
gap: 1em;
}
</style>
<div class="box">
<slot name="header">No header was provided</slot>
<p>Some content between header and footer</p>
<slot name="footer"></slot>
</div>

@ -0,0 +1,35 @@
---
title: <svelte:fragment>
---
The `<svelte:fragment>` element allows you to place content in a named slot without wrapping it in a container DOM element. This keeps the flow layout of your document intact.
In the example notice how we applied a flex layout with a gap of `1em` to the box.
```sv
<!-- Box.svelte -->
<style>
.box {
display: flex;
flex-direction: column;
gap: 1em;
}
</style>
<div class="box">
<slot name="header">No header was provided</slot>
<p>Some content between header and footer</p>
<slot name="footer"></slot>
</div>
```
However, the content in the footer is not spaced out according to this rhythm because wrapping it in a div created a new flow layout.
We can solve this by changing `<div slot="footer">` in the `App` component. Replace the `<div>` with `<svelte:fragment>`:
```sv
<svelte:fragment slot="footer">
<p>All rights reserved.</p>
<p>Copyright (c) 2019 Svelte Industries</p>
</svelte:fragment>
```

@ -198,7 +198,6 @@
.chapter-markup::-webkit-scrollbar-thumb { .chapter-markup::-webkit-scrollbar-thumb {
background-color: rgba(255,255,255,.7); background-color: rgba(255,255,255,.7);
border-radius: 1em; border-radius: 1em;
outline: 1px solid green;
} }
.chapter-markup :global(p) > :global(code), .chapter-markup :global(p) > :global(code),

@ -32,6 +32,7 @@ import { is_reserved_keyword } from './utils/reserved_keywords';
import { apply_preprocessor_sourcemap } from '../utils/mapped_code'; import { apply_preprocessor_sourcemap } from '../utils/mapped_code';
import Element from './nodes/Element'; import Element from './nodes/Element';
import { DecodedSourceMap, RawSourceMap } from '@ampproject/remapping/dist/types/types'; import { DecodedSourceMap, RawSourceMap } from '@ampproject/remapping/dist/types/types';
import { clone } from '../utils/clone';
interface ComponentOptions { interface ComponentOptions {
namespace?: string; namespace?: string;
@ -116,12 +117,12 @@ export default class Component {
// the instance JS gets mutated, so we park // the instance JS gets mutated, so we park
// a copy here for later. TODO this feels gross // a copy here for later. TODO this feels gross
this.original_ast = { this.original_ast = clone({
html: ast.html, html: ast.html,
css: ast.css, css: ast.css,
instance: ast.instance && JSON.parse(JSON.stringify(ast.instance)), instance: ast.instance,
module: ast.module module: ast.module
}; });
this.file = this.file =
compile_options.filename && compile_options.filename &&

@ -9,6 +9,7 @@ import { TemplateNode } from '../../interfaces';
import Element from './Element'; import Element from './Element';
import InlineComponent from './InlineComponent'; import InlineComponent from './InlineComponent';
import Window from './Window'; import Window from './Window';
import { clone } from '../../utils/clone';
// TODO this should live in a specific binding // TODO this should live in a specific binding
const read_only_media_attributes = new Set([ const read_only_media_attributes = new Set([
@ -42,7 +43,7 @@ export default class Binding extends Node {
this.name = info.name; this.name = info.name;
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);
this.raw_expression = JSON.parse(JSON.stringify(info.expression)); this.raw_expression = clone(info.expression);
const { name } = get_object(this.expression.node); const { name } = get_object(this.expression.node);

@ -2,6 +2,7 @@ import { x } from 'code-red';
import { Node, Identifier, Expression } from 'estree'; import { Node, Identifier, Expression } from 'estree';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import is_reference from 'is-reference'; import is_reference from 'is-reference';
import { clone } from '../../../utils/clone';
export interface Context { export interface Context {
key: Identifier; key: Identifier;
@ -81,7 +82,7 @@ function update_reference(contexts: Context[], n: number, expression: Expression
} }
// NOTE: avoid unnecessary deep clone? // NOTE: avoid unnecessary deep clone?
expression = JSON.parse(JSON.stringify(expression)) as Expression; expression = clone(expression) as Expression;
walk(expression, { walk(expression, {
enter(node, parent: Node) { enter(node, parent: Node) {
if (is_reference(node, parent)) { if (is_reference(node, parent)) {

@ -16,6 +16,7 @@ import { is_reserved_keyword } from '../../utils/reserved_keywords';
import replace_object from '../../utils/replace_object'; import replace_object from '../../utils/replace_object';
import is_contextual from './is_contextual'; import is_contextual from './is_contextual';
import EachBlock from '../EachBlock'; import EachBlock from '../EachBlock';
import { clone } from '../../../utils/clone';
type Owner = INode; type Owner = INode;
@ -195,7 +196,7 @@ export default class Expression {
const node = walk(this.node, { const node = walk(this.node, {
enter(node: any, parent: any) { enter(node: any, parent: any) {
if (node.type === 'Property' && node.shorthand) { if (node.type === 'Property' && node.shorthand) {
node.value = JSON.parse(JSON.stringify(node.value)); node.value = clone(node.value);
node.shorthand = false; node.shorthand = false;
} }

@ -5,6 +5,8 @@ import { decode_map } from './decode_sourcemap';
import { replace_in_code, slice_source } from './replace_in_code'; import { replace_in_code, slice_source } from './replace_in_code';
import { MarkupPreprocessor, Source, Preprocessor, PreprocessorGroup, Processed } from './types'; import { MarkupPreprocessor, Source, Preprocessor, PreprocessorGroup, Processed } from './types';
export * from './types';
interface SourceUpdate { interface SourceUpdate {
string?: string; string?: string;
map?: DecodedSourceMap; map?: DecodedSourceMap;

@ -0,0 +1,33 @@
// adapted from klona v2.0.4 - https://github.com/lukeed/klona
// (c) Luke Edwards, under MIT License
// The sole modification is to skip function values in objects when cloning, so we don't break tests.
export function clone(val) {
let k, out, tmp;
if (Array.isArray(val)) {
out = Array(k=val.length);
while (k--) out[k] = (tmp=val[k]) && typeof tmp === 'object' ? clone(tmp) : tmp;
return out;
}
if (Object.prototype.toString.call(val) === '[object Object]') {
out = {}; // null
for (k in val) {
if (k === '__proto__') {
Object.defineProperty(out, k, {
value: clone(val[k]),
configurable: true,
enumerable: true,
writable: true
});
} else if (typeof val[k] !== 'function') { // MODIFICATION: skip functions
out[k] = (tmp=val[k]) && typeof tmp === 'object' ? clone(tmp) : tmp;
}
}
return out;
}
return val;
}

@ -0,0 +1 @@
<Component><div slot='foo'></div></Component>

@ -0,0 +1,42 @@
{
"html": {
"start": 0,
"end": 45,
"type": "Fragment",
"children": [
{
"start": 0,
"end": 45,
"type": "InlineComponent",
"name": "Component",
"attributes": [],
"children": [
{
"start": 11,
"end": 33,
"type": "Element",
"name": "div",
"attributes": [
{
"start": 16,
"end": 26,
"type": "Attribute",
"name": "slot",
"value": [
{
"start": 22,
"end": 25,
"type": "Text",
"raw": "foo",
"data": "foo"
}
]
}
],
"children": []
}
]
}
]
}
}

@ -9,50 +9,44 @@
"end": 61, "end": 61,
"type": "Element", "type": "Element",
"name": "textarea", "name": "textarea",
"attributes": [ "attributes": [],
"children": [
{ {
"type": "Attribute", "start": 10,
"name": "value", "end": 41,
"value": [ "type": "Text",
{ "raw": "\n\t<p>not actually an element. ",
"start": 10, "data": "\n\t<p>not actually an element. "
"end": 41, },
"type": "Text", {
"raw": "\n\t<p>not actually an element. ", "start": 40,
"data": "\n\t<p>not actually an element. " "end": 45,
}, "type": "MustacheTag",
{ "expression": {
"start": 40, "type": "Identifier",
"end": 45, "start": 41,
"type": "MustacheTag", "end": 44,
"expression": { "loc": {
"type": "Identifier", "start": {
"start": 41, "line": 2,
"end": 44, "column": 30
"loc": { },
"start": { "end": {
"line": 2, "line": 2,
"column": 30 "column": 33
},
"end": {
"line": 2,
"column": 33
}
},
"name": "foo"
} }
}, },
{ "name": "foo"
"start": 45, }
"end": 50, },
"type": "Text", {
"raw": "</p>\n", "start": 45,
"data": "</p>\n" "end": 50,
} "type": "Text",
] "raw": "</p>\n",
"data": "</p>\n"
} }
], ]
"children": []
} }
] ]
} }

Loading…
Cancel
Save