Skip to content

Commit 0ecc52c

Browse files
authored
(refactor) move htmlx2jsx handlers into own files (#518)
part 1 of #514
1 parent f2468c0 commit 0ecc52c

20 files changed

+721
-598
lines changed

packages/svelte2tsx/src/htmlxtojsx/index.ts

Lines changed: 55 additions & 588 deletions
Large diffs are not rendered by default.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import MagicString from 'magic-string';
2+
import { Node } from 'estree-walker';
3+
4+
/**
5+
* use:xxx ---> {...__sveltets_ensureAction(__sveltets_mapElementTag('ParentNodeName', xxx))}
6+
*/
7+
export function handleActionDirective(
8+
htmlx: string,
9+
str: MagicString,
10+
attr: Node,
11+
parent: Node,
12+
): void {
13+
str.overwrite(
14+
attr.start,
15+
attr.start + 'use:'.length,
16+
`{...__sveltets_ensureAction(__sveltets_mapElementTag('${parent.name}'),`,
17+
);
18+
19+
if (!attr.expression) {
20+
str.appendLeft(attr.end, ')}');
21+
return;
22+
}
23+
24+
str.overwrite(attr.start + `use:${attr.name}`.length, attr.expression.start, ',');
25+
str.appendLeft(attr.expression.end, ')');
26+
if (htmlx[attr.end - 1] == '"') {
27+
str.remove(attr.end - 1, attr.end);
28+
}
29+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import MagicString from 'magic-string';
2+
import { Node } from 'estree-walker';
3+
4+
/**
5+
* animation:xxx(yyy) ---> {...__sveltets_ensureAnimation(xxx, yyy)}
6+
*/
7+
export function handleAnimateDirective(htmlx: string, str: MagicString, attr: Node): void {
8+
str.overwrite(
9+
attr.start,
10+
htmlx.indexOf(':', attr.start) + 1,
11+
'{...__sveltets_ensureAnimation(',
12+
);
13+
14+
if (!attr.expression) {
15+
str.appendLeft(attr.end, ', {})}');
16+
return;
17+
}
18+
str.overwrite(
19+
htmlx.indexOf(':', attr.start) + 1 + `${attr.name}`.length,
20+
attr.expression.start,
21+
', ',
22+
);
23+
str.appendLeft(attr.expression.end, ')');
24+
if (htmlx[attr.end - 1] == '"') {
25+
str.remove(attr.end - 1, attr.end);
26+
}
27+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import MagicString from 'magic-string';
2+
import { Node } from 'estree-walker';
3+
import svgAttributes from '../svgattributes';
4+
5+
/**
6+
* List taken from `svelte-jsx.d.ts` by searching for all attributes of type number
7+
*/
8+
const numberOnlyAttributes = new Set([
9+
'cols',
10+
'colspan',
11+
'currenttime',
12+
'defaultplaybackrate',
13+
'high',
14+
'low',
15+
'marginheight',
16+
'marginwidth',
17+
'minlength',
18+
'maxlength',
19+
'optimum',
20+
'rows',
21+
'rowspan',
22+
'size',
23+
'span',
24+
'start',
25+
'tabindex',
26+
'results',
27+
'volume',
28+
]);
29+
30+
/**
31+
* Handle various kinds of attributes and make them conform to JSX.
32+
* - {x} ---> x={x}
33+
* - x="{..}" ---> x={..}
34+
* - lowercase DOM attributes
35+
* - multi-value handling
36+
*/
37+
export function handleAttribute(htmlx: string, str: MagicString, attr: Node, parent: Node): void {
38+
let transformedFromDirectiveOrNamespace = false;
39+
40+
//if we are on an "element" we are case insensitive, lowercase to match our JSX
41+
if (parent.type == 'Element') {
42+
const sapperNoScroll = attr.name === 'sapper:noscroll';
43+
//skip Attribute shorthand, that is handled below
44+
if (
45+
(attr.value !== true &&
46+
!(
47+
attr.value.length &&
48+
attr.value.length == 1 &&
49+
attr.value[0].type == 'AttributeShorthand'
50+
)) ||
51+
sapperNoScroll
52+
) {
53+
let name = attr.name;
54+
if (!svgAttributes.find((x) => x == name)) {
55+
name = name.toLowerCase();
56+
}
57+
58+
//strip ":" from out attribute name and uppercase the next letter to convert to jsx attribute
59+
const colonIndex = name.indexOf(':');
60+
if (colonIndex >= 0) {
61+
const parts = name.split(':');
62+
name = parts[0] + parts[1][0].toUpperCase() + parts[1].substring(1);
63+
}
64+
65+
str.overwrite(attr.start, attr.start + attr.name.length, name);
66+
67+
transformedFromDirectiveOrNamespace = true;
68+
}
69+
}
70+
71+
//we are a bare attribute
72+
if (attr.value === true) {
73+
if (
74+
parent.type === 'Element' &&
75+
!transformedFromDirectiveOrNamespace &&
76+
parent.name !== '!DOCTYPE'
77+
) {
78+
str.overwrite(attr.start, attr.end, attr.name.toLowerCase());
79+
}
80+
return;
81+
}
82+
83+
if (attr.value.length == 0) return; //wut?
84+
//handle single value
85+
if (attr.value.length == 1) {
86+
const attrVal = attr.value[0];
87+
88+
if (attr.name == 'slot') {
89+
str.remove(attr.start, attr.end);
90+
return;
91+
}
92+
93+
if (attrVal.type == 'AttributeShorthand') {
94+
let attrName = attrVal.expression.name;
95+
if (parent.type == 'Element') {
96+
// eslint-disable-next-line max-len
97+
attrName = svgAttributes.find((a) => a == attrName)
98+
? attrName
99+
: attrName.toLowerCase();
100+
}
101+
102+
str.appendRight(attr.start, `${attrName}=`);
103+
return;
104+
}
105+
106+
const equals = htmlx.lastIndexOf('=', attrVal.start);
107+
if (attrVal.type == 'Text') {
108+
const endsWithQuote =
109+
htmlx.lastIndexOf('"', attrVal.end) === attrVal.end - 1 ||
110+
htmlx.lastIndexOf("'", attrVal.end) === attrVal.end - 1;
111+
const needsQuotes = attrVal.end == attr.end && !endsWithQuote;
112+
113+
const hasBrackets =
114+
htmlx.lastIndexOf('}', attrVal.end) === attrVal.end - 1 ||
115+
htmlx.lastIndexOf('}"', attrVal.end) === attrVal.end - 1 ||
116+
htmlx.lastIndexOf("}'", attrVal.end) === attrVal.end - 1;
117+
const needsNumberConversion =
118+
!hasBrackets &&
119+
parent.type === 'Element' &&
120+
numberOnlyAttributes.has(attr.name.toLowerCase()) &&
121+
!isNaN(attrVal.data);
122+
123+
if (needsNumberConversion) {
124+
if (needsQuotes) {
125+
str.prependRight(equals + 1, '{');
126+
str.appendLeft(attr.end, '}');
127+
} else {
128+
str.overwrite(equals + 1, equals + 2, '{');
129+
str.overwrite(attr.end - 1, attr.end, '}');
130+
}
131+
} else if (needsQuotes) {
132+
str.prependRight(equals + 1, '"');
133+
str.appendLeft(attr.end, '"');
134+
}
135+
return;
136+
}
137+
138+
if (attrVal.type == 'MustacheTag') {
139+
//if the end doesn't line up, we are wrapped in quotes
140+
if (attrVal.end != attr.end) {
141+
str.remove(attrVal.start - 1, attrVal.start);
142+
str.remove(attr.end - 1, attr.end);
143+
}
144+
return;
145+
}
146+
return;
147+
}
148+
149+
// we have multiple attribute values, so we build a string out of them.
150+
// technically the user can do something funky like attr="text "{value} or even attr=text{value}
151+
// so instead of trying to maintain a nice sourcemap with prepends etc, we just overwrite the whole thing
152+
153+
const equals = htmlx.lastIndexOf('=', attr.value[0].start);
154+
str.overwrite(equals, attr.value[0].start, '={`');
155+
156+
for (const n of attr.value) {
157+
if (n.type == 'MustacheTag') {
158+
str.appendRight(n.start, '$');
159+
}
160+
}
161+
162+
if (htmlx[attr.end - 1] == '"') {
163+
str.overwrite(attr.end - 1, attr.end, '`}');
164+
} else {
165+
str.appendLeft(attr.end, '`}');
166+
}
167+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import MagicString from 'magic-string';
2+
import { Node } from 'estree-walker';
3+
import { isShortHandAttribute, getThisType } from '../utils/node-utils';
4+
5+
const oneWayBindingAttributes: Map<string, string> = new Map(
6+
['clientWidth', 'clientHeight', 'offsetWidth', 'offsetHeight']
7+
.map((e) => [e, 'HTMLDivElement'] as [string, string])
8+
.concat(
9+
['duration', 'buffered', 'seekable', 'seeking', 'played', 'ended'].map((e) => [
10+
e,
11+
'HTMLMediaElement',
12+
]),
13+
),
14+
);
15+
16+
/**
17+
* Transform bind:xxx into something that conforms to JSX
18+
*/
19+
export function handleBinding(htmlx: string, str: MagicString, attr: Node, el: Node): void {
20+
//bind group on input
21+
if (attr.name == 'group' && el.name == 'input') {
22+
str.remove(attr.start, attr.expression.start);
23+
str.appendLeft(attr.expression.start, '{...__sveltets_empty(');
24+
25+
const endBrackets = ')}';
26+
if (isShortHandAttribute(attr)) {
27+
str.prependRight(attr.end, endBrackets);
28+
} else {
29+
str.overwrite(attr.expression.end, attr.end, endBrackets);
30+
}
31+
return;
32+
}
33+
34+
const supportsBindThis = ['InlineComponent', 'Element', 'Body'];
35+
36+
//bind this
37+
if (attr.name === 'this' && supportsBindThis.includes(el.type)) {
38+
const thisType = getThisType(el);
39+
40+
if (thisType) {
41+
str.remove(attr.start, attr.expression.start);
42+
str.appendLeft(attr.expression.start, `{...__sveltets_ensureType(${thisType}, `);
43+
str.overwrite(attr.expression.end, attr.end, ')}');
44+
return;
45+
}
46+
}
47+
48+
//one way binding
49+
if (oneWayBindingAttributes.has(attr.name) && el.type === 'Element') {
50+
str.remove(attr.start, attr.expression.start);
51+
str.appendLeft(attr.expression.start, `{...__sveltets_empty(`);
52+
if (isShortHandAttribute(attr)) {
53+
// eslint-disable-next-line max-len
54+
str.appendLeft(
55+
attr.end,
56+
`=__sveltets_instanceOf(${oneWayBindingAttributes.get(attr.name)}).${attr.name})}`,
57+
);
58+
} else {
59+
// eslint-disable-next-line max-len
60+
str.overwrite(
61+
attr.expression.end,
62+
attr.end,
63+
`=__sveltets_instanceOf(${oneWayBindingAttributes.get(attr.name)}).${attr.name})}`,
64+
);
65+
}
66+
return;
67+
}
68+
69+
str.remove(attr.start, attr.start + 'bind:'.length);
70+
if (attr.expression.start === attr.start + 'bind:'.length) {
71+
str.prependLeft(attr.expression.start, `${attr.name}={`);
72+
str.appendLeft(attr.end, `}`);
73+
return;
74+
}
75+
76+
//remove possible quotes
77+
if (htmlx[attr.end - 1] === '"') {
78+
const firstQuote = htmlx.indexOf('"', attr.start);
79+
str.remove(firstQuote, firstQuote + 1);
80+
str.remove(attr.end - 1, attr.end);
81+
}
82+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import MagicString from 'magic-string';
2+
import { Node } from 'estree-walker';
3+
4+
/**
5+
* class:xx={yyy} ---> {...__sveltets_ensureType(Boolean, !!(yyy))}
6+
*/
7+
export function handleClassDirective(str: MagicString, attr: Node): void {
8+
str.overwrite(attr.start, attr.expression.start, `{...__sveltets_ensureType(Boolean, !!(`);
9+
const endBrackets = `))}`;
10+
if (attr.end !== attr.expression.end) {
11+
str.overwrite(attr.expression.end, attr.end, endBrackets);
12+
} else {
13+
str.appendLeft(attr.end, endBrackets);
14+
}
15+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import MagicString from 'magic-string';
2+
import { Node } from 'estree-walker';
3+
4+
/**
5+
* Removes comment
6+
*/
7+
export function handleComment(str: MagicString, node: Node): void {
8+
str.remove(node.start, node.end);
9+
}

packages/svelte2tsx/src/htmlxtojsx/nodes/component-type.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)