Skip to content
Merged
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
12 changes: 12 additions & 0 deletions packages/svelte2tsx/create-files.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const fs = require('fs');

let svelteShims = fs.readFileSync('./svelte-shims.d.ts', { encoding: 'utf-8' });
svelteShims = svelteShims.substr(svelteShims.indexOf('declare class Sv')).replace(/`/g, '\\`');
fs.writeFileSync(
'./src/svelte2tsx/svelteShims.ts',
`/* eslint-disable */
// Auto-generated, do not change
// prettier-ignore
export const svelteShims = \`${svelteShims}\`;
`
);
8 changes: 8 additions & 0 deletions packages/svelte2tsx/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,13 @@ export default function svelte2tsx(
* The namespace option from svelte config
*/
namespace?: string;
/**
* When setting this to 'dts', all tsx/jsx code and the template code will be thrown out,
* all shims will be inlined and the component export is written 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.
*/
mode?: 'tsx' | 'dts'
}
): SvelteCompiledToTsx
7 changes: 4 additions & 3 deletions packages/svelte2tsx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@
"typescript": "^4.1.2"
},
"scripts": {
"build": "rollup -c",
"build": "npm run create-files && rollup -c",
"prepublishOnly": "npm run build",
"dev": "rollup -c -w",
"test": "mocha test/test.ts"
"dev": "npm run create-files && rollup -c -w",
"test": "npm run create-files && mocha test/test.ts",
"create-files": "node ./create-files.js"
},
"files": [
"index.mjs",
Expand Down
82 changes: 59 additions & 23 deletions packages/svelte2tsx/src/svelte2tsx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from './processInstanceScriptContent';
import { processModuleScriptTag } from './processModuleScriptTag';
import { ScopeStack } from './utils/Scope';
import { svelteShims } from './svelteShims';

interface CreateRenderFunctionPara extends InstanceScriptProcessResult {
str: MagicString;
Expand All @@ -49,6 +50,7 @@ interface AddComponentExportPara {
exportedNames: ExportedNames;
fileName?: string;
componentDocumentation: ComponentDocumentation;
mode: 'dts' | 'tsx';
}

type TemplateProcessResult = {
Expand Down Expand Up @@ -320,7 +322,8 @@ function addComponentExport({
usesAccessors,
exportedNames,
fileName,
componentDocumentation
componentDocumentation,
mode
}: AddComponentExportPara) {
const eventsDef = strictEvents ? 'render()' : '__sveltets_with_any_event(render())';
let propDef = '';
Expand All @@ -336,15 +339,30 @@ function addComponentExport({
}

const doc = componentDocumentation.getFormatted();
const className = fileName && classNameFromFilename(fileName);

const statement =
`\n\n${doc}export default class${
className ? ` ${className}` : ''
} extends createSvelte2TsxComponent(${propDef}) {` +
createClassGetters(getters) +
(usesAccessors ? createClassAccessors(getters, exportedNames) : '') +
'\n}';
const className = fileName && classNameFromFilename(fileName, mode !== 'dts');

let statement: string;
if (mode === 'dts') {
statement =
`\nconst __propDef = ${propDef};\n` +
`export type ${className}Props = typeof __propDef.props;\n` +
`export type ${className}Events = typeof __propDef.events;\n` +
`export type ${className}Slots = typeof __propDef.slots;\n` +
`\n${doc}export default class${
className ? ` ${className}` : ''
} extends SvelteComponentTyped<${className}Props, ${className}Events, ${className}Slots> {` + // eslint-disable-line max-len
createClassGetters(getters) +
(usesAccessors ? createClassAccessors(getters, exportedNames) : '') +
'\n}';
} else {
statement =
`\n\n${doc}export default class${
className ? ` ${className}` : ''
} extends createSvelte2TsxComponent(${propDef}) {` +
createClassGetters(getters) +
(usesAccessors ? createClassAccessors(getters, exportedNames) : '') +
'\n}';
}

str.append(statement);
}
Expand All @@ -355,7 +373,7 @@ function addComponentExport({
*
* https://svelte.dev/docs#Tags
*/
function classNameFromFilename(filename: string): string | undefined {
function classNameFromFilename(filename: string, appendSuffix: boolean): string | undefined {
try {
const withoutExtensions = path.parse(filename).name?.split('.')[0];
const withoutInvalidCharacters = withoutExtensions
Expand All @@ -371,7 +389,7 @@ function classNameFromFilename(filename: string): string | undefined {
const withoutLeadingInvalidCharacters = withoutInvalidCharacters.substr(firstValidCharIdx);
const inPascalCase = pascalCase(withoutLeadingInvalidCharacters);
const finalName = firstValidCharIdx === -1 ? `A${inPascalCase}` : inPascalCase;
return `${finalName}${COMPONENT_SUFFIX}`;
return `${finalName}${appendSuffix ? COMPONENT_SUFFIX : ''}`;
} catch (error) {
console.warn(`Failed to create a name for the component class from filename ${filename}`);
return undefined;
Expand Down Expand Up @@ -453,12 +471,13 @@ function createRenderFunction({

export function svelte2tsx(
svelte: string,
options?: {
options: {
filename?: string;
isTsFile?: boolean;
emitOnTemplateError?: boolean;
namespace?: string;
}
mode?: 'tsx' | 'dts';
} = {}
) {
const str = new MagicString(svelte);
// process the htmlx as a svelte template
Expand Down Expand Up @@ -545,15 +564,32 @@ export function svelte2tsx(
exportedNames,
usesAccessors,
fileName: options?.filename,
componentDocumentation
componentDocumentation,
mode: options.mode
});

str.prepend('///<reference types="svelte" />\n');

return {
code: str.toString(),
map: str.generateMap({ hires: true, source: options?.filename }),
exportedNames,
events: events.createAPI()
};
if (options.mode === 'dts') {
// Prepend the import and all shims so the file is self-contained.
// TypeScript's dts generation will remove the unused parts later.
str.prepend('import { SvelteComponentTyped } from "svelte"\n' + svelteShims + '\n');
let code = str.toString();
// Remove all tsx occurences and the template part from the output
code =
code
.substr(0, code.indexOf('\n() => (<>'))
// prepended before each script block
.replace('<></>;', '')
.replace('<></>;', '') + code.substr(code.lastIndexOf('</>);') + '</>);'.length);
return {
code
};
} else {
str.prepend('///<reference types="svelte" />\n');
return {
code: str.toString(),
map: str.generateMap({ hires: true, source: options?.filename }),
exportedNames,
events: events.createAPI()
};
}
}
204 changes: 204 additions & 0 deletions packages/svelte2tsx/src/svelte2tsx/svelteShims.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/* eslint-disable */
// Auto-generated, do not change
// prettier-ignore
export const svelteShims = `declare class Svelte2TsxComponent<
Props extends {} = {},
Events extends {} = {},
Slots extends {} = {}
> {
// svelte2tsx-specific
/**
* @internal This is for type checking capabilities only
* and does not exist at runtime. Don't use this property.
*/
$$prop_def: Props;
/**
* @internal This is for type checking capabilities only
* and does not exist at runtime. Don't use this property.
*/
$$events_def: Events;
/**
* @internal This is for type checking capabilities only
* and does not exist at runtime. Don't use this property.
*/
$$slot_def: Slots;
// https://svelte.dev/docs#Client-side_component_API
constructor(options: Svelte2TsxComponentConstructorParameters<Props>);
/**
* Causes the callback function to be called whenever the component dispatches an event.
* A function is returned that will remove the event listener when called.
*/
$on<K extends keyof Events & string>(event: K, handler: (e: Events[K]) => any): () => void;
/**
* Removes a component from the DOM and triggers any \`onDestroy\` handlers.
*/
$destroy(): void;
/**
* Programmatically sets props on an instance.
* \`component.$set({ x: 1 })\` is equivalent to \`x = 1\` inside the component's \`<script>\` block.
* Calling this method schedules an update for the next microtask — the DOM is __not__ updated synchronously.
*/
$set(props?: Partial<Props>): void;
// From SvelteComponent(Dev) definition
$$: any;
$capture_state(): void;
$inject_state(): void;
}

interface Svelte2TsxComponentConstructorParameters<Props extends {}> {
/**
* An HTMLElement to render to. This option is required.
*/
target: Element;
/**
* A child of \`target\` to render the component immediately before.
*/
anchor?: Element;
/**
* An object of properties to supply to the component.
*/
props?: Props;
hydrate?: boolean;
intro?: boolean;
$$inline?: boolean;
}

type AConstructorTypeOf<T, U extends any[] = any[]> = new (...args: U) => T;
type SvelteComponentConstructor<T, U extends Svelte2TsxComponentConstructorParameters<any>> = new (options: U) => T;

type SvelteActionReturnType = {
update?: (args: any) => 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
}

type SvelteTransitionReturnType = SvelteTransitionConfig | (() => SvelteTransitionConfig)

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

type SvelteWithOptionalProps<Props, Keys extends keyof Props> = Omit<Props, Keys> & Partial<Pick<Props, Keys>>;
type SvelteAllProps = { [index: string]: any }
type SveltePropsAnyFallback<Props> = {[K in keyof Props]: Props[K] extends undefined ? any : Props[K]}
type SvelteRestProps = { [index: string]: any }
type SvelteSlots = { [index: string]: any }
type SvelteStore<T> = { subscribe: (run: (value: T) => any, invalidate?: any) => any }

// Forces TypeScript to look into the type which results in a better representation of it
// which helps for error messages
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;

declare var process: NodeJS.Process & { browser: boolean }
declare var __sveltets_AnimationMove: { from: DOMRect, to: DOMRect }

declare function __sveltets_ensureAnimation(animationCall: SvelteAnimationReturnType): {};
declare function __sveltets_ensureAction(actionCall: SvelteActionReturnType): {};
declare function __sveltets_ensureTransition(transitionCall: SvelteTransitionReturnType): {};
declare function __sveltets_ensureFunction(expression: (e: Event & { detail?: any }) => unknown ): {};
declare function __sveltets_ensureType<T>(type: AConstructorTypeOf<T>, el: T): {};
declare function __sveltets_cssProp(prop: Record<string, any>): {};
declare function __sveltets_ctorOf<T>(type: T): AConstructorTypeOf<T>;
declare function __sveltets_instanceOf<T = any>(type: AConstructorTypeOf<T>): T;
declare function __sveltets_allPropsType(): SvelteAllProps
declare function __sveltets_restPropsType(): SvelteRestProps
declare function __sveltets_slotsType<Slots, Key extends keyof Slots>(slots: Slots): Record<Key, boolean>;

// Overload of the following two functions is necessary.
// An empty array of optionalProps makes OptionalProps type any, which means we lose the prop typing.
// optionalProps need to be first or its type cannot be infered correctly.

declare function __sveltets_partial<Props = {}, Events = {}, Slots = {}>(
render: {props?: Props, events?: Events, slots?: Slots }
): {props?: SveltePropsAnyFallback<Props>, events?: Events, slots?: Slots }
declare function __sveltets_partial<Props = {}, Events = {}, Slots = {}, OptionalProps extends keyof Props = any>(
optionalProps: OptionalProps[],
render: {props?: Props, events?: Events, slots?: Slots }
): {props?: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>>, events?: Events, slots?: Slots }

declare function __sveltets_partial_with_any<Props = {}, Events = {}, Slots = {}>(
render: {props?: Props, events?: Events, slots?: Slots }
): {props?: SveltePropsAnyFallback<Props> & SvelteAllProps, events?: Events, slots?: Slots }
declare function __sveltets_partial_with_any<Props = {}, Events = {}, Slots = {}, OptionalProps extends keyof Props = any>(
optionalProps: OptionalProps[],
render: {props?: Props, events?: Events, slots?: Slots }
): {props?: Expand<SvelteWithOptionalProps<SveltePropsAnyFallback<Props>, OptionalProps>> & SvelteAllProps, events?: Events, slots?: Slots }


declare function __sveltets_with_any<Props = {}, Events = {}, Slots = {}>(
render: {props?: Props, events?: Events, slots?: Slots }
): {props?: Props & SvelteAllProps, events?: Events, slots?: Slots }

declare function __sveltets_with_any_event<Props = {}, Events = {}, Slots = {}>(
render: {props?: Props, events?: Events, slots?: Slots }
): {props?: Props, events?: Events & {[evt: string]: CustomEvent<any>;}, slots?: Slots }

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<Svelte2TsxComponent<any, any, any>>
declare function __sveltets_invalidate<T>(getValue: () => T): 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_mapElementTag<K extends keyof ElementTagNameMap>(
tag: K
): ElementTagNameMap[K];
declare function __sveltets_mapElementTag<K extends keyof SVGElementTagNameMap>(
tag: K
): SVGElementTagNameMap[K];
declare function __sveltets_mapElementTag(
tag: any
): HTMLElement;

declare function __sveltets_bubbleEventDef<Events, K extends keyof Events>(
events: Events, eventKey: K
): Events[K];
declare function __sveltets_bubbleEventDef(
events: any, eventKey: string
): any;

declare const __sveltets_customEvent: CustomEvent<any>;
declare function __sveltets_toEventTypings<Typings>(): {[Key in keyof Typings]: CustomEvent<Typings[Key]>};

declare function __sveltets_unionType<T1, T2>(t1: T1, t2: T2): T1 | T2;
declare function __sveltets_unionType<T1, T2, T3>(t1: T1, t2: T2, t3: T3): T1 | T2 | T3;
declare function __sveltets_unionType<T1, T2, T3, T4>(t1: T1, t2: T2, t3: T3, t4: T4): T1 | T2 | T3 | T4;
declare function __sveltets_unionType(...types: any[]): any;

declare function __sveltets_awaitThen<T>(
promise: T,
onfulfilled: (value: T extends PromiseLike<infer U> ? U : T) => any,
onrejected?: (value: T extends PromiseLike<any> ? any : never) => any
): any;

declare function __sveltets_each<T>(
array: ArrayLike<T>,
callbackfn: (value: T, index: number) => any
): any;

declare function createSvelte2TsxComponent<Props, Events, Slots>(
render: {props?: Props, events?: Events, slots?: Slots }
): SvelteComponentConstructor<Svelte2TsxComponent<Props, Events, Slots>,Svelte2TsxComponentConstructorParameters<Props>>;

declare function __sveltets_unwrapArr<T>(arr: ArrayLike<T>): T
declare function __sveltets_unwrapPromiseLike<T>(promise: PromiseLike<T> | T): T
`;
Loading