Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

alternative attachments syntax #15076

Open
wants to merge 4 commits into
base: attachments
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/svelte/src/compiler/phases/1-parse/read/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const ALLOWED_ATTRIBUTES = ['context', 'generics', 'lang', 'module'];
/**
* @param {Parser} parser
* @param {number} start
* @param {Array<AST.Attribute | AST.SpreadAttribute | AST.Directive | AST.AttachTag>} attributes
* @param {Array<AST.Attribute | AST.SpreadAttribute | AST.Directive | AST.Attachment>} attributes
* @returns {AST.Script}
*/
export function read_script(parser, start, attributes) {
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/compiler/phases/1-parse/read/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const REGEX_HTML_COMMENT_CLOSE = /-->/;
/**
* @param {Parser} parser
* @param {number} start
* @param {Array<AST.Attribute | AST.SpreadAttribute | AST.Directive | AST.AttachTag>} attributes
* @param {Array<AST.Attribute | AST.SpreadAttribute | AST.Directive | AST.Attachment>} attributes
* @returns {AST.CSS.StyleSheet}
*/
export default function read_style(parser, start, attributes) {
Expand Down
44 changes: 26 additions & 18 deletions packages/svelte/src/compiler/phases/1-parse/state/element.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @import { Expression } from 'estree' */
/** @import { CallExpression, Expression } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { Parser } from '../index.js' */
import { is_void } from '../../../../utils.js';
Expand All @@ -14,6 +14,8 @@ import { get_attribute_expression, is_expression_attribute } from '../../../util
import { closing_tag_omitted } from '../../../../html-tree-validation.js';
import { list } from '../../../utils/string.js';
import { regex_whitespace } from '../../patterns.js';
import { find_matching_bracket } from '../utils/bracket.js';
import { parse_expression_at } from '../acorn.js';

const regex_invalid_unquoted_attribute_value = /^(\/>|[\s"'=<>`])/;
const regex_closing_textarea_tag = /^<\/textarea(\s[^>]*)?>/i;
Expand Down Expand Up @@ -480,31 +482,37 @@ function read_static_attribute(parser) {

/**
* @param {Parser} parser
* @returns {AST.Attribute | AST.SpreadAttribute | AST.Directive | AST.AttachTag | null}
* @returns {AST.Attribute | AST.SpreadAttribute | AST.Directive | AST.Attachment | null}
*/
function read_attribute(parser) {
const start = parser.index;

if (parser.eat('{')) {
parser.allow_whitespace();
if (parser.match('attach(')) {
const end = find_matching_bracket(parser.template, start + 7, '(');

if (parser.eat('@attach')) {
parser.require_whitespace();
if (end === undefined) {
e.unexpected_eof(parser.template.length);
}

const expression = read_expression(parser);
parser.allow_whitespace();
parser.eat('}', true);
const sliced = parser.template.slice(0, end + 1);

/** @type {AST.AttachTag} */
const attachment = {
type: 'AttachTag',
start,
end: parser.index,
expression
};
const call = /** @type {CallExpression} */ (parse_expression_at(sliced, parser.ts, start));

return attachment;
}
/** @type {AST.Attachment} */
const attachment = {
type: 'Attachment',
start,
end,
attachments: call.arguments
};

parser.index = end + 1;

return attachment;
}

if (parser.eat('{')) {
parser.allow_whitespace();

if (parser.eat('...')) {
const expression = read_expression(parser);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export function visit_component(node, context) {
attribute.type !== 'LetDirective' &&
attribute.type !== 'OnDirective' &&
attribute.type !== 'BindDirective' &&
attribute.type !== 'AttachTag'
attribute.type !== 'Attachment'
) {
e.component_invalid_directive(attribute);
}
Expand Down Expand Up @@ -110,10 +110,6 @@ export function visit_component(node, context) {
if (attribute.type === 'BindDirective' && attribute.name !== 'this') {
context.state.analysis.uses_component_bindings = true;
}

if (attribute.type === 'AttachTag') {
disallow_unparenthesized_sequences(attribute.expression, context.state.analysis.source);
}
}

// If the component has a slot attribute — `<Foo slot="whatever" .../>` —
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import { TitleElement } from './visitors/TitleElement.js';
import { TransitionDirective } from './visitors/TransitionDirective.js';
import { UpdateExpression } from './visitors/UpdateExpression.js';
import { UseDirective } from './visitors/UseDirective.js';
import { AttachTag } from './visitors/AttachTag.js';
import { Attachment } from './visitors/Attachment.js';
import { VariableDeclaration } from './visitors/VariableDeclaration.js';

/** @type {Visitors} */
Expand Down Expand Up @@ -132,7 +132,7 @@ const visitors = {
TransitionDirective,
UpdateExpression,
UseDirective,
AttachTag,
Attachment,
VariableDeclaration
};

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/** @import { Expression } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types' */
import * as b from '../../../../utils/builders.js';

/**
* @param {AST.Attachment} node
* @param {ComponentContext} context
*/
export function Attachment(node, context) {
for (const attachment of node.attachments) {
context.state.init.push(
b.stmt(
b.call(
'$.attach',
context.state.node,
b.thunk(
/** @type {Expression} */ (
context.visit(attachment.type === 'SpreadElement' ? attachment.argument : attachment)
)
)
)
)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function RegularElement(node, context) {
/** @type {AST.StyleDirective[]} */
const style_directives = [];

/** @type {Array<AST.AnimateDirective | AST.BindDirective | AST.OnDirective | AST.TransitionDirective | AST.UseDirective | AST.AttachTag>} */
/** @type {Array<AST.AnimateDirective | AST.BindDirective | AST.OnDirective | AST.TransitionDirective | AST.UseDirective | AST.Attachment>} */
const other_directives = [];

/** @type {ExpressionStatement[]} */
Expand Down Expand Up @@ -153,7 +153,7 @@ export function RegularElement(node, context) {
other_directives.push(attribute);
break;

case 'AttachTag':
case 'Attachment':
other_directives.push(attribute);
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,16 +261,20 @@ export function build_component(node, component_name, context, anchor = context.
);
}
}
} else if (attribute.type === 'AttachTag') {
} else if (attribute.type === 'Attachment') {
// TODO do we need to create a derived here?
push_prop(
b.prop(
'get',
b.call('Symbol'),
/** @type {Expression} */ (context.visit(attribute.expression)),
true
)
);
for (const attachment of attribute.attachments) {
push_prop(
b.prop(
'get',
b.call('Symbol'),
/** @type {Expression} */ (
context.visit(attachment.type === 'SpreadElement' ? attachment.argument : attachment)
),
true
)
);
}
}
}

Expand Down
15 changes: 8 additions & 7 deletions packages/svelte/src/compiler/types/template.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import type {
Program,
ChainExpression,
SimpleCallExpression,
SequenceExpression
SequenceExpression,
SpreadElement
} from 'estree';
import type { Scope } from '../phases/scope';
import type { _CSS } from './css';
Expand Down Expand Up @@ -174,10 +175,10 @@ export namespace AST {
};
}

/** A `{@attach foo(...)} tag */
export interface AttachTag extends BaseNode {
type: 'AttachTag';
expression: Expression;
/** An `attach(...)` attribute */
export interface Attachment extends BaseNode {
type: 'Attachment';
attachments: Array<Expression | SpreadElement>;
}

/** An `animate:` directive */
Expand Down Expand Up @@ -279,7 +280,7 @@ export namespace AST {

interface BaseElement extends BaseNode {
name: string;
attributes: Array<Attribute | SpreadAttribute | Directive | AttachTag>;
attributes: Array<Attribute | SpreadAttribute | Directive | Attachment>;
fragment: Fragment;
}

Expand Down Expand Up @@ -555,7 +556,7 @@ export namespace AST {
| AST.Attribute
| AST.SpreadAttribute
| Directive
| AST.AttachTag
| AST.Attachment
| AST.Comment
| Block;

Expand Down
14 changes: 10 additions & 4 deletions packages/svelte/src/internal/client/dom/elements/attachments.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ import { effect } from '../../reactivity/effects.js';
*/
export function attach(node, get_fn) {
effect(() => {
const fn = get_fn();
const attachment = get_fn();

// we use `&&` rather than `?.` so that things like
// `{@attach DEV && something_dev_only()}` work
return fn && fn(node);
if (Array.isArray(attachment)) {
for (const fn of attachment) {
if (fn) {
effect(() => fn(node));
}
}
} else if (attachment) {
return attachment(node);
}
});
}
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
<div {@attach (node) => {}} {@attach (node) => {}}></div>
<div attach(a)></div>
<div attach(a, b, c)></div>
<div attach(...stuff)></div>
<div attach(a, b, c, ...stuff)></div>
<div attach((node) => {})></div>
Loading
Loading