diff --git a/packages/create-svelte/index.js b/packages/create-svelte/index.js index ee6d6ec458bc..ef140e15cf8f 100755 --- a/packages/create-svelte/index.js +++ b/packages/create-svelte/index.js @@ -17,7 +17,7 @@ export async function create(cwd, options) { /** * @param {string} template - * @param {boolean} typescript + * @param {false | "checkjs" | "typescript"} types * @param {string} name * @param {string} cwd */ @@ -40,7 +40,7 @@ function write_template_files(template, types, name, cwd) { } /** - build* + * * @param {string} cwd * @param {import('./types/internal').Options} options * @param {string} name diff --git a/packages/create-svelte/scripts/build-templates.js b/packages/create-svelte/scripts/build-templates.js index 3367ede9a46d..399ba925c216 100644 --- a/packages/create-svelte/scripts/build-templates.js +++ b/packages/create-svelte/scripts/build-templates.js @@ -5,21 +5,7 @@ import prettier from 'prettier'; import { transform } from 'sucrase'; import glob from 'tiny-glob/sync.js'; import { mkdirp, rimraf } from '../utils.js'; - -/** @param {string} typescript */ -function convert_typescript(typescript) { - const transformed = transform(typescript, { - transforms: ['typescript'] - }); - - return prettier.format(transformed.code, { - parser: 'babel', - useTabs: true, - singleQuote: true, - trailingComma: 'none', - printWidth: 100 - }); -} +import { transpile, extract_types } from '../transformers.js'; /** @param {Set} shared */ async function generate_templates(shared) { @@ -88,7 +74,7 @@ async function generate_templates(shared) { } else if (file.name.endsWith('.ts')) { js.push({ name: file.name.replace(/\.ts$/, '.js'), - contents: convert_typescript(file.contents) + contents: transpile(file.contents) }); } else if (file.name.endsWith('.svelte')) { // we jump through some hoops, rather than just using svelte.preprocess, @@ -143,6 +129,18 @@ async function generate_templates(shared) { } } + JSON.parse(fs.readFileSync(meta_file, 'utf-8')).generateDeclarationsFor?.forEach( + (/** @type string */ name) => { + if (!name.endsWith('.ts')) throw new Error(`${name} is not a .ts file`); + + const filepath = path.join(cwd, name); + js.push({ + name: path.normalize(name.replace(/.ts$/, '.d.ts')), + contents: extract_types(filepath) + }); + } + ); + fs.copyFileSync(meta_file, `${dir}/meta.json`); fs.writeFileSync(`${dir}/files.ts.json`, JSON.stringify(ts, null, '\t')); fs.writeFileSync(`${dir}/files.js.json`, JSON.stringify(js, null, '\t')); @@ -190,7 +188,7 @@ async function generate_shared() { name: js_name, include: [...include], exclude: [...exclude, 'typescript'], - contents: convert_typescript(contents) + contents: transpile(contents) }); include.push('typescript'); diff --git a/packages/create-svelte/templates/default/.meta.json b/packages/create-svelte/templates/default/.meta.json index a8ae4ca55d49..822603511c4c 100644 --- a/packages/create-svelte/templates/default/.meta.json +++ b/packages/create-svelte/templates/default/.meta.json @@ -1,3 +1,4 @@ { - "description": "SvelteKit demo app" + "description": "SvelteKit demo app", + "generateDeclarationsFor": ["src/lib/form.ts"] } diff --git a/packages/create-svelte/transformers.js b/packages/create-svelte/transformers.js new file mode 100644 index 000000000000..e4960391ab99 --- /dev/null +++ b/packages/create-svelte/transformers.js @@ -0,0 +1,47 @@ +import { transform } from 'sucrase'; +import ts from 'typescript'; +import prettier from 'prettier'; + +/** + * Transpile a TypeScript source string to JavaScript. + * @param {string} typescript + */ +export function transpile(typescript) { + const transformed = transform(typescript, { transforms: ['typescript'] }); + + return prettier.format(transformed.code, { + parser: 'babel', + useTabs: true, + singleQuote: true, + trailingComma: 'none', + printWidth: 100 + }); +} + +/** + * Extract type declarations from a TypeScript file. + * @param {string} filepath the path to the file to extract types from + * @returns {string} + */ +export function extract_types(filepath) { + // Create a Program with an in-memory emit + const host = ts.createCompilerHost({ + declaration: true, + emitDeclarationOnly: true + }); + let declaration; + host.writeFile = (_, contents) => (declaration = contents); + + // Prepare and emit the d.ts files + const program = ts.createProgram( + [filepath], + { + declaration: true, + emitDeclarationOnly: true + }, + host + ); + program.emit(); + + return declaration; +}