Merge branch 'version-4' into custom-elements-rework

pull/8457/head
Simon Holthausen 3 years ago
commit 1e8271839d

@ -22,14 +22,20 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
- node-version: 18 - node-version: 18
os: ubuntu-latest os: ubuntu-latest
- node-version: 20
os: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
with:
version: ${{ matrix.node-version == 14 && 7 || 8 }}
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: npm cache: pnpm
- run: npm install - run: pnpm install --frozen-lockfile
- run: npm run test:integration - run: node node_modules/puppeteer/install.js
- run: pnpm test:integration
env: env:
CI: true CI: true
Lint: Lint:
@ -37,10 +43,11 @@ jobs:
timeout-minutes: 5 timeout-minutes: 5
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
cache: npm cache: pnpm
- run: 'npm i && npm run lint' - run: 'pnpm i && pnpm lint'
Unit: Unit:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
timeout-minutes: 10 timeout-minutes: 10
@ -57,12 +64,14 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
- node-version: 18 - node-version: 18
os: ubuntu-latest os: ubuntu-latest
- node-version: 20
os: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
cache: npm node-version: ${{ matrix.node-version }}
- run: npm install cache: pnpm
env: - run: pnpm install
SKIP_PREPARE: true - run: pnpm test:unit
- run: npm run test:unit

@ -1,9 +1,12 @@
const is_unit_test = process.env.UNIT_TEST; const is_unit_test = process.env.UNIT_TEST;
module.exports = { module.exports = {
file: is_unit_test ? [] : ['test/test.ts'], file: is_unit_test ? [] : ['test/test.js'],
require: [ require: [
'sucrase/register' 'sucrase/register'
],
"node-option": [
"experimental-modules"
] ]
}; };

@ -9,6 +9,7 @@
* **breaking** Stricter types for `Action` and `ActionReturn` (see PR for migration instructions) ([#7224](https://github.com/sveltejs/svelte/pull/7224)) * **breaking** Stricter types for `Action` and `ActionReturn` (see PR for migration instructions) ([#7224](https://github.com/sveltejs/svelte/pull/7224))
* **breaking** Stricter types for `onMount` - now throws a type error when returning a function asynchronously to catch potential mistakes around callback functions (see PR for migration instructions) ([#8136](https://github.com/sveltejs/svelte/pull/8136)) * **breaking** Stricter types for `onMount` - now throws a type error when returning a function asynchronously to catch potential mistakes around callback functions (see PR for migration instructions) ([#8136](https://github.com/sveltejs/svelte/pull/8136))
* **breaking** Overhaul and drastically improve creating custom elements with Svelte (see PR for list of changes and migration instructions) ([#8457](https://github.com/sveltejs/svelte/pull/8457)) * **breaking** Overhaul and drastically improve creating custom elements with Svelte (see PR for list of changes and migration instructions) ([#8457](https://github.com/sveltejs/svelte/pull/8457))
* **breaking** Deprecate `SvelteComponentTyped`, use `SvelteComponent` instead ([#8512](https://github.com/sveltejs/svelte/pull/8512))
* Add `a11y no-noninteractive-element-interactions` rule ([#8391](https://github.com/sveltejs/svelte/pull/8391)) * Add `a11y no-noninteractive-element-interactions` rule ([#8391](https://github.com/sveltejs/svelte/pull/8391))
* Add `a11y-no-static-element-interactions`rule ([#8251](https://github.com/sveltejs/svelte/pull/8251)) * Add `a11y-no-static-element-interactions`rule ([#8251](https://github.com/sveltejs/svelte/pull/8251))
* Bind `null` option and input values consistently ([#8312](https://github.com/sveltejs/svelte/issues/8312)) * Bind `null` option and input values consistently ([#8312](https://github.com/sveltejs/svelte/issues/8312))

@ -72,9 +72,9 @@ Small pull requests are much easier to review and more likely to get merged.
### Installation ### Installation
1. Ensure you have [npm](https://www.npmjs.com/get-npm) installed. 1. Ensure you have [pnpm](https://pnpm.io/installation) installed.
1. After cloning the repository, run `npm install` in the root of the repository. 1. After cloning the repository, run `pnpm install` in the root of the repository.
1. To start a development server, run `npm run dev`. 1. To compile in watch mode, run `pnpm dev`.
### Creating a branch ### Creating a branch
@ -94,8 +94,8 @@ Test samples are kept in `/test/xxx/samples` folder.
#### Running tests #### Running tests
1. To run test, run `npm run test`. 1. To run test, run `pnpm test`.
1. To run test for a specific feature, you can use the `-g` (aka `--grep`) option. For example, to only run test involving transitions, run `npm run test -- -g transition`. 1. To run test for a specific feature, you can use the `-g` (aka `--grep`) option. For example, to only run test involving transitions, run `pnpm test -- -g transition`.
##### Running solo test ##### Running solo test
@ -106,11 +106,11 @@ Test samples are kept in `/test/xxx/samples` folder.
##### Updating `.expected` files ##### Updating `.expected` files
1. Tests suites like `css`, `js`, `server-side-rendering` asserts that the generated output has to match the content in the `.expected` file. For example, in the `js` test suites, the generated js code is compared against the content in `expected.js`. 1. Tests suites like `css`, `js`, `server-side-rendering` asserts that the generated output has to match the content in the `.expected` file. For example, in the `js` test suites, the generated js code is compared against the content in `expected.js`.
1. To update the content of the `.expected` file, run the test with `--update` flag. (`npm run test --update`) 1. To update the content of the `.expected` file, run the test with `--update` flag. (`pnpm test --update`)
### Style guide ### Style guide
[Eslint](https://eslint.org) will catch most styling issues that may exist in your code. You can check the status of your code styling by simply running `npm run lint`. [Eslint](https://eslint.org) will catch most styling issues that may exist in your code. You can check the status of your code styling by simply running `pnpm lint`.
#### Code conventions #### Code conventions
@ -122,8 +122,8 @@ Test samples are kept in `/test/xxx/samples` folder.
Please make sure the following is done when submitting a pull request: Please make sure the following is done when submitting a pull request:
1. Describe your **test plan** in your pull request description. Make sure to test your changes. 1. Describe your **test plan** in your pull request description. Make sure to test your changes.
1. Make sure your code lints (`npm run lint`). 1. Make sure your code lints (`pnpm lint`).
1. Make sure your tests pass (`npm run test`). 1. Make sure your tests pass (`pnpm test`).
All pull requests should be opened against the `master` branch. Make sure the PR does only one thing, otherwise please split it. All pull requests should be opened against the `master` branch. Make sure the PR does only one thing, otherwise please split it.

@ -34,7 +34,7 @@ To install and work on Svelte locally:
```bash ```bash
git clone https://github.com/sveltejs/svelte.git git clone https://github.com/sveltejs/svelte.git
cd svelte cd svelte
npm install pnpm install
``` ```
> Do not use Yarn to install the dependencies, as the specific package versions in `package-lock.json` are used to build and test Svelte. > Do not use Yarn to install the dependencies, as the specific package versions in `package-lock.json` are used to build and test Svelte.
@ -42,13 +42,13 @@ npm install
To build the compiler and all the other modules included in the package: To build the compiler and all the other modules included in the package:
```bash ```bash
npm run build pnpm build
``` ```
To watch for changes and continually rebuild the package (this is useful if you're using [npm link](https://docs.npmjs.com/cli/link.html) to test out changes in a project locally): To watch for changes and continually rebuild the package (this is useful if you're using [`pnpm link`](https://pnpm.io/cli/link) to test out changes in a project locally):
```bash ```bash
npm run dev pnpm dev
``` ```
The compiler is written in [TypeScript](https://www.typescriptlang.org/), but don't let that put you off — it's basically just JavaScript with type annotations. You'll pick it up in no time. If you're using an editor other than [Visual Studio Code](https://code.visualstudio.com/), you may need to install a plugin in order to get syntax highlighting and code hints, etc. The compiler is written in [TypeScript](https://www.typescriptlang.org/), but don't let that put you off — it's basically just JavaScript with type annotations. You'll pick it up in no time. If you're using an editor other than [Visual Studio Code](https://code.visualstudio.com/), you may need to install a plugin in order to get syntax highlighting and code hints, etc.
@ -57,13 +57,13 @@ The compiler is written in [TypeScript](https://www.typescriptlang.org/), but do
### Running Tests ### Running Tests
```bash ```bash
npm run test pnpm test
``` ```
To filter tests, use `-g` (aka `--grep`). For example, to only run tests involving transitions: To filter tests, use `-g` (aka `--grep`). For example, to only run tests involving transitions:
```bash ```bash
npm run test -- -g transition pnpm test -- -g transition
``` ```

@ -84,9 +84,9 @@ export interface DOMAttributes<T extends EventTarget> {
'on:beforeinput'?: EventHandler<InputEvent, T> | undefined | null; 'on:beforeinput'?: EventHandler<InputEvent, T> | undefined | null;
'on:input'?: FormEventHandler<T> | undefined | null; 'on:input'?: FormEventHandler<T> | undefined | null;
'on:reset'?: FormEventHandler<T> | undefined | null; 'on:reset'?: FormEventHandler<T> | undefined | null;
'on:submit'?: EventHandler<Event & { readonly submitter: HTMLElement | null; }, T> | undefined | null; // TODO make this SubmitEvent once we require TS>=4.4 'on:submit'?: EventHandler<SubmitEvent, T> | undefined | null;
'on:invalid'?: EventHandler<Event, T> | undefined | null; 'on:invalid'?: EventHandler<Event, T> | undefined | null;
'on:formdata'?: EventHandler<Event & { readonly formData: FormData; }, T> | undefined | null; // TODO make this FormDataEvent once we require TS>=4.4 'on:formdata'?: EventHandler<FormDataEvent, T> | undefined | null;
// Image Events // Image Events
'on:load'?: EventHandler | undefined | null; 'on:load'?: EventHandler | undefined | null;
@ -547,9 +547,9 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
'bind:innerText'?: string | undefined | null; 'bind:innerText'?: string | undefined | null;
readonly 'bind:contentRect'?: DOMRectReadOnly | undefined | null; readonly 'bind:contentRect'?: DOMRectReadOnly | undefined | null;
readonly 'bind:contentBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4 readonly 'bind:contentBoxSize'?: Array<ResizeObserverSize> | undefined | null;
readonly 'bind:borderBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4 readonly 'bind:borderBoxSize'?: Array<ResizeObserverSize> | undefined | null;
readonly 'bind:devicePixelContentBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4 readonly 'bind:devicePixelContentBoxSize'?: Array<ResizeObserverSize> | undefined | null;
// SvelteKit // SvelteKit
'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null; 'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null;
@ -558,6 +558,9 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null; 'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null;
'data-sveltekit-reload'?: true | '' | 'off' | undefined | null; 'data-sveltekit-reload'?: true | '' | 'off' | undefined | null;
'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null; 'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null;
// allow any data- attribute
[key: `data-${string}`]: any;
} }
export type HTMLAttributeAnchorTarget = export type HTMLAttributeAnchorTarget =

9435
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -130,6 +130,7 @@
"@rollup/plugin-virtual": "^3.0.1", "@rollup/plugin-virtual": "^3.0.1",
"@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.8.0", "@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.8.0",
"@types/aria-query": "^5.0.1", "@types/aria-query": "^5.0.1",
"@types/estree": "^1.0.0",
"@types/mocha": "^10.0.1", "@types/mocha": "^10.0.1",
"@types/node": "^14.14.31", "@types/node": "^14.14.31",
"@typescript-eslint/eslint-plugin": "^5.58.0", "@typescript-eslint/eslint-plugin": "^5.58.0",
@ -159,5 +160,9 @@
"tslib": "^2.5.0", "tslib": "^2.5.0",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"util": "^0.12.5" "util": "^0.12.5"
},
"packageManager": "pnpm@7.32.0",
"engines": {
"pnpm": ">=7.0.0"
} }
} }

File diff suppressed because it is too large Load Diff

@ -38,6 +38,7 @@ import compiler_warnings from './compiler_warnings';
import compiler_errors from './compiler_errors'; import compiler_errors from './compiler_errors';
import { extract_ignores_above_position, extract_svelte_ignore_from_comments } from '../utils/extract_svelte_ignore'; import { extract_ignores_above_position, extract_svelte_ignore_from_comments } from '../utils/extract_svelte_ignore';
import check_enable_sourcemap from './utils/check_enable_sourcemap'; import check_enable_sourcemap from './utils/check_enable_sourcemap';
import Tag from './nodes/shared/Tag';
interface ComponentOptions { interface ComponentOptions {
namespace?: string; namespace?: string;
@ -112,6 +113,8 @@ export default class Component {
slots: Map<string, Slot> = new Map(); slots: Map<string, Slot> = new Map();
slot_outlets: Set<string> = new Set(); slot_outlets: Set<string> = new Set();
tags: Tag[] = [];
constructor( constructor(
ast: Ast, ast: Ast,
source: string, source: string,
@ -766,6 +769,7 @@ export default class Component {
this.hoist_instance_declarations(); this.hoist_instance_declarations();
this.extract_reactive_declarations(); this.extract_reactive_declarations();
this.check_if_tags_content_dynamic();
} }
post_template_walk() { post_template_walk() {
@ -1484,6 +1488,12 @@ export default class Component {
unsorted_reactive_declarations.forEach(add_declaration); unsorted_reactive_declarations.forEach(add_declaration);
} }
check_if_tags_content_dynamic() {
this.tags.forEach(tag => {
tag.check_if_content_dynamic();
});
}
warn_if_undefined(name: string, node, template_scope: TemplateScope) { warn_if_undefined(name: string, node, template_scope: TemplateScope) {
if (name[0] === '$') { if (name[0] === '$') {
if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) { if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) {

@ -59,6 +59,11 @@ export default class Attribute extends Node {
return expression; return expression;
}); });
} }
if (this.dependencies.size > 0) {
parent.cannot_use_innerhtml();
parent.not_static_content();
}
} }
get_dependencies() { get_dependencies() {

@ -27,6 +27,8 @@ export default class AwaitBlock extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);

@ -33,6 +33,8 @@ export default class EachBlock extends AbstractBlock {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);
this.context = info.context.name || 'each'; // TODO this is used to facilitate binding; currently fails with destructuring this.context = info.context.name || 'each'; // TODO this is used to facilitate binding; currently fails with destructuring

@ -15,6 +15,7 @@ import { is_name_contenteditable, get_contenteditable_attr, has_contenteditable_
import { regex_dimensions, regex_starts_with_newline, regex_non_whitespace_character, regex_box_size } from '../../utils/patterns'; import { regex_dimensions, regex_starts_with_newline, regex_non_whitespace_character, regex_box_size } from '../../utils/patterns';
import fuzzymatch from '../../utils/fuzzymatch'; import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list'; import list from '../../utils/list';
import hash from '../utils/hash';
import Let from './Let'; import Let from './Let';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces'; import { INode } from './interfaces';
@ -503,6 +504,25 @@ export default class Element extends Node {
this.optimise(); this.optimise();
component.apply_stylesheet(this); component.apply_stylesheet(this);
if (this.parent) {
if (this.actions.length > 0 ||
this.animation ||
this.bindings.length > 0 ||
this.classes.length > 0 ||
this.intro || this.outro ||
this.handlers.length > 0 ||
this.styles.length > 0 ||
this.name === 'option' ||
this.is_dynamic_element ||
this.tag_expr.dynamic_dependencies().length ||
this.is_dynamic_element ||
component.compile_options.dev
) {
this.parent.cannot_use_innerhtml(); // need to use add_location
this.parent.not_static_content();
}
}
} }
validate() { validate() {
@ -1262,6 +1282,20 @@ export default class Element extends Node {
} }
}); });
} }
get can_use_textcontent() {
return this.is_static_content && this.children.every(node => node.type === 'Text' || node.type === 'MustacheTag');
}
get can_optimise_to_html_string() {
const can_use_textcontent = this.can_use_textcontent;
const is_template_with_text_content = this.name === 'template' && can_use_textcontent;
return !is_template_with_text_content && !this.namespace && (this.can_use_innerhtml || can_use_textcontent) && this.children.length > 0;
}
hash() {
return `svelte-${hash(this.component.source.slice(this.start, this.end))}`;
}
} }
const regex_starts_with_vowel = /^[aeiou]/; const regex_starts_with_vowel = /^[aeiou]/;

@ -15,6 +15,8 @@ export default class Head extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.cannot_use_innerhtml();
if (info.attributes.length) { if (info.attributes.length) {
component.error(info.attributes[0], compiler_errors.invalid_attribute_head); component.error(info.attributes[0], compiler_errors.invalid_attribute_head);
return; return;

@ -18,6 +18,8 @@ export default class IfBlock extends AbstractBlock {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.scope = scope.child(); this.scope = scope.child();
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, this.scope, info.expression); this.expression = new Expression(component, this, this.scope, info.expression);
([this.const_tags, this.children] = get_const_tags(info.children, component, this, this)); ([this.const_tags, this.children] = get_const_tags(info.children, component, this, this));

@ -28,6 +28,9 @@ export default class InlineComponent extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
if (info.name !== 'svelte:component' && info.name !== 'svelte:self') { if (info.name !== 'svelte:component' && info.name !== 'svelte:self') {
const name = info.name.split('.')[0]; // accommodate namespaces const name = info.name.split('.')[0]; // accommodate namespaces
component.warn_if_undefined(name, info, scope); component.warn_if_undefined(name, info, scope);

@ -13,6 +13,8 @@ export default class KeyBlock extends AbstractBlock {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);

@ -2,4 +2,9 @@ import Tag from './shared/Tag';
export default class RawMustacheTag extends Tag { export default class RawMustacheTag extends Tag {
type: 'RawMustacheTag'; type: 'RawMustacheTag';
constructor(component, parent, scope, info) {
super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
}
} }

@ -60,5 +60,8 @@ export default class Slot extends Element {
} }
component.slots.set(this.slot_name, this); component.slots.set(this.slot_name, this);
this.cannot_use_innerhtml();
this.not_static_content();
} }
} }

@ -18,6 +18,7 @@ const elements_without_text = new Set([
]); ]);
const regex_ends_with_svg = /svg$/; const regex_ends_with_svg = /svg$/;
const regex_non_whitespace_characters = /[\S\u00A0]/;
export default class Text extends Node { export default class Text extends Node {
type: 'Text'; type: 'Text';
@ -63,4 +64,11 @@ export default class Text extends Node {
return false; return false;
} }
use_space(): boolean {
if (this.component.compile_options.preserveWhitespace) return false;
if (regex_non_whitespace_characters.test(this.data)) return false;
return !this.within_pre();
}
} }

@ -197,6 +197,15 @@ export default class Expression {
}); });
} }
dynamic_contextual_dependencies() {
return Array.from(this.contextual_dependencies).filter(name => {
return Array.from(this.template_scope.dependencies_for_name.get(name)).some(variable_name => {
const variable = this.component.var_lookup.get(variable_name);
return is_dynamic(variable);
});
});
}
// TODO move this into a render-dom wrapper? // TODO move this into a render-dom wrapper?
manipulate(block?: Block, ctx?: string | void) { manipulate(block?: Block, ctx?: string | void) {
// TODO ideally we wouldn't end up calling this method // TODO ideally we wouldn't end up calling this method

@ -15,6 +15,7 @@ export default class Node {
next?: INode; next?: INode;
can_use_innerhtml: boolean; can_use_innerhtml: boolean;
is_static_content: boolean;
var: string; var: string;
attributes: Attribute[]; attributes: Attribute[];
@ -33,6 +34,9 @@ export default class Node {
value: parent value: parent
} }
}); });
this.can_use_innerhtml = true;
this.is_static_content = true;
} }
cannot_use_innerhtml() { cannot_use_innerhtml() {
@ -42,6 +46,11 @@ export default class Node {
} }
} }
not_static_content() {
this.is_static_content = false;
if (this.parent) this.parent.not_static_content();
}
find_nearest(selector: RegExp) { find_nearest(selector: RegExp) {
if (selector.test(this.type)) return this; if (selector.test(this.type)) return this;
if (this.parent) return this.parent.find_nearest(selector); if (this.parent) return this.parent.find_nearest(selector);

@ -8,6 +8,9 @@ export default class Tag extends Node {
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
component.tags.push(this);
this.cannot_use_innerhtml();
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);
this.should_cache = ( this.should_cache = (
@ -15,4 +18,12 @@ export default class Tag extends Node {
(this.expression.dependencies.size && scope.names.has(info.expression.name)) (this.expression.dependencies.size && scope.names.has(info.expression.name))
); );
} }
is_dependencies_static() {
return this.expression.dynamic_contextual_dependencies().length === 0 && this.expression.dynamic_dependencies().length === 0;
}
check_if_content_dynamic() {
if (!this.is_dependencies_static()) {
this.not_static_content();
}
}
} }

@ -143,9 +143,6 @@ export default class AwaitBlockWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
block.add_dependencies(this.node.expression.dependencies); block.add_dependencies(this.node.expression.dependencies);
let is_dynamic = false; let is_dynamic = false;

@ -38,4 +38,10 @@ export default class CommentWrapper extends Wrapper {
parent_node parent_node
); );
} }
text() {
if (!this.renderer.options.preserveComments) return '';
return `<!--${this.node.data}-->`;
}
} }

@ -80,8 +80,6 @@ export default class EachBlockWrapper extends Wrapper {
next_sibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
const { dependencies } = node.expression; const { dependencies } = node.expression;
block.add_dependencies(dependencies); block.add_dependencies(dependencies);

@ -36,9 +36,6 @@ export class BaseAttributeWrapper {
this.parent = parent; this.parent = parent;
if (node.dependencies.size > 0) { if (node.dependencies.size > 0) {
parent.cannot_use_innerhtml();
parent.not_static_content();
block.add_dependencies(node.dependencies); block.add_dependencies(node.dependencies);
} }
} }

@ -29,6 +29,7 @@ import is_dynamic from '../shared/is_dynamic';
import { is_name_contenteditable, has_contenteditable_attr } from '../../../utils/contenteditable'; import { is_name_contenteditable, has_contenteditable_attr } from '../../../utils/contenteditable';
import create_debugging_comment from '../shared/create_debugging_comment'; import create_debugging_comment from '../shared/create_debugging_comment';
import { push_array } from '../../../../utils/push_array'; import { push_array } from '../../../../utils/push_array';
import CommentWrapper from '../Comment';
interface BindingGroup { interface BindingGroup {
events: string[]; events: string[];
@ -288,24 +289,6 @@ export default class ElementWrapper extends Wrapper {
} }
}); });
if (this.parent) {
if (node.actions.length > 0 ||
node.animation ||
node.bindings.length > 0 ||
node.classes.length > 0 ||
node.intro || node.outro ||
node.handlers.length > 0 ||
node.styles.length > 0 ||
this.node.name === 'option' ||
node.tag_expr.dynamic_dependencies().length ||
node.is_dynamic_element ||
renderer.options.dev
) {
this.parent.cannot_use_innerhtml(); // need to use add_location
this.parent.not_static_content();
}
}
this.fragment = new FragmentWrapper(renderer, block, node.children, this, strip_whitespace, next_sibling); this.fragment = new FragmentWrapper(renderer, block, node.children, this, strip_whitespace, next_sibling);
this.element_data_name = block.get_unique_name(`${this.var.name}_data`); this.element_data_name = block.get_unique_name(`${this.var.name}_data`);
@ -445,6 +428,7 @@ export default class ElementWrapper extends Wrapper {
render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
const { renderer } = this; const { renderer } = this;
const hydratable = renderer.options.hydratable;
if (this.node.name === 'noscript') return; if (this.node.name === 'noscript') return;
@ -458,13 +442,15 @@ export default class ElementWrapper extends Wrapper {
b`${node} = ${render_statement};` b`${node} = ${render_statement};`
); );
if (renderer.options.hydratable) { const { can_use_textcontent, can_optimise_to_html_string } = this.node;
if (hydratable) {
if (parent_nodes) { if (parent_nodes) {
block.chunks.claim.push(b` block.chunks.claim.push(b`
${node} = ${this.get_claim_statement(block, parent_nodes)}; ${node} = ${this.get_claim_statement(block, parent_nodes, can_optimise_to_html_string)};
`); `);
if (!this.void && this.node.children.length > 0) { if (!can_optimise_to_html_string && !this.void && this.node.children.length > 0) {
block.chunks.claim.push(b` block.chunks.claim.push(b`
var ${nodes} = ${children}; var ${nodes} = ${children};
`); `);
@ -502,15 +488,19 @@ export default class ElementWrapper extends Wrapper {
// insert static children with textContent or innerHTML // insert static children with textContent or innerHTML
// skip textcontent for <template>. append nodes to TemplateElement.content instead // skip textcontent for <template>. append nodes to TemplateElement.content instead
const can_use_textcontent = this.can_use_textcontent(); if (can_optimise_to_html_string) {
const is_template = this.node.name === 'template';
const is_template_with_text_content = is_template && can_use_textcontent;
if (!is_template_with_text_content && !this.node.namespace && (this.can_use_innerhtml || can_use_textcontent) && this.fragment.nodes.length > 0) {
if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') { if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') {
block.chunks.create.push( let text: Node = string_literal((this.fragment.nodes[0] as TextWrapper).data);
b`${node}.textContent = ${string_literal((this.fragment.nodes[0] as TextWrapper).data)};` if (hydratable) {
); const variable = block.get_unique_name('textContent');
block.add_variable(variable, text);
text = variable;
}
block.chunks.create.push(b`${node}.textContent = ${text};`);
if (hydratable) {
block.chunks.claim.push(b`if (@get_svelte_dataset(${node}) !== "${this.node.hash()}") ${node}.textContent = ${text};`);
}
} else { } else {
const state = { const state = {
quasi: { quasi: {
@ -519,25 +509,33 @@ export default class ElementWrapper extends Wrapper {
} }
}; };
const literal = { let literal: Node = {
type: 'TemplateLiteral', type: 'TemplateLiteral',
expressions: [], expressions: [],
quasis: [] quasis: []
}; };
const can_use_raw_text = !this.can_use_innerhtml && can_use_textcontent; const can_use_raw_text = !this.node.can_use_innerhtml && can_use_textcontent;
to_html((this.fragment.nodes as unknown as Array<ElementWrapper | TextWrapper>), block, literal, state, can_use_raw_text); to_html((this.fragment.nodes as unknown as Array<ElementWrapper | CommentWrapper | TextWrapper>), block, literal, state, can_use_raw_text);
literal.quasis.push(state.quasi); literal.quasis.push(state.quasi as any);
block.chunks.create.push( if (hydratable) {
b`${node}.${this.can_use_innerhtml ? 'innerHTML' : 'textContent'} = ${literal};` const variable = block.get_unique_name('textContent');
); block.add_variable(variable, literal);
literal = variable;
}
const property = this.node.can_use_innerhtml ? 'innerHTML' : 'textContent';
block.chunks.create.push(b`${node}.${property} = ${literal};`);
if (hydratable) {
block.chunks.claim.push(b`if (@get_svelte_dataset(${node}) !== "${this.node.hash()}") ${node}.${property} = ${literal};`);
}
} }
} else { } else {
this.fragment.nodes.forEach((child: Wrapper) => { this.fragment.nodes.forEach((child: Wrapper) => {
child.render( child.render(
block, block,
is_template ? x`${node}.content` : node, this.node.name === 'template' ? x`${node}.content` : node,
nodes, nodes,
{ element_data_name: this.element_data_name } { element_data_name: this.element_data_name }
); );
@ -566,7 +564,7 @@ export default class ElementWrapper extends Wrapper {
this.add_styles(block); this.add_styles(block);
this.add_manual_style_scoping(block); this.add_manual_style_scoping(block);
if (nodes && this.renderer.options.hydratable && !this.void) { if (nodes && hydratable && !this.void && !can_optimise_to_html_string) {
block.chunks.claim.push( block.chunks.claim.push(
b`${this.node.children.length > 0 ? nodes : children}.forEach(@detach);` b`${this.node.children.length > 0 ? nodes : children}.forEach(@detach);`
); );
@ -582,10 +580,6 @@ export default class ElementWrapper extends Wrapper {
block.renderer.dirty(this.node.tag_expr.dynamic_dependencies()); block.renderer.dirty(this.node.tag_expr.dynamic_dependencies());
} }
can_use_textcontent() {
return this.is_static_content && this.fragment.nodes.every(node => node.node.type === 'Text' || node.node.type === 'MustacheTag');
}
get_render_statement(block: Block) { get_render_statement(block: Block) {
const { name, namespace, tag_expr } = this.node; const { name, namespace, tag_expr } = this.node;
const reference = tag_expr.manipulate(block); const reference = tag_expr.manipulate(block);
@ -606,7 +600,7 @@ export default class ElementWrapper extends Wrapper {
return x`@element(${reference})`; return x`@element(${reference})`;
} }
get_claim_statement(block: Block, nodes: Identifier) { get_claim_statement(block: Block, nodes: Identifier, to_optimise_hydration: boolean) {
const attributes = this.attributes const attributes = this.attributes
.filter((attr) => !(attr instanceof SpreadAttributeWrapper) && !attr.property_name) .filter((attr) => !(attr instanceof SpreadAttributeWrapper) && !attr.property_name)
.map((attr) => p`${(attr as StyleAttributeWrapper | AttributeWrapper).name}: true`); .map((attr) => p`${(attr as StyleAttributeWrapper | AttributeWrapper).name}: true`);
@ -624,6 +618,10 @@ export default class ElementWrapper extends Wrapper {
reference = x`(${this.node.tag_expr.manipulate(block)} || 'null').toUpperCase()`; reference = x`(${this.node.tag_expr.manipulate(block)} || 'null').toUpperCase()`;
} }
if (to_optimise_hydration) {
attributes.push(p`["data-svelte-h"]: true`);
}
if (this.node.namespace === namespaces.svg) { if (this.node.namespace === namespaces.svg) {
return x`@claim_svg_element(${nodes}, ${reference}, { ${attributes} })`; return x`@claim_svg_element(${nodes}, ${reference}, { ${attributes} })`;
} else { } else {
@ -1288,13 +1286,19 @@ export default class ElementWrapper extends Wrapper {
const regex_backticks = /`/g; const regex_backticks = /`/g;
const regex_dollar_signs = /\$/g; const regex_dollar_signs = /\$/g;
function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapper | RawMustacheTagWrapper>, block: Block, literal: any, state: any, can_use_raw_text?: boolean) { function to_html(wrappers: Array<CommentWrapper | ElementWrapper | TextWrapper | MustacheTagWrapper | RawMustacheTagWrapper>, block: Block, literal: any, state: any, can_use_raw_text?: boolean) {
wrappers.forEach(wrapper => { wrappers.forEach(wrapper => {
if (wrapper instanceof TextWrapper) { if (wrapper instanceof CommentWrapper) {
state.quasi.value.raw += wrapper.text();
} else if (wrapper instanceof TextWrapper) {
// Don't add the <pre>/<textarea> newline logic here because pre/textarea.innerHTML // Don't add the <pre>/<textarea> newline logic here because pre/textarea.innerHTML
// would keep the leading newline, too, only someParent.innerHTML = '..<pre/textarea>..' won't // would keep the leading newline, too, only someParent.innerHTML = '..<pre/textarea>..' won't
if ((wrapper as TextWrapper).use_space()) state.quasi.value.raw += ' '; if (wrapper.use_space()) {
// use space instead of the text content
state.quasi.value.raw += ' ';
return;
}
const parent = wrapper.node.parent as Element; const parent = wrapper.node.parent as Element;

@ -20,8 +20,6 @@ export default class HeadWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.can_use_innerhtml = false;
this.fragment = new FragmentWrapper( this.fragment = new FragmentWrapper(
renderer, renderer,
block, block,

@ -105,9 +105,6 @@ export default class IfBlockWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
this.branches = []; this.branches = [];
const blocks: Block[] = []; const blocks: Block[] = [];

@ -44,9 +44,6 @@ export default class InlineComponentWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
if (this.node.expression) { if (this.node.expression) {
block.add_dependencies(this.node.expression.dependencies); block.add_dependencies(this.node.expression.dependencies);
} }

@ -24,9 +24,6 @@ export default class KeyBlockWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
this.dependencies = node.expression.dynamic_dependencies(); this.dependencies = node.expression.dynamic_dependencies();
if (this.dependencies.length) { if (this.dependencies.length) {

@ -20,8 +20,6 @@ export default class RawMustacheTagWrapper extends Tag {
node: MustacheTag | RawMustacheTag node: MustacheTag | RawMustacheTag
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
} }
render(block: Block, parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, parent_node: Identifier, _parent_nodes: Identifier) {

@ -30,8 +30,6 @@ export default class SlotWrapper extends Wrapper {
next_sibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
if (this.node.children.length) { if (this.node.children.length) {
this.fallback = block.child({ this.fallback = block.child({

@ -5,11 +5,9 @@ import Wrapper from './shared/Wrapper';
import { x } from 'code-red'; import { x } from 'code-red';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
const regex_non_whitespace_characters = /[\S\u00A0]/;
export default class TextWrapper extends Wrapper { export default class TextWrapper extends Wrapper {
node: Text; node: Text;
data: string; _data: string;
skip: boolean; skip: boolean;
var: Identifier; var: Identifier;
@ -23,15 +21,22 @@ export default class TextWrapper extends Wrapper {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.skip = this.node.should_skip(); this.skip = this.node.should_skip();
this.data = data; this._data = data;
this.var = (this.skip ? null : x`t`) as unknown as Identifier; this.var = (this.skip ? null : x`t`) as unknown as Identifier;
} }
use_space() { use_space() {
if (this.renderer.component.component_options.preserveWhitespace) return false; return this.node.use_space();
if (regex_non_whitespace_characters.test(this.data)) return false; }
return !this.node.within_pre(); set data(value: string) {
// when updating `this.data` during optimisation
// propagate the changes over to the underlying node
// so that the node.use_space reflects on the latest `data` value
this.node.data = this._data = value;
}
get data() {
return this._data;
} }
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {

@ -12,18 +12,9 @@ export default class Tag extends Wrapper {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) { constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
if (!this.is_dependencies_static()) {
this.not_static_content();
}
block.add_dependencies(node.expression.dependencies); block.add_dependencies(node.expression.dependencies);
} }
is_dependencies_static() {
return this.node.expression.contextual_dependencies.size === 0 && this.node.expression.dynamic_dependencies().length === 0;
}
rename_this_method( rename_this_method(
block: Block, block: Block,
update: ((value: Node) => (Node | Node[])) update: ((value: Node) => (Node | Node[]))

@ -13,8 +13,6 @@ export default class Wrapper {
next: Wrapper | null; next: Wrapper | null;
var: Identifier; var: Identifier;
can_use_innerhtml: boolean;
is_static_content: boolean;
constructor( constructor(
renderer: Renderer, renderer: Renderer,
@ -35,22 +33,9 @@ export default class Wrapper {
} }
}); });
this.can_use_innerhtml = !renderer.options.hydratable;
this.is_static_content = !renderer.options.hydratable;
block.wrappers.push(this); block.wrappers.push(this);
} }
cannot_use_innerhtml() {
this.can_use_innerhtml = false;
if (this.parent) this.parent.cannot_use_innerhtml();
}
not_static_content() {
this.is_static_content = false;
if (this.parent) this.parent.not_static_content();
}
get_or_create_anchor(block: Block, parent_node: Identifier, parent_nodes: Identifier) { get_or_create_anchor(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
// TODO use this in EachBlock and IfBlock — tricky because // TODO use this in EachBlock and IfBlock — tricky because
// children need to be created first // children need to be created first

@ -48,6 +48,7 @@ const handlers: Record<string, Handler> = {
export interface RenderOptions extends CompileOptions{ export interface RenderOptions extends CompileOptions{
locate: (c: number) => { line: number; column: number }; locate: (c: number) => { line: number; column: number };
head_id?: string; head_id?: string;
has_added_svelte_hash?: boolean;
} }
export default class Renderer { export default class Renderer {

@ -158,6 +158,13 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
} }
}); });
if (options.hydratable) {
if (node.can_optimise_to_html_string && !options.has_added_svelte_hash) {
renderer.add_string(` data-svelte-h="${node.hash()}"`);
options = { ...options, has_added_svelte_hash: true };
}
}
renderer.add_string('>'); renderer.add_string('>');
if (node_contents !== undefined) { if (node_contents !== undefined) {

@ -5,7 +5,9 @@ import Element from '../../nodes/Element';
export default function(node: Text, renderer: Renderer, _options: RenderOptions) { export default function(node: Text, renderer: Renderer, _options: RenderOptions) {
let text = node.data; let text = node.data;
if ( if (node.use_space()) {
text = ' ';
} else if (
!node.parent || !node.parent ||
node.parent.type !== 'Element' || node.parent.type !== 'Element' ||
((node.parent as Element).name !== 'script' && (node.parent as Element).name !== 'style') ((node.parent as Element).name !== 'script' && (node.parent as Element).name !== 'style')

@ -39,6 +39,7 @@ export default function remove_whitespace_children(children: INode[], next?: INo
continue; continue;
} }
child.data = data;
nodes.unshift(child); nodes.unshift(child);
link(last_child, last_child = child); link(last_child, last_child = child);
} else { } else {

@ -1,4 +1,5 @@
export function string_literal(data: string) { import { Literal } from 'estree';
export function string_literal(data: string): Literal {
return { return {
type: 'Literal', type: 'Literal',
value: data value: data

@ -157,10 +157,13 @@ export function construct_svelte_component_dev(component, props) {
} }
} }
type Props = Record<string, any>; export interface SvelteComponentDev<
export interface SvelteComponentDev { Props extends Record<string, any> = any,
$set(props?: Props): void; Events extends Record<string, any> = any,
$on(event: string, callback: ((event: any) => void) | null | undefined): () => void; Slots extends Record<string, any> = any // eslint-disable-line @typescript-eslint/no-unused-vars
> {
$set(props?: Partial<Props>): void;
$on<K extends Extract<keyof Events, string>>(type: K, callback: ((e: Events[K]) => void) | null | undefined): () => void;
$destroy(): void; $destroy(): void;
[accessor: string]: any; [accessor: string]: any;
} }
@ -177,8 +180,33 @@ export interface ComponentConstructorOptions<Props extends Record<string, any> =
/** /**
* Base class for Svelte components with some minor dev-enhancements. Used when dev=true. * Base class for Svelte components with some minor dev-enhancements. Used when dev=true.
*
* Can be used to create strongly typed Svelte components.
*
* ### Example:
*
* You have component library on npm called `component-library`, from which
* you export a component called `MyComponent`. For Svelte+TypeScript users,
* you want to provide typings. Therefore you create a `index.d.ts`:
* ```ts
* import { SvelteComponent } from "svelte";
* export class MyComponent extends SvelteComponent<{foo: string}> {}
* ```
* Typing this makes it possible for IDEs like VS Code with the Svelte extension
* to provide intellisense and to use the component like this in a Svelte file
* with TypeScript:
* ```svelte
* <script lang="ts">
* import { MyComponent } from "component-library";
* </script>
* <MyComponent foo={'bar'} />
* ```
*/ */
export class SvelteComponentDev extends SvelteComponent { export class SvelteComponentDev<
Props extends Record<string, any> = any,
Events extends Record<string, any> = any,
Slots extends Record<string, any> = any
> extends SvelteComponent {
/** /**
* @private * @private
* For type checking capabilities only. * For type checking capabilities only.
@ -192,19 +220,16 @@ export class SvelteComponentDev extends SvelteComponent {
* Does not exist at runtime. * Does not exist at runtime.
* ### DO NOT USE! * ### DO NOT USE!
*/ */
$$events_def: any; $$events_def: Events;
/** /**
* @private * @private
* For type checking capabilities only. * For type checking capabilities only.
* Does not exist at runtime. * Does not exist at runtime.
* ### DO NOT USE! * ### DO NOT USE!
*/ */
$$slot_def: any; $$slot_def: Slots;
/** The custom element version of the component. Only present if compiled with the `customElement` compiler option */
static element?: typeof HTMLElement;
constructor(options: ComponentConstructorOptions) { constructor(options: ComponentConstructorOptions<Props>) {
if (!options || (!options.target && !options.$$inline)) { if (!options || (!options.target && !options.$$inline)) {
throw new Error("'target' is a required option"); throw new Error("'target' is a required option");
} }
@ -224,82 +249,21 @@ export class SvelteComponentDev extends SvelteComponent {
$inject_state() {} $inject_state() {}
} }
// TODO https://github.com/microsoft/TypeScript/issues/41770 is the reason // eslint-disable-next-line @typescript-eslint/no-empty-interface
// why we have to split out SvelteComponentTyped to not break existing usage of SvelteComponent.
// Try to find a better way for Svelte 4.0.
export interface SvelteComponentTyped< export interface SvelteComponentTyped<
Props extends Record<string, any> = any, Props extends Record<string, any> = any,
Events extends Record<string, any> = any, Events extends Record<string, any> = any,
Slots extends Record<string, any> = any // eslint-disable-line @typescript-eslint/no-unused-vars Slots extends Record<string, any> = any
> { > extends SvelteComponentDev<Props, Events, Slots> {}
$set(props?: Partial<Props>): void;
$on<K extends Extract<keyof Events, string>>(type: K, callback: ((e: Events[K]) => void) | null | undefined): () => void;
$destroy(): void;
[accessor: string]: any;
}
/** /**
* Base class to create strongly typed Svelte components. * @deprecated Use `SvelteComponent` instead. See PR for more information: https://github.com/sveltejs/svelte/pull/8512
* This only exists for typing purposes and should be used in `.d.ts` files.
*
* ### Example:
*
* You have component library on npm called `component-library`, from which
* you export a component called `MyComponent`. For Svelte+TypeScript users,
* you want to provide typings. Therefore you create a `index.d.ts`:
* ```ts
* import { SvelteComponentTyped } from "svelte";
* export class MyComponent extends SvelteComponentTyped<{foo: string}> {}
* ```
* Typing this makes it possible for IDEs like VS Code with the Svelte extension
* to provide intellisense and to use the component like this in a Svelte file
* with TypeScript:
* ```svelte
* <script lang="ts">
* import { MyComponent } from "component-library";
* </script>
* <MyComponent foo={'bar'} />
* ```
*
* #### Why not make this part of `SvelteComponent(Dev)`?
* Because
* ```ts
* class ASubclassOfSvelteComponent extends SvelteComponent<{foo: string}> {}
* const component: typeof SvelteComponent = ASubclassOfSvelteComponent;
* ```
* will throw a type error, so we need to separate the more strictly typed class.
*/ */
export class SvelteComponentTyped< export class SvelteComponentTyped<
Props extends Record<string, any> = any, Props extends Record<string, any> = any,
Events extends Record<string, any> = any, Events extends Record<string, any> = any,
Slots extends Record<string, any> = any Slots extends Record<string, any> = any
> extends SvelteComponentDev { > extends SvelteComponentDev<Props, Events, Slots> {}
/**
* @private
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*/
$$prop_def: Props;
/**
* @private
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*/
$$events_def: Events;
/**
* @private
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*/
$$slot_def: Slots;
constructor(options: ComponentConstructorOptions<Props>) {
super(options);
}
}
/** /**
* Convenience type to get the type of a Svelte component. Useful for example in combination with * Convenience type to get the type of a Svelte component. Useful for example in combination with
@ -308,23 +272,23 @@ export class SvelteComponentTyped<
* Example: * Example:
* ```html * ```html
* <script lang="ts"> * <script lang="ts">
* import type { ComponentType, SvelteComponentTyped } from 'svelte'; * import type { ComponentType, SvelteComponent } from 'svelte';
* import Component1 from './Component1.svelte'; * import Component1 from './Component1.svelte';
* import Component2 from './Component2.svelte'; * import Component2 from './Component2.svelte';
* *
* const component: ComponentType = someLogic() ? Component1 : Component2; * const component: ComponentType = someLogic() ? Component1 : Component2;
* const componentOfCertainSubType: ComponentType<SvelteComponentTyped<{ needsThisProp: string }>> = someLogic() ? Component1 : Component2; * const componentOfCertainSubType: ComponentType<SvelteComponent<{ needsThisProp: string }>> = someLogic() ? Component1 : Component2;
* </script> * </script>
* *
* <svelte:component this={component} /> * <svelte:component this={component} />
* <svelte:component this={componentOfCertainSubType} needsThisProp="hello" /> * <svelte:component this={componentOfCertainSubType} needsThisProp="hello" />
* ``` * ```
*/ */
export type ComponentType<Component extends SvelteComponentTyped = SvelteComponentTyped> = (new ( export type ComponentType<Component extends SvelteComponentDev = SvelteComponentDev> = new (
options: ComponentConstructorOptions< options: ComponentConstructorOptions<
Component extends SvelteComponentTyped<infer Props> ? Props : Record<string, any> Component extends SvelteComponentDev<infer Props> ? Props : Record<string, any>
> >
) => Component) & { ) => Component & {
/** The custom element version of the component. Only present if compiled with the `customElement` compiler option */ /** The custom element version of the component. Only present if compiled with the `customElement` compiler option */
element?: typeof HTMLElement element?: typeof HTMLElement
}; };
@ -340,7 +304,7 @@ export type ComponentType<Component extends SvelteComponentTyped = SvelteCompone
* </script> * </script>
* ``` * ```
*/ */
export type ComponentProps<Component extends SvelteComponent> = Component extends SvelteComponentTyped<infer Props> export type ComponentProps<Component extends SvelteComponent> = Component extends SvelteComponentDev<infer Props>
? Props ? Props
: never; : never;
@ -360,7 +324,7 @@ export type ComponentProps<Component extends SvelteComponent> = Component extend
* ``` * ```
*/ */
export type ComponentEvents<Component extends SvelteComponent> = export type ComponentEvents<Component extends SvelteComponent> =
Component extends SvelteComponentTyped<any, infer Events> ? Events : never; Component extends SvelteComponentDev<any, infer Events> ? Events : never;
export function loop_guard(timeout) { export function loop_guard(timeout) {
const start = Date.now(); const start = Date.now();

@ -362,6 +362,10 @@ export function xlink_attr(node, attribute, value) {
node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value); node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value);
} }
export function get_svelte_dataset(node: HTMLElement) {
return node.dataset.svelteH;
}
export function get_binding_group_value(group, __value, checked) { export function get_binding_group_value(group, __value, checked) {
const value = new Set(); const value = new Set();
for (let i = 0; i < group.length; i += 1) { for (let i = 0; i < group.length; i += 1) {

@ -81,6 +81,7 @@ describe('custom-elements', function() {
const bundle = await rollup({ const bundle = await rollup({
input: `${__dirname}/samples/${dir}/test.js`, input: `${__dirname}/samples/${dir}/test.js`,
plugins: [ plugins: [
// @ts-ignore -- TODO: fix this
{ {
resolveId(importee) { resolveId(importee) {
if (importee === 'svelte/internal' || importee === './internal') { if (importee === 'svelte/internal' || importee === './internal') {

@ -4,11 +4,15 @@ import glob from 'tiny-glob/sync';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import * as colors from 'kleur'; import * as colors from 'kleur';
export const assert = (assert$1 as unknown) as typeof assert$1 & { htmlEqual: (actual, expected, message?) => void, htmlEqualWithComments: (actual, expected, message?) => void };
/**
* @type {typeof assert$1 & { htmlEqual: (actual: string, expected: string, message?: string) => void, htmlEqualWithOptions: (actual: string, expected: string, options: { preserveComments?: boolean, withoutNormalizeHtml?: boolean }, message?: string) => void }}
*/
export const assert = /** @type {any} */ (assert$1);
// for coverage purposes, we need to test source files, // for coverage purposes, we need to test source files,
// but for sanity purposes, we need to test dist files // but for sanity purposes, we need to test dist files
export function loadSvelte(test: boolean = false) { export function loadSvelte(test = false) {
process.env.TEST = test ? 'true' : ''; process.env.TEST = test ? 'true' : '';
const resolved = require.resolve('../compiler.js'); const resolved = require.resolve('../compiler.js');
@ -140,11 +144,19 @@ function cleanChildren(node) {
} }
} }
export function normalizeHtml(window, html, preserveComments = false) { /**
*
* @param {Window} window
* @param {string} html
* @param {{ removeDataSvelte?: boolean, preserveComments?: boolean }} param2
* @returns
*/
export function normalizeHtml(window, html, { removeDataSvelte = false, preserveComments = false }) {
try { try {
const node = window.document.createElement('div'); const node = window.document.createElement('div');
node.innerHTML = html node.innerHTML = html
.replace(/(<!--.*?-->)/g, preserveComments ? '$1' : '') .replace(/(<!--.*?-->)/g, preserveComments ? '$1' : '')
.replace(/(data-svelte-h="[^"]+")/g, removeDataSvelte ? '' : '$1')
.replace(/>[ \t\n\r\f]+</g, '><') .replace(/>[ \t\n\r\f]+</g, '><')
.trim(); .trim();
cleanChildren(node); cleanChildren(node);
@ -154,22 +166,44 @@ export function normalizeHtml(window, html, preserveComments = false) {
} }
} }
export function setupHtmlEqual() { /**
* @param {string} html
* @returns {string}
*/
export function normalizeNewline(html) {
return html.replace(/\r\n/g, '\n');
}
/**
* @param {{ removeDataSvelte?: boolean }} options
*/
export function setupHtmlEqual(options = {}) {
const window = env(); const window = env();
// eslint-disable-next-line no-import-assign // eslint-disable-next-line no-import-assign
assert.htmlEqual = (actual, expected, message) => { assert.htmlEqual = (actual, expected, message) => {
assert.deepEqual( assert.deepEqual(
normalizeHtml(window, actual), normalizeHtml(window, actual, options),
normalizeHtml(window, expected), normalizeHtml(window, expected, options),
message message
); );
}; };
// eslint-disable-next-line no-import-assign
assert.htmlEqualWithComments = (actual, expected, message) => { /**
*
* @param {string} actual
* @param {string} expected
* @param {{ preserveComments?: boolean, withoutNormalizeHtml?: boolean }} param2
* @param {string?} message
*/
assert.htmlEqualWithOptions = (actual, expected, { preserveComments, withoutNormalizeHtml }, message) => {
assert.deepEqual( assert.deepEqual(
normalizeHtml(window, actual, true), withoutNormalizeHtml
normalizeHtml(window, expected, true), ? normalizeNewline(actual).replace(/(\sdata-svelte-h="[^"]+")/g, options.removeDataSvelte ? '' : '$1')
: normalizeHtml(window, actual, { ...options, preserveComments }),
withoutNormalizeHtml
? normalizeNewline(expected).replace(/(\sdata-svelte-h="[^"]+")/g, options.removeDataSvelte ? '' : '$1')
: normalizeHtml(window, expected, { ...options, preserveComments }),
message message
); );
}; };
@ -243,6 +277,7 @@ const original_set_timeout = global.setTimeout;
export function useFakeTimers() { export function useFakeTimers() {
const callbacks = []; const callbacks = [];
// @ts-ignore
global.setTimeout = function (fn) { global.setTimeout = function (fn) {
callbacks.push(fn); callbacks.push(fn);
}; };
@ -280,7 +315,14 @@ export function prettyPrintPuppeteerAssertionError(message) {
} }
} }
export async function retryAsync<T>(fn: () => Promise<T>, maxAttempts: number = 3, interval: number = 1000): Promise<T> { /**
*
* @param {() => Promise<import ('puppeteer').Browser>} fn
* @param {number} maxAttempts
* @param {number} interval
* @returns {Promise<import ('puppeteer').Browser>}
*/
export async function retryAsync(fn, maxAttempts = 3, interval = 1000) {
let attempts = 0; let attempts = 0;
while (attempts <= maxAttempts) { while (attempts <= maxAttempts) {
try { try {
@ -292,8 +334,15 @@ export async function retryAsync<T>(fn: () => Promise<T>, maxAttempts: number =
} }
} }
// NOTE: Chromium may exit with SIGSEGV, so retry in that case /**
export async function executeBrowserTest<T>(browser, launchPuppeteer: () => Promise<T>, additionalAssertion: () => void, onError: (err: Error) => void) { * NOTE: Chromium may exit with SIGSEGV, so retry in that case
* @param {import ('puppeteer').Browser} browser
* @param {() => Promise<import ('puppeteer').Browser>} launchPuppeteer
* @param {() => void} additionalAssertion
* @param {(err: Error) => void} onError
* @returns {Promise<import ('puppeteer').Browser>}
*/
export async function executeBrowserTest(browser, launchPuppeteer, additionalAssertion, onError) {
let count = 0; let count = 0;
do { do {
count++; count++;
@ -301,6 +350,7 @@ export async function executeBrowserTest<T>(browser, launchPuppeteer: () => Prom
const page = await browser.newPage(); const page = await browser.newPage();
page.on('console', (type) => { page.on('console', (type) => {
// @ts-ignore -- TODO: Fix type
console[type._type](type._text); console[type._type](type._text);
}); });
@ -309,7 +359,10 @@ export async function executeBrowserTest<T>(browser, launchPuppeteer: () => Prom
console.error(error); console.error(error);
}); });
await page.goto('http://localhost:6789'); await page.goto('http://localhost:6789');
const result = await page.evaluate(() => test(document.querySelector('main'))); const result = await page.evaluate(() => {
// @ts-ignore -- It runs in browser context.
return test(document.querySelector('main'));
});
if (result) console.log(result); if (result) console.log(result);
additionalAssertion(); additionalAssertion();
await page.close(); await page.close();

@ -106,6 +106,13 @@ describe('hydration', () => {
} }
} }
if (config.snapshot) {
const snapshot_after = config.snapshot(target);
for (const s in snapshot_after) {
assert.equal(snapshot_after[s], snapshot[s], `Expected snapshot key "${s}" to have same value/reference`);
}
}
if (config.test) { if (config.test) {
config.test(assert, target, snapshot, component, window); config.test(assert, target, snapshot, component, window);
} else { } else {
@ -128,6 +135,6 @@ describe('hydration', () => {
} }
fs.readdirSync(`${__dirname}/samples`).forEach(dir => { fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
runTest(dir, null); runTest(dir);
}); });
}); });

@ -1 +1 @@
<h1>Hello world!</h1> <h1 data-svelte-h="svelte-1vv3a6r">Hello world!</h1>

@ -1 +1 @@
<h1>Hello world!</h1> <h1 data-svelte-h="svelte-1vv3a6r">Hello world!</h1>

@ -6,12 +6,5 @@ export default {
h1, h1,
text: h1.childNodes[0] text: h1.childNodes[0]
}; };
},
test(assert, target, snapshot) {
const h1 = target.querySelector('h1');
assert.equal(h1, snapshot.h1);
assert.equal(h1.childNodes[0], snapshot.text);
} }
}; };

@ -10,13 +10,8 @@ export default {
}; };
}, },
async test(assert, target, snapshot, component, window) { async test(assert, target, _, component, window) {
const input = target.querySelector('input'); const input = target.querySelector('input');
const p = target.querySelector('p');
assert.equal(input, snapshot.input);
assert.equal(p, snapshot.p);
input.value = 'everybody'; input.value = 'everybody';
await input.dispatchEvent(new window.Event('input')); await input.dispatchEvent(new window.Event('input'));

@ -1 +1,3 @@
<div><!-- test1 --><!-- test2 --></div> <div><!-- test1 --><!-- test2 --></div>
p
<div><!-- test1 --><!-- test2 --></div>

@ -1 +1,3 @@
<div><!-- test1 --></div> <div><!-- test1 --></div>
p
<div><!-- test1 --><!-- test2 --></div>

@ -3,18 +3,8 @@ export default {
preserveComments:true preserveComments:true
}, },
snapshot(target) { snapshot(target) {
const div = target.querySelector('div');
return { return {
div, div: target.querySelectorAll('div')[1]
comment: div.childNodes[0]
}; };
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
assert.equal(div, snapshot.div);
assert.equal(div.childNodes[0], snapshot.comment);
assert.equal(div.childNodes[1].nodeType, 8);
} }
}; };

@ -1 +1,3 @@
<div><!-- test1 --><!-- test2 --></div> <div><!-- test1 --><!-- test2 --></div>
{"p"}
<div><!-- test1 --><!-- test2 --></div>

@ -0,0 +1,8 @@
<div data-svelte-h="xxx">hello</div>
<div data-svelte-h="xxx"><div>bye</div></div>
<div data-svelte-h="xxx">
<div>aaa</div>
<div>bbb</div>
</div>

@ -0,0 +1,8 @@
<div data-svelte-h="xxx">hello</div>
<div data-svelte-h="xxx"><div data-svelte-h="yyy">bye</div></div>
<div data-svelte-h="xxx">
<div data-svelte-h="yyy">aaa</div>
<div data-svelte-h="zzz">bbb</div>
</div>

@ -0,0 +1,8 @@
<div>hello</div>
<div><div>bye</div></div>
<div>
<div>aaa</div>
<div>bbb</div>
</div>

@ -0,0 +1,8 @@
<div>hello</div>
<div><div>bye</div></div>
<div>
<div>aaa</div>
<div>bbb</div>
</div>

@ -0,0 +1,8 @@
<div>hello</div>
<div><div>bye</div></div>
<div>
<div>aaa</div>
<div>bbb</div>
</div>

@ -0,0 +1,8 @@
<div>hello</div>
<div><div>bye</div></div>
<div>
<div>aaa</div>
<div>bbb</div>
</div>

@ -0,0 +1,8 @@
export default {
snapshot(target) {
return {
main: target.querySelector('main'),
p: target.querySelector('p')
};
}
};

@ -1,3 +1,3 @@
<div> <div>
<p>nested</p> <p data-svelte-h="svelte-1x3hbnh">nested</p>
</div> </div>

@ -1,3 +1,3 @@
<div> <div>
<p>nested</p> <p data-svelte-h="svelte-1x3hbnh">nested</p>
</div> </div>

@ -8,14 +8,5 @@ export default {
p, p,
text: p.childNodes[0] text: p.childNodes[0]
}; };
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
const p = target.querySelector('p');
assert.equal(div, snapshot.div);
assert.equal(p, snapshot.p);
assert.equal(p.childNodes[0], snapshot.text);
} }
}; };

@ -1 +1 @@
<p>nested</p> <p data-svelte-h="svelte-1x3hbnh">nested</p>

@ -1 +1 @@
<p>nested</p> <p data-svelte-h="svelte-1x3hbnh">nested</p>

@ -6,12 +6,5 @@ export default {
p, p,
text: p.childNodes[0] text: p.childNodes[0]
}; };
},
test(assert, target, snapshot) {
const p = target.querySelector('p');
assert.equal(p, snapshot.p);
assert.equal(p.childNodes[0], snapshot.text);
} }
}; };

@ -10,12 +10,5 @@ export default {
h1, h1,
text: h1.childNodes[0] text: h1.childNodes[0]
}; };
},
test(assert, target, snapshot) {
const h1 = target.querySelector('h1');
assert.equal(h1, snapshot.h1);
assert.equal(h1.childNodes[0], snapshot.text);
} }
}; };

@ -9,13 +9,5 @@ export default {
nullText, nullText,
undefinedText undefinedText
}; };
},
test(assert, target, snapshot) {
const nullText = target.querySelectorAll('p')[0].textContent;
const undefinedText = target.querySelectorAll('p')[1].textContent;
assert.equal(nullText, snapshot.nullText);
assert.equal(undefinedText, snapshot.undefinedText);
} }
}; };

@ -10,12 +10,5 @@ export default {
h1, h1,
text: h1.childNodes[0] text: h1.childNodes[0]
}; };
},
test(assert, target, snapshot) {
const h1 = target.querySelector('h1');
assert.equal(h1, snapshot.h1);
assert.equal(h1.childNodes[0], snapshot.text);
} }
}; };

@ -15,17 +15,9 @@ export default {
return { return {
ul, ul,
lis lis0: lis[0],
lis1: lis[1],
lis2: lis[2]
}; };
},
test(assert, target, snapshot) {
const ul = target.querySelector('ul');
const lis = ul.querySelectorAll('li');
assert.equal(ul, snapshot.ul);
assert.equal(lis[0], snapshot.lis[0]);
assert.equal(lis[1], snapshot.lis[1]);
assert.equal(lis[2], snapshot.lis[2]);
} }
}; };

@ -13,17 +13,9 @@ export default {
return { return {
ul, ul,
lis lis0: lis[0],
lis1: lis[1],
lis2: lis[2]
}; };
},
test(assert, target, snapshot) {
const ul = target.querySelector('ul');
const lis = ul.querySelectorAll('li');
assert.equal(ul, snapshot.ul);
assert.equal(lis[0], snapshot.lis[0]);
assert.equal(lis[1], snapshot.lis[1]);
assert.equal(lis[2], snapshot.lis[2]);
} }
}; };

@ -9,11 +9,5 @@ export default {
return { return {
div div
}; };
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
assert.equal(div, snapshot.div);
} }
}; };

@ -9,11 +9,5 @@ export default {
return { return {
div div
}; };
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
assert.equal(div, snapshot.div);
} }
}; };

@ -9,11 +9,5 @@ export default {
return { return {
div div
}; };
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
assert.equal(div, snapshot.div);
} }
}; };

@ -5,11 +5,5 @@ export default {
return { return {
div div
}; };
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
assert.equal(div, snapshot.div);
} }
}; };

@ -7,13 +7,5 @@ export default {
span: p.querySelector('span'), span: p.querySelector('span'),
code: p.querySelector('code') code: p.querySelector('code')
}; };
},
test(assert, target, snapshot) {
const p = target.querySelector('p');
assert.equal(p, snapshot.p);
assert.equal(p.querySelector('span'), snapshot.span);
assert.equal(p.querySelector('code'), snapshot.code);
} }
}; };

@ -1,3 +1,3 @@
<div> <div data-svelte-h="svelte-1aqf5aj">
<p>nested</p> <p>nested</p>
</div> </div>

@ -1,3 +1,3 @@
<div> <div data-svelte-h="svelte-1aqf5aj">
<p>nested</p> <p>nested</p>
</div> </div>

@ -6,12 +6,5 @@ export default {
div, div,
p: div.querySelector('p') p: div.querySelector('p')
}; };
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
assert.equal(div, snapshot.div);
assert.equal(div.querySelector('p'), snapshot.p);
} }
}; };

@ -7,10 +7,8 @@ export default {
}; };
}, },
test(assert, target, snapshot, component) { test(assert, target, _, component) {
const h1 = target.querySelector('h1'); const h1 = target.querySelector('h1');
assert.equal(h1, snapshot.h1);
assert.equal(component.h1, h1); assert.equal(component.h1, h1);
} }
}; };

@ -11,10 +11,8 @@ export default {
}; };
}, },
async test(assert, target, snapshot, component, window) { async test(assert, target, _, component, window) {
const button = target.querySelector('button'); const button = target.querySelector('button');
assert.equal(button, snapshot.button);
await button.dispatchEvent(new window.MouseEvent('click')); await button.dispatchEvent(new window.MouseEvent('click'));
assert.ok(component.clicked); assert.ok(component.clicked);

@ -7,13 +7,5 @@ export default {
text: p.childNodes[0], text: p.childNodes[0],
span: p.querySelector('span') span: p.querySelector('span')
}; };
},
test(assert, target, snapshot) {
const p = target.querySelector('p');
assert.equal(p, snapshot.p);
assert.equal(p.childNodes[0], snapshot.text);
assert.equal(p.querySelector('span'), snapshot.span);
} }
}; };

@ -13,14 +13,5 @@ export default {
p0: ps[0], p0: ps[0],
p1: ps[1] p1: ps[1]
}; };
},
test(assert, target, snapshot) {
const div = target.querySelector('div');
const ps = target.querySelectorAll('p');
assert.equal(div, snapshot.div);
assert.equal(ps[0], snapshot.p0);
assert.equal(ps[1], snapshot.p1);
} }
}; };

@ -9,11 +9,5 @@ export default {
return { return {
p p
}; };
},
test(assert, target, snapshot) {
const p = target.querySelector('p');
assert.equal(p, snapshot.p);
} }
}; };

@ -12,10 +12,7 @@ export default {
}; };
}, },
test(assert, target, snapshot, component) { test(assert, target, _, component) {
const p = target.querySelector('p');
assert.equal(p, snapshot.p);
component.foo = false; component.foo = false;
component.bar = true; component.bar = true;

@ -9,11 +9,5 @@ export default {
return { return {
p p
}; };
},
test(assert, target, snapshot) {
const p = target.querySelector('p');
assert.equal(p, snapshot.p);
} }
}; };

@ -14,14 +14,5 @@ export default {
p1: ps[1], p1: ps[1],
text1: ps[1].firstChild text1: ps[1].firstChild
}; };
},
test(assert, target, snapshot) {
const ps = target.querySelectorAll('p');
assert.equal(ps[0], snapshot.p0);
assert.equal(ps[0].firstChild, snapshot.text0);
assert.equal(ps[1], snapshot.p1);
assert.equal(ps[1].firstChild, snapshot.text1);
} }
}; };

@ -9,17 +9,7 @@ function foo() {
} }
const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => { const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => {
return ` return ` <div class="class1 class2" style="color:red;">-</div> <div${add_attribute("class", const1, 0)}>-</div> <div${add_attribute("class", const1, 0)}>-</div> <div class="${"class1 " + escape('class2', true)}">-</div> <div class="${"class1 " + escape(const2, true)}">-</div> <div class="${"class1 " + escape(const2, true)}"${add_attribute("style", foo(), 0)}>-</div>`;
<div class="class1 class2" style="color:red;">-</div>
<div${add_attribute("class", const1, 0)}>-</div>
<div${add_attribute("class", const1, 0)}>-</div>
<div class="${"class1 " + escape('class2', true)}">-</div>
<div class="${"class1 " + escape(const2, true)}">-</div>
<div class="${"class1 " + escape(const2, true)}"${add_attribute("style", foo(), 0)}>-</div>`;
}); });
export default Component; export default Component;

@ -8,11 +8,8 @@ const Component = create_ssr_component(($$result, $$props, $$bindings, slots) =>
if ($$props.foo === void 0 && $$bindings.foo && foo !== void 0) $$bindings.foo(foo); if ($$props.foo === void 0 && $$bindings.foo && foo !== void 0) $$bindings.foo(foo);
return `${each(things, thing => { return `${each(things, thing => {
return `<span>${escape(thing.name)}</span> return `<span>${escape(thing.name)}</span> ${debug(null, 7, 2, { foo })}`;
${debug(null, 7, 2, { foo })}`; })} <p>foo: ${escape(foo)}</p>`;
})}
<p>foo: ${escape(foo)}</p>`;
}); });
export default Component; export default Component;

@ -27,7 +27,6 @@ function get_each_context(ctx, list, i) {
function create_each_block(ctx) { function create_each_block(ctx) {
let div; let div;
let strong; let strong;
let t0;
let t1; let t1;
let span; let span;
let t2_value = /*comment*/ ctx[4].author + ""; let t2_value = /*comment*/ ctx[4].author + "";
@ -44,7 +43,7 @@ function create_each_block(ctx) {
c() { c() {
div = element("div"); div = element("div");
strong = element("strong"); strong = element("strong");
t0 = text(/*i*/ ctx[6]); strong.textContent = `${/*i*/ ctx[6]}`;
t1 = space(); t1 = space();
span = element("span"); span = element("span");
t2 = text(t2_value); t2 = text(t2_value);
@ -60,7 +59,6 @@ function create_each_block(ctx) {
m(target, anchor) { m(target, anchor) {
insert(target, div, anchor); insert(target, div, anchor);
append(div, strong); append(div, strong);
append(strong, t0);
append(div, t1); append(div, t1);
append(div, span); append(div, span);
append(span, t2); append(span, t2);

@ -2,9 +2,7 @@
import { create_ssr_component } from "svelte/internal"; import { create_ssr_component } from "svelte/internal";
const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => { const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => {
return `<div>content</div> return `<div>content</div> <!-- comment --> <div>more content</div>`;
<!-- comment -->
<div>more content</div>`;
}); });
export default Component; export default Component;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save