From 7c1e6a6ce7cce68f41a7628d6b59340673e05046 Mon Sep 17 00:00:00 2001 From: halfnelson Date: Sun, 25 Oct 2020 22:35:10 +1000 Subject: [PATCH 01/44] refactor sourcemap and preprocessor tests (#5583) Co-authored-by: Milan Hauth --- .gitignore | 4 - package.json | 2 +- test/preprocess/index.ts | 9 +- test/preprocess/samples/filename/_config.js | 4 +- test/sourcemaps/index.ts | 108 ++++++++++++------ test/sourcemaps/samples/basic/test.js | 12 +- .../samples/binding-shorthand.skip/test.js | 13 ++- test/sourcemaps/samples/binding/test.js | 12 +- test/sourcemaps/samples/css/test.js | 10 +- test/sourcemaps/samples/each-block/test.js | 10 +- .../samples/script-after-comment/test.js | 10 +- test/sourcemaps/samples/script/test.js | 8 +- .../samples/static-no-script/test.js | 12 +- test/sourcemaps/samples/two-scripts/test.js | 8 +- 14 files changed, 133 insertions(+), 89 deletions(-) diff --git a/.gitignore b/.gitignore index f7fac04eba..a471c3aaa2 100644 --- a/.gitignore +++ b/.gitignore @@ -18,10 +18,6 @@ node_modules /coverage/ /coverage.lcov /test/*/samples/_ -/test/sourcemaps/samples/*/output.js -/test/sourcemaps/samples/*/output.js.map -/test/sourcemaps/samples/*/output.css -/test/sourcemaps/samples/*/output.css.map /yarn-error.log _actual*.* _output diff --git a/package.json b/package.json index 6d25f68a25..c2b15d50d0 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "posttest": "agadoo internal/index.mjs", "prepublishOnly": "npm run lint && PUBLISH=true npm test", "tsd": "tsc -p src/compiler --emitDeclarationOnly && tsc -p src/runtime --emitDeclarationOnly", - "lint": "eslint '{src,test}/**/*.{ts,js}'" + "lint": "eslint \"{src,test}/**/*.{ts,js}\"" }, "repository": { "type": "git", diff --git a/test/preprocess/index.ts b/test/preprocess/index.ts index abb45012ce..60d3acbabb 100644 --- a/test/preprocess/index.ts +++ b/test/preprocess/index.ts @@ -8,16 +8,21 @@ describe('preprocess', () => { const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); const solo = config.solo || /\.solo/.test(dir); + const skip = config.skip || /\.skip/.test(dir); if (solo && process.env.CI) { throw new Error('Forgot to remove `solo: true` from test'); } - (config.skip ? it.skip : solo ? it.only : it)(dir, async () => { + (skip ? it.skip : solo ? it.only : it)(dir, async () => { const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8'); const expected = fs.readFileSync(`${__dirname}/samples/${dir}/output.svelte`, 'utf-8'); - const result = await svelte.preprocess(input, config.preprocess); + const result = await svelte.preprocess( + input, + config.preprocess || {}, + config.options || { filename: 'input.svelte' } + ); fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, result.code); assert.equal(result.code, expected); diff --git a/test/preprocess/samples/filename/_config.js b/test/preprocess/samples/filename/_config.js index 2656a835d7..161f084730 100644 --- a/test/preprocess/samples/filename/_config.js +++ b/test/preprocess/samples/filename/_config.js @@ -1,6 +1,5 @@ export default { preprocess: { - filename: 'file.svelte', markup: ({ content, filename }) => { return { code: content.replace('__MARKUP_FILENAME__', filename) @@ -16,5 +15,8 @@ export default { code: content.replace('__SCRIPT_FILENAME__', filename) }; } + }, + options: { + filename: 'file.svelte' } }; diff --git a/test/sourcemaps/index.ts b/test/sourcemaps/index.ts index 2302685bae..7659948744 100644 --- a/test/sourcemaps/index.ts +++ b/test/sourcemaps/index.ts @@ -1,73 +1,113 @@ import * as fs from 'fs'; import * as path from 'path'; import * as assert from 'assert'; -import { svelte } from '../helpers'; -import { SourceMapConsumer } from 'source-map'; +import { loadConfig, svelte } from '../helpers'; +// keep source-map at version 0.7.x +// https://github.com/mozilla/source-map/issues/400 import { getLocator } from 'locate-character'; +import { SourceMapConsumer } from 'source-map'; + describe('sourcemaps', () => { fs.readdirSync(`${__dirname}/samples`).forEach(dir => { if (dir[0] === '.') return; + const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); + // add .solo to a sample directory name to only run that test - const solo = /\.solo/.test(dir); - const skip = /\.skip/.test(dir); + const solo = config.solo || /\.solo/.test(dir); + const skip = config.skip || /\.skip/.test(dir); if (solo && process.env.CI) { throw new Error('Forgot to remove `solo: true` from test'); } (solo ? it.only : skip ? it.skip : it)(dir, async () => { - const filename = path.resolve( - `${__dirname}/samples/${dir}/input.svelte` - ); - const outputFilename = path.resolve( - `${__dirname}/samples/${dir}/output` - ); + const { test } = require(`./samples/${dir}/test.js`); + const inputFile = path.resolve(`${__dirname}/samples/${dir}/input.svelte`); + const outputName = '_actual'; + const outputBase = path.resolve(`${__dirname}/samples/${dir}/${outputName}`); + + const inputCode = fs.readFileSync(inputFile, 'utf-8'); + const input = { + code: inputCode, + locate: getLocator(inputCode) + }; - const input = fs.readFileSync(filename, 'utf-8').replace(/\s+$/, ''); - const { js, css } = svelte.compile(input, { - filename, - outputFilename: `${outputFilename}.js`, - cssOutputFilename: `${outputFilename}.css` + const preprocessed = await svelte.preprocess( + input.code, + config.preprocess || {}, + { + filename: 'input.svelte' + } + ); + + const { js, css } = svelte.compile( + preprocessed.code, { + filename: 'input.svelte', + // filenames for sourcemaps + outputFilename: `${outputName}.js`, + cssOutputFilename: `${outputName}.css` }); - const _code = js.code.replace(/Svelte v\d+\.\d+\.\d+/, match => match.replace(/\d/g, 'x')); + js.code = js.code.replace( + /generated by Svelte v\d+\.\d+\.\d+/, + match => match.replace(/\d/g, 'x') + ); + fs.writeFileSync(`${outputBase}.svelte`, preprocessed.code); + if (preprocessed.map) { + fs.writeFileSync( + `${outputBase}.svelte.map`, + // TODO encode mappings for output - svelte.preprocess returns decoded mappings + JSON.stringify(preprocessed.map, null, 2) + ); + } fs.writeFileSync( - `${outputFilename}.js`, - `${_code}\n//# sourceMappingURL=output.js.map` + `${outputBase}.js`, + `${js.code}\n//# sourceMappingURL=${outputName}.js.map` ); fs.writeFileSync( - `${outputFilename}.js.map`, - JSON.stringify(js.map, null, ' ') + `${outputBase}.js.map`, + JSON.stringify(js.map, null, 2) ); - if (css.code) { fs.writeFileSync( - `${outputFilename}.css`, - `${css.code}\n/*# sourceMappingURL=output.css.map */` + `${outputBase}.css`, + `${css.code}\n/*# sourceMappingURL=${outputName}.css.map */` ); fs.writeFileSync( - `${outputFilename}.css.map`, + `${outputBase}.css.map`, JSON.stringify(css.map, null, ' ') ); } - assert.deepEqual(js.map.sources, ['input.svelte']); - if (css.map) assert.deepEqual(css.map.sources, ['input.svelte']); - - const { test } = require(`./samples/${dir}/test.js`); + assert.deepEqual( + js.map.sources.slice().sort(), + (config.js_map_sources || ['input.svelte']).sort() + ); + if (css.map) { + assert.deepEqual( + css.map.sources.slice().sort(), + (config.css_map_sources || ['input.svelte']).sort() + ); + } - const locateInSource = getLocator(input); + // use locate_1 with mapConsumer: + // lines are one-based, columns are zero-based - const smc = await new SourceMapConsumer(js.map); - const locateInGenerated = getLocator(_code); + preprocessed.mapConsumer = preprocessed.map && await new SourceMapConsumer(preprocessed.map); + preprocessed.locate = getLocator(preprocessed.code); + preprocessed.locate_1 = getLocator(preprocessed.code, { offsetLine: 1 }); - const smcCss = css.map && await new SourceMapConsumer(css.map); - const locateInGeneratedCss = getLocator(css.code || ''); + js.mapConsumer = js.map && await new SourceMapConsumer(js.map); + js.locate = getLocator(js.code); + js.locate_1 = getLocator(js.code, { offsetLine: 1 }); - test({ assert, code: _code, map: js.map, smc, smcCss, locateInSource, locateInGenerated, locateInGeneratedCss }); + css.mapConsumer = css.map && await new SourceMapConsumer(css.map); + css.locate = getLocator(css.code || ''); + css.locate_1 = getLocator(css.code || '', { offsetLine: 1 }); + test({ assert, input, preprocessed, js, css }); }); }); }); diff --git a/test/sourcemaps/samples/basic/test.js b/test/sourcemaps/samples/basic/test.js index 7059761674..34c619c128 100644 --- a/test/sourcemaps/samples/basic/test.js +++ b/test/sourcemaps/samples/basic/test.js @@ -1,12 +1,12 @@ -export function test({ assert, smc, locateInSource, locateInGenerated }) { - const expected = locateInSource('foo.bar.baz'); +export function test({ assert, input, js }) { + const expected = input.locate('foo.bar.baz'); let start; let actual; - start = locateInGenerated('ctx[0].bar.baz'); + start = js.locate('ctx[0].bar.baz'); - actual = smc.originalPositionFor({ + actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); @@ -18,9 +18,9 @@ export function test({ assert, smc, locateInSource, locateInGenerated }) { column: expected.column }); - start = locateInGenerated('ctx[0].bar.baz', start.character + 1); + start = js.locate('ctx[0].bar.baz', start.character + 1); - actual = smc.originalPositionFor({ + actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); diff --git a/test/sourcemaps/samples/binding-shorthand.skip/test.js b/test/sourcemaps/samples/binding-shorthand.skip/test.js index cdfbbdc091..13ecdbf889 100644 --- a/test/sourcemaps/samples/binding-shorthand.skip/test.js +++ b/test/sourcemaps/samples/binding-shorthand.skip/test.js @@ -1,13 +1,14 @@ -export function test({ assert, smc, locateInSource, locateInGenerated }) { - const expected = locateInSource('potato'); +export function test({ assert, input, js }) { + const expected = input.locate('potato'); let start; - start = locateInGenerated('potato'); - start = locateInGenerated('potato', start.character + 1); - start = locateInGenerated('potato', start.character + 1); // we need the third instance of 'potato' + start = js.locate('potato'); + start = js.locate('potato', start.character + 1); + start = js.locate('potato', start.character + 1); + // we need the third instance of 'potato' - const actual = smc.originalPositionFor({ + const actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); diff --git a/test/sourcemaps/samples/binding/test.js b/test/sourcemaps/samples/binding/test.js index 31c0e98442..3cb3246e50 100644 --- a/test/sourcemaps/samples/binding/test.js +++ b/test/sourcemaps/samples/binding/test.js @@ -1,12 +1,12 @@ -export function test({ assert, smc, locateInSource, locateInGenerated }) { - const expected = locateInSource('bar.baz'); +export function test({ assert, input, js }) { + const expected = input.locate('bar.baz'); let start; let actual; - start = locateInGenerated('bar.baz'); + start = js.locate('bar.baz'); - actual = smc.originalPositionFor({ + actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); @@ -18,9 +18,9 @@ export function test({ assert, smc, locateInSource, locateInGenerated }) { column: expected.column }); - start = locateInGenerated('bar.baz', start.character + 1); + start = js.locate('bar.baz', start.character + 1); - actual = smc.originalPositionFor({ + actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); diff --git a/test/sourcemaps/samples/css/test.js b/test/sourcemaps/samples/css/test.js index 54082bd4ee..1e0dda1dff 100644 --- a/test/sourcemaps/samples/css/test.js +++ b/test/sourcemaps/samples/css/test.js @@ -1,14 +1,14 @@ -export function test({ assert, smcCss, locateInSource, locateInGeneratedCss }) { - const expected = locateInSource( '.foo' ); +export function test({ assert, input, css }) { + const expected = input.locate('.foo'); - const start = locateInGeneratedCss( '.foo' ); + const start = css.locate('.foo'); - const actual = smcCss.originalPositionFor({ + const actual = css.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); - assert.deepEqual( actual, { + assert.deepEqual(actual, { source: 'input.svelte', name: null, line: expected.line + 1, diff --git a/test/sourcemaps/samples/each-block/test.js b/test/sourcemaps/samples/each-block/test.js index 35479986a5..08b37686a4 100644 --- a/test/sourcemaps/samples/each-block/test.js +++ b/test/sourcemaps/samples/each-block/test.js @@ -1,10 +1,10 @@ -export function test({ assert, code, smc, map, locateInSource, locateInGenerated }) { - const startIndex = code.indexOf('create_main_fragment'); +export function test({ assert, input, js }) { + const startIndex = js.code.indexOf('create_main_fragment'); - const expected = locateInSource('each'); - const start = locateInGenerated('length', startIndex); + const expected = input.locate('each'); + const start = js.locate('length', startIndex); - const actual = smc.originalPositionFor({ + const actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); diff --git a/test/sourcemaps/samples/script-after-comment/test.js b/test/sourcemaps/samples/script-after-comment/test.js index b473970112..06ecc46929 100644 --- a/test/sourcemaps/samples/script-after-comment/test.js +++ b/test/sourcemaps/samples/script-after-comment/test.js @@ -1,13 +1,13 @@ -export function test({ assert, smc, locateInSource, locateInGenerated }) { - const expected = locateInSource( 'assertThisLine' ); - const start = locateInGenerated( 'assertThisLine' ); +export function test({ assert, input, js }) { + const expected = input.locate('assertThisLine'); + const start = js.locate('assertThisLine'); - const actual = smc.originalPositionFor({ + const actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); - assert.deepEqual( actual, { + assert.deepEqual(actual, { source: 'input.svelte', name: null, line: expected.line + 1, diff --git a/test/sourcemaps/samples/script/test.js b/test/sourcemaps/samples/script/test.js index 73971ef487..e6a91f51e1 100644 --- a/test/sourcemaps/samples/script/test.js +++ b/test/sourcemaps/samples/script/test.js @@ -1,8 +1,8 @@ -export function test({ assert, smc, locateInSource, locateInGenerated }) { - const expected = locateInSource( '42' ); - const start = locateInGenerated( '42' ); +export function test({ assert, input, js }) { + const expected = input.locate('42'); + const start = js.locate('42'); - const actual = smc.originalPositionFor({ + const actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); diff --git a/test/sourcemaps/samples/static-no-script/test.js b/test/sourcemaps/samples/static-no-script/test.js index a8f4d89ab8..c683c94d6b 100644 --- a/test/sourcemaps/samples/static-no-script/test.js +++ b/test/sourcemaps/samples/static-no-script/test.js @@ -1,9 +1,9 @@ -const fs = require( 'fs' ); -const path = require( 'path' ); +const fs = require('fs'); +const path = require('path'); -export function test({ assert, map }) { - assert.deepEqual( map.sources, [ 'input.svelte' ]); - assert.deepEqual( map.sourcesContent, [ - fs.readFileSync( path.join( __dirname, 'input.svelte' ), 'utf-8' ) +export function test({ assert, js }) { + assert.deepEqual(js.map.sources, ['input.svelte']); + assert.deepEqual(js.map.sourcesContent, [ + fs.readFileSync(path.join(__dirname, 'input.svelte'), 'utf-8') ]); } diff --git a/test/sourcemaps/samples/two-scripts/test.js b/test/sourcemaps/samples/two-scripts/test.js index b473970112..70901af8c9 100644 --- a/test/sourcemaps/samples/two-scripts/test.js +++ b/test/sourcemaps/samples/two-scripts/test.js @@ -1,8 +1,8 @@ -export function test({ assert, smc, locateInSource, locateInGenerated }) { - const expected = locateInSource( 'assertThisLine' ); - const start = locateInGenerated( 'assertThisLine' ); +export function test({ assert, input, js }) { + const expected = input.locate( 'assertThisLine' ); + const start = js.locate( 'assertThisLine' ); - const actual = smc.originalPositionFor({ + const actual = js.mapConsumer.originalPositionFor({ line: start.line + 1, column: start.column }); From 7ea6a2994a2a35e96e47d843250cc61c1314ec39 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Mon, 26 Oct 2020 10:25:03 -0700 Subject: [PATCH 02/44] Curly braces linting fixes (#5585) --- src/compiler/compile/Component.ts | 21 ++++++++++++------- src/compiler/compile/nodes/Binding.ts | 20 +++++++++++------- .../render_dom/wrappers/Element/Attribute.ts | 3 ++- src/compiler/parse/index.ts | 3 ++- src/compiler/parse/read/script.ts | 10 +++++---- src/compiler/parse/state/mustache.ts | 10 +++++---- src/compiler/utils/fuzzymatch.ts | 3 ++- src/runtime/motion/spring.ts | 6 ++++-- test/hydration/index.ts | 8 ++++--- 9 files changed, 52 insertions(+), 32 deletions(-) diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index c16862165c..d2542c9830 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -521,8 +521,7 @@ export default class Component { if (this.hoistable_nodes.has(node)) return false; if (this.reactive_declaration_nodes.has(node)) return false; if (node.type === 'ImportDeclaration') return false; - if (node.type === 'ExportDeclaration' && node.specifiers.length > 0) - return false; + if (node.type === 'ExportDeclaration' && node.specifiers.length > 0) return false; return true; }); } @@ -1038,8 +1037,9 @@ export default class Component { this.vars.find( variable => variable.name === name && variable.module ) - ) + ) { return false; + } return true; }); @@ -1288,8 +1288,9 @@ export default class Component { declaration.dependencies.forEach(name => { if (declaration.assignees.has(name)) return; const earlier_declarations = lookup.get(name); - if (earlier_declarations) + if (earlier_declarations) { earlier_declarations.forEach(add_declaration); + } }); this.reactive_declarations.push(declaration); @@ -1319,8 +1320,9 @@ export default class Component { if (globals.has(name) && node.type !== 'InlineComponent') return; let message = `'${name}' is not defined`; - if (!this.ast.instance) + if (!this.ast.instance) { message += `. Consider adding a + +

name: {name}

+

$$props: {JSON.stringify($$props)}

+

$$restProps: {JSON.stringify($$restProps)}

+ diff --git a/test/custom-elements/samples/$$props/test.js b/test/custom-elements/samples/$$props/test.js new file mode 100644 index 0000000000..94cad86577 --- /dev/null +++ b/test/custom-elements/samples/$$props/test.js @@ -0,0 +1,13 @@ +import * as assert from 'assert'; +import './main.svelte'; + +export default function (target) { + target.innerHTML = ''; + const el = target.querySelector('custom-element'); + + assert.htmlEqual(el.shadowRoot.innerHTML, ` +

name: world

+

$$props: {"name":"world","answer":"42","test":"svelte"}

+

$$restProps: {"answer":"42","test":"svelte"}

+ `); +} diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js index a0a0ebe021..82a39e5924 100644 --- a/test/js/samples/css-shadow-dom-keyframes/expected.js +++ b/test/js/samples/css-shadow-dom-keyframes/expected.js @@ -1,6 +1,7 @@ /* generated by Svelte vX.Y.Z */ import { SvelteElement, + attribute_to_object, detach, element, init, @@ -34,7 +35,18 @@ class Component extends SvelteElement { constructor(options) { super(); this.shadowRoot.innerHTML = ``; - init(this, { target: this.shadowRoot }, null, create_fragment, safe_not_equal, {}); + + init( + this, + { + target: this.shadowRoot, + props: attribute_to_object(this.attributes) + }, + null, + create_fragment, + safe_not_equal, + {} + ); if (options) { if (options.target) { From 5d7ffdb8a7231a772da6b259a8f2c78a4007c53b Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Fri, 30 Oct 2020 03:15:54 +0800 Subject: [PATCH 08/44] fix function slot props based on context (#5607) --- CHANGELOG.md | 1 + .../compile/nodes/shared/Expression.ts | 65 ++++++++++++++----- .../compile/render_dom/wrappers/EachBlock.ts | 27 ++++---- .../Nested.svelte | 14 ++++ .../_config.js | 36 ++++++++++ .../main.svelte | 8 +++ .../Nested.svelte | 11 ++++ .../_config.js | 19 ++++++ .../main.svelte | 8 +++ .../Inner.svelte | 9 +++ .../Nested.svelte | 8 +++ .../_config.js | 19 ++++++ .../main.svelte | 8 +++ 13 files changed, 202 insertions(+), 31 deletions(-) create mode 100644 test/runtime/samples/component-slot-context-props-each-nested/Nested.svelte create mode 100644 test/runtime/samples/component-slot-context-props-each-nested/_config.js create mode 100644 test/runtime/samples/component-slot-context-props-each-nested/main.svelte create mode 100644 test/runtime/samples/component-slot-context-props-each/Nested.svelte create mode 100644 test/runtime/samples/component-slot-context-props-each/_config.js create mode 100644 test/runtime/samples/component-slot-context-props-each/main.svelte create mode 100644 test/runtime/samples/component-slot-context-props-let/Inner.svelte create mode 100644 test/runtime/samples/component-slot-context-props-let/Nested.svelte create mode 100644 test/runtime/samples/component-slot-context-props-let/_config.js create mode 100644 test/runtime/samples/component-slot-context-props-let/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index cfff198368..4afd7f20e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * Fix `$$props` and `$$restProps` when compiling to a custom element ([#5482](https://github.com/sveltejs/svelte/issues/5482)) +* Fix function calls in `` props that use contextual values ([#5565](https://github.com/sveltejs/svelte/issues/5565)) * Add `Element` and `Node` to known globals ([#5586](https://github.com/sveltejs/svelte/issues/5586)) ## 3.29.4 diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts index 027e962e29..ef134f4359 100644 --- a/src/compiler/compile/nodes/shared/Expression.ts +++ b/src/compiler/compile/nodes/shared/Expression.ts @@ -4,7 +4,6 @@ import is_reference from 'is-reference'; import flatten_reference from '../../utils/flatten_reference'; import { create_scopes, Scope, extract_names } from '../../utils/scope'; import { sanitize } from '../../../utils/names'; -import Wrapper from '../../render_dom/wrappers/shared/Wrapper'; import TemplateScope from './TemplateScope'; import get_object from '../../utils/get_object'; import Block from '../../render_dom/Block'; @@ -12,12 +11,12 @@ import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic'; import { b } from 'code-red'; import { invalidate } from '../../render_dom/invalidate'; import { Node, FunctionExpression, Identifier } from 'estree'; -import { TemplateNode } from '../../../interfaces'; +import { INode } from '../interfaces'; import { is_reserved_keyword } from '../../utils/reserved_keywords'; import replace_object from '../../utils/replace_object'; import EachBlock from '../EachBlock'; -type Owner = Wrapper | TemplateNode; +type Owner = INode; export default class Expression { type: 'Expression' = 'Expression'; @@ -37,7 +36,6 @@ export default class Expression { manipulated: Node; - // todo: owner type constructor(component: Component, owner: Owner, template_scope: TemplateScope, info, lazy?: boolean) { // TODO revert to direct property access in prod? Object.defineProperties(this, { @@ -276,10 +274,12 @@ export default class Expression { else { // we need a combo block/init recipe const deps = Array.from(contextual_dependencies); + const function_expression = node as FunctionExpression; - (node as FunctionExpression).params = [ + const has_args = function_expression.params.length > 0; + function_expression.params = [ ...deps.map(name => ({ type: 'Identifier', name } as Identifier)), - ...(node as FunctionExpression).params + ...function_expression.params ]; const context_args = deps.map(name => block.renderer.reference(name)); @@ -291,18 +291,49 @@ export default class Expression { this.replace(id as any); - if ((node as FunctionExpression).params.length > 0) { - declarations.push(b` - function ${id}(...args) { - return ${callee}(${context_args}, ...args); - } - `); + const func_declaration = has_args + ? b`function ${id}(...args) { + return ${callee}(${context_args}, ...args); + }` + : b`function ${id}() { + return ${callee}(${context_args}); + }`; + + if (owner.type === 'Attribute' && owner.parent.name === 'slot') { + const dep_scopes = new Set(deps.map(name => template_scope.get_owner(name))); + // find the nearest scopes + let node: INode = owner.parent; + while (node && !dep_scopes.has(node)) { + node = node.parent; + } + + const func_expression = func_declaration[0]; + + if (node.type === 'InlineComponent') { + // + this.replace(func_expression); + } else { + // {#each}, {#await} + const func_id = component.get_unique_name(id.name + '_func'); + block.renderer.add_to_context(func_id.name, true); + // rename #ctx -> child_ctx; + walk(func_expression, { + enter(node) { + if (node.type === 'Identifier' && node.name === '#ctx') { + node.name = 'child_ctx'; + } + } + }); + // add to get_xxx_context + // child_ctx[x] = function () { ... } + (template_scope.get_owner(deps[0]) as EachBlock).contexts.push({ + key: func_id, + modifier: () => func_expression + }); + this.replace(block.renderer.reference(func_id)); + } } else { - declarations.push(b` - function ${id}() { - return ${callee}(${context_args}); - } - `); + declarations.push(func_declaration); } } diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index b31d2bcba7..a2c20e0074 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -196,11 +196,6 @@ export default class EachBlockWrapper extends Wrapper { ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node(); - this.context_props = this.node.contexts.map(prop => b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.modifier(x`list[i]`)};`); - - 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;`); - const snippet = this.node.expression.manipulate(block); block.chunks.init.push(b`let ${this.vars.each_block_value} = ${snippet};`); @@ -208,15 +203,6 @@ export default class EachBlockWrapper extends Wrapper { block.chunks.init.push(b`@validate_each_argument(${this.vars.each_block_value});`); } - // TODO which is better — Object.create(array) or array.slice()? - renderer.blocks.push(b` - function ${this.vars.get_each_context}(#ctx, list, i) { - const child_ctx = #ctx.slice(); - ${this.context_props} - return child_ctx; - } - `); - const initial_anchor_node: Identifier = { type: 'Identifier', name: parent_node ? 'null' : '#anchor' }; const initial_mount_node: Identifier = parent_node || { type: 'Identifier', name: '#target' }; const update_anchor_node = needs_anchor @@ -360,6 +346,19 @@ export default class EachBlockWrapper extends Wrapper { if (this.else) { 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]`)};`); + + 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;`); + // TODO which is better — Object.create(array) or array.slice()? + renderer.blocks.push(b` + function ${this.vars.get_each_context}(#ctx, list, i) { + const child_ctx = #ctx.slice(); + ${this.context_props} + return child_ctx; + } + `); } render_keyed({ diff --git a/test/runtime/samples/component-slot-context-props-each-nested/Nested.svelte b/test/runtime/samples/component-slot-context-props-each-nested/Nested.svelte new file mode 100644 index 0000000000..fab3ae1890 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-each-nested/Nested.svelte @@ -0,0 +1,14 @@ + + +{#each items as item (item)} + {#each keys as key (key)} + setKey(key, value, item)} /> + {/each} +{/each} \ No newline at end of file diff --git a/test/runtime/samples/component-slot-context-props-each-nested/_config.js b/test/runtime/samples/component-slot-context-props-each-nested/_config.js new file mode 100644 index 0000000000..869cc555b1 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-each-nested/_config.js @@ -0,0 +1,36 @@ +export default { + html: ` + + + + + `, + async test({ assert, target, window, component }) { + const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button'); + const click = new window.MouseEvent('click'); + + await btn1.dispatchEvent(click); + assert.deepEqual(component.log, ['setKey(a, value-a-c, c)']); + + await btn2.dispatchEvent(click); + assert.deepEqual(component.log, [ + 'setKey(a, value-a-c, c)', + 'setKey(b, value-b-c, c)' + ]); + + await btn3.dispatchEvent(click); + assert.deepEqual(component.log, [ + 'setKey(a, value-a-c, c)', + 'setKey(b, value-b-c, c)', + 'setKey(a, value-a-d, d)' + ]); + + await btn4.dispatchEvent(click); + assert.deepEqual(component.log, [ + 'setKey(a, value-a-c, c)', + 'setKey(b, value-b-c, c)', + 'setKey(a, value-a-d, d)', + 'setKey(b, value-b-d, d)' + ]); + } +}; diff --git a/test/runtime/samples/component-slot-context-props-each-nested/main.svelte b/test/runtime/samples/component-slot-context-props-each-nested/main.svelte new file mode 100644 index 0000000000..7feb55aaf6 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-each-nested/main.svelte @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-context-props-each/Nested.svelte b/test/runtime/samples/component-slot-context-props-each/Nested.svelte new file mode 100644 index 0000000000..a29ffe0376 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-each/Nested.svelte @@ -0,0 +1,11 @@ + + +{#each keys as key (key)} + setKey(key, value)} /> +{/each} \ No newline at end of file diff --git a/test/runtime/samples/component-slot-context-props-each/_config.js b/test/runtime/samples/component-slot-context-props-each/_config.js new file mode 100644 index 0000000000..f374e7f827 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-each/_config.js @@ -0,0 +1,19 @@ +export default { + html: ` + + + `, + async test({ assert, target, window, component }) { + const [btn1, btn2] = target.querySelectorAll('button'); + const click = new window.MouseEvent('click'); + + await btn1.dispatchEvent(click); + assert.deepEqual(component.log, ['setKey(a, value-a)']); + + await btn2.dispatchEvent(click); + assert.deepEqual(component.log, [ + 'setKey(a, value-a)', + 'setKey(b, value-b)' + ]); + } +}; diff --git a/test/runtime/samples/component-slot-context-props-each/main.svelte b/test/runtime/samples/component-slot-context-props-each/main.svelte new file mode 100644 index 0000000000..7dc17b4c95 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-each/main.svelte @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-context-props-let/Inner.svelte b/test/runtime/samples/component-slot-context-props-let/Inner.svelte new file mode 100644 index 0000000000..6beaabbb68 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-let/Inner.svelte @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-context-props-let/Nested.svelte b/test/runtime/samples/component-slot-context-props-let/Nested.svelte new file mode 100644 index 0000000000..97c44c5145 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-let/Nested.svelte @@ -0,0 +1,8 @@ + + + + set(key, value)} /> + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-context-props-let/_config.js b/test/runtime/samples/component-slot-context-props-let/_config.js new file mode 100644 index 0000000000..f374e7f827 --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-let/_config.js @@ -0,0 +1,19 @@ +export default { + html: ` + + + `, + async test({ assert, target, window, component }) { + const [btn1, btn2] = target.querySelectorAll('button'); + const click = new window.MouseEvent('click'); + + await btn1.dispatchEvent(click); + assert.deepEqual(component.log, ['setKey(a, value-a)']); + + await btn2.dispatchEvent(click); + assert.deepEqual(component.log, [ + 'setKey(a, value-a)', + 'setKey(b, value-b)' + ]); + } +}; diff --git a/test/runtime/samples/component-slot-context-props-let/main.svelte b/test/runtime/samples/component-slot-context-props-let/main.svelte new file mode 100644 index 0000000000..a061f73b3a --- /dev/null +++ b/test/runtime/samples/component-slot-context-props-let/main.svelte @@ -0,0 +1,8 @@ + + + + + From 169fd8497c6dc97f989ccf5b564f69769f5bf335 Mon Sep 17 00:00:00 2001 From: Cameron Messinides Date: Thu, 29 Oct 2020 16:24:39 -0400 Subject: [PATCH 09/44] docs: add documentation for $$slots (#5277) --- site/content/docs/02-template-syntax.md | 25 ++++++++ .../04-optional-slots/app-a/App.svelte | 57 +++++++++++++++++ .../04-optional-slots/app-a/Comment.svelte | 56 ++++++++++++++++ .../04-optional-slots/app-a/Project.svelte | 62 ++++++++++++++++++ .../04-optional-slots/app-b/App.svelte | 57 +++++++++++++++++ .../04-optional-slots/app-b/Comment.svelte | 56 ++++++++++++++++ .../04-optional-slots/app-b/Project.svelte | 64 +++++++++++++++++++ .../14-composition/04-optional-slots/text.md | 28 ++++++++ .../app-a/App.svelte | 0 .../app-a/Hoverable.svelte | 0 .../app-b/App.svelte | 0 .../app-b/Hoverable.svelte | 0 .../{04-slot-props => 05-slot-props}/text.md | 0 13 files changed, 405 insertions(+) create mode 100755 site/content/tutorial/14-composition/04-optional-slots/app-a/App.svelte create mode 100755 site/content/tutorial/14-composition/04-optional-slots/app-a/Comment.svelte create mode 100755 site/content/tutorial/14-composition/04-optional-slots/app-a/Project.svelte create mode 100755 site/content/tutorial/14-composition/04-optional-slots/app-b/App.svelte create mode 100755 site/content/tutorial/14-composition/04-optional-slots/app-b/Comment.svelte create mode 100755 site/content/tutorial/14-composition/04-optional-slots/app-b/Project.svelte create mode 100644 site/content/tutorial/14-composition/04-optional-slots/text.md rename site/content/tutorial/14-composition/{04-slot-props => 05-slot-props}/app-a/App.svelte (100%) rename site/content/tutorial/14-composition/{04-slot-props => 05-slot-props}/app-a/Hoverable.svelte (100%) rename site/content/tutorial/14-composition/{04-slot-props => 05-slot-props}/app-b/App.svelte (100%) rename site/content/tutorial/14-composition/{04-slot-props => 05-slot-props}/app-b/Hoverable.svelte (100%) rename site/content/tutorial/14-composition/{04-slot-props => 05-slot-props}/text.md (100%) diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index d955f650e2..119488aacc 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -1317,6 +1317,31 @@ Named slots allow consumers to target specific areas. They can also have fallbac ``` +#### [`$$slots`](slots_object) + +--- + +`$$slots` is an object whose keys are the names of the slots passed into the component by the parent. If the parent does not pass in a slot with a particular name, that name will not be a present in `$$slots`. This allows components to render a slot (and other elements, like wrappers for styling) only if the parent provides it. + +Note that explicitly passing in an empty named slot will add that slot's name to `$$slots`. For example, if a parent passes `
` to a child component, `$$slots.title` will be truthy within the child. + +```sv + + +

Blog Post Title

+
+ + +
+ + {#if $$slots.description} + +
+ + {/if} +
+``` + #### [``](slot_let) --- diff --git a/site/content/tutorial/14-composition/04-optional-slots/app-a/App.svelte b/site/content/tutorial/14-composition/04-optional-slots/app-a/App.svelte new file mode 100755 index 0000000000..80a4df59f8 --- /dev/null +++ b/site/content/tutorial/14-composition/04-optional-slots/app-a/App.svelte @@ -0,0 +1,57 @@ + + + + +

+ Projects +

+ +
    +
  • + +
    + +

    Those interface tests are now passing.

    +
    +
    +
    +
  • +
  • + +
  • +
diff --git a/site/content/tutorial/14-composition/04-optional-slots/app-a/Comment.svelte b/site/content/tutorial/14-composition/04-optional-slots/app-a/Comment.svelte new file mode 100755 index 0000000000..8d15ffa882 --- /dev/null +++ b/site/content/tutorial/14-composition/04-optional-slots/app-a/Comment.svelte @@ -0,0 +1,56 @@ + + + + +
+
+ +
+

{name}

+ +
+
+
+ +
+
diff --git a/site/content/tutorial/14-composition/04-optional-slots/app-a/Project.svelte b/site/content/tutorial/14-composition/04-optional-slots/app-a/Project.svelte new file mode 100755 index 0000000000..38d19fc638 --- /dev/null +++ b/site/content/tutorial/14-composition/04-optional-slots/app-a/Project.svelte @@ -0,0 +1,62 @@ + + + + +
+
+

{title}

+

{tasksCompleted}/{totalTasks} tasks completed

+
+
+

Comments

+ +
+
diff --git a/site/content/tutorial/14-composition/04-optional-slots/app-b/App.svelte b/site/content/tutorial/14-composition/04-optional-slots/app-b/App.svelte new file mode 100755 index 0000000000..80a4df59f8 --- /dev/null +++ b/site/content/tutorial/14-composition/04-optional-slots/app-b/App.svelte @@ -0,0 +1,57 @@ + + + + +

+ Projects +

+ +
    +
  • + +
    + +

    Those interface tests are now passing.

    +
    +
    +
    +
  • +
  • + +
  • +
diff --git a/site/content/tutorial/14-composition/04-optional-slots/app-b/Comment.svelte b/site/content/tutorial/14-composition/04-optional-slots/app-b/Comment.svelte new file mode 100755 index 0000000000..8d15ffa882 --- /dev/null +++ b/site/content/tutorial/14-composition/04-optional-slots/app-b/Comment.svelte @@ -0,0 +1,56 @@ + + + + +
+
+ +
+

{name}

+ +
+
+
+ +
+
diff --git a/site/content/tutorial/14-composition/04-optional-slots/app-b/Project.svelte b/site/content/tutorial/14-composition/04-optional-slots/app-b/Project.svelte new file mode 100755 index 0000000000..87a1d0c3ac --- /dev/null +++ b/site/content/tutorial/14-composition/04-optional-slots/app-b/Project.svelte @@ -0,0 +1,64 @@ + + + + +
+
+

{title}

+

{tasksCompleted}/{totalTasks} tasks completed

+
+ {#if $$slots.comments} +
+

Comments

+ +
+ {/if} +
diff --git a/site/content/tutorial/14-composition/04-optional-slots/text.md b/site/content/tutorial/14-composition/04-optional-slots/text.md new file mode 100644 index 0000000000..b875f4c87d --- /dev/null +++ b/site/content/tutorial/14-composition/04-optional-slots/text.md @@ -0,0 +1,28 @@ +--- +title: Checking for slot content +--- + +In some cases, you may want to control parts of your component based on whether the parent passes in content for a certain slot. Perhaps you have a wrapper around that slot, and you don't want to render it if the slot is empty. Or perhaps you'd like to apply a class only if the slot is present. You can do this by checking the properties of the special `$$slots` variable. + +`$$slots` is an object whose keys are the names of the slots passed in by the parent component. If the parent leaves a slot empty, then `$$slots` will not have an entry for that slot. + +Notice that both instances of `` in this example render a container for comments and a notification dot, even though only one has comments. We want to use `$$slots` to make sure we only render these elements when the parent `` passes in content for the `comments` slot. + +In `Project.svelte`, update the `class:has-discussion` directive on the `
`: + +```html +
+``` + +Next, wrap the `comments` slot and its wrapping `
` in an `if` block that checks `$$slots`: + +```html +{#if $$slots.comments} +
+

Comments

+ +
+{/if} +``` + +Now the comments container and the notification dot won't render when `` leaves the `comments` slot empty. diff --git a/site/content/tutorial/14-composition/04-slot-props/app-a/App.svelte b/site/content/tutorial/14-composition/05-slot-props/app-a/App.svelte similarity index 100% rename from site/content/tutorial/14-composition/04-slot-props/app-a/App.svelte rename to site/content/tutorial/14-composition/05-slot-props/app-a/App.svelte diff --git a/site/content/tutorial/14-composition/04-slot-props/app-a/Hoverable.svelte b/site/content/tutorial/14-composition/05-slot-props/app-a/Hoverable.svelte similarity index 100% rename from site/content/tutorial/14-composition/04-slot-props/app-a/Hoverable.svelte rename to site/content/tutorial/14-composition/05-slot-props/app-a/Hoverable.svelte diff --git a/site/content/tutorial/14-composition/04-slot-props/app-b/App.svelte b/site/content/tutorial/14-composition/05-slot-props/app-b/App.svelte similarity index 100% rename from site/content/tutorial/14-composition/04-slot-props/app-b/App.svelte rename to site/content/tutorial/14-composition/05-slot-props/app-b/App.svelte diff --git a/site/content/tutorial/14-composition/04-slot-props/app-b/Hoverable.svelte b/site/content/tutorial/14-composition/05-slot-props/app-b/Hoverable.svelte similarity index 100% rename from site/content/tutorial/14-composition/04-slot-props/app-b/Hoverable.svelte rename to site/content/tutorial/14-composition/05-slot-props/app-b/Hoverable.svelte diff --git a/site/content/tutorial/14-composition/04-slot-props/text.md b/site/content/tutorial/14-composition/05-slot-props/text.md similarity index 100% rename from site/content/tutorial/14-composition/04-slot-props/text.md rename to site/content/tutorial/14-composition/05-slot-props/text.md From 148b6105ed6862953ac2466e5bf5c8f9c792c468 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Fri, 30 Oct 2020 04:32:12 +0800 Subject: [PATCH 10/44] fix else block transition update (#5591) --- CHANGELOG.md | 1 + .../compile/render_dom/wrappers/IfBlock.ts | 2 ++ .../samples/transition-abort/_config.js | 31 +++++++++++++++++++ .../samples/transition-abort/main.svelte | 21 +++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 test/runtime/samples/transition-abort/_config.js create mode 100644 test/runtime/samples/transition-abort/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 4afd7f20e0..a3fb361e5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Fix `$$props` and `$$restProps` when compiling to a custom element ([#5482](https://github.com/sveltejs/svelte/issues/5482)) * Fix function calls in `` props that use contextual values ([#5565](https://github.com/sveltejs/svelte/issues/5565)) +* Fix handling aborted transitions in `{:else}` blocks ([#5573](https://github.com/sveltejs/svelte/issues/5573)) * Add `Element` and `Node` to known globals ([#5586](https://github.com/sveltejs/svelte/issues/5586)) ## 3.29.4 diff --git a/src/compiler/compile/render_dom/wrappers/IfBlock.ts b/src/compiler/compile/render_dom/wrappers/IfBlock.ts index 0cb31036e6..a95f64f4d2 100644 --- a/src/compiler/compile/render_dom/wrappers/IfBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/IfBlock.ts @@ -447,6 +447,8 @@ export default class IfBlockWrapper extends Wrapper { if (!${name}) { ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#ctx); ${name}.c(); + } else { + ${name}.p(#ctx, #dirty); } ${has_transitions && b`@transition_in(${name}, 1);`} ${name}.m(${update_mount_node}, ${anchor}); diff --git a/test/runtime/samples/transition-abort/_config.js b/test/runtime/samples/transition-abort/_config.js new file mode 100644 index 0000000000..4f31c44a4d --- /dev/null +++ b/test/runtime/samples/transition-abort/_config.js @@ -0,0 +1,31 @@ +// expect aborting halfway through outro transition +// to behave the same in `{#if}` block as in `{:else}` block +export default { + html: ` +
a
+ +
a
+ `, + + async test({ assert, component, target, window, raf }) { + component.visible = false; + + // abort halfway through the outro transition + raf.tick(50); + + await component.$set({ + visible: true, + array: ['a', 'b', 'c'] + }); + + assert.htmlEqual(target.innerHTML, ` +
a
+
b
+
c
+ +
a
+
b
+
c
+ `); + } +}; diff --git a/test/runtime/samples/transition-abort/main.svelte b/test/runtime/samples/transition-abort/main.svelte new file mode 100644 index 0000000000..b574229712 --- /dev/null +++ b/test/runtime/samples/transition-abort/main.svelte @@ -0,0 +1,21 @@ + + +{#if visible} + {#each array as item} +
{item}
+ {/each} +{/if} + +{#if !visible} +{:else} + {#each array as item} +
{item}
+ {/each} +{/if} \ No newline at end of file From 33d1fc741ad0c36f43c6b27ebedc20218b413594 Mon Sep 17 00:00:00 2001 From: Daniel Sandoval Date: Sun, 1 Nov 2020 01:13:10 -0800 Subject: [PATCH 11/44] "What's new in Svelte" November newsletter (#5554) * outline and showcase so far * respond to feedback, fill out features * Update site/content/blog/2020-11-01-whats-new-in-svelte-november-2020.md Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * Update site/content/blog/2020-11-01-whats-new-in-svelte-november-2020.md Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> * Update number of speakers Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- ...11-01-whats-new-in-svelte-november-2020.md | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 site/content/blog/2020-11-01-whats-new-in-svelte-november-2020.md diff --git a/site/content/blog/2020-11-01-whats-new-in-svelte-november-2020.md b/site/content/blog/2020-11-01-whats-new-in-svelte-november-2020.md new file mode 100644 index 0000000000..15813dfa69 --- /dev/null +++ b/site/content/blog/2020-11-01-whats-new-in-svelte-november-2020.md @@ -0,0 +1,46 @@ +--- +title: What's new in Svelte: November 2020 +description: Slot forwarding fixes, SvelteKit for faster local development, and more from Svelte Summit +author: Daniel Sandoval +authorURL: https://desandoval.net +--- + +Welcome back to the "What's new in Svelte" series! This month, we're covering new features & bug fixes, last month's Svelte Summit and some stand-out sites and libraries... + +## New features & impactful bug fixes + +1. Destructuring Promises now works as expected by using the `{#await}` syntax + (**3.29.3**, [Example](https://svelte.dev/repl/3fd4e2cecfa14d629961478f1dac2445?version=3.29.3)) +2. Slot forwarding (released in 3.29.0) should no longer hang during compilation (**3.29.3**, [Example](https://svelte.dev/repl/29959e70103f4868a6525c0734934936?version=3.29.3)) +3. Better typings for the `get` function in `svelte/store` and on lifecycle hooks (**3.29.1**) + +**What's going on in Sapper?** + +Sapper got some new types in its `preload` function, which will make typing easier if you are using TypeScript. See the [Sapper docs](https://sapper.svelte.dev/docs#Typing_the_function) on how to use them. There also were fixes to `preload` links in exported sites. Route layouts got a few fixes too - including ensuring CSS is applied to nested route layouts. You can also better organize your files now that extensions with multiple dots are supported. (**0.28.10**) + + +For all the features and bugfixes see the CHANGELOGs for [Svelte](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md) and [Sapper](https://github.com/sveltejs/sapper/blob/master/CHANGELOG.md). + + +## [Svelte Summit](https://sveltesummit.com/) was Svelte-tacular! +- Rich Harris demoed the possible future of Svelte development in a talk titled "Futuristic Web Development". The not-yet-public project is called SvelteKit (name may change) and will bring a first-class developer experience and more flexibility for build outputs. If you want to get the full sneak-peek, [check out the video](https://www.youtube.com/watch?v=qSfdtmcZ4d0). +- 17 speakers made the best of the conference's virtual format... From floating heads to seamless demos, Svelte developers from every skill level will find something of interest in this year's [YouTube playlist](https://www.youtube.com/playlist?list=PL8bMgX1kyZThM1sbYCoWdTcpiYysJsSeu) + +--- + +## Community Showcase +- [Svelte Lab](https://sveltelab.app/) showcases a variety of components, visualizations and interactions that can be acheived in Svelte. You can click into any component to see its source or edit it, using the site's built-in REPL +- [svelte-electron-boilerplate](https://github.com/hjalmar/svelte-electron-boilerplate) is a fast way to get up and running with a Svelte app built in the desktop javascript framework, Electron +- [React Hooks in Svelte](https://github.com/benflap/tabler-icons-svelte) showcases examples of common React Hooks ported to Svelte. +- [gurlic](https://gurlic.com/) is a social network and internet expirement that is super snappy thanks to Svelte +- [Interference 2020](https://interference2020.org/) visualizes reported foreign interference in the 2020 U.S. elections. You can learn more about how it was built in [YYY's talk at Svelte Summit]() +- [jitsi-svelte](https://github.com/relm-us/jitsi-svelte) lets you to easily create your own custom Jitsi client by providing out-of-the-box components built with Svelte +- [Ellx](https://ellx.io/) is part spreadsheet, part notebook and part IDE. It's super smooth thanks to Svelte 😎 +- [This New Zealand news site](https://www.nzherald.co.nz/nz/election-2020-latest-results-party-vote-electorate-vote-and-full-data/5CFVO4ENKNQDE3SICRRNPU5GZM/) breaks down the results of the 2020 Parliamentary elections using Svelte +- [Budibase](https://github.com/Budibase/budibase) is a no-code app builder, powered by Svelte +- [Svelt-yjs](https://github.com/relm-us/svelt-yjs) combines the collaborative, local-first technology of Yjs with the power of Svelte to enable multiple users across the internet to stay in sync. +- [tabler-icons-svelte](https://github.com/benflap/tabler-icons-svelte) is a Svelte wrapper for over 850 free MIT-licensed high-quality SVG icons for you to use in your web projects. + +## See you next month! + +Got an idea for something to add to the Showcase? Want to get involved more with Svelte? We're always looking for maintainers, contributors and fanatics... Check out the [Svelte Society](https://sveltesociety.dev/), [Reddit](https://www.reddit.com/r/sveltejs/) and [Discord](https://discord.com/invite/yy75DKs) to get involved! From 2db9cc2bc9dc2c3e482b7e61ad2726960a64bbb6 Mon Sep 17 00:00:00 2001 From: hmt Date: Mon, 2 Nov 2020 00:51:03 +0100 Subject: [PATCH 12/44] Some typos [ci-skip] (#5632) --- .../blog/2020-11-01-whats-new-in-svelte-november-2020.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/content/blog/2020-11-01-whats-new-in-svelte-november-2020.md b/site/content/blog/2020-11-01-whats-new-in-svelte-november-2020.md index 15813dfa69..4e14c0aec8 100644 --- a/site/content/blog/2020-11-01-whats-new-in-svelte-november-2020.md +++ b/site/content/blog/2020-11-01-whats-new-in-svelte-november-2020.md @@ -29,12 +29,12 @@ For all the features and bugfixes see the CHANGELOGs for [Svelte](https://github --- ## Community Showcase -- [Svelte Lab](https://sveltelab.app/) showcases a variety of components, visualizations and interactions that can be acheived in Svelte. You can click into any component to see its source or edit it, using the site's built-in REPL +- [Svelte Lab](https://sveltelab.app/) showcases a variety of components, visualizations and interactions that can be achieved in Svelte. You can click into any component to see its source or edit it, using the site's built-in REPL - [svelte-electron-boilerplate](https://github.com/hjalmar/svelte-electron-boilerplate) is a fast way to get up and running with a Svelte app built in the desktop javascript framework, Electron - [React Hooks in Svelte](https://github.com/benflap/tabler-icons-svelte) showcases examples of common React Hooks ported to Svelte. -- [gurlic](https://gurlic.com/) is a social network and internet expirement that is super snappy thanks to Svelte +- [gurlic](https://gurlic.com/) is a social network and internet experiment that is super snappy thanks to Svelte - [Interference 2020](https://interference2020.org/) visualizes reported foreign interference in the 2020 U.S. elections. You can learn more about how it was built in [YYY's talk at Svelte Summit]() -- [jitsi-svelte](https://github.com/relm-us/jitsi-svelte) lets you to easily create your own custom Jitsi client by providing out-of-the-box components built with Svelte +- [jitsi-svelte](https://github.com/relm-us/jitsi-svelte) lets you easily create your own custom Jitsi client by providing out-of-the-box components built with Svelte - [Ellx](https://ellx.io/) is part spreadsheet, part notebook and part IDE. It's super smooth thanks to Svelte 😎 - [This New Zealand news site](https://www.nzherald.co.nz/nz/election-2020-latest-results-party-vote-electorate-vote-and-full-data/5CFVO4ENKNQDE3SICRRNPU5GZM/) breaks down the results of the 2020 Parliamentary elections using Svelte - [Budibase](https://github.com/Budibase/budibase) is a no-code app builder, powered by Svelte From fb8ef740fa5396050b473c865b91fad42db64cf7 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 2 Nov 2020 08:41:30 +0100 Subject: [PATCH 13/44] Blog post: Fix link --- .../blog/2020-11-01-whats-new-in-svelte-november-2020.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/blog/2020-11-01-whats-new-in-svelte-november-2020.md b/site/content/blog/2020-11-01-whats-new-in-svelte-november-2020.md index 4e14c0aec8..433bafaa29 100644 --- a/site/content/blog/2020-11-01-whats-new-in-svelte-november-2020.md +++ b/site/content/blog/2020-11-01-whats-new-in-svelte-november-2020.md @@ -31,7 +31,7 @@ For all the features and bugfixes see the CHANGELOGs for [Svelte](https://github ## Community Showcase - [Svelte Lab](https://sveltelab.app/) showcases a variety of components, visualizations and interactions that can be achieved in Svelte. You can click into any component to see its source or edit it, using the site's built-in REPL - [svelte-electron-boilerplate](https://github.com/hjalmar/svelte-electron-boilerplate) is a fast way to get up and running with a Svelte app built in the desktop javascript framework, Electron -- [React Hooks in Svelte](https://github.com/benflap/tabler-icons-svelte) showcases examples of common React Hooks ported to Svelte. +- [React Hooks in Svelte](https://github.com/joshnuss/react-hooks-in-svelte) showcases examples of common React Hooks ported to Svelte. - [gurlic](https://gurlic.com/) is a social network and internet experiment that is super snappy thanks to Svelte - [Interference 2020](https://interference2020.org/) visualizes reported foreign interference in the 2020 U.S. elections. You can learn more about how it was built in [YYY's talk at Svelte Summit]() - [jitsi-svelte](https://github.com/relm-us/jitsi-svelte) lets you easily create your own custom Jitsi client by providing out-of-the-box components built with Svelte From c8334c8b2839a7acea26ef2e6d6fe7c2cfa8cbbd Mon Sep 17 00:00:00 2001 From: Shriji Date: Tue, 3 Nov 2020 10:51:35 +0530 Subject: [PATCH 14/44] Update README.md (#5620) adds number of people online on discord. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9fbada7aa3..d1befaf1ce 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ license - Chat + Chat

From 703ffe94dc358268b2d6d46c505b0f957a5083fa Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 4 Nov 2020 01:00:17 -0500 Subject: [PATCH 15/44] draft blog post about sveltekit (#5638) * draft blog post about sveltekit * make language less scary * mention SvelteKit sooner * expand on TS support * tweak migration section * change publication date * replace video link with an embed * small tweak --- ...020-11-03-whats-the-deal-with-sveltekit.md | 103 ++++++++++++++++++ site/src/routes/blog/[slug].svelte | 13 --- 2 files changed, 103 insertions(+), 13 deletions(-) create mode 100644 site/content/blog/2020-11-03-whats-the-deal-with-sveltekit.md diff --git a/site/content/blog/2020-11-03-whats-the-deal-with-sveltekit.md b/site/content/blog/2020-11-03-whats-the-deal-with-sveltekit.md new file mode 100644 index 0000000000..f780cd0226 --- /dev/null +++ b/site/content/blog/2020-11-03-whats-the-deal-with-sveltekit.md @@ -0,0 +1,103 @@ +--- +title: What's the deal with SvelteKit? +description: We're rethinking how to build Svelte apps. Here's what you need to know +author: Rich Harris +authorURL: https://twitter.com/rich_harris +--- + + + +If you attended [Svelte Summit](https://sveltesummit.com/) last month you may have seen my talk, Futuristic Web Development, in which I finally tackled one of the most frequently asked questions about Svelte: when will Sapper reach version 1.0? + +The answer: never. + +This was slightly tongue-in-cheek — as the talk explains, it's really more of a rewrite of Sapper coupled with a rebrand — but it raised a lot of new questions from the community, and it's time we offered a bit more clarity on what you can expect from Sapper's successor, SvelteKit. + +
+
+
+ +
+ +
'Futuristic Web Development' from Svelte Summit
+
+
+ + +## What's Sapper? + +[Sapper](https://sapper.svelte.dev) is an *app framework* (or 'metaframework') built on top of Svelte (which is a *component* framework). Its job is to make it easy to build Svelte apps with all the modern best practices like server-side rendering (SSR) and code-splitting, and to provide a project structure that makes development productive and fun. It uses *filesystem-based routing* (as popularised by [Next](https://nextjs.org/) and adopted by many other frameworks, albeit with some enhancements) — your project's file structure mirrors the structure of the app itself. + +While the Svelte homepage and documentation encourages you to [degit](https://github.com/Rich-Harris/degit) the [sveltejs/template](https://github.com/sveltejs/template) repo to start building an app, Sapper has long been our recommended way to build apps; this very blog post is (at the time of writing!) rendered with Sapper. + + +## Why are we migrating to something new? + +Firstly, the distinction between [sveltejs/template](https://github.com/sveltejs/template) and [sveltejs/sapper-template](https://github.com/sveltejs/sapper-template) is confusing, particularly to newcomers to Svelte. Having a single recommended way to start building apps with Svelte will bring enormous benefits: we simplify onboarding, reduce the maintenance and support burden, and can potentially begin to explore the new possibilities that are unlocked by having a predictable project structure. (This last part is deliberately vague because it will take time to fully understand what those possibilities are.) + +Aside from all that, we've been tempted by the thought of rewriting Sapper for a while. This is partly because the codebase has become a little unkempt over the years ([Sapper started in 2017](/blog/sapper-towards-the-ideal-web-app-framework)), but mostly because the web has changed a lot recently, and it's time to rethink some of our foundational assumptions. + + +## How is this new thing different? + +The first of those foundational assumptions is that you need to use a module bundler like [webpack](https://webpack.js.org/) or [Rollup](http://rollupjs.org/) to build apps. These tools trace the dependency graph of your application, analysing and transforming code along the way (turning Svelte components to JS modules, for example), in order to create bundles of code that can run anywhere. As the original creator of Rollup, I can attest that it is a surprisingly complex problem with fiendish edge cases. + +You certainly needed a bundler several years ago, because browsers didn't natively support the `import` keyword, but it's much less true today. Right now, we're seeing the rise of the *unbundled development* workflow, which is radically simpler: instead of eagerly bundling your app, a dev server can serve modules (converted to JavaScript, if necessary) *on-demand*, meaning startup is essentially instantaneous however large your app becomes. + +[Snowpack](https://www.snowpack.dev/) is at the vanguard of this movement, and it's what powers SvelteKit. It's astonishingly fast, and has a beautiful development experience (hot module reloading, error overlays and so on), and we've been working closely with the Snowpack team on features like SSR. The hot module reloading is particularly revelatory if you're used to using Sapper with Rollup (which has never had first-class HMR support owing to its architecture, which prioritises the most efficient output). + +That's not to say we're abandoning bundlers altogether. It's still essential to optimise your app for production, and SvelteKit uses Rollup to make your apps as fast and lean as they possibly can be (which includes things like extracting styles into static `.css` files). + +The other foundational assumption is that a server-rendered app needs, well, a server. Sapper effectively has two modes — `sapper build`, which creates a standalone app that has to run on a Node server, and `sapper export` which bakes your app out as a collection of static files suitable for hosting on services like GitHub Pages. + +Static files can go pretty much anywhere, but running a Node server (and monitoring/scaling it etc) is less straightforward. Nowadays we're witnessing a shift towards *serverless platforms*, in which you as the app author don't need to think about the server your code is running on, with all the attendant complexity. You can get Sapper apps running on serverless platforms, thanks to things like [vercel-sapper](https://github.com/thgh/vercel-sapper), but it's certainly not what you'd call idiomatic. + + + +SvelteKit fully embraces the serverless paradigm, and will launch with support for all the major serverless providers, with an 'adapter' API for targeting any platforms that we don't officially cater to. In addition, we'll be able to do partial pre-rendering, which means that static pages can be generated at build time but dynamic ones get rendered on-demand. + + +## When can I start using it? + +If you're feeling brave, you can start right now: + +```bash +npm init svelte@next +``` + +This will scaffold a new project and install the `@sveltejs/kit` CLI, which provides the tools for developing and building an app. + +We don't recommend it though! There are no docs, and we won't be able to offer any form of support. It's also likely to break often. + +The work is being done in a private monorepo while we're still in exploration mode. Our plan is to get a public beta ready and announce it here once we've closed a few issues — the repo itself will remain private at that time, but we'll create a place to collect feedback from the YOLO crowd. After that, we'll work towards a 1.0 release which will involve opening the repo up. + +I'm not going to make any firm promises about timings, because I don't like to break promises. But I *think* we're talking about weeks rather than months. + + +## What if I don't want to use SvelteKit? + +You won't have to — it will always be possible to use Svelte as a standalone package or via a bundler integration like [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte). We think it's essential that you can bend Svelte to fit your workflow, however esoteric, and use third-party app frameworks like [Elder.js](https://github.com/Elderjs/elderjs), [Routify](https://routify.dev/), [Plenti](https://plenti.co/), [Crown](https://crownframework.com/), [JungleJS](https://www.junglejs.org/) and others. + + +## TypeScript? + +Don't worry, we won't launch without full TypeScript support. + + +## How can I migrate my existing Sapper apps? + +For the most part, it should be relatively straightforward to migrate a Sapper codebase. + +There are some unavoidable changes (being able to run on serverless platforms means we need to replace custom `server.js` files and `(req, res) => {...}` functions with more portable equivalents), and we're taking the opportunity to fix a few design flaws, but on the whole a SvelteKit app will feel very familiar to Sapper users. + +Detailed migration guides will accompany the 1.0 launch. + + +## How can I contribute? + +Keep your eyes peeled for announcements about when we'll launch the public beta and open up the repo. (Also, blog post TODO but I would be remiss if I didn't mention that we now have an [OpenCollective](https://opencollective.com/svelte) where you can contribute financially to the project if it's been valuable to you. Many, many thanks to those of you who already have.) + + +## Where can I learn more? + +Follow [@sveltejs](https://twitter.com/sveltejs) and [@SvelteSociety](https://twitter.com/SvelteSociety) on Twitter, and visit [svelte.dev/chat](https://svelte.dev/chat). You should also subscribe to [Svelte Radio](https://www.svelteradio.com/), where Kevin and his co-hosts will grill me about this project on an upcoming episode (and between now and next week when we record it, [reply to this Twitter thread](https://twitter.com/Rich_Harris/status/1323376048571121665) with your additional questions). \ No newline at end of file diff --git a/site/src/routes/blog/[slug].svelte b/site/src/routes/blog/[slug].svelte index b97b5ef136..236d649d56 100644 --- a/site/src/routes/blog/[slug].svelte +++ b/site/src/routes/blog/[slug].svelte @@ -172,17 +172,4 @@ margin: 2em auto; } } - - /* @media (min-width: 1460px) { - .post :global(iframe) { - width: 1360px; - margin: 2em -280px; - } - } - - @media (min-height: 800px) { - .post :global(iframe) { - height: 640px; - } - } */ From 80300f9702a3abea052df096a6fe224dc049e455 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 4 Nov 2020 21:28:35 -0500 Subject: [PATCH 16/44] update pubdate --- ...h-sveltekit.md => 2020-11-05-whats-the-deal-with-sveltekit.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename site/content/blog/{2020-11-03-whats-the-deal-with-sveltekit.md => 2020-11-05-whats-the-deal-with-sveltekit.md} (100%) diff --git a/site/content/blog/2020-11-03-whats-the-deal-with-sveltekit.md b/site/content/blog/2020-11-05-whats-the-deal-with-sveltekit.md similarity index 100% rename from site/content/blog/2020-11-03-whats-the-deal-with-sveltekit.md rename to site/content/blog/2020-11-05-whats-the-deal-with-sveltekit.md From 9745b61aab0a623384b27c0cbbf7f91d929a8bd5 Mon Sep 17 00:00:00 2001 From: Mikail Khan Date: Thu, 5 Nov 2020 22:06:53 -0500 Subject: [PATCH 17/44] fixed typo in each loop animation error (#5648) --- src/compiler/compile/nodes/Animation.ts | 2 +- src/compiler/compile/nodes/EachBlock.ts | 2 +- test/validator/samples/animation-not-in-each/errors.json | 4 ++-- .../validator/samples/animation-not-in-keyed-each/errors.json | 4 ++-- test/validator/samples/animation-siblings/errors.json | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compiler/compile/nodes/Animation.ts b/src/compiler/compile/nodes/Animation.ts index 88c9175d04..3a21a52e5f 100644 --- a/src/compiler/compile/nodes/Animation.ts +++ b/src/compiler/compile/nodes/Animation.ts @@ -27,7 +27,7 @@ export default class Animation extends Node { // TODO can we relax the 'immediate child' rule? component.error(this, { code: 'invalid-animation', - message: 'An element that use the animate directive must be the immediate child of a keyed each block' + message: 'An element that uses the animate directive must be the immediate child of a keyed each block' }); } diff --git a/src/compiler/compile/nodes/EachBlock.ts b/src/compiler/compile/nodes/EachBlock.ts index 38cc976e6f..8b8e7d251a 100644 --- a/src/compiler/compile/nodes/EachBlock.ts +++ b/src/compiler/compile/nodes/EachBlock.ts @@ -61,7 +61,7 @@ export default class EachBlock extends AbstractBlock { const child = this.children.find(child => !!(child as Element).animation); component.error((child as Element).animation, { code: 'invalid-animation', - message: 'An element that use the animate directive must be the sole child of a keyed each block' + message: 'An element that uses the animate directive must be the sole child of a keyed each block' }); } } diff --git a/test/validator/samples/animation-not-in-each/errors.json b/test/validator/samples/animation-not-in-each/errors.json index 67e2ceb7c2..c737617f98 100644 --- a/test/validator/samples/animation-not-in-each/errors.json +++ b/test/validator/samples/animation-not-in-each/errors.json @@ -1,6 +1,6 @@ [{ "code": "invalid-animation", - "message": "An element that use the animate directive must be the immediate child of a keyed each block", + "message": "An element that uses the animate directive must be the immediate child of a keyed each block", "start": { "line": 5, "column": 5, @@ -12,4 +12,4 @@ "character": 55 }, "pos": 44 -}] \ No newline at end of file +}] diff --git a/test/validator/samples/animation-not-in-keyed-each/errors.json b/test/validator/samples/animation-not-in-keyed-each/errors.json index 1081589e6f..3e0b2d3c0c 100644 --- a/test/validator/samples/animation-not-in-keyed-each/errors.json +++ b/test/validator/samples/animation-not-in-keyed-each/errors.json @@ -1,6 +1,6 @@ [{ "code": "invalid-animation", - "message": "An element that use the animate directive must be the immediate child of a keyed each block", + "message": "An element that uses the animate directive must be the immediate child of a keyed each block", "start": { "line": 6, "column": 6, @@ -12,4 +12,4 @@ "character": 80 }, "pos": 69 -}] \ No newline at end of file +}] diff --git a/test/validator/samples/animation-siblings/errors.json b/test/validator/samples/animation-siblings/errors.json index af15113b94..3def56f0a5 100644 --- a/test/validator/samples/animation-siblings/errors.json +++ b/test/validator/samples/animation-siblings/errors.json @@ -1,6 +1,6 @@ [{ "code": "invalid-animation", - "message": "An element that use the animate directive must be the sole child of a keyed each block", + "message": "An element that uses the animate directive must be the sole child of a keyed each block", "start": { "line": 6, "column": 6, @@ -12,4 +12,4 @@ "character": 88 }, "pos": 77 -}] \ No newline at end of file +}] From 240f5410f38a8ee0ca55e56aeb6cb1aac7ae0025 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Sun, 8 Nov 2020 21:44:49 -0800 Subject: [PATCH 18/44] Curly brace cleanup and enforcement (#5647) --- package-lock.json | 4 ++-- package.json | 2 +- src/compiler/compile/Component.ts | 8 ++------ src/compiler/compile/css/Selector.ts | 16 ++++------------ src/compiler/compile/css/Stylesheet.ts | 4 +--- .../compile/css/gather_possible_values.ts | 8 ++------ src/compiler/compile/nodes/Attribute.ts | 4 +--- src/compiler/compile/nodes/Body.ts | 4 +--- src/compiler/compile/nodes/Window.ts | 12 +++--------- src/compiler/compile/nodes/shared/Expression.ts | 8 ++------ .../wrappers/Element/StyleAttribute.ts | 4 +--- .../compile/render_dom/wrappers/Element/index.ts | 16 ++++------------ .../render_dom/wrappers/RawMustacheTag.ts | 4 +--- src/compiler/parse/state/mustache.ts | 6 ++---- src/runtime/internal/keyed_each.ts | 12 +++--------- src/runtime/internal/transitions.ts | 4 +--- 16 files changed, 31 insertions(+), 85 deletions(-) diff --git a/package-lock.json b/package-lock.json index e04cc22175..5d4aa46575 100644 --- a/package-lock.json +++ b/package-lock.json @@ -144,8 +144,8 @@ } }, "@sveltejs/eslint-config": { - "version": "github:sveltejs/eslint-config#54081d752d199dba97db9f578665c87f18469da3", - "from": "github:sveltejs/eslint-config#v5.5.0", + "version": "github:sveltejs/eslint-config#cca8177349dd5a02b19a5865afc4a7066921409a", + "from": "github:sveltejs/eslint-config#v5.6.0", "dev": true }, "@tootallnate/once": { diff --git a/package.json b/package.json index a0f0b9e455..75e5c9e21f 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@rollup/plugin-sucrase": "^3.0.0", "@rollup/plugin-typescript": "^2.0.1", "@rollup/plugin-virtual": "^2.0.0", - "@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.5.0", + "@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.6.0", "@types/mocha": "^7.0.0", "@types/node": "^8.10.53", "@typescript-eslint/eslint-plugin": "^3.0.2", diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index d2542c9830..6e7e41f385 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -267,17 +267,13 @@ export default class Component { this.helpers.set(name, alias); node.name = alias.name; } - } - - else if (node.name[0] !== '#' && !is_valid(node.name)) { + } else if (node.name[0] !== '#' && !is_valid(node.name)) { // this hack allows x`foo.${bar}` where bar could be invalid const literal: Literal = { type: 'Literal', value: node.name }; if (parent.type === 'Property' && key === 'key') { parent.key = literal; - } - - else if (parent.type === 'MemberExpression' && key === 'property') { + } else if (parent.type === 'MemberExpression' && key === 'property') { parent.property = literal; parent.computed = true; } diff --git a/src/compiler/compile/css/Selector.ts b/src/compiler/compile/css/Selector.ts index e20aa43fb6..e910f4333c 100644 --- a/src/compiler/compile/css/Selector.ts +++ b/src/compiler/compile/css/Selector.ts @@ -248,25 +248,17 @@ function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToN if (selector.type === 'ClassSelector') { if (!attribute_matches(node, 'class', name, '~=', false) && !node.classes.some(c => c.name === name)) return BlockAppliesToNode.NotPossible; - } - - else if (selector.type === 'IdSelector') { + } else if (selector.type === 'IdSelector') { if (!attribute_matches(node, 'id', name, '=', false)) return BlockAppliesToNode.NotPossible; - } - - else if (selector.type === 'AttributeSelector') { + } else if (selector.type === 'AttributeSelector') { if ( !(whitelist_attribute_selector.has(node.name.toLowerCase()) && whitelist_attribute_selector.get(node.name.toLowerCase()).has(selector.name.name.toLowerCase())) && !attribute_matches(node, selector.name.name, selector.value && unquote(selector.value), selector.matcher, selector.flags)) { return BlockAppliesToNode.NotPossible; } - } - - else if (selector.type === 'TypeSelector') { + } else if (selector.type === 'TypeSelector') { if (node.name.toLowerCase() !== name.toLowerCase() && name !== '*') return BlockAppliesToNode.NotPossible; - } - - else { + } else { return BlockAppliesToNode.UnknownSelectorType; } } diff --git a/src/compiler/compile/css/Stylesheet.ts b/src/compiler/compile/css/Stylesheet.ts index 5547c6eb0c..b0dab12b19 100644 --- a/src/compiler/compile/css/Stylesheet.ts +++ b/src/compiler/compile/css/Stylesheet.ts @@ -167,9 +167,7 @@ class Atrule { this.children.forEach(child => { child.apply(node); }); - } - - else if (is_keyframes_node(this.node)) { + } else if (is_keyframes_node(this.node)) { this.children.forEach((rule: Rule) => { rule.selectors.forEach(selector => { selector.used = true; diff --git a/src/compiler/compile/css/gather_possible_values.ts b/src/compiler/compile/css/gather_possible_values.ts index 4390e23c8d..e5560e4f87 100644 --- a/src/compiler/compile/css/gather_possible_values.ts +++ b/src/compiler/compile/css/gather_possible_values.ts @@ -5,14 +5,10 @@ export const UNKNOWN = {}; export function gather_possible_values(node: Node, set: Set) { if (node.type === 'Literal') { set.add(node.value); - } - - else if (node.type === 'ConditionalExpression') { + } else if (node.type === 'ConditionalExpression') { gather_possible_values(node.consequent, set); gather_possible_values(node.alternate, set); - } - - else { + } else { set.add(UNKNOWN); } } diff --git a/src/compiler/compile/nodes/Attribute.ts b/src/compiler/compile/nodes/Attribute.ts index 97d2fd7b2e..3844c750a2 100644 --- a/src/compiler/compile/nodes/Attribute.ts +++ b/src/compiler/compile/nodes/Attribute.ts @@ -38,9 +38,7 @@ export default class Attribute extends Node { this.chunks = null; this.is_static = false; - } - - else { + } else { this.name = info.name; this.is_true = info.value === true; this.is_static = true; diff --git a/src/compiler/compile/nodes/Body.ts b/src/compiler/compile/nodes/Body.ts index 74c63b1f57..f004b90e24 100644 --- a/src/compiler/compile/nodes/Body.ts +++ b/src/compiler/compile/nodes/Body.ts @@ -13,9 +13,7 @@ export default class Body extends Node { info.attributes.forEach(node => { if (node.type === 'EventHandler') { this.handlers.push(new EventHandler(component, this, scope, node)); - } - - else { + } else { // TODO there shouldn't be anything else here... } }); diff --git a/src/compiler/compile/nodes/Window.ts b/src/compiler/compile/nodes/Window.ts index d0ad313c6f..fef003c6e4 100644 --- a/src/compiler/compile/nodes/Window.ts +++ b/src/compiler/compile/nodes/Window.ts @@ -28,9 +28,7 @@ export default class Window extends Node { info.attributes.forEach(node => { if (node.type === 'EventHandler') { this.handlers.push(new EventHandler(component, this, scope, node)); - } - - else if (node.type === 'Binding') { + } else if (node.type === 'Binding') { if (node.expression.type !== 'Identifier') { const { parts } = flatten_reference(node.expression); @@ -64,13 +62,9 @@ export default class Window extends Node { } this.bindings.push(new Binding(component, this, scope, node)); - } - - else if (node.type === 'Action') { + } else if (node.type === 'Action') { this.actions.push(new Action(component, this, scope, node)); - } - - else { + } else { // TODO there shouldn't be anything else here... } }); diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts index ef134f4359..1fc23830bd 100644 --- a/src/compiler/compile/nodes/shared/Expression.ts +++ b/src/compiler/compile/nodes/shared/Expression.ts @@ -261,17 +261,13 @@ export default class Expression { hoistable: true, referenced: true }); - } - - else if (contextual_dependencies.size === 0) { + } else if (contextual_dependencies.size === 0) { // function can be hoisted inside the component init component.partly_hoisted.push(declaration); block.renderer.add_to_context(id.name); this.replace(block.renderer.reference(id)); - } - - else { + } else { // we need a combo block/init recipe const deps = Array.from(contextual_dependencies); const function_expression = node as FunctionExpression; diff --git a/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts b/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts index 06ead11a3c..27157f46e4 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts @@ -164,9 +164,7 @@ function get_style_value(chunks: Array) { break; } - } - - else { + } else { value.push(chunk); } } diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index c6b88357a8..d273326a20 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -745,9 +745,7 @@ export default class ElementWrapper extends Wrapper { } block.chunks.destroy.push(b`if (detaching && ${name}) ${name}.end();`); - } - - else { + } else { const intro_name = intro && block.get_unique_name(`${this.var.name}_intro`); const outro_name = outro && block.get_unique_name(`${this.var.name}_outro`); @@ -920,22 +918,16 @@ function to_html(wrappers: Array deltas.get(old_key)) { diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index 343a8c82e4..21d67a12ba 100644 --- a/src/runtime/internal/transitions.ts +++ b/src/runtime/internal/transitions.ts @@ -318,9 +318,7 @@ export function create_bidirectional_transition(node: Element & ElementCSSInline } running_program = null; - } - - else if (now >= running_program.start) { + } else if (now >= running_program.start) { const p = now - running_program.start; t = running_program.a + running_program.d * easing(p / running_program.duration); tick(t, 1 - t); From 4e114d3b0dc0404ed9a71021113b6bfa174457ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20Bru=C3=A8re?= Date: Mon, 9 Nov 2020 15:51:26 +0100 Subject: [PATCH 19/44] docs: remove broken link to easing function (#5635) --- site/content/docs/03-run-time.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md index 3b6df3071d..846e09d774 100644 --- a/site/content/docs/03-run-time.md +++ b/site/content/docs/03-run-time.md @@ -777,7 +777,7 @@ The `flip` function calculates the start and end position of an element and anim * `delay` (`number`, default 0) — milliseconds before starting * `duration` (`number` | `function`, default `d => Math.sqrt(d) * 120`) — see below -* `easing` (`function`, default [`cubicOut`](docs#cubicOut)) — an [easing function](docs#svelte_easing) +* `easing` (`function`, default `cubicOut`) — an [easing function](docs#svelte_easing) `duration` can be be provided as either: From f7bddb23d3ee9ff2feaa52cb1763a17de6103a04 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Mon, 9 Nov 2020 22:57:40 +0800 Subject: [PATCH 20/44] add extension to request to support webpack 5 (#5624) --- CHANGELOG.md | 1 + rollup.config.js | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3fb361e5f..b93a6a7fd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Fix function calls in `` props that use contextual values ([#5565](https://github.com/sveltejs/svelte/issues/5565)) * Fix handling aborted transitions in `{:else}` blocks ([#5573](https://github.com/sveltejs/svelte/issues/5573)) * Add `Element` and `Node` to known globals ([#5586](https://github.com/sveltejs/svelte/issues/5586)) +* Fix internal `import`s so that we're exposing a valid ES module ([#5617](https://github.com/sveltejs/svelte/issues/5617)) ## 3.29.4 diff --git a/rollup.config.js b/rollup.config.js index 82a2b9ae34..e9ee666337 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -30,12 +30,12 @@ export default [ { file: `index.mjs`, format: 'esm', - paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '.')}` + paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '.')}/index.mjs` }, { file: `index.js`, format: 'cjs', - paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '.')}` + paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '.')}/index.js` } ], external, @@ -50,12 +50,12 @@ export default [ { file: `${dir}/index.mjs`, format: 'esm', - paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '..')}` + paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '..')}/index.mjs` }, { file: `${dir}/index.js`, format: 'cjs', - paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '..')}` + paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '..')}/index.js` } ], external, From 7949d8efac308123c5b2e770456d36a680ce9bba Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Mon, 9 Nov 2020 23:11:37 +0800 Subject: [PATCH 21/44] support $$slots in custom elements (#5619) --- CHANGELOG.md | 1 + src/compiler/compile/render_dom/index.ts | 8 ++++- src/runtime/internal/dom.ts | 10 +++++- .../samples/$$slot/main.svelte | 31 +++++++++++++++++++ test/custom-elements/samples/$$slot/test.js | 28 +++++++++++++++++ 5 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 test/custom-elements/samples/$$slot/main.svelte create mode 100644 test/custom-elements/samples/$$slot/test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index b93a6a7fd1..b36b4357d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Fix function calls in `` props that use contextual values ([#5565](https://github.com/sveltejs/svelte/issues/5565)) * Fix handling aborted transitions in `{:else}` blocks ([#5573](https://github.com/sveltejs/svelte/issues/5573)) * Add `Element` and `Node` to known globals ([#5586](https://github.com/sveltejs/svelte/issues/5586)) +* Fix `$$slots` when compiling to custom elements ([#5594](https://github.com/sveltejs/svelte/issues/5594)) * Fix internal `import`s so that we're exposing a valid ES module ([#5617](https://github.com/sveltejs/svelte/issues/5617)) ## 3.29.4 diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 024aafde14..2f86202a34 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -467,6 +467,12 @@ export default function dom( } if (options.customElement) { + + let init_props = x`@attribute_to_object(this.attributes)`; + if (uses_slots) { + init_props = x`{ ...${init_props}, $$slots: @get_custom_elements_slots(this) }`; + } + const declaration = b` class ${name} extends @SvelteElement { constructor(options) { @@ -474,7 +480,7 @@ export default function dom( ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} - @init(this, { target: this.shadowRoot, props: @attribute_to_object(this.attributes) }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); + @init(this, { target: this.shadowRoot, props: ${init_props} }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); ${dev_props_check} diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index ad06d6ff08..91e575ebf6 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -361,10 +361,18 @@ export class HtmlTag { } } -export function attribute_to_object(attributes) { +export function attribute_to_object(attributes: NamedNodeMap) { const result = {}; for (const attribute of attributes) { result[attribute.name] = attribute.value; } return result; } + +export function get_custom_elements_slots(element: HTMLElement) { + const result = {}; + element.childNodes.forEach((node: Element) => { + result[node.slot || 'default'] = true; + }); + return result; +} diff --git a/test/custom-elements/samples/$$slot/main.svelte b/test/custom-elements/samples/$$slot/main.svelte new file mode 100644 index 0000000000..05e1ac3284 --- /dev/null +++ b/test/custom-elements/samples/$$slot/main.svelte @@ -0,0 +1,31 @@ + + + + + + +

$$slots: {toString($$slots)}

+{#if $$slots.b} +
+ +
+{:else} +

Slot b is not available

+{/if} \ No newline at end of file diff --git a/test/custom-elements/samples/$$slot/test.js b/test/custom-elements/samples/$$slot/test.js new file mode 100644 index 0000000000..567e93f509 --- /dev/null +++ b/test/custom-elements/samples/$$slot/test.js @@ -0,0 +1,28 @@ +import * as assert from 'assert'; +import './main.svelte'; + +export default function (target) { + target.innerHTML = ` + hello worldbyeworld + hello worldhello worldbye world + `; + + const [a, b] = target.querySelectorAll('custom-element'); + + assert.htmlEqual(a.shadowRoot.innerHTML, ` + + +

$$slots: {"a":true,"default":true}

+

Slot b is not available

+ `); + + assert.htmlEqual(b.shadowRoot.innerHTML, ` + + +

$$slots: {"a":true,"b":true,"default":true}

+
+ `); + + assert.equal(a.getData(), ''); + assert.equal(b.getData(), 'foo'); +} From 41d16c02be538e372ac1e98e93805f319faf5ecf Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Mon, 9 Nov 2020 23:26:33 +0800 Subject: [PATCH 22/44] add export map (#5566) --- CHANGELOG.md | 1 + package.json | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b36b4357d4..23ce507de9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * Fix `$$props` and `$$restProps` when compiling to a custom element ([#5482](https://github.com/sveltejs/svelte/issues/5482)) +* Include an export map in `package.json` ([#5556](https://github.com/sveltejs/svelte/issues/5556)) * Fix function calls in `` props that use contextual values ([#5565](https://github.com/sveltejs/svelte/issues/5565)) * Fix handling aborted transitions in `{:else}` blocks ([#5573](https://github.com/sveltejs/svelte/issues/5573)) * Add `Element` and `Node` to known globals ([#5586](https://github.com/sveltejs/svelte/issues/5586)) diff --git a/package.json b/package.json index 75e5c9e21f..2dda09dcf5 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,40 @@ "svelte", "README.md" ], + "exports": { + ".": { + "import": "./index.mjs", + "require": "./index.js" + }, + "./compiler": { + "import": "./compiler.mjs", + "require": "./compiler.js" + }, + "./animate": { + "import": "./animate/index.mjs", + "require": "./animate/index.js" + }, + "./easing": { + "import": "./easing/index.mjs", + "require": "./easing/index.js" + }, + "./internal": { + "import": "./internal/index.mjs", + "require": "./internal/index.js" + }, + "./motion": { + "import": "./motion/index.mjs", + "require": "./motion/index.js" + }, + "./store": { + "import": "./store/index.mjs", + "require": "./store/index.js" + }, + "./transition": { + "import": "./transition/index.mjs", + "require": "./transition/index.js" + } + }, "engines": { "node": ">= 8" }, From e2fa0e0fdb3ade785a13719c919494ae77df7b39 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 9 Nov 2020 11:19:57 -0500 Subject: [PATCH 23/44] fix package when publishing with npm@7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2dda09dcf5..eda4d42cab 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "codecov": "codecov", "precodecov": "npm run coverage", "build": "rollup -c && npm run tsd", - "prepare": "npm run build", + "prepare": "PUBLISH=true npm run build", "dev": "rollup -cw", "pretest": "npm run build", "posttest": "agadoo internal/index.mjs", From b135055c5c6f7628c5f9bcf99e9513965aead1cd Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 9 Nov 2020 11:20:06 -0500 Subject: [PATCH 24/44] -> v3.29.5 --- CHANGELOG.md | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23ce507de9..66c8647186 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Svelte changelog -## Unreleased +## 3.29.5 * Fix `$$props` and `$$restProps` when compiling to a custom element ([#5482](https://github.com/sveltejs/svelte/issues/5482)) * Include an export map in `package.json` ([#5556](https://github.com/sveltejs/svelte/issues/5556)) diff --git a/package-lock.json b/package-lock.json index 5d4aa46575..6d2488414c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.29.4", + "version": "3.29.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index eda4d42cab..4e2393cbb0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.29.4", + "version": "3.29.5", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From fdd750db21e878a3e72a7f597c0d0fdaa2a5f986 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 9 Nov 2020 11:47:35 -0500 Subject: [PATCH 25/44] Revert "fix package when publishing with npm@7" This reverts commit e2fa0e0fdb3ade785a13719c919494ae77df7b39. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4e2393cbb0..7fcb311dea 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "codecov": "codecov", "precodecov": "npm run coverage", "build": "rollup -c && npm run tsd", - "prepare": "PUBLISH=true npm run build", + "prepare": "npm run build", "dev": "rollup -cw", "pretest": "npm run build", "posttest": "agadoo internal/index.mjs", From 627aae8c5c3da499b3a23089a7011035a2d830a9 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Mon, 9 Nov 2020 08:52:51 -0800 Subject: [PATCH 26/44] Add package.json to exports map (#5660) --- CHANGELOG.md | 4 ++++ package.json | 1 + 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66c8647186..d346d2dde7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Svelte changelog +## Unreleased + +* Include `./package.json` in export map ([#5659](https://github.com/sveltejs/svelte/issues/5659)) + ## 3.29.5 * Fix `$$props` and `$$restProps` when compiling to a custom element ([#5482](https://github.com/sveltejs/svelte/issues/5482)) diff --git a/package.json b/package.json index 7fcb311dea..e6f44c7d09 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "README.md" ], "exports": { + "./package.json": "./package.json", ".": { "import": "./index.mjs", "require": "./index.js" From ed4288fe435dc4a04d403f9065f63dc1cc12e8f0 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 9 Nov 2020 11:53:32 -0500 Subject: [PATCH 27/44] -> v3.29.6 --- CHANGELOG.md | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d346d2dde7..90ea29fbd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Svelte changelog -## Unreleased +## 3.29.6 * Include `./package.json` in export map ([#5659](https://github.com/sveltejs/svelte/issues/5659)) diff --git a/package-lock.json b/package-lock.json index 6d2488414c..5937018cb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.29.5", + "version": "3.29.6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e6f44c7d09..163a105506 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.29.5", + "version": "3.29.6", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From 795e5855ba496bcd921c8885d241c269dd984bee Mon Sep 17 00:00:00 2001 From: Kieran Barker <29986418+kieranbarker@users.noreply.github.com> Date: Tue, 10 Nov 2020 13:13:29 +0000 Subject: [PATCH 28/44] blog: fix link to Svelte package on npm (#5669) --- site/content/blog/2019-04-16-svelte-for-new-developers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/blog/2019-04-16-svelte-for-new-developers.md b/site/content/blog/2019-04-16-svelte-for-new-developers.md index 921e699842..a305f0fcc0 100644 --- a/site/content/blog/2019-04-16-svelte-for-new-developers.md +++ b/site/content/blog/2019-04-16-svelte-for-new-developers.md @@ -45,7 +45,7 @@ A full introduction to the command line is out of the scope of this guide, but h Once installed, you'll have access to three new commands: * `node my-file.js` — runs the JavaScript in `my-file.js` -* `npm [subcommand]` — [npm](https://www.npmjs.com/) is a way to install 'packages' that your application depends on, such as the [svelte](https://www.npmjs.com/) package +* `npm [subcommand]` — [npm](https://www.npmjs.com/) is a way to install 'packages' that your application depends on, such as the [svelte](https://www.npmjs.com/package/svelte) package * `npx [subcommand]` — a convenient way to run programs available on npm without permanently installing them From e5124e404516d858d9aaee31a7c3bb6593609986 Mon Sep 17 00:00:00 2001 From: Fraser Darwent Date: Tue, 10 Nov 2020 15:52:02 +0000 Subject: [PATCH 29/44] add register to exports map (#5671) --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 163a105506..06f2015718 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,9 @@ "import": "./motion/index.mjs", "require": "./motion/index.js" }, + "./register": { + "require": "./register.js" + }, "./store": { "import": "./store/index.mjs", "require": "./store/index.js" From 4524566fdaa0d9471c9d0d1ea85fde89e8829c4d Mon Sep 17 00:00:00 2001 From: Conduitry Date: Tue, 10 Nov 2020 10:53:12 -0500 Subject: [PATCH 30/44] -> v3.29.7 --- CHANGELOG.md | 4 ++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90ea29fbd2..a9ff0e72de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Svelte changelog +## 3.29.7 + +* Include `./register` in exports map ([#5670](https://github.com/sveltejs/svelte/issues/5670)) + ## 3.29.6 * Include `./package.json` in export map ([#5659](https://github.com/sveltejs/svelte/issues/5659)) diff --git a/package-lock.json b/package-lock.json index 5937018cb6..95167488ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.29.6", + "version": "3.29.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 06f2015718..61c213494c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.29.6", + "version": "3.29.7", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From 02e560c9dafbbe295d46df4ee9c9c9ffd5d9156c Mon Sep 17 00:00:00 2001 From: Geoff Rich Date: Mon, 16 Nov 2020 10:59:56 -0800 Subject: [PATCH 31/44] docs a11y: increase color contrast in code block syntax highlighting (#5681) --- site/static/prism.css | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/site/static/prism.css b/site/static/prism.css index c803829288..0bedd0f02b 100644 --- a/site/static/prism.css +++ b/site/static/prism.css @@ -7,12 +7,12 @@ /* colors --------------------------------- */ pre[class*='language-'] { --background: var(--back-light); - --base: hsl(45, 7%, 45%); - --comment: hsl(210, 25%, 60%); - --keyword: hsl(204, 58%, 45%); - --function: hsl(19, 67%, 45%); - --string: hsl(41, 37%, 45%); - --number: hsl(102, 27%, 50%); + --base: #545454; + --comment: #696969; + --keyword: #007f8a; + --function: #bb5525; + --string: #856e3d; + --number: #008000; --tags: var(--function); --important: var(--string); } From e750b7284e2edfde71ef93957c01d243ae57d9ac Mon Sep 17 00:00:00 2001 From: swyx Date: Tue, 17 Nov 2020 18:41:35 +0800 Subject: [PATCH 32/44] document await...catch shorthand (#5682) --- site/content/docs/02-template-syntax.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index 119488aacc..ac7ca7abf3 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -300,6 +300,9 @@ An each block can also have an `{:else}` clause, which is rendered if the list i ```sv {#await expression then name}...{/await} ``` +```sv +{#await expression catch name}...{/await} +``` --- @@ -342,6 +345,16 @@ If you don't care about the pending state, you can also omit the initial block. {/await} ``` +--- + +If conversely you only want to show the error state, you can omit the `then` block. + +```sv +{#await promise catch error} +

The error is {error}

+{/await} +``` + ### {#key ...} ```sv From ddd79e3f9d11a73c02beb9bb908e4184d6c818a9 Mon Sep 17 00:00:00 2001 From: fivem Date: Wed, 18 Nov 2020 15:34:44 +0300 Subject: [PATCH 33/44] tutorial: remove unneccessary performance.now() call inside raf (#5513) --- .../tutorial/06-bindings/12-bind-this/app-a/App.svelte | 8 +++----- .../tutorial/06-bindings/12-bind-this/app-b/App.svelte | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/site/content/tutorial/06-bindings/12-bind-this/app-a/App.svelte b/site/content/tutorial/06-bindings/12-bind-this/app-a/App.svelte index d40c45ef6c..f8f93edc2b 100644 --- a/site/content/tutorial/06-bindings/12-bind-this/app-a/App.svelte +++ b/site/content/tutorial/06-bindings/12-bind-this/app-a/App.svelte @@ -5,9 +5,9 @@ onMount(() => { const ctx = canvas.getContext('2d'); - let frame; + let frame = requestAnimationFrame(loop); - (function loop() { + function loop(t) { frame = requestAnimationFrame(loop); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); @@ -17,8 +17,6 @@ const x = i % canvas.width; const y = i / canvas.height >>> 0; - const t = window.performance.now(); - const r = 64 + (128 * x / canvas.width) + (64 * Math.sin(t / 1000)); const g = 64 + (128 * y / canvas.height) + (64 * Math.cos(t / 1000)); const b = 128; @@ -30,7 +28,7 @@ } ctx.putImageData(imageData, 0, 0); - }()); + } return () => { cancelAnimationFrame(frame); diff --git a/site/content/tutorial/06-bindings/12-bind-this/app-b/App.svelte b/site/content/tutorial/06-bindings/12-bind-this/app-b/App.svelte index 8e4b3c5bef..17005e273a 100644 --- a/site/content/tutorial/06-bindings/12-bind-this/app-b/App.svelte +++ b/site/content/tutorial/06-bindings/12-bind-this/app-b/App.svelte @@ -5,9 +5,9 @@ onMount(() => { const ctx = canvas.getContext('2d'); - let frame; + let frame = requestAnimationFrame(loop); - (function loop() { + function loop(t) { frame = requestAnimationFrame(loop); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); @@ -17,8 +17,6 @@ const x = i % canvas.width; const y = i / canvas.height >>> 0; - const t = window.performance.now(); - const r = 64 + (128 * x / canvas.width) + (64 * Math.sin(t / 1000)); const g = 64 + (128 * y / canvas.height) + (64 * Math.cos(t / 1000)); const b = 128; @@ -30,7 +28,7 @@ } ctx.putImageData(imageData, 0, 0); - }()); + } return () => { cancelAnimationFrame(frame); From daec25604f16362315389bc660781fa9a2db00b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20=C3=85berg=20Kultalahti?= Date: Wed, 18 Nov 2020 15:41:47 +0100 Subject: [PATCH 34/44] docs: add conditional slot example (#5383) --- .../04-conditional-slots/App.svelte | 13 ++++++++++ .../04-conditional-slots/Profile.svelte | 24 +++++++++++++++++++ .../04-conditional-slots/meta.json | 3 +++ .../{04-modal => 05-modal}/App.svelte | 0 .../{04-modal => 05-modal}/Modal.svelte | 0 .../{04-modal => 05-modal}/meta.json | 0 6 files changed, 40 insertions(+) create mode 100644 site/content/examples/15-composition/04-conditional-slots/App.svelte create mode 100644 site/content/examples/15-composition/04-conditional-slots/Profile.svelte create mode 100644 site/content/examples/15-composition/04-conditional-slots/meta.json rename site/content/examples/15-composition/{04-modal => 05-modal}/App.svelte (100%) rename site/content/examples/15-composition/{04-modal => 05-modal}/Modal.svelte (100%) rename site/content/examples/15-composition/{04-modal => 05-modal}/meta.json (100%) diff --git a/site/content/examples/15-composition/04-conditional-slots/App.svelte b/site/content/examples/15-composition/04-conditional-slots/App.svelte new file mode 100644 index 0000000000..69142b807d --- /dev/null +++ b/site/content/examples/15-composition/04-conditional-slots/App.svelte @@ -0,0 +1,13 @@ + + + + Bob + bob@email.com + + + + Alice + 12345678 + diff --git a/site/content/examples/15-composition/04-conditional-slots/Profile.svelte b/site/content/examples/15-composition/04-conditional-slots/Profile.svelte new file mode 100644 index 0000000000..09026cd6d6 --- /dev/null +++ b/site/content/examples/15-composition/04-conditional-slots/Profile.svelte @@ -0,0 +1,24 @@ + + +
+
Name
+ + {#if $$slots.email} +
Email
+ + {/if} + {#if $$slots.phone} +
Phone
+ + {/if} +
diff --git a/site/content/examples/15-composition/04-conditional-slots/meta.json b/site/content/examples/15-composition/04-conditional-slots/meta.json new file mode 100644 index 0000000000..95809f4c93 --- /dev/null +++ b/site/content/examples/15-composition/04-conditional-slots/meta.json @@ -0,0 +1,3 @@ +{ + "title": "Conditional Slots" +} \ No newline at end of file diff --git a/site/content/examples/15-composition/04-modal/App.svelte b/site/content/examples/15-composition/05-modal/App.svelte similarity index 100% rename from site/content/examples/15-composition/04-modal/App.svelte rename to site/content/examples/15-composition/05-modal/App.svelte diff --git a/site/content/examples/15-composition/04-modal/Modal.svelte b/site/content/examples/15-composition/05-modal/Modal.svelte similarity index 100% rename from site/content/examples/15-composition/04-modal/Modal.svelte rename to site/content/examples/15-composition/05-modal/Modal.svelte diff --git a/site/content/examples/15-composition/04-modal/meta.json b/site/content/examples/15-composition/05-modal/meta.json similarity index 100% rename from site/content/examples/15-composition/04-modal/meta.json rename to site/content/examples/15-composition/05-modal/meta.json From 96ac898370971e5f993e6556bd96fc2ac38139e1 Mon Sep 17 00:00:00 2001 From: Phippsy Date: Thu, 19 Nov 2020 04:24:43 +1100 Subject: [PATCH 35/44] docs: mention that degit requires Git (#5532) --- site/content/blog/2019-04-16-svelte-for-new-developers.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/site/content/blog/2019-04-16-svelte-for-new-developers.md b/site/content/blog/2019-04-16-svelte-for-new-developers.md index a305f0fcc0..76308da547 100644 --- a/site/content/blog/2019-04-16-svelte-for-new-developers.md +++ b/site/content/blog/2019-04-16-svelte-for-new-developers.md @@ -58,9 +58,7 @@ To write code, you need a good editor. The most popular choice is [Visual Studio We're going to follow the instructions in part two of [The easiest way to get started with Svelte](/blog/the-easiest-way-to-get-started). -First, we'll use npx to run [degit](https://github.com/Rich-Harris/degit), a program for cloning project templates from [GitHub](https://github.com) and other code storage websites. You don't have to use a project template, but it means you have to do a lot less setup work. - -(Eventually you'll probably have to learn [git](https://git-scm.com/), which most programmers use to manage their projects. But you don't need to worry about it just yet.) +First, we'll use npx to run [degit](https://github.com/Rich-Harris/degit), a program for cloning project templates from [GitHub](https://github.com) and other code storage websites. You don't have to use a project template, but it means you have to do a lot less setup work. You will need to have [Git](https://git-scm.com/) installed in order to use degit. (Eventually you'll probably have to learn [Git](https://git-scm.com/) itself, which most programmers use to manage their projects.) On the command line, navigate to where you want to create a new project, then type the following lines (you can paste the whole lot, but you'll develop better muscle memory if you get into the habit of writing each line out one at a time then running it): From 449fd1ca1f3e82f2f6d68ca06ffd5d731da197ea Mon Sep 17 00:00:00 2001 From: Antony Jones Date: Wed, 18 Nov 2020 18:40:29 +0000 Subject: [PATCH 36/44] Add envinfo note to github bug template --- .github/ISSUE_TEMPLATE/bug_report.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 94f0340e8e..2aa885cfcb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -37,6 +37,8 @@ If you have a stack trace to include, we recommend putting inside a `
`
**Information about your Svelte project:** +To make your life easier, just run `npx envinfo --system --npmPackages svelte,rollup,webpack --binaries --browsers` and paste the output here. + - Your browser and the version: (e.x. Chrome 52.1, Firefox 48.0, IE 10) - Your operating system: (e.x. OS X 10, Ubuntu Linux 19.10, Windows XP, etc) From 1fa46fde4f17fd3e9872339b38243e1d220e610c Mon Sep 17 00:00:00 2001 From: pushkin Date: Thu, 19 Nov 2020 00:30:47 +0100 Subject: [PATCH 37/44] Correct `onDestroy` behavior description (#5590) * Update 03-run-time.md * Update site/content/docs/03-run-time.md Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- site/content/docs/03-run-time.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md index 846e09d774..b35af93838 100644 --- a/site/content/docs/03-run-time.md +++ b/site/content/docs/03-run-time.md @@ -102,7 +102,7 @@ onDestroy(callback: () => void) --- -Schedules a callback to run once the component is unmounted. +Schedules a callback to run immediately before the component is unmounted. Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the only one that runs inside a server-side component. From dcfbd6951634408d97757f2f66cbfbe622d107c2 Mon Sep 17 00:00:00 2001 From: halfnelson Date: Fri, 20 Nov 2020 04:38:41 +1000 Subject: [PATCH 38/44] Preprocessor sourcemap support (#5584) Co-authored-by: Milan Hauth Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- package-lock.json | 22 +- package.json | 2 + src/compiler/compile/Component.ts | 4 + src/compiler/compile/index.ts | 1 + src/compiler/compile/render_dom/index.ts | 5 + src/compiler/interfaces.ts | 1 + src/compiler/preprocess/index.ts | 154 +++++++--- src/compiler/utils/string_with_sourcemap.ts | 275 ++++++++++++++++++ test/preprocess/index.ts | 3 + test/setup.js | 2 +- test/sourcemaps/helpers.ts | 20 ++ test/sourcemaps/index.ts | 8 +- .../samples/compile-option-dev/_config.js | 21 ++ .../samples/compile-option-dev/input.svelte | 15 + .../samples/compile-option-dev/test.js | 40 +++ .../samples/decoded-sourcemap/_config.js | 15 + .../samples/decoded-sourcemap/input.svelte | 2 + .../samples/decoded-sourcemap/test.js | 19 ++ .../samples/preprocessed-markup/_config.js | 12 + .../samples/preprocessed-markup/input.svelte | 5 + .../samples/preprocessed-markup/test.js | 32 ++ .../samples/preprocessed-multiple/_config.js | 25 ++ .../preprocessed-multiple/input.svelte | 9 + .../samples/preprocessed-multiple/test.js | 32 ++ .../samples/preprocessed-script/_config.js | 12 + .../samples/preprocessed-script/input.svelte | 9 + .../samples/preprocessed-script/test.js | 32 ++ .../samples/preprocessed-styles/_config.js | 12 + .../samples/preprocessed-styles/input.svelte | 12 + .../samples/preprocessed-styles/test.js | 32 ++ .../samples/sourcemap-names/_config.js | 33 +++ .../samples/sourcemap-names/input.svelte | 12 + .../samples/sourcemap-names/test.js | 42 +++ .../samples/sourcemap-sources/_config.js | 60 ++++ .../samples/sourcemap-sources/input.svelte | 4 + .../samples/sourcemap-sources/test.js | 29 ++ 36 files changed, 970 insertions(+), 43 deletions(-) create mode 100644 src/compiler/utils/string_with_sourcemap.ts create mode 100644 test/sourcemaps/helpers.ts create mode 100644 test/sourcemaps/samples/compile-option-dev/_config.js create mode 100644 test/sourcemaps/samples/compile-option-dev/input.svelte create mode 100644 test/sourcemaps/samples/compile-option-dev/test.js create mode 100644 test/sourcemaps/samples/decoded-sourcemap/_config.js create mode 100644 test/sourcemaps/samples/decoded-sourcemap/input.svelte create mode 100644 test/sourcemaps/samples/decoded-sourcemap/test.js create mode 100644 test/sourcemaps/samples/preprocessed-markup/_config.js create mode 100644 test/sourcemaps/samples/preprocessed-markup/input.svelte create mode 100644 test/sourcemaps/samples/preprocessed-markup/test.js create mode 100644 test/sourcemaps/samples/preprocessed-multiple/_config.js create mode 100644 test/sourcemaps/samples/preprocessed-multiple/input.svelte create mode 100644 test/sourcemaps/samples/preprocessed-multiple/test.js create mode 100644 test/sourcemaps/samples/preprocessed-script/_config.js create mode 100644 test/sourcemaps/samples/preprocessed-script/input.svelte create mode 100644 test/sourcemaps/samples/preprocessed-script/test.js create mode 100644 test/sourcemaps/samples/preprocessed-styles/_config.js create mode 100644 test/sourcemaps/samples/preprocessed-styles/input.svelte create mode 100644 test/sourcemaps/samples/preprocessed-styles/test.js create mode 100644 test/sourcemaps/samples/sourcemap-names/_config.js create mode 100644 test/sourcemaps/samples/sourcemap-names/input.svelte create mode 100644 test/sourcemaps/samples/sourcemap-names/test.js create mode 100644 test/sourcemaps/samples/sourcemap-sources/_config.js create mode 100644 test/sourcemaps/samples/sourcemap-sources/input.svelte create mode 100644 test/sourcemaps/samples/sourcemap-sources/test.js diff --git a/package-lock.json b/package-lock.json index 95167488ab..61afea32b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,16 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@ampproject/remapping": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-0.3.0.tgz", + "integrity": "sha512-dqmASpaTCavldZqwdEpokgG4yOXmEiEGPP3ATTsBbdXXSKf6kx8jt2fPcKhodABdZlYe82OehR2oFK1y9gwZxw==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "1.0.0", + "sourcemap-codec": "1.4.8" + } + }, "@babel/code-frame": { "version": "7.10.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", @@ -36,6 +46,12 @@ "integrity": "sha512-KioOCsSvSvXx6xUNLiJz+P+VMb7NRcePjoefOr74Y5P6lEKsiOn35eZyZzgpK4XCNJdXTDR7+zykj0lwxRvZ2g==", "dev": true }, + "@jridgewell/resolve-uri": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-1.0.0.tgz", + "integrity": "sha512-9oLAnygRMi8Q5QkYEU4XWK04B+nuoXoxjRvRxgjuChkLZFBja0YPSgdZ7dZtwhncLBcQe/I/E+fLuk5qxcYVJA==", + "dev": true + }, "@rollup/plugin-commonjs": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.0.0.tgz", @@ -3737,9 +3753,9 @@ } }, "sourcemap-codec": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz", - "integrity": "sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "dev": true }, "spdx-correct": { diff --git a/package.json b/package.json index 61c213494c..1103d56abb 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ }, "homepage": "https://github.com/sveltejs/svelte#README", "devDependencies": { + "@ampproject/remapping": "^0.3.0", "@rollup/plugin-commonjs": "^11.0.0", "@rollup/plugin-json": "^4.0.1", "@rollup/plugin-node-resolve": "^6.0.0", @@ -127,6 +128,7 @@ "rollup": "^1.27.14", "source-map": "^0.7.3", "source-map-support": "^0.5.13", + "sourcemap-codec": "^1.4.8", "tiny-glob": "^0.2.6", "tslib": "^1.10.0", "typescript": "^3.5.3" diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 6e7e41f385..b2c8820351 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -29,7 +29,9 @@ import add_to_set from './utils/add_to_set'; import check_graph_for_cycles from './utils/check_graph_for_cycles'; import { print, x, b } from 'code-red'; import { is_reserved_keyword } from './utils/reserved_keywords'; +import { apply_preprocessor_sourcemap } from '../utils/string_with_sourcemap'; import Element from './nodes/Element'; +import { DecodedSourceMap, RawSourceMap } from '@ampproject/remapping/dist/types/types'; interface ComponentOptions { namespace?: string; @@ -326,6 +328,8 @@ export default class Component { js.map.sourcesContent = [ this.source ]; + + js.map = apply_preprocessor_sourcemap(this.file, js.map, compile_options.sourcemap as (string | RawSourceMap | DecodedSourceMap)); } return { diff --git a/src/compiler/compile/index.ts b/src/compiler/compile/index.ts index 1faa33ee1e..842539fcde 100644 --- a/src/compiler/compile/index.ts +++ b/src/compiler/compile/index.ts @@ -11,6 +11,7 @@ const valid_options = [ 'format', 'name', 'filename', + 'sourcemap', 'generate', 'outputFilename', 'cssOutputFilename', diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 2f86202a34..fe1ee368fc 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -7,6 +7,8 @@ import { extract_names, Scope } from '../utils/scope'; import { invalidate } from './invalidate'; import Block from './Block'; import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression, Expression } from 'estree'; +import { apply_preprocessor_sourcemap } from '../../utils/string_with_sourcemap'; +import { RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types'; export default function dom( component: Component, @@ -30,6 +32,9 @@ export default function dom( } const css = component.stylesheet.render(options.filename, !options.customElement); + + css.map = apply_preprocessor_sourcemap(options.filename, css.map, options.sourcemap as string | RawSourceMap | DecodedSourceMap); + const styles = component.stylesheet.has_styles && options.dev ? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` : css.code; diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index 5249c2fd48..689b59529d 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -110,6 +110,7 @@ export interface CompileOptions { filename?: string; generate?: 'dom' | 'ssr' | false; + sourcemap?: object | string; outputFilename?: string; cssOutputFilename?: string; sveltePath?: string; diff --git a/src/compiler/preprocess/index.ts b/src/compiler/preprocess/index.ts index 1d7d74ceac..1de41cf9bf 100644 --- a/src/compiler/preprocess/index.ts +++ b/src/compiler/preprocess/index.ts @@ -1,6 +1,11 @@ +import { RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types'; +import { decode as decode_mappings } from 'sourcemap-codec'; +import { getLocator } from 'locate-character'; +import { StringWithSourcemap, sourcemap_add_offset, combine_sourcemaps } from '../utils/string_with_sourcemap'; + export interface Processed { code: string; - map?: object | string; + map?: string | object; // we are opaque with the type here to avoid dependency on the remapping module for our public types. dependencies?: string[]; } @@ -37,12 +42,18 @@ function parse_attributes(str: string) { interface Replacement { offset: number; length: number; - replacement: string; + replacement: StringWithSourcemap; } -async function replace_async(str: string, re: RegExp, func: (...any) => Promise) { +async function replace_async( + filename: string, + source: string, + get_location: ReturnType, + re: RegExp, + func: (...any) => Promise +): Promise { const replacements: Array> = []; - str.replace(re, (...args) => { + source.replace(re, (...args) => { replacements.push( func(...args).then( res => @@ -55,16 +66,55 @@ async function replace_async(str: string, re: RegExp, func: (...any) => Promise< ); return ''; }); - let out = ''; + const out = new StringWithSourcemap(); let last_end = 0; for (const { offset, length, replacement } of await Promise.all( replacements )) { - out += str.slice(last_end, offset) + replacement; + // content = unchanged source characters before the replaced segment + const content = StringWithSourcemap.from_source( + filename, source.slice(last_end, offset), get_location(last_end)); + out.concat(content).concat(replacement); last_end = offset + length; } - out += str.slice(last_end); - return out; + // final_content = unchanged source characters after last replaced segment + const final_content = StringWithSourcemap.from_source( + filename, source.slice(last_end), get_location(last_end)); + return out.concat(final_content); +} + +/** + * Convert a preprocessor output and its leading prefix and trailing suffix into StringWithSourceMap + */ +function get_replacement( + filename: string, + offset: number, + get_location: ReturnType, + original: string, + processed: Processed, + prefix: string, + suffix: string +): StringWithSourcemap { + + // Convert the unchanged prefix and suffix to StringWithSourcemap + const prefix_with_map = StringWithSourcemap.from_source( + filename, prefix, get_location(offset)); + const suffix_with_map = StringWithSourcemap.from_source( + filename, suffix, get_location(offset + prefix.length + original.length)); + + // Convert the preprocessed code and its sourcemap to a StringWithSourcemap + let decoded_map: DecodedSourceMap; + if (processed.map) { + decoded_map = typeof processed.map === 'string' ? JSON.parse(processed.map) : processed.map; + if (typeof(decoded_map.mappings) === 'string') { + decoded_map.mappings = decode_mappings(decoded_map.mappings); + } + sourcemap_add_offset(decoded_map, get_location(offset + prefix.length)); + } + const processed_with_map = StringWithSourcemap.from_processed(processed.code, decoded_map); + + // Surround the processed code with the prefix and suffix, retaining valid sourcemappings + return prefix_with_map.concat(processed_with_map).concat(suffix_with_map); } export default async function preprocess( @@ -76,60 +126,92 @@ export default async function preprocess( const filename = (options && options.filename) || preprocessor.filename; // legacy const dependencies = []; - const preprocessors = Array.isArray(preprocessor) ? preprocessor : [preprocessor]; + const preprocessors = preprocessor + ? Array.isArray(preprocessor) ? preprocessor : [preprocessor] + : []; const markup = preprocessors.map(p => p.markup).filter(Boolean); const script = preprocessors.map(p => p.script).filter(Boolean); const style = preprocessors.map(p => p.style).filter(Boolean); + // sourcemap_list is sorted in reverse order from last map (index 0) to first map (index -1) + // so we use sourcemap_list.unshift() to add new maps + // https://github.com/ampproject/remapping#multiple-transformations-of-a-file + const sourcemap_list: Array = []; + + // TODO keep track: what preprocessor generated what sourcemap? to make debugging easier = detect low-resolution sourcemaps in fn combine_mappings + for (const fn of markup) { + + // run markup preprocessor const processed = await fn({ content: source, filename }); - if (processed && processed.dependencies) dependencies.push(...processed.dependencies); - source = processed ? processed.code : source; + + if (!processed) continue; + + if (processed.dependencies) dependencies.push(...processed.dependencies); + source = processed.code; + if (processed.map) { + sourcemap_list.unshift( + typeof(processed.map) === 'string' + ? JSON.parse(processed.map) + : processed.map + ); + } } - for (const fn of script) { - source = await replace_async( + async function preprocess_tag_content(tag_name: 'style' | 'script', preprocessor: Preprocessor) { + const get_location = getLocator(source); + const tag_regex = tag_name == 'style' + ? /|([^]*?)<\/style>|\/>)/gi + : /|([^]*?)<\/script>|\/>)/gi; + + const res = await replace_async( + filename, source, - /|([^]*?)<\/script>|\/>)/gi, - async (match, attributes = '', content = '') => { + get_location, + tag_regex, + async (match, attributes = '', content = '', offset) => { + const no_change = () => StringWithSourcemap.from_source( + filename, match, get_location(offset)); if (!attributes && !content) { - return match; + return no_change(); } attributes = attributes || ''; - const processed = await fn({ + content = content || ''; + + // run script preprocessor + const processed = await preprocessor({ content, attributes: parse_attributes(attributes), filename }); - if (processed && processed.dependencies) dependencies.push(...processed.dependencies); - return processed ? `${processed.code}` : match; + + if (!processed) return no_change(); + if (processed.dependencies) dependencies.push(...processed.dependencies); + return get_replacement(filename, offset, get_location, content, processed, `<${tag_name}${attributes}>`, ``); } ); + source = res.string; + sourcemap_list.unshift(res.map); + } + + for (const fn of script) { + await preprocess_tag_content('script', fn); } for (const fn of style) { - source = await replace_async( - source, - /|([^]*?)<\/style>|\/>)/gi, - async (match, attributes = '', content = '') => { - if (!attributes && !content) { - return match; - } - const processed: Processed = await fn({ - content, - attributes: parse_attributes(attributes), - filename - }); - if (processed && processed.dependencies) dependencies.push(...processed.dependencies); - return processed ? `${processed.code}` : match; - } - ); + await preprocess_tag_content('style', fn); } + // Combine all the source maps for each preprocessor function into one + const map: RawSourceMap = combine_sourcemaps( + filename, + sourcemap_list + ); + return { // TODO return separated output, in future version where svelte.compile supports it: // style: { code: styleCode, map: styleMap }, @@ -138,7 +220,7 @@ export default async function preprocess( code: source, dependencies: [...new Set(dependencies)], - + map: (map as object), toString() { return source; } diff --git a/src/compiler/utils/string_with_sourcemap.ts b/src/compiler/utils/string_with_sourcemap.ts new file mode 100644 index 0000000000..7f8a0ec1eb --- /dev/null +++ b/src/compiler/utils/string_with_sourcemap.ts @@ -0,0 +1,275 @@ +import { DecodedSourceMap, RawSourceMap, SourceMapLoader } from '@ampproject/remapping/dist/types/types'; +import remapping from '@ampproject/remapping'; +import { SourceMap } from 'magic-string'; + +type SourceLocation = { + line: number; + column: number; +}; + +function last_line_length(s: string) { + return s.length - s.lastIndexOf('\n') - 1; +} + +// mutate map in-place +export function sourcemap_add_offset( + map: DecodedSourceMap, offset: SourceLocation +) { + if (map.mappings.length == 0) return map; + // shift columns in first line + const segment_list = map.mappings[0]; + for (let segment = 0; segment < segment_list.length; segment++) { + const seg = segment_list[segment]; + if (seg[3]) seg[3] += offset.column; + } + // shift lines + for (let line = 0; line < map.mappings.length; line++) { + const segment_list = map.mappings[line]; + for (let segment = 0; segment < segment_list.length; segment++) { + const seg = segment_list[segment]; + if (seg[2]) seg[2] += offset.line; + } + } +} + +function merge_tables(this_table: T[], other_table: T[]): [T[], number[], boolean, boolean] { + const new_table = this_table.slice(); + const idx_map = []; + other_table = other_table || []; + let val_changed = false; + for (const [other_idx, other_val] of other_table.entries()) { + const this_idx = this_table.indexOf(other_val); + if (this_idx >= 0) { + idx_map[other_idx] = this_idx; + } else { + const new_idx = new_table.length; + new_table[new_idx] = other_val; + idx_map[other_idx] = new_idx; + val_changed = true; + } + } + let idx_changed = val_changed; + if (val_changed) { + if (idx_map.find((val, idx) => val != idx) === undefined) { + // idx_map is identity map [0, 1, 2, 3, 4, ....] + idx_changed = false; + } + } + return [new_table, idx_map, val_changed, idx_changed]; +} + +function pushArray(_this: T[], other: T[]) { + // We use push to mutate in place for memory and perf reasons + // We use the for loop instead of _this.push(...other) to avoid the JS engine's function argument limit (65,535 in JavascriptCore) + for (let i = 0; i < other.length; i++) { + _this.push(other[i]); + } +} + +export class StringWithSourcemap { + string: string; + map: DecodedSourceMap; + + constructor(string = '', map: DecodedSourceMap = null) { + this.string = string; + if (map) { + this.map = map as DecodedSourceMap; + } else { + this.map = { + version: 3, + mappings: [], + sources: [], + names: [] + }; + } + } + + /** + * concat in-place (mutable), return this (chainable) + * will also mutate the `other` object + */ + concat(other: StringWithSourcemap): StringWithSourcemap { + // noop: if one is empty, return the other + if (other.string == '') return this; + if (this.string == '') { + this.string = other.string; + this.map = other.map; + return this; + } + + this.string += other.string; + + const m1 = this.map; + const m2 = other.map; + + if (m2.mappings.length == 0) return this; + + // combine sources and names + const [sources, new_source_idx, sources_changed, sources_idx_changed] = merge_tables(m1.sources, m2.sources); + const [names, new_name_idx, names_changed, names_idx_changed] = merge_tables(m1.names, m2.names); + + if (sources_changed) m1.sources = sources; + if (names_changed) m1.names = names; + + // unswitched loops are faster + if (sources_idx_changed && names_idx_changed) { + for (let line = 0; line < m2.mappings.length; line++) { + const segment_list = m2.mappings[line]; + for (let segment = 0; segment < segment_list.length; segment++) { + const seg = segment_list[segment]; + if (seg[1]) seg[1] = new_source_idx[seg[1]]; + if (seg[4]) seg[4] = new_name_idx[seg[4]]; + } + } + } else if (sources_idx_changed) { + for (let line = 0; line < m2.mappings.length; line++) { + const segment_list = m2.mappings[line]; + for (let segment = 0; segment < segment_list.length; segment++) { + const seg = segment_list[segment]; + if (seg[1]) seg[1] = new_source_idx[seg[1]]; + } + } + } else if (names_idx_changed) { + for (let line = 0; line < m2.mappings.length; line++) { + const segment_list = m2.mappings[line]; + for (let segment = 0; segment < segment_list.length; segment++) { + const seg = segment_list[segment]; + if (seg[4]) seg[4] = new_name_idx[seg[4]]; + } + } + } + + // combine the mappings + + // combine + // 1. last line of first map + // 2. first line of second map + // columns of 2 must be shifted + + const column_offset = last_line_length(this.string); + if (m2.mappings.length > 0 && column_offset > 0) { + const first_line = m2.mappings[0]; + for (let i = 0; i < first_line.length; i++) { + first_line[i][0] += column_offset; + } + } + + // combine last line + first line + pushArray(m1.mappings[m1.mappings.length - 1], m2.mappings.shift()); + + // append other lines + pushArray(m1.mappings, m2.mappings); + + return this; + } + + static from_processed(string: string, map?: DecodedSourceMap): StringWithSourcemap { + if (map) return new StringWithSourcemap(string, map); + if (string == '') return new StringWithSourcemap(); + map = { version: 3, names: [], sources: [], mappings: [] }; + + // add empty SourceMapSegment[] for every line + const line_count = (string.match(/\n/g) || '').length; + for (let i = 0; i < line_count; i++) map.mappings.push([]); + return new StringWithSourcemap(string, map); + } + + static from_source( + source_file: string, source: string, offset?: SourceLocation + ): StringWithSourcemap { + if (!offset) offset = { line: 0, column: 0 }; + const map: DecodedSourceMap = { version: 3, names: [], sources: [source_file], mappings: [] }; + if (source == '') return new StringWithSourcemap(source, map); + + // we create a high resolution identity map here, + // we know that it will eventually be merged with svelte's map, + // at which stage the resolution will decrease. + const line_list = source.split('\n'); + for (let line = 0; line < line_list.length; line++) { + map.mappings.push([]); + const token_list = line_list[line].split(/([^\d\w\s]|\s+)/g); + for (let token = 0, column = 0; token < token_list.length; token++) { + if (token_list[token] == '') continue; + map.mappings[line].push([column, 0, offset.line + line, column]); + column += token_list[token].length; + } + } + // shift columns in first line + const segment_list = map.mappings[0]; + for (let segment = 0; segment < segment_list.length; segment++) { + segment_list[segment][3] += offset.column; + } + return new StringWithSourcemap(source, map); + } +} + +export function combine_sourcemaps( + filename: string, + sourcemap_list: Array +): RawSourceMap { + if (sourcemap_list.length == 0) return null; + + let map_idx = 1; + const map: RawSourceMap = + sourcemap_list.slice(0, -1) + .find(m => m.sources.length !== 1) === undefined + + ? remapping( // use array interface + // only the oldest sourcemap can have multiple sources + sourcemap_list, + () => null, + true // skip optional field `sourcesContent` + ) + + : remapping( // use loader interface + sourcemap_list[0], // last map + function loader(sourcefile) { + if (sourcefile === filename && sourcemap_list[map_idx]) { + return sourcemap_list[map_idx++]; // idx 1, 2, ... + // bundle file = branch node + } + else return null; // source file = leaf node + } as SourceMapLoader, + true + ); + + if (!map.file) delete map.file; // skip optional field `file` + + return map; +} + +// browser vs node.js +const b64enc = typeof btoa == 'function' ? btoa : b => Buffer.from(b).toString('base64'); + +export function apply_preprocessor_sourcemap(filename: string, svelte_map: SourceMap, preprocessor_map_input: string | DecodedSourceMap | RawSourceMap): SourceMap { + if (!svelte_map || !preprocessor_map_input) return svelte_map; + + const preprocessor_map = typeof preprocessor_map_input === 'string' ? JSON.parse(preprocessor_map_input) : preprocessor_map_input; + + const result_map = combine_sourcemaps( + filename, + [ + svelte_map as RawSourceMap, + preprocessor_map + ] + ) as RawSourceMap; + + // Svelte expects a SourceMap which includes toUrl and toString. Instead of wrapping our output in a class, + // we just tack on the extra properties. + Object.defineProperties(result_map, { + toString: { + enumerable: false, + value: function toString() { + return JSON.stringify(this); + } + }, + toUrl: { + enumerable: false, + value: function toUrl() { + return 'data:application/json;charset=utf-8;base64,' + b64enc(this.toString()); + } + } + }); + + return result_map as SourceMap; +} diff --git a/test/preprocess/index.ts b/test/preprocess/index.ts index 60d3acbabb..be898bbbfd 100644 --- a/test/preprocess/index.ts +++ b/test/preprocess/index.ts @@ -24,6 +24,9 @@ describe('preprocess', () => { config.options || { filename: 'input.svelte' } ); fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, result.code); + if (result.map) { + fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html.map`, JSON.stringify(result.map, null, 2)); + } assert.equal(result.code, expected); diff --git a/test/setup.js b/test/setup.js index 7406a07dd9..74250c10eb 100644 --- a/test/setup.js +++ b/test/setup.js @@ -12,7 +12,7 @@ require.extensions['.js'] = function(module, filename) { .replace(/^import (\w+) from ['"]([^'"]+)['"];?/gm, 'var {default: $1} = require("$2");') .replace(/^import {([^}]+)} from ['"](.+)['"];?/gm, 'var {$1} = require("$2");') .replace(/^export default /gm, 'exports.default = ') - .replace(/^export (const|let|var|class|function) (\w+)/gm, (match, type, name) => { + .replace(/^export (const|let|var|class|function|async\s+function) (\w+)/gm, (match, type, name) => { exports.push(name); return `${type} ${name}`; }) diff --git a/test/sourcemaps/helpers.ts b/test/sourcemaps/helpers.ts new file mode 100644 index 0000000000..d0bea310e6 --- /dev/null +++ b/test/sourcemaps/helpers.ts @@ -0,0 +1,20 @@ +import MagicString from 'magic-string'; + +export function magic_string_preprocessor_result(filename: string, src: MagicString) { + return { + code: src.toString(), + map: src.generateMap({ + source: filename, + hires: true, + includeContent: false + }) + }; +} + +export function magic_string_replace_all(src: MagicString, search: string, replace: string) { + let idx = src.original.indexOf(search); + if (idx == -1) throw new Error('search not found in src'); + do { + src.overwrite(idx, idx + search.length, replace, { storeName: true }); + } while ((idx = src.original.indexOf(search, idx + 1)) != -1); +} diff --git a/test/sourcemaps/index.ts b/test/sourcemaps/index.ts index 7659948744..4122c3a419 100644 --- a/test/sourcemaps/index.ts +++ b/test/sourcemaps/index.ts @@ -37,7 +37,7 @@ describe('sourcemaps', () => { const preprocessed = await svelte.preprocess( input.code, config.preprocess || {}, - { + config.options || { filename: 'input.svelte' } ); @@ -46,8 +46,10 @@ describe('sourcemaps', () => { preprocessed.code, { filename: 'input.svelte', // filenames for sourcemaps + sourcemap: preprocessed.map, outputFilename: `${outputName}.js`, - cssOutputFilename: `${outputName}.css` + cssOutputFilename: `${outputName}.css`, + ...(config.compile_options || {}) }); js.code = js.code.replace( @@ -107,7 +109,7 @@ describe('sourcemaps', () => { css.mapConsumer = css.map && await new SourceMapConsumer(css.map); css.locate = getLocator(css.code || ''); css.locate_1 = getLocator(css.code || '', { offsetLine: 1 }); - test({ assert, input, preprocessed, js, css }); + await test({ assert, input, preprocessed, js, css }); }); }); }); diff --git a/test/sourcemaps/samples/compile-option-dev/_config.js b/test/sourcemaps/samples/compile-option-dev/_config.js new file mode 100644 index 0000000000..b6ea851b6d --- /dev/null +++ b/test/sourcemaps/samples/compile-option-dev/_config.js @@ -0,0 +1,21 @@ +import MagicString from 'magic-string'; +import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers'; + +export default { + compile_options: { + dev: true + }, + preprocess: [ + { style: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, '--replace-me-once', '\n --done-replace-once'); + magic_string_replace_all(src, '--replace-me-twice', '\n--almost-done-replace-twice'); + return magic_string_preprocessor_result(filename, src); + } }, + { style: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, '--almost-done-replace-twice', '\n --done-replace-twice'); + return magic_string_preprocessor_result(filename, src); + } } + ] +}; diff --git a/test/sourcemaps/samples/compile-option-dev/input.svelte b/test/sourcemaps/samples/compile-option-dev/input.svelte new file mode 100644 index 0000000000..6d5f91158d --- /dev/null +++ b/test/sourcemaps/samples/compile-option-dev/input.svelte @@ -0,0 +1,15 @@ +

Testing Styles

+

Testing Styles 2

+
Testing Styles 3
+ + diff --git a/test/sourcemaps/samples/compile-option-dev/test.js b/test/sourcemaps/samples/compile-option-dev/test.js new file mode 100644 index 0000000000..bf240a5a89 --- /dev/null +++ b/test/sourcemaps/samples/compile-option-dev/test.js @@ -0,0 +1,40 @@ +import { SourceMapConsumer } from 'source-map'; + +const b64dec = s => Buffer.from(s, 'base64').toString(); + +export async function test({ assert, css, js }) { + + // We check that the css source map embedded in the js is accurate + const match = js.code.match(/\tstyle\.textContent = "(.*?)(?:\\n\/\*# sourceMappingURL=data:(.*?);charset=(.*?);base64,(.*?) \*\/)?";\n/); + assert.notEqual(match, null); + + const [mimeType, encoding, cssMapBase64] = match.slice(2); + assert.equal(mimeType, 'application/json'); + assert.equal(encoding, 'utf-8'); + + const cssMapJson = b64dec(cssMapBase64); + css.mapConsumer = await new SourceMapConsumer(cssMapJson); + + // TODO make util fn + move to test index.js + const sourcefile = 'input.svelte'; + [ + // TODO how to get line + column numbers? + [css, '--keep-me', 13, 2], + [css, '--done-replace-once', 6, 5], + [css, '--done-replace-twice', 9, 5] + ] + .forEach(([where, content, line, column]) => { + assert.deepEqual( + where.mapConsumer.originalPositionFor( + where.locate_1(content) + ), + { + source: sourcefile, + name: null, + line, + column + }, + `failed to locate "${content}" from "${sourcefile}"` + ); + }); +} diff --git a/test/sourcemaps/samples/decoded-sourcemap/_config.js b/test/sourcemaps/samples/decoded-sourcemap/_config.js new file mode 100644 index 0000000000..dd4eee9461 --- /dev/null +++ b/test/sourcemaps/samples/decoded-sourcemap/_config.js @@ -0,0 +1,15 @@ +import MagicString from 'magic-string'; +import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers'; + +export default { + + js_map_sources: [], // test component has no scripts + + preprocess: { + markup: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, 'replace me', 'success'); + return magic_string_preprocessor_result(filename, src); + } + } +}; diff --git a/test/sourcemaps/samples/decoded-sourcemap/input.svelte b/test/sourcemaps/samples/decoded-sourcemap/input.svelte new file mode 100644 index 0000000000..b233d7f670 --- /dev/null +++ b/test/sourcemaps/samples/decoded-sourcemap/input.svelte @@ -0,0 +1,2 @@ +

decoded-sourcemap

+
replace me
diff --git a/test/sourcemaps/samples/decoded-sourcemap/test.js b/test/sourcemaps/samples/decoded-sourcemap/test.js new file mode 100644 index 0000000000..5a44ee83cf --- /dev/null +++ b/test/sourcemaps/samples/decoded-sourcemap/test.js @@ -0,0 +1,19 @@ +export function test({ assert, input, preprocessed }) { + + const expected = input.locate('replace me'); + + const start = preprocessed.locate('success'); + + const actualbar = preprocessed.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbar, { + source: 'input.svelte', + name: 'replace me', + line: expected.line + 1, + column: expected.column + }); + +} diff --git a/test/sourcemaps/samples/preprocessed-markup/_config.js b/test/sourcemaps/samples/preprocessed-markup/_config.js new file mode 100644 index 0000000000..0b2baeb49c --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-markup/_config.js @@ -0,0 +1,12 @@ +import MagicString from 'magic-string'; +import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers'; + +export default { + preprocess: { + markup: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, 'baritone', 'bar'); + return magic_string_preprocessor_result(filename, src); + } + } +}; diff --git a/test/sourcemaps/samples/preprocessed-markup/input.svelte b/test/sourcemaps/samples/preprocessed-markup/input.svelte new file mode 100644 index 0000000000..ee4b90372a --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-markup/input.svelte @@ -0,0 +1,5 @@ + + +{foo.baritone.baz} diff --git a/test/sourcemaps/samples/preprocessed-markup/test.js b/test/sourcemaps/samples/preprocessed-markup/test.js new file mode 100644 index 0000000000..b587103969 --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-markup/test.js @@ -0,0 +1,32 @@ +export function test({ assert, input, js }) { + const expectedBar = input.locate('baritone.baz'); + const expectedBaz = input.locate('.baz'); + + let start = js.locate('bar.baz'); + + const actualbar = js.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbar, { + source: 'input.svelte', + name: 'baritone', + line: expectedBar.line + 1, + column: expectedBar.column + }); + + start = js.locate('.baz'); + + const actualbaz = js.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbaz, { + source: 'input.svelte', + name: null, + line: expectedBaz.line + 1, + column: expectedBaz.column + }); +} diff --git a/test/sourcemaps/samples/preprocessed-multiple/_config.js b/test/sourcemaps/samples/preprocessed-multiple/_config.js new file mode 100644 index 0000000000..39259a02a8 --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-multiple/_config.js @@ -0,0 +1,25 @@ +import MagicString from 'magic-string'; +import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers'; + +export default { + preprocess: { + markup: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, 'baritone', 'bar'); + magic_string_replace_all(src, '--bazitone', '--baz'); + return magic_string_preprocessor_result(filename, src); + }, + script: ({ content, filename }) => { + const src = new MagicString(content); + const idx = content.indexOf('bar'); + src.prependLeft(idx, ' '); + return magic_string_preprocessor_result(filename, src); + }, + style: ({ content, filename }) => { + const src = new MagicString(content); + const idx = content.indexOf('--baz'); + src.prependLeft(idx, ' '); + return magic_string_preprocessor_result(filename, src); + } + } +}; diff --git a/test/sourcemaps/samples/preprocessed-multiple/input.svelte b/test/sourcemaps/samples/preprocessed-multiple/input.svelte new file mode 100644 index 0000000000..e656d399ae --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-multiple/input.svelte @@ -0,0 +1,9 @@ + + +

multiple {foo}

diff --git a/test/sourcemaps/samples/preprocessed-multiple/test.js b/test/sourcemaps/samples/preprocessed-multiple/test.js new file mode 100644 index 0000000000..a0cfe1fa8a --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-multiple/test.js @@ -0,0 +1,32 @@ +export function test({ assert, input, js, css }) { + const expectedBar = input.locate('baritone'); + const expectedBaz = input.locate('--bazitone'); + + let start = js.locate('bar'); + + const actualbar = js.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbar, { + source: 'input.svelte', + name: 'baritone', + line: expectedBar.line + 1, + column: expectedBar.column + }); + + start = css.locate('--baz'); + + const actualbaz = css.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbaz, { + source: 'input.svelte', + name: '--bazitone', + line: expectedBaz.line + 1, + column: expectedBaz.column + }); +} diff --git a/test/sourcemaps/samples/preprocessed-script/_config.js b/test/sourcemaps/samples/preprocessed-script/_config.js new file mode 100644 index 0000000000..94bd83c22f --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-script/_config.js @@ -0,0 +1,12 @@ +import MagicString from 'magic-string'; +import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers'; + +export default { + preprocess: { + script: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, 'baritone', 'bar'); + return magic_string_preprocessor_result(filename, src); + } + } +}; diff --git a/test/sourcemaps/samples/preprocessed-script/input.svelte b/test/sourcemaps/samples/preprocessed-script/input.svelte new file mode 100644 index 0000000000..11586619e1 --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-script/input.svelte @@ -0,0 +1,9 @@ + + +

{foo.bar.baz}

diff --git a/test/sourcemaps/samples/preprocessed-script/test.js b/test/sourcemaps/samples/preprocessed-script/test.js new file mode 100644 index 0000000000..20a366f6d2 --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-script/test.js @@ -0,0 +1,32 @@ +export function test({ assert, input, js }) { + const expectedBar = input.locate('baritone:'); + const expectedBaz = input.locate('baz:'); + + let start = js.locate('bar:'); + + const actualbar = js.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbar, { + source: 'input.svelte', + name: 'baritone', + line: expectedBar.line + 1, + column: expectedBar.column + }, "couldn't find bar: in source"); + + start = js.locate('baz:'); + + const actualbaz = js.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbaz, { + source: 'input.svelte', + name: null, + line: expectedBaz.line + 1, + column: expectedBaz.column + }, "couldn't find baz: in source"); +} diff --git a/test/sourcemaps/samples/preprocessed-styles/_config.js b/test/sourcemaps/samples/preprocessed-styles/_config.js new file mode 100644 index 0000000000..04c8bcda7a --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-styles/_config.js @@ -0,0 +1,12 @@ +import MagicString from 'magic-string'; +import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers'; + +export default { + preprocess: { + style: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, 'baritone', 'bar'); + return magic_string_preprocessor_result(filename, src); + } + } +}; diff --git a/test/sourcemaps/samples/preprocessed-styles/input.svelte b/test/sourcemaps/samples/preprocessed-styles/input.svelte new file mode 100644 index 0000000000..0d942390f4 --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-styles/input.svelte @@ -0,0 +1,12 @@ +

Testing Styles

+

Testing Styles 2

+ + diff --git a/test/sourcemaps/samples/preprocessed-styles/test.js b/test/sourcemaps/samples/preprocessed-styles/test.js new file mode 100644 index 0000000000..5b28a12514 --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-styles/test.js @@ -0,0 +1,32 @@ +export function test({ assert, input, css }) { + const expectedBar = input.locate('--baritone'); + const expectedBaz = input.locate('--baz'); + + let start = css.locate('--bar'); + + const actualbar = css.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbar, { + source: 'input.svelte', + name: null, + line: expectedBar.line + 1, + column: expectedBar.column + }, "couldn't find bar in source"); + + start = css.locate('--baz'); + + const actualbaz = css.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbaz, { + source: 'input.svelte', + name: null, + line: expectedBaz.line + 1, + column: expectedBaz.column + }, "couldn't find baz in source"); +} diff --git a/test/sourcemaps/samples/sourcemap-names/_config.js b/test/sourcemaps/samples/sourcemap-names/_config.js new file mode 100644 index 0000000000..c8557ba465 --- /dev/null +++ b/test/sourcemaps/samples/sourcemap-names/_config.js @@ -0,0 +1,33 @@ +import MagicString from 'magic-string'; +import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers'; + +export default { + preprocess: [ + { + markup: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, 'baritone', 'bar'); + magic_string_replace_all(src,'--bazitone', '--baz'); + magic_string_replace_all(src,'old_name_1', 'temp_new_name_1'); + magic_string_replace_all(src,'old_name_2', 'temp_new_name_2'); + return magic_string_preprocessor_result(filename, src); + } + }, + { + markup: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, 'temp_new_name_1', 'temp_temp_new_name_1'); + magic_string_replace_all(src, 'temp_new_name_2', 'temp_temp_new_name_2'); + return magic_string_preprocessor_result(filename, src); + } + }, + { + markup: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, 'temp_temp_new_name_1', 'new_name_1'); + magic_string_replace_all(src, 'temp_temp_new_name_2', 'new_name_2'); + return magic_string_preprocessor_result(filename, src); + } + } + ] +}; diff --git a/test/sourcemaps/samples/sourcemap-names/input.svelte b/test/sourcemaps/samples/sourcemap-names/input.svelte new file mode 100644 index 0000000000..b62715a857 --- /dev/null +++ b/test/sourcemaps/samples/sourcemap-names/input.svelte @@ -0,0 +1,12 @@ + + +

use-names

+
{old_name_1.baritone}
+
{old_name_2}
diff --git a/test/sourcemaps/samples/sourcemap-names/test.js b/test/sourcemaps/samples/sourcemap-names/test.js new file mode 100644 index 0000000000..cd70bd25ce --- /dev/null +++ b/test/sourcemaps/samples/sourcemap-names/test.js @@ -0,0 +1,42 @@ +// needed for workaround, TODO remove +import { getLocator } from 'locate-character'; + +export function test({ assert, preprocessed, js, css }) { + + assert.deepEqual( + preprocessed.map.names.sort(), + ['baritone', '--bazitone', 'old_name_1', 'old_name_2'].sort() + ); + + function test_name(old_name, new_name, where) { + + let loc = { character: -1 }; + while (loc = where.locate(new_name, loc.character + 1)) { + const actualMapping = where.mapConsumer.originalPositionFor({ + line: loc.line + 1, column: loc.column + }); + if (actualMapping.line === null) { + // location is not mapped - ignore + continue; + } + assert.equal(actualMapping.name, old_name); + } + if (loc === undefined) { + // workaround for bug in locate-character, TODO remove + // https://github.com/Rich-Harris/locate-character/pull/5 + where.locate = getLocator(where.code); + } + } + + test_name('baritone', 'bar', js); + test_name('baritone', 'bar', preprocessed); + + test_name('--bazitone', '--baz', css); + test_name('--bazitone', '--baz', preprocessed); + + test_name('old_name_1', 'new_name_1', js); + test_name('old_name_1', 'new_name_1', preprocessed); + + test_name('old_name_2', 'new_name_2', js); + test_name('old_name_2', 'new_name_2', preprocessed); +} diff --git a/test/sourcemaps/samples/sourcemap-sources/_config.js b/test/sourcemaps/samples/sourcemap-sources/_config.js new file mode 100644 index 0000000000..97024cea5f --- /dev/null +++ b/test/sourcemaps/samples/sourcemap-sources/_config.js @@ -0,0 +1,60 @@ +/* eslint-disable import/no-duplicates */ +/* the code that transforms these to commonjs, can't handle "MagicString, { Bundle } from.." */ + +import MagicString from 'magic-string'; +import { Bundle } from 'magic-string'; + + +function add(bundle, filename, source) { + bundle.addSource({ + filename, + content: new MagicString(source), + separator: '\n' + //separator: '' // ERROR. probably a bug in magic-string + }); +} + +function result(bundle, filename) { + return { + code: bundle.toString(), + map: bundle.generateMap({ + file: filename, + includeContent: false, + hires: true // required for remapping + }) + }; +} + +export default { + js_map_sources: [ + 'input.svelte', + 'foo.js', + 'bar.js', + 'foo2.js', + 'bar2.js' + ], + preprocess: [ + { + script: ({ content, filename }) => { + const bundle = new Bundle(); + + add(bundle, filename, content); + add(bundle, 'foo.js', 'var answer = 42; // foo.js\n'); + add(bundle, 'bar.js', 'console.log(answer); // bar.js\n'); + + return result(bundle, filename); + } + }, + { + script: ({ content, filename }) => { + const bundle = new Bundle(); + + add(bundle, filename, content); + add(bundle, 'foo2.js', 'var answer2 = 84; // foo2.js\n'); + add(bundle, 'bar2.js', 'console.log(answer2); // bar2.js\n'); + + return result(bundle, filename); + } + } + ] +}; diff --git a/test/sourcemaps/samples/sourcemap-sources/input.svelte b/test/sourcemaps/samples/sourcemap-sources/input.svelte new file mode 100644 index 0000000000..33c8a9d9a6 --- /dev/null +++ b/test/sourcemaps/samples/sourcemap-sources/input.svelte @@ -0,0 +1,4 @@ + +

sourcemap-sources

diff --git a/test/sourcemaps/samples/sourcemap-sources/test.js b/test/sourcemaps/samples/sourcemap-sources/test.js new file mode 100644 index 0000000000..78a4c80a17 --- /dev/null +++ b/test/sourcemaps/samples/sourcemap-sources/test.js @@ -0,0 +1,29 @@ +export function test({ assert, preprocessed, js }) { + + assert.equal(preprocessed.error, undefined); + + // sourcemap stores location only for 'answer = 42;' + // not for 'var answer = 42;' + [ + [js, 'foo.js', 'answer = 42;', 4], + [js, 'bar.js', 'console.log(answer);', 0], + [js, 'foo2.js', 'answer2 = 84;', 4], + [js, 'bar2.js', 'console.log(answer2);', 0] + ] + .forEach(([where, sourcefile, content, column]) => { + + assert.deepEqual( + where.mapConsumer.originalPositionFor( + where.locate_1(content) + ), + { + source: sourcefile, + name: null, + line: 1, + column + }, + `failed to locate "${content}" from "${sourcefile}"` + ); + + }); +} From 67dea941bb1e61f0912ebd2257666b899c1ccefa Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Thu, 19 Nov 2020 10:45:03 -0800 Subject: [PATCH 39/44] Fix eslint issue (#5698) --- src/compiler/utils/string_with_sourcemap.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/utils/string_with_sourcemap.ts b/src/compiler/utils/string_with_sourcemap.ts index 7f8a0ec1eb..421a0c1fbd 100644 --- a/src/compiler/utils/string_with_sourcemap.ts +++ b/src/compiler/utils/string_with_sourcemap.ts @@ -227,8 +227,9 @@ export function combine_sourcemaps( if (sourcefile === filename && sourcemap_list[map_idx]) { return sourcemap_list[map_idx++]; // idx 1, 2, ... // bundle file = branch node + } else { + return null; // source file = leaf node } - else return null; // source file = leaf node } as SourceMapLoader, true ); From 8a20709ce1c3a0e9f7f95ec0d6725f59f2e47951 Mon Sep 17 00:00:00 2001 From: Wei Zhu Date: Tue, 24 Nov 2020 00:23:03 +0800 Subject: [PATCH 40/44] fix: add missing `walk` type in compiler.d.ts (#5696) --- rollup.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rollup.config.js b/rollup.config.js index e9ee666337..81fa24bd9f 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -20,7 +20,7 @@ const ts_plugin = is_publish const external = id => id.startsWith('svelte/'); -fs.writeFileSync(`./compiler.d.ts`, `export { compile, parse, preprocess, VERSION } from './types/compiler/index';`); +fs.writeFileSync(`./compiler.d.ts`, `export { compile, parse, preprocess, walk, VERSION } from './types/compiler/index';`); export default [ /* runtime */ From 0aa8cc1c9faf81396e731ca85237df8dab66bc83 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 23 Nov 2020 11:24:05 -0500 Subject: [PATCH 41/44] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9ff0e72de..c8c6ca5f7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Svelte changelog +## Unreleased + +* Fix missing `walk` types in `svelte/compiler` ([#5696](https://github.com/sveltejs/svelte/pull/5696)) + ## 3.29.7 * Include `./register` in exports map ([#5670](https://github.com/sveltejs/svelte/issues/5670)) From 01ead95cbe5c8c8c384149c9c11d16b2f959e5ca Mon Sep 17 00:00:00 2001 From: pushkin Date: Mon, 23 Nov 2020 17:29:01 +0100 Subject: [PATCH 42/44] add hasContext lifecycle function (#5690) --- site/content/docs/03-run-time.md | 20 ++++++++++++++++++++ src/runtime/internal/lifecycle.ts | 4 ++++ 2 files changed, 24 insertions(+) diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md index b35af93838..386c18d5fd 100644 --- a/site/content/docs/03-run-time.md +++ b/site/content/docs/03-run-time.md @@ -178,6 +178,26 @@ Retrieves the context that belongs to the closest parent component with the spec ``` +#### `hasContext` + +```js +hasContext: boolean = hasContext(key: any) +``` + +--- + +Checks whether a given `key` has been set in the context of a parent component. Must be called during component initialisation. + +```sv + +``` + #### `createEventDispatcher` ```js diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts index 4a7616bdea..002bd78d24 100644 --- a/src/runtime/internal/lifecycle.ts +++ b/src/runtime/internal/lifecycle.ts @@ -54,6 +54,10 @@ export function getContext(key): T { return get_current_component().$$.context.get(key); } +export function hasContext(key): boolean { + return get_current_component().$$.context.has(key); +} + // TODO figure out if we still want to support // shorthand events, or if we want to implement // a real bubbling mechanism From 342c1e427cef72b1856f381eccff062b4ff39c2a Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 23 Nov 2020 11:30:33 -0500 Subject: [PATCH 43/44] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8c6ca5f7e..adbdf5a95f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Add `hasContext` lifecycle function ([#5690](https://github.com/sveltejs/svelte/pull/5690)) * Fix missing `walk` types in `svelte/compiler` ([#5696](https://github.com/sveltejs/svelte/pull/5696)) ## 3.29.7 From 24c44b9177b74e4010c4349242dec4ac9b712a71 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Tue, 24 Nov 2020 00:41:56 +0800 Subject: [PATCH 44/44] fix order of html tags with {#if} in {#key} block (#5685) --- CHANGELOG.md | 1 + .../compile/render_dom/wrappers/KeyBlock.ts | 2 +- .../samples/key-block-static-if/_config.js | 21 +++++++++++++++++++ .../samples/key-block-static-if/main.svelte | 17 +++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 test/runtime/samples/key-block-static-if/_config.js create mode 100644 test/runtime/samples/key-block-static-if/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index adbdf5a95f..29857123dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Fix ordering of elements when using `{#if}` inside `{#key}` ([#5680](https://github.com/sveltejs/svelte/issues/5680)) * Add `hasContext` lifecycle function ([#5690](https://github.com/sveltejs/svelte/pull/5690)) * Fix missing `walk` types in `svelte/compiler` ([#5696](https://github.com/sveltejs/svelte/pull/5696)) diff --git a/src/compiler/compile/render_dom/wrappers/KeyBlock.ts b/src/compiler/compile/render_dom/wrappers/KeyBlock.ts index 9688337d7c..cbd021ff6c 100644 --- a/src/compiler/compile/render_dom/wrappers/KeyBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/KeyBlock.ts @@ -44,7 +44,7 @@ export default class KeyBlockWrapper extends Wrapper { renderer, this.block, node.children, - parent, + this, strip_whitespace, next_sibling ); diff --git a/test/runtime/samples/key-block-static-if/_config.js b/test/runtime/samples/key-block-static-if/_config.js new file mode 100644 index 0000000000..2926ad7c24 --- /dev/null +++ b/test/runtime/samples/key-block-static-if/_config.js @@ -0,0 +1,21 @@ +export default { + html: ` +
+
Second
+
+ + `, + async test({ assert, component, target, window }) { + const button = target.querySelector('button'); + + await button.dispatchEvent(new window.Event('click')); + + assert.htmlEqual(target.innerHTML, ` +
+
First
+
Second
+
+ + `); + } +}; diff --git a/test/runtime/samples/key-block-static-if/main.svelte b/test/runtime/samples/key-block-static-if/main.svelte new file mode 100644 index 0000000000..cbacb77674 --- /dev/null +++ b/test/runtime/samples/key-block-static-if/main.svelte @@ -0,0 +1,17 @@ + + +
+ {#key slide} + {#if num} +
First
+ {/if} + {/key} +
Second
+
+ + \ No newline at end of file