Skip to content

Commit 6fad3cb

Browse files
authored
Merge pull request #787 from sveltejs/gh-763
Implement <slot>
2 parents 3eaa558 + 2fb0f50 commit 6fad3cb

File tree

66 files changed

+605
-149
lines changed

Some content is hidden

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

66 files changed

+605
-149
lines changed

src/generators/Generator.ts

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export default class Generator {
4343
hasJs: boolean;
4444
computations: Computation[];
4545
templateProperties: Record<string, Node>;
46+
slots: Set<string>;
4647

4748
code: MagicString;
4849

@@ -76,6 +77,7 @@ export default class Generator {
7677
this.events = new Set();
7778
this.transitions = new Set();
7879
this.importedComponents = new Map();
80+
this.slots = new Set();
7981

8082
this.bindingGroups = [];
8183
this.indirectDependencies = new Map();

src/generators/dom/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export default function dom(
7272
generator.stylesheet.warnOnUnusedSelectors(options.onwarn);
7373

7474
parsed.html.children.forEach((node: Node) => {
75-
visit(generator, block, state, node, []);
75+
visit(generator, block, state, node, [], []);
7676
});
7777

7878
const builder = new CodeBuilder();
@@ -181,6 +181,7 @@ export default function dom(
181181
this._root = options._root || this;
182182
this._yield = options._yield;
183183
this._bind = options._bind;
184+
${generator.slots.size && `this._slotted = options.slots || {};`}
184185
185186
${generator.stylesheet.hasStyles &&
186187
options.css !== false &&
@@ -200,6 +201,8 @@ export default function dom(
200201
`}
201202
`}
202203
204+
${generator.slots.size && `this.slots = {};`}
205+
203206
this._fragment = @create_main_fragment( this._state, this );
204207
205208
if ( options.target ) {

src/generators/dom/interfaces.ts

+13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import { DomGenerator } from './index';
2+
import Block from './Block';
3+
import { Node } from '../../interfaces';
4+
15
export interface State {
26
name?: string;
37
namespace: string;
@@ -11,3 +15,12 @@ export interface State {
1115
usesComponent?: boolean;
1216
selectBindingDependencies?: string[];
1317
}
18+
19+
export type Visitor = (
20+
generator: DomGenerator,
21+
block: Block,
22+
state: State,
23+
node: Node,
24+
elementStack: Node[],
25+
componentStack: Node[]
26+
) => void;

src/generators/dom/preprocess.ts

+30-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Block from './Block';
22
import { trimStart, trimEnd } from '../../utils/trim';
33
import { assign } from '../../shared/index.js';
4+
import getStaticAttributeValue from '../shared/getStaticAttributeValue';
45
import { DomGenerator } from './index';
56
import { Node } from '../../interfaces';
67
import { State } from './interfaces';
@@ -41,6 +42,7 @@ const preprocessors = {
4142
state: State,
4243
node: Node,
4344
elementStack: Node[],
45+
componentStack: Node[],
4446
stripWhitespace: boolean
4547
) => {
4648
const dependencies = block.findDependencies(node.expression);
@@ -57,6 +59,7 @@ const preprocessors = {
5759
state: State,
5860
node: Node,
5961
elementStack: Node[],
62+
componentStack: Node[],
6063
stripWhitespace: boolean
6164
) => {
6265
const dependencies = block.findDependencies(node.expression);
@@ -74,6 +77,7 @@ const preprocessors = {
7477
state: State,
7578
node: Node,
7679
elementStack: Node[],
80+
componentStack: Node[],
7781
stripWhitespace: boolean
7882
) => {
7983
node._state = getChildState(state);
@@ -94,6 +98,7 @@ const preprocessors = {
9498
node: Node,
9599
inEachBlock: boolean,
96100
elementStack: Node[],
101+
componentStack: Node[],
97102
stripWhitespace: boolean,
98103
nextSibling: Node
99104
) => {
@@ -113,7 +118,7 @@ const preprocessors = {
113118
node._state = getChildState(state);
114119

115120
blocks.push(node._block);
116-
preprocessChildren(generator, node._block, node._state, node, inEachBlock, elementStack, stripWhitespace, nextSibling);
121+
preprocessChildren(generator, node._block, node._state, node, inEachBlock, elementStack, componentStack, stripWhitespace, nextSibling);
117122

118123
if (node._block.dependencies.size > 0) {
119124
dynamic = true;
@@ -140,6 +145,7 @@ const preprocessors = {
140145
node.else,
141146
inEachBlock,
142147
elementStack,
148+
componentStack,
143149
stripWhitespace,
144150
nextSibling
145151
);
@@ -169,6 +175,7 @@ const preprocessors = {
169175
node: Node,
170176
inEachBlock: boolean,
171177
elementStack: Node[],
178+
componentStack: Node[],
172179
stripWhitespace: boolean,
173180
nextSibling: Node
174181
) => {
@@ -221,7 +228,7 @@ const preprocessors = {
221228
});
222229

223230
generator.blocks.push(node._block);
224-
preprocessChildren(generator, node._block, node._state, node, true, elementStack, stripWhitespace, nextSibling);
231+
preprocessChildren(generator, node._block, node._state, node, true, elementStack, componentStack, stripWhitespace, nextSibling);
225232
block.addDependencies(node._block.dependencies);
226233
node._block.hasUpdateMethod = node._block.dependencies.size > 0;
227234

@@ -240,6 +247,7 @@ const preprocessors = {
240247
node.else,
241248
inEachBlock,
242249
elementStack,
250+
componentStack,
243251
stripWhitespace,
244252
nextSibling
245253
);
@@ -254,6 +262,7 @@ const preprocessors = {
254262
node: Node,
255263
inEachBlock: boolean,
256264
elementStack: Node[],
265+
componentStack: Node[],
257266
stripWhitespace: boolean,
258267
nextSibling: Node
259268
) => {
@@ -326,10 +335,23 @@ const preprocessors = {
326335
generator.components.has(node.name) || node.name === ':Self';
327336

328337
if (isComponent) {
338+
const name = block.getUniqueName(
339+
(node.name === ':Self' ? generator.name : node.name).toLowerCase()
340+
);
341+
329342
node._state = getChildState(state, {
343+
name,
344+
parentNode: `${name}._slotted.default`,
330345
isYield: true
331346
});
332347
} else {
348+
const slot = getStaticAttributeValue(node, 'slot');
349+
if (slot) {
350+
// TODO validate slots — no nesting, no dynamic names...
351+
const component = componentStack[componentStack.length - 1];
352+
component._slots.add(slot);
353+
}
354+
333355
const name = block.getUniqueName(
334356
node.name.replace(/[^a-zA-Z0-9_$]/g, '_')
335357
);
@@ -355,17 +377,12 @@ const preprocessors = {
355377
(node.name === ':Self' ? generator.name : node.name).toLowerCase()
356378
);
357379

358-
node._block = block.child({
359-
name: generator.getUniqueName(`create_${name}_yield_fragment`),
360-
});
380+
if (node.children) node._slots = new Set(['default']); // TODO only include default if there are unslotted children
361381

362-
generator.blocks.push(node._block);
363-
preprocessChildren(generator, node._block, node._state, node, inEachBlock, elementStack, stripWhitespace, nextSibling);
364-
block.addDependencies(node._block.dependencies);
365-
node._block.hasUpdateMethod = node._block.dependencies.size > 0;
382+
preprocessChildren(generator, block, node._state, node, inEachBlock, elementStack, componentStack.concat(node), stripWhitespace, nextSibling);
366383
} else {
367384
if (node.name === 'pre' || node.name === 'textarea') stripWhitespace = false;
368-
preprocessChildren(generator, block, node._state, node, inEachBlock, elementStack.concat(node), stripWhitespace, nextSibling);
385+
preprocessChildren(generator, block, node._state, node, inEachBlock, elementStack.concat(node), componentStack, stripWhitespace, nextSibling);
369386
}
370387
}
371388
},
@@ -378,6 +395,7 @@ function preprocessChildren(
378395
node: Node,
379396
inEachBlock: boolean,
380397
elementStack: Node[],
398+
componentStack: Node[],
381399
stripWhitespace: boolean,
382400
nextSibling: Node
383401
) {
@@ -407,7 +425,7 @@ function preprocessChildren(
407425

408426
cleaned.forEach((child: Node, i: number) => {
409427
const preprocessor = preprocessors[child.type];
410-
if (preprocessor) preprocessor(generator, block, state, child, inEachBlock, elementStack, stripWhitespace, cleaned[i + 1] || nextSibling);
428+
if (preprocessor) preprocessor(generator, block, state, child, inEachBlock, elementStack, componentStack, stripWhitespace, cleaned[i + 1] || nextSibling);
411429

412430
if (lastChild) {
413431
lastChild.next = child;
@@ -471,7 +489,7 @@ export default function preprocess(
471489
};
472490

473491
generator.blocks.push(block);
474-
preprocessChildren(generator, block, state, node, false, [], true, null);
492+
preprocessChildren(generator, block, state, node, false, [], [], true, null);
475493
block.hasUpdateMethod = true;
476494

477495
return { block, state };

src/generators/dom/visit.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ export default function visit(
99
block: Block,
1010
state: State,
1111
node: Node,
12-
elementStack: Node[]
12+
elementStack: Node[],
13+
componentStack: Node[]
1314
) {
1415
const visitor = visitors[node.type];
15-
visitor(generator, block, state, node, elementStack);
16+
visitor(generator, block, state, node, elementStack, componentStack);
1617
}

src/generators/dom/visitors/Component.ts

+6-31
Original file line numberDiff line numberDiff line change
@@ -43,47 +43,22 @@ export default function visitComponent(
4343
block: Block,
4444
state: State,
4545
node: Node,
46-
elementStack: Node[]
46+
elementStack: Node[],
47+
componentStack: Node[]
4748
) {
4849
generator.hasComponents = true;
4950

50-
const name = block.getUniqueName(
51-
(node.name === ':Self' ? generator.name : node.name).toLowerCase()
52-
);
51+
const name = node._state.name;
5352

5453
const componentInitProperties = [`_root: #component._root`];
5554

56-
// Component has children, put them in a separate {{yield}} block
5755
if (node.children.length > 0) {
58-
const params = block.params.join(', ');
59-
60-
const childBlock = node._block;
56+
const slots = Array.from(node._slots).map(name => `${name}: @createFragment()`);
57+
componentInitProperties.push(`slots: { ${slots.join(', ')} }`);
6158

6259
node.children.forEach((child: Node) => {
63-
visit(generator, childBlock, node._state, child, elementStack);
60+
visit(generator, block, node._state, child, elementStack, componentStack.concat(node));
6461
});
65-
66-
const yield_fragment = block.getUniqueName(`${name}_yield_fragment`);
67-
68-
block.builders.init.addLine(
69-
`var ${yield_fragment} = ${childBlock.name}( ${params}, #component );`
70-
);
71-
72-
block.builders.create.addLine(`${yield_fragment}.create();`);
73-
74-
block.builders.claim.addLine(
75-
`${yield_fragment}.claim( ${state.parentNodes} );`
76-
);
77-
78-
if (childBlock.hasUpdateMethod) {
79-
block.builders.update.addLine(
80-
`${yield_fragment}.update( changed, ${params} );`
81-
);
82-
}
83-
84-
block.builders.destroy.addLine(`${yield_fragment}.destroy();`);
85-
86-
componentInitProperties.push(`_yield: ${yield_fragment}`);
8762
}
8863

8964
const allContexts = new Set();

src/generators/dom/visitors/EachBlock.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ export default function visitEachBlock(
1010
block: Block,
1111
state: State,
1212
node: Node,
13-
elementStack: Node[]
13+
elementStack: Node[],
14+
componentStack: Node[]
1415
) {
1516
const each_block = generator.getUniqueName(`each_block`);
1617
const create_each_block = node._block.name;
@@ -125,12 +126,12 @@ export default function visitEachBlock(
125126
}
126127

127128
node.children.forEach((child: Node) => {
128-
visit(generator, node._block, node._state, child, elementStack);
129+
visit(generator, node._block, node._state, child, elementStack, componentStack);
129130
});
130131

131132
if (node.else) {
132133
node.else.children.forEach((child: Node) => {
133-
visit(generator, node.else._block, node.else._state, child, elementStack);
134+
visit(generator, node.else._block, node.else._state, child, elementStack, componentStack);
134135
});
135136
}
136137
}

src/generators/dom/visitors/Element/Attribute.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import attributeLookup from './lookup';
22
import deindent from '../../../../utils/deindent';
33
import { stringify } from '../../../../utils/stringify';
4-
import getStaticAttributeValue from './getStaticAttributeValue';
4+
import getStaticAttributeValue from '../../../shared/getStaticAttributeValue';
55
import { DomGenerator } from '../../index';
66
import Block from '../../Block';
77
import { Node } from '../../../../interfaces';

src/generators/dom/visitors/Element/Binding.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import deindent from '../../../../utils/deindent';
22
import flattenReference from '../../../../utils/flattenReference';
3-
import getStaticAttributeValue from './getStaticAttributeValue';
3+
import getStaticAttributeValue from '../../../shared/getStaticAttributeValue';
44
import { DomGenerator } from '../../index';
55
import Block from '../../Block';
66
import { Node } from '../../../../interfaces';

0 commit comments

Comments
 (0)