Skip to content

Commit a3d52bd

Browse files
authored
(feat) add dts output target to svelte2tsx (#1050)
When setting the new mode option to 'dts', all tsx/jsx code and the template code will be thrown out, all shims will be inlined and the export is rewritten differently. Only the `code` property will be set on the returned element. Use this as an intermediate step to generate type definitions from a component. It is expected to pass the result to TypeScript which should handle emitting the d.ts files.
1 parent d50a2a5 commit a3d52bd

File tree

10 files changed

+775
-28
lines changed

10 files changed

+775
-28
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const fs = require('fs');
2+
3+
let svelteShims = fs.readFileSync('./svelte-shims.d.ts', { encoding: 'utf-8' });
4+
svelteShims = svelteShims.substr(svelteShims.indexOf('declare class Sv')).replace(/`/g, '\\`');
5+
fs.writeFileSync(
6+
'./src/svelte2tsx/svelteShims.ts',
7+
`/* eslint-disable */
8+
// Auto-generated, do not change
9+
// prettier-ignore
10+
export const svelteShims = \`${svelteShims}\`;
11+
`
12+
);

packages/svelte2tsx/index.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,13 @@ export default function svelte2tsx(
3636
* The namespace option from svelte config
3737
*/
3838
namespace?: string;
39+
/**
40+
* When setting this to 'dts', all tsx/jsx code and the template code will be thrown out,
41+
* all shims will be inlined and the component export is written differently.
42+
* Only the `code` property will be set on the returned element.
43+
* Use this as an intermediate step to generate type definitions from a component.
44+
* It is expected to pass the result to TypeScript which should handle emitting the d.ts files.
45+
*/
46+
mode?: 'tsx' | 'dts'
3947
}
4048
): SvelteCompiledToTsx

packages/svelte2tsx/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@
4444
"typescript": "^4.1.2"
4545
},
4646
"scripts": {
47-
"build": "rollup -c",
47+
"build": "npm run create-files && rollup -c",
4848
"prepublishOnly": "npm run build",
49-
"dev": "rollup -c -w",
50-
"test": "mocha test/test.ts"
49+
"dev": "npm run create-files && rollup -c -w",
50+
"test": "npm run create-files && mocha test/test.ts",
51+
"create-files": "node ./create-files.js"
5152
},
5253
"files": [
5354
"index.mjs",

packages/svelte2tsx/src/svelte2tsx/index.ts

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
} from './processInstanceScriptContent';
2626
import { processModuleScriptTag } from './processModuleScriptTag';
2727
import { ScopeStack } from './utils/Scope';
28+
import { svelteShims } from './svelteShims';
2829

2930
interface CreateRenderFunctionPara extends InstanceScriptProcessResult {
3031
str: MagicString;
@@ -49,6 +50,7 @@ interface AddComponentExportPara {
4950
exportedNames: ExportedNames;
5051
fileName?: string;
5152
componentDocumentation: ComponentDocumentation;
53+
mode: 'dts' | 'tsx';
5254
}
5355

5456
type TemplateProcessResult = {
@@ -320,7 +322,8 @@ function addComponentExport({
320322
usesAccessors,
321323
exportedNames,
322324
fileName,
323-
componentDocumentation
325+
componentDocumentation,
326+
mode
324327
}: AddComponentExportPara) {
325328
const eventsDef = strictEvents ? 'render()' : '__sveltets_with_any_event(render())';
326329
let propDef = '';
@@ -336,15 +339,30 @@ function addComponentExport({
336339
}
337340

338341
const doc = componentDocumentation.getFormatted();
339-
const className = fileName && classNameFromFilename(fileName);
340-
341-
const statement =
342-
`\n\n${doc}export default class${
343-
className ? ` ${className}` : ''
344-
} extends createSvelte2TsxComponent(${propDef}) {` +
345-
createClassGetters(getters) +
346-
(usesAccessors ? createClassAccessors(getters, exportedNames) : '') +
347-
'\n}';
342+
const className = fileName && classNameFromFilename(fileName, mode !== 'dts');
343+
344+
let statement: string;
345+
if (mode === 'dts') {
346+
statement =
347+
`\nconst __propDef = ${propDef};\n` +
348+
`export type ${className}Props = typeof __propDef.props;\n` +
349+
`export type ${className}Events = typeof __propDef.events;\n` +
350+
`export type ${className}Slots = typeof __propDef.slots;\n` +
351+
`\n${doc}export default class${
352+
className ? ` ${className}` : ''
353+
} extends SvelteComponentTyped<${className}Props, ${className}Events, ${className}Slots> {` + // eslint-disable-line max-len
354+
createClassGetters(getters) +
355+
(usesAccessors ? createClassAccessors(getters, exportedNames) : '') +
356+
'\n}';
357+
} else {
358+
statement =
359+
`\n\n${doc}export default class${
360+
className ? ` ${className}` : ''
361+
} extends createSvelte2TsxComponent(${propDef}) {` +
362+
createClassGetters(getters) +
363+
(usesAccessors ? createClassAccessors(getters, exportedNames) : '') +
364+
'\n}';
365+
}
348366

349367
str.append(statement);
350368
}
@@ -355,7 +373,7 @@ function addComponentExport({
355373
*
356374
* https://svelte.dev/docs#Tags
357375
*/
358-
function classNameFromFilename(filename: string): string | undefined {
376+
function classNameFromFilename(filename: string, appendSuffix: boolean): string | undefined {
359377
try {
360378
const withoutExtensions = path.parse(filename).name?.split('.')[0];
361379
const withoutInvalidCharacters = withoutExtensions
@@ -371,7 +389,7 @@ function classNameFromFilename(filename: string): string | undefined {
371389
const withoutLeadingInvalidCharacters = withoutInvalidCharacters.substr(firstValidCharIdx);
372390
const inPascalCase = pascalCase(withoutLeadingInvalidCharacters);
373391
const finalName = firstValidCharIdx === -1 ? `A${inPascalCase}` : inPascalCase;
374-
return `${finalName}${COMPONENT_SUFFIX}`;
392+
return `${finalName}${appendSuffix ? COMPONENT_SUFFIX : ''}`;
375393
} catch (error) {
376394
console.warn(`Failed to create a name for the component class from filename ${filename}`);
377395
return undefined;
@@ -453,12 +471,13 @@ function createRenderFunction({
453471

454472
export function svelte2tsx(
455473
svelte: string,
456-
options?: {
474+
options: {
457475
filename?: string;
458476
isTsFile?: boolean;
459477
emitOnTemplateError?: boolean;
460478
namespace?: string;
461-
}
479+
mode?: 'tsx' | 'dts';
480+
} = {}
462481
) {
463482
const str = new MagicString(svelte);
464483
// process the htmlx as a svelte template
@@ -545,15 +564,32 @@ export function svelte2tsx(
545564
exportedNames,
546565
usesAccessors,
547566
fileName: options?.filename,
548-
componentDocumentation
567+
componentDocumentation,
568+
mode: options.mode
549569
});
550570

551-
str.prepend('///<reference types="svelte" />\n');
552-
553-
return {
554-
code: str.toString(),
555-
map: str.generateMap({ hires: true, source: options?.filename }),
556-
exportedNames,
557-
events: events.createAPI()
558-
};
571+
if (options.mode === 'dts') {
572+
// Prepend the import and all shims so the file is self-contained.
573+
// TypeScript's dts generation will remove the unused parts later.
574+
str.prepend('import { SvelteComponentTyped } from "svelte"\n' + svelteShims + '\n');
575+
let code = str.toString();
576+
// Remove all tsx occurences and the template part from the output
577+
code =
578+
code
579+
.substr(0, code.indexOf('\n() => (<>'))
580+
// prepended before each script block
581+
.replace('<></>;', '')
582+
.replace('<></>;', '') + code.substr(code.lastIndexOf('</>);') + '</>);'.length);
583+
return {
584+
code
585+
};
586+
} else {
587+
str.prepend('///<reference types="svelte" />\n');
588+
return {
589+
code: str.toString(),
590+
map: str.generateMap({ hires: true, source: options?.filename }),
591+
exportedNames,
592+
events: events.createAPI()
593+
};
594+
}
559595
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/* eslint-disable */
2+
// Auto-generated, do not change
3+
// prettier-ignore
4+
export const svelteShims = `declare class Svelte2TsxComponent<
5+
Props extends {} = {},
6+
Events extends {} = {},
7+
Slots extends {} = {}
8+
> {
9+
// svelte2tsx-specific
10+
/**
11+
* @internal This is for type checking capabilities only
12+
* and does not exist at runtime. Don't use this property.
13+
*/
14+
$$prop_def: Props;
15+
/**
16+
* @internal This is for type checking capabilities only
17+
* and does not exist at runtime. Don't use this property.
18+
*/
19+
$$events_def: Events;
20+
/**
21+
* @internal This is for type checking capabilities only
22+
* and does not exist at runtime. Don't use this property.
23+
*/
24+
$$slot_def: Slots;
25+
// https://svelte.dev/docs#Client-side_component_API
26+
constructor(options: Svelte2TsxComponentConstructorParameters<Props>);
27+
/**
28+
* Causes the callback function to be called whenever the component dispatches an event.
29+
* A function is returned that will remove the event listener when called.
30+
*/
31+
$on<K extends keyof Events & string>(event: K, handler: (e: Events[K]) => any): () => void;
32+
/**
33+
* Removes a component from the DOM and triggers any \`onDestroy\` handlers.
34+
*/
35+
$destroy(): void;
36+
/**
37+
* Programmatically sets props on an instance.
38+
* \`component.$set({ x: 1 })\` is equivalent to \`x = 1\` inside the component's \`<script>\` block.
39+
* Calling this method schedules an update for the next microtask — the DOM is __not__ updated synchronously.
40+
*/
41+
$set(props?: Partial<Props>): void;
42+
// From SvelteComponent(Dev) definition
43+
$$: any;
44+
$capture_state(): void;
45+
$inject_state(): void;
46+
}
47+
48+
interface Svelte2TsxComponentConstructorParameters<Props extends {}> {
49+
/**
50+
* An HTMLElement to render to. This option is required.
51+
*/
52+
target: Element;
53+
/**
54+
* A child of \`target\` to render the component immediately before.
55+
*/
56+
anchor?: Element;
57+
/**
58+
* An object of properties to supply to the component.
59+
*/
60+
props?: Props;
61+
hydrate?: boolean;
62+
intro?: boolean;
63+
$$inline?: boolean;
64+
}
65+
66+
type AConstructorTypeOf<T, U extends any[] = any[]> = new (...args: U) => T;
67+
type SvelteComponentConstructor<T, U extends Svelte2TsxComponentConstructorParameters<any>> = new (options: U) => T;
68+
69+
type SvelteActionReturnType = {
70+
update?: (args: any) => void,
71+
destroy?: () => void
72+
} | void
73+
74+
type SvelteTransitionConfig = {
75+
delay?: number,
76+
duration?: number,
77+
easing?: (t: number) => number,
78+
css?: (t: number, u: number) => string,
79+
tick?: (t: number, u: number) => void
80+
}
81+
82+
type SvelteTransitionReturnType = SvelteTransitionConfig | (() => SvelteTransitionConfig)
83+
84+
type SvelteAnimationReturnType = {
85+
delay?: number,
86+
duration?: number,
87+
easing?: (t: number) => number,
88+
css?: (t: number, u: number) => string,
89+
tick?: (t: number, u: number) => void
90+
}
91+
92+
type SvelteWithOptionalProps<Props, Keys extends keyof Props> = Omit<Props, Keys> & Partial<Pick<Props, Keys>>;
93+
type SvelteAllProps = { [index: string]: any }
94+
type SveltePropsAnyFallback<Props> = {[K in keyof Props]: Props[K] extends undefined ? any : Props[K]}
95+
type SvelteRestProps = { [index: string]: any }
96+
type SvelteSlots = { [index: string]: any }
97+
type SvelteStore<T> = { subscribe: (run: (value: T) => any, invalidate?: any) => any }
98+
99+
// Forces TypeScript to look into the type which results in a better representation of it
100+
// which helps for error messages
101+
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
102+
103+
declare var process: NodeJS.Process & { browser: boolean }
104+
declare var __sveltets_AnimationMove: { from: DOMRect, to: DOMRect }
105+
106+
declare function __sveltets_ensureAnimation(animationCall: SvelteAnimationReturnType): {};
107+
declare function __sveltets_ensureAction(actionCall: SvelteActionReturnType): {};
108+
declare function __sveltets_ensureTransition(transitionCall: SvelteTransitionReturnType): {};
109+
declare function __sveltets_ensureFunction(expression: (e: Event & { detail?: any }) => unknown ): {};
110+
declare function __sveltets_ensureType<T>(type: AConstructorTypeOf<T>, el: T): {};
111+
declare function __sveltets_cssProp(prop: Record<string, any>): {};
112+
declare function __sveltets_ctorOf<T>(type: T): AConstructorTypeOf<T>;
113+
declare function __sveltets_instanceOf<T = any>(type: AConstructorTypeOf<T>): T;
114+
declare function __sveltets_allPropsType(): SvelteAllProps
115+
declare function __sveltets_restPropsType(): SvelteRestProps
116+
declare function __sveltets_slotsType<Slots, Key extends keyof Slots>(slots: Slots): Record<Key, boolean>;
117+
118+
// Overload of the following two functions is necessary.
119+
// An empty array of optionalProps makes OptionalProps type any, which means we lose the prop typing.
120+
// optionalProps need to be first or its type cannot be infered correctly.
121+
122+
declare function __sveltets_partial<Props = {}, Events = {}, Slots = {}>(
123+
render: {props?: Props, events?: Events, slots?: Slots }
124+
): {props?: SveltePropsAnyFallback<Props>, events?: Events, slots?: Slots }
125+
declare function __sveltets_partial<Props = {}, Events = {}, Slots = {}, OptionalProps extends keyof Props = any>(
126+
optionalProps: OptionalProps[],
127+
render: {props?: Props, events?: Events, slots?: Slots }
128+
): {props?: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>>, events?: Events, slots?: Slots }
129+
130+
declare function __sveltets_partial_with_any<Props = {}, Events = {}, Slots = {}>(
131+
render: {props?: Props, events?: Events, slots?: Slots }
132+
): {props?: SveltePropsAnyFallback<Props> & SvelteAllProps, events?: Events, slots?: Slots }
133+
declare function __sveltets_partial_with_any<Props = {}, Events = {}, Slots = {}, OptionalProps extends keyof Props = any>(
134+
optionalProps: OptionalProps[],
135+
render: {props?: Props, events?: Events, slots?: Slots }
136+
): {props?: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>> & SvelteAllProps, events?: Events, slots?: Slots }
137+
138+
139+
declare function __sveltets_with_any<Props = {}, Events = {}, Slots = {}>(
140+
render: {props?: Props, events?: Events, slots?: Slots }
141+
): {props?: Props & SvelteAllProps, events?: Events, slots?: Slots }
142+
143+
declare function __sveltets_with_any_event<Props = {}, Events = {}, Slots = {}>(
144+
render: {props?: Props, events?: Events, slots?: Slots }
145+
): {props?: Props, events?: Events & {[evt: string]: CustomEvent<any>;}, slots?: Slots }
146+
147+
declare function __sveltets_store_get<T = any>(store: SvelteStore<T>): T
148+
declare function __sveltets_any(dummy: any): any;
149+
declare function __sveltets_empty(dummy: any): {};
150+
declare function __sveltets_componentType(): AConstructorTypeOf<Svelte2TsxComponent<any, any, any>>
151+
declare function __sveltets_invalidate<T>(getValue: () => T): T
152+
153+
declare function __sveltets_mapWindowEvent<K extends keyof HTMLBodyElementEventMap>(
154+
event: K
155+
): HTMLBodyElementEventMap[K];
156+
declare function __sveltets_mapBodyEvent<K extends keyof WindowEventMap>(
157+
event: K
158+
): WindowEventMap[K];
159+
declare function __sveltets_mapElementEvent<K extends keyof HTMLElementEventMap>(
160+
event: K
161+
): HTMLElementEventMap[K];
162+
declare function __sveltets_mapElementTag<K extends keyof ElementTagNameMap>(
163+
tag: K
164+
): ElementTagNameMap[K];
165+
declare function __sveltets_mapElementTag<K extends keyof SVGElementTagNameMap>(
166+
tag: K
167+
): SVGElementTagNameMap[K];
168+
declare function __sveltets_mapElementTag(
169+
tag: any
170+
): HTMLElement;
171+
172+
declare function __sveltets_bubbleEventDef<Events, K extends keyof Events>(
173+
events: Events, eventKey: K
174+
): Events[K];
175+
declare function __sveltets_bubbleEventDef(
176+
events: any, eventKey: string
177+
): any;
178+
179+
declare const __sveltets_customEvent: CustomEvent<any>;
180+
declare function __sveltets_toEventTypings<Typings>(): {[Key in keyof Typings]: CustomEvent<Typings[Key]>};
181+
182+
declare function __sveltets_unionType<T1, T2>(t1: T1, t2: T2): T1 | T2;
183+
declare function __sveltets_unionType<T1, T2, T3>(t1: T1, t2: T2, t3: T3): T1 | T2 | T3;
184+
declare function __sveltets_unionType<T1, T2, T3, T4>(t1: T1, t2: T2, t3: T3, t4: T4): T1 | T2 | T3 | T4;
185+
declare function __sveltets_unionType(...types: any[]): any;
186+
187+
declare function __sveltets_awaitThen<T>(
188+
promise: T,
189+
onfulfilled: (value: T extends PromiseLike<infer U> ? U : T) => any,
190+
onrejected?: (value: T extends PromiseLike<any> ? any : never) => any
191+
): any;
192+
193+
declare function __sveltets_each<T>(
194+
array: ArrayLike<T>,
195+
callbackfn: (value: T, index: number) => any
196+
): any;
197+
198+
declare function createSvelte2TsxComponent<Props, Events, Slots>(
199+
render: {props?: Props, events?: Events, slots?: Slots }
200+
): SvelteComponentConstructor<Svelte2TsxComponent<Props, Events, Slots>,Svelte2TsxComponentConstructorParameters<Props>>;
201+
202+
declare function __sveltets_unwrapArr<T>(arr: ArrayLike<T>): T
203+
declare function __sveltets_unwrapPromiseLike<T>(promise: PromiseLike<T> | T): T
204+
`;

0 commit comments

Comments
 (0)