Skip to content

(feat) (svelte2tsx) Type check event forward from element on component #303

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

Merged
merged 12 commits into from
Jul 28, 2020
Merged
5 changes: 3 additions & 2 deletions packages/svelte2tsx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ function render() {
<>
<h1>hello {world}</h1>
</>;
return { props: { world }, slots: {} };
return { props: { world }, slots: {}, events: {} };
}

export default class {
$$prop_def = __sveltets_partial(render().props);
$$slot_def = render().slots;
$$slot_def = render().slots
$on = __sveltets_eventDef(render().events).$on;
}
```

Expand Down
18 changes: 14 additions & 4 deletions packages/svelte2tsx/src/htmlxtojsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ const stripDoctype = (str: MagicString) => {
if (result) str.remove(result.index, result.index + result[0].length);
};

// eslint-disable-next-line max-len
export function convertHtmlxToJsx(
str: MagicString,
ast: Node,
Expand Down Expand Up @@ -70,11 +69,22 @@ export function convertHtmlxToJsx(
);
}
} else {
//We don't know the type of the event handler
if (attr.expression) {
const on = 'on';
//for handler assignment, we changeIt to call to our __sveltets_ensureFunction
str.overwrite(attr.start, attr.expression.start, '{...__sveltets_ensureFunction((');
str.overwrite(attr.expression.end, attr.end, '))}');
str.appendRight(
attr.start, `{__sveltets_instanceOf(${parent.name}).$`
);
const eventNameIndex = htmlx.indexOf(':', attr.start) + 1;
str.overwrite(
htmlx.indexOf(on, attr.start) + on.length,
eventNameIndex,
`('`
);
const eventEnd = htmlx.lastIndexOf('=', attr.expression.start);
str.overwrite(eventEnd, attr.expression.start, `', `);
str.overwrite(attr.expression.end, attr.end, ')}');
str.move(attr.start, attr.end, parent.end);
} else {
//for passthrough handlers, we just remove
str.remove(attr.start, attr.end);
Expand Down
1 change: 1 addition & 0 deletions packages/svelte2tsx/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ export interface CreateRenderFunctionPara extends InstanceScriptProcessResult {
scriptTag: Node;
scriptDestination: number;
slots: Map<string, Map<string, string>>;
events: Map<string, string | string[]>;
isTsFile: boolean;
}
63 changes: 63 additions & 0 deletions packages/svelte2tsx/src/nodes/event-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Node } from 'estree-walker';

export function createEventHandlerTransformer() {
const events = new Map<string, string | string[]>();

const handleEventHandler = (node: Node, parent: Node) => {
const eventName = node.name;

const handleEventHandlerBubble = () => {
const componentEventDef = `__sveltets_instanceOf(${parent.name})`;
const exp = `__sveltets_bubbleEventDef(${componentEventDef}.$on, '${eventName}')`;

const exist = events.get(eventName);
events.set(eventName, exist ? [].concat(exist, exp) : exp);
};

// pass-through/ bubble
if (!node.expression) {
if (parent.type === 'InlineComponent') {
handleEventHandlerBubble();
} else {
events.set(
eventName,
getEventDefExpressionForNonCompoent(eventName, parent)
);
}
}
};

return {
handleEventHandler,
getEvents: () => events,
};
}

function getEventDefExpressionForNonCompoent(eventName: string, ele: Node) {
switch (ele.type) {
case 'Element':
return `__sveltets_mapElementEvent('${eventName}')`;
case 'Body':
return `__sveltets_mapBodyEvent('${eventName}')`;
case 'Window':
return `__sveltets_mapWindowEvent('${eventName}')`;
default:
break;
}
}

export function eventMapToString(events: Map<string, string | string[]>) {
return '{' +
Array.from(events.entries()).map(eventMapEntryToString).join(', ') +
'}';

}

function eventMapEntryToString([eventName, expression]: [
string,
string | string[]
]) {
return `'${eventName}':${
Array.isArray(expression) ? `[${expression}]` : expression
}`;
}
22 changes: 18 additions & 4 deletions packages/svelte2tsx/src/svelte2tsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { parseHtmlx } from './htmlxparser';
import { convertHtmlxToJsx } from './htmlxtojsx';
import { Node } from 'estree-walker';
import * as ts from 'typescript';
import { createEventHandlerTransformer, eventMapToString } from './nodes/event-handler';
import { findExortKeyword } from './utils/tsAst';
import { InstanceScriptProcessResult, CreateRenderFunctionPara } from './interfaces';
import { createRenderFunctionGetterStr, createClassGetters } from './nodes/exportgetters';
Expand Down Expand Up @@ -45,6 +46,7 @@ type TemplateProcessResult = {
moduleScriptTag: Node;
/** To be added later as a comment on the default class export */
componentDocumentation: string | null;
events: Map<string, string | string[]>;
};

class Scope {
Expand Down Expand Up @@ -282,6 +284,8 @@ function processSvelteTemplate(str: MagicString): TemplateProcessResult {
str.remove(node.start, node.end);
};

const { handleEventHandler, getEvents } = createEventHandlerTransformer();

const onHtmlxWalk = (node: Node, parent: Node, prop: string) => {
if (
prop == 'params' &&
Expand Down Expand Up @@ -318,6 +322,9 @@ function processSvelteTemplate(str: MagicString): TemplateProcessResult {
case 'ArrowFunctionExpression':
enterArrowFunctionExpression();
break;
case 'EventHandler':
handleEventHandler(node, parent);
break;
case 'VariableDeclarator':
isDeclaration = true;
break;
Expand Down Expand Up @@ -362,6 +369,7 @@ function processSvelteTemplate(str: MagicString): TemplateProcessResult {
moduleScriptTag,
scriptTag,
slots,
events: getEvents(),
uses$$props,
uses$$restProps,
componentDocumentation,
Expand Down Expand Up @@ -526,7 +534,7 @@ function processInstanceScriptContent(str: MagicString, script: Node): InstanceS
if (
(ts.isPrefixUnaryExpression(parent) || ts.isPostfixUnaryExpression(parent)) &&
parent.operator !==
ts.SyntaxKind.ExclamationToken /* `!$store` does not need processing */
ts.SyntaxKind.ExclamationToken /* `!$store` does not need processing */
) {
let simpleOperator: string;
if (parent.operator === ts.SyntaxKind.PlusPlusToken) {
Expand Down Expand Up @@ -853,6 +861,7 @@ function addComponentExport(
className ? `${className} ` : ''
}{\n $$prop_def = ${propDef}\n $$slot_def = render().slots` +
createClassGetters(getters) +
`\n $on = __sveltets_eventDef(render().events)` +
'\n}';

str.append(statement);
Expand Down Expand Up @@ -891,6 +900,7 @@ function createRenderFunction({
scriptDestination,
slots,
getters,
events,
exportedNames,
isTsFile,
uses$$props,
Expand Down Expand Up @@ -932,9 +942,11 @@ function createRenderFunction({
.join(', ') +
'}';

const returnString = `\nreturn { props: ${exportedNames.createPropsStr(
isTsFile,
)}, slots: ${slotsAsDef}, getters: ${createRenderFunctionGetterStr(getters)} }}`;
const returnString =
`\nreturn { props: ${exportedNames.createPropsStr(
isTsFile
)}, slots: ${slotsAsDef}, getters: ${createRenderFunctionGetterStr(getters)}` +
`, events: ${eventMapToString(events)} }}`;
str.append(returnString);
}

Expand All @@ -950,6 +962,7 @@ export function svelte2tsx(
slots,
uses$$props,
uses$$restProps,
events,
componentDocumentation,
} = processSvelteTemplate(str);

Expand Down Expand Up @@ -991,6 +1004,7 @@ export function svelte2tsx(
scriptTag,
scriptDestination: instanceScriptTarget,
slots,
events,
getters,
exportedNames,
isTsFile: options?.isTsFile,
Expand Down
60 changes: 42 additions & 18 deletions packages/svelte2tsx/svelte-shims.d.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,49 @@
declare module '*.svelte' {
export default class {
$$prop_def: any;
$$slot_def: any;
$$prop_def: any;
$$slot_def: any;

$on(event: string, handler: (e: Event) => any): () => void
}
}

type AConstructorTypeOf<T> = new (...args:any[]) => T;
type AConstructorTypeOf<T> = new (...args: any[]) => T;

type SvelteAction<U extends any[]> = (node: HTMLElement, ...args:U) => {
update?: (...args:U) => void,
destroy?: () => void
} | void


type SvelteTransitionConfig = {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
}

type SvelteTransition<U extends any[]> = (node: Element, ...args:U) => SvelteTransitionConfig | (() => SvelteTransitionConfig)
type SvelteTransition<U extends any[]> = (node: Element, ...args: U) => SvelteTransitionConfig | (() => SvelteTransitionConfig)

type SvelteAnimation<U extends any[]> = (node: Element, move: { from: DOMRect, to: DOMRect}, ...args:U) => {
type SvelteAnimation<U extends any[]> = (node: Element, move: { from: DOMRect, to: DOMRect }, ...args: U) => {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
}

type SvelteAllProps = { [index: string]: any }
type SvelteAllProps = { [index: string]: any }
type SvelteRestProps = { [index: string]: any }
type SvelteStore<T> = { subscribe: (run: (value:T) => any, invalidate?: any) => any }
type SvelteStore<T> = { subscribe: (run: (value: T) => any, invalidate?: any) => any }
type SvelteComponent = import('*.svelte').default
type SvelteEventRecord = Record<string, Event | Event[]>
type SvelteExtractEvent<T> = T extends any[] ? T[number] : T;
type SvelteOnEvent<T, K extends keyof T> = (
event: K,
handler: (e: SvelteExtractEvent<T[K]>) => any
) => () => void;
type SvelteRestEvent = (event: string, handler: (e: CustomEvent) => any) => () => void
type SvelteOnAllEvent<T> = SvelteOnEvent<T, keyof T> & SvelteRestEvent

declare var process: NodeJS.Process & { browser: boolean }

Expand All @@ -49,8 +58,23 @@ declare function __sveltets_restPropsType(): SvelteRestProps
declare function __sveltets_partial<T>(obj: T): Partial<T>;
declare function __sveltets_partial_with_any<T>(obj: T): Partial<T> & SvelteAllProps
declare function __sveltets_with_any<T>(obj: T): T & SvelteAllProps
declare function __sveltets_store_get<T=any>(store: SvelteStore<T>): T
declare function __sveltets_store_get<T = any>(store: SvelteStore<T>): T
declare function __sveltets_any(dummy: any): any;
declare function __sveltets_empty(dummy: any): {};
declare function __sveltets_componentType(): AConstructorTypeOf<SvelteComponent>
declare function __sveltets_invalidate<T>(getValue: () => T): T
declare function __sveltets_eventDef<T extends SvelteEventRecord>(def: T): SvelteOnAllEvent<T>
declare function __sveltets_mapWindowEvent<K extends keyof HTMLBodyElementEventMap>(
event: K
): HTMLBodyElementEventMap[K];
declare function __sveltets_mapBodyEvent<K extends keyof WindowEventMap>(
event: K
): WindowEventMap[K];
declare function __sveltets_mapElementEvent<K extends keyof HTMLElementEventMap>(
event: K
): HTMLElementEventMap[K];
declare function __sveltets_bubbleEventDef<
T extends SvelteEventRecord,
TEvent,
TKey extends keyof T = TEvent extends keyof T ? TEvent : string
>(on: SvelteOnAllEvent<T>, event: TEvent): T[TKey];
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<><h1 onclick={()=>console.log("click")}>Hello</h1>
<Component {...__sveltets_ensureFunction((test))}/>
<Component />{__sveltets_instanceOf(Component).$on('click', test)}
<img {...__sveltets_ensureAction(action,thing)} />
<img {...__sveltets_ensureTransition(fade, params)} />
<img {...__sveltets_ensureType(Boolean, !!(classthing))} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<><Component {...__sveltets_ensureFunction((() => click()))} {...__sveltets_ensureFunction((() => log('hi')))}/></>
<><Component />{__sveltets_instanceOf(Component).$on('event', () => click())}{__sveltets_instanceOf(Component).$on('UpperCaseEvent', () => log('hi'))}</>
7 changes: 4 additions & 3 deletions packages/svelte2tsx/test/sourcemaps/event-binding.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
<></>;function render() {
<><Component {...__sveltets_ensureFunction((__sveltets_store_get(check) ? method1 : method2))} />
1==== 2==================
<><Component />{__sveltets_instanceOf(Component).$on('click', __sveltets_store_get(check) ? method1 : method2)}
1==== 2==================
<button onclick={__sveltets_store_get(check) ? method1 : method2} >Bla</button></>
3==== 4==================
return { props: {}, slots: {}, getters: {} }}
return { props: {}, slots: {}, getters: {}, events: {} }}

export default class {
$$prop_def = __sveltets_partial(render().props)
$$slot_def = render().slots
$on = __sveltets_eventDef(render().events)
}
!Expected
<Component on:click={$check ? method1 : method2} />
Expand Down
3 changes: 2 additions & 1 deletion packages/svelte2tsx/test/sourcemaps/let.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
;
<>
</>
return { props: {}, slots: {}, getters: {} }}
return { props: {}, slots: {}, getters: {}, events: {} }}

export default class {
$$prop_def = __sveltets_partial(render().props)
$$slot_def = render().slots
$on = __sveltets_eventDef(render().events)
}
!Expected
<script>
Expand Down
7 changes: 4 additions & 3 deletions packages/svelte2tsx/test/sourcemaps/repl.html
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,10 @@
rollupUrl={rollupUrl}
orientation={mobile ? 'columns' : 'rows'}
fixed={mobile}
{...__sveltets_ensureFunction((handle_change))}

injectedJS={mapbox_setup}
relaxed
/>
/>{__sveltets_instanceOf(Repl).$on('change', handle_change)}
</div>
</div>

Expand All @@ -177,11 +177,12 @@
</>}}}
</div>
</>
return { props: {slug: slug , chapter: chapter}, slots: {}, getters: {} }}
return { props: {slug: slug , chapter: chapter}, slots: {}, getters: {}, events: {} }}

export default class {
$$prop_def = __sveltets_partial(render().props)
$$slot_def = render().slots
$on = __sveltets_eventDef(render().events)
}
!Expected
<script context="module">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
let [a,b,c] = [1,2,3];
;
<></>
return { props: {a: a , b: b , c: c}, slots: {}, getters: {} }}
return { props: {a: a , b: b , c: c}, slots: {}, getters: {}, events: {} }}

export default class Input__SvelteComponent_ {
$$prop_def = __sveltets_partial(render().props)
$$slot_def = render().slots
$on = __sveltets_eventDef(render().events)
}
Loading