diff --git a/packages/svelte2tsx/create-files.js b/packages/svelte2tsx/create-files.js
new file mode 100644
index 000000000..5c41bc221
--- /dev/null
+++ b/packages/svelte2tsx/create-files.js
@@ -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}\`;
+`
+);
diff --git a/packages/svelte2tsx/index.d.ts b/packages/svelte2tsx/index.d.ts
index eb01dbe5e..5c895dca9 100644
--- a/packages/svelte2tsx/index.d.ts
+++ b/packages/svelte2tsx/index.d.ts
@@ -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
diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json
index 7c98edb3e..fac08011e 100644
--- a/packages/svelte2tsx/package.json
+++ b/packages/svelte2tsx/package.json
@@ -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",
diff --git a/packages/svelte2tsx/src/svelte2tsx/index.ts b/packages/svelte2tsx/src/svelte2tsx/index.ts
index a25ff3a43..2430e54bd 100644
--- a/packages/svelte2tsx/src/svelte2tsx/index.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/index.ts
@@ -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;
@@ -49,6 +50,7 @@ interface AddComponentExportPara {
exportedNames: ExportedNames;
fileName?: string;
componentDocumentation: ComponentDocumentation;
+ mode: 'dts' | 'tsx';
}
type TemplateProcessResult = {
@@ -320,7 +322,8 @@ function addComponentExport({
usesAccessors,
exportedNames,
fileName,
- componentDocumentation
+ componentDocumentation,
+ mode
}: AddComponentExportPara) {
const eventsDef = strictEvents ? 'render()' : '__sveltets_with_any_event(render())';
let propDef = '';
@@ -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);
}
@@ -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
@@ -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;
@@ -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
@@ -545,15 +564,32 @@ export function svelte2tsx(
exportedNames,
usesAccessors,
fileName: options?.filename,
- componentDocumentation
+ componentDocumentation,
+ mode: options.mode
});
- str.prepend('///\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('///\n');
+ return {
+ code: str.toString(),
+ map: str.generateMap({ hires: true, source: options?.filename }),
+ exportedNames,
+ events: events.createAPI()
+ };
+ }
}
diff --git a/packages/svelte2tsx/src/svelte2tsx/svelteShims.ts b/packages/svelte2tsx/src/svelte2tsx/svelteShims.ts
new file mode 100644
index 000000000..9c67eec34
--- /dev/null
+++ b/packages/svelte2tsx/src/svelte2tsx/svelteShims.ts
@@ -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);
+ /**
+ * 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(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 \`
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx
new file mode 100644
index 000000000..169fa2156
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx
@@ -0,0 +1,225 @@
+import { SvelteComponentTyped } from "svelte"
+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);
+ /**
+ * 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(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 `
+
+
+
+
+
\ No newline at end of file