Skip to content
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

svelte-kit package: Generate type definitions #1646

Merged
merged 39 commits into from
Jul 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
69948d9
svelte-kit package: Generate type definitions
Jun 7, 2021
f33dcd5
only create types for lib + packagejson field
Jun 9, 2021
0f95426
use new svelte2tsx which handles dts transformation internally
Jun 11, 2021
87acbbf
fix validation: return option, not omit it
Jun 11, 2021
e5704ac
add types option
Jun 11, 2021
47bbda1
bump svelte-check
Jun 11, 2021
35b28f8
make . the default for types
Jun 11, 2021
0766165
fix tests
Jun 11, 2021
4ba1d53
fix tests
dummdidumm Jun 11, 2021
a0d08ed
lint
dummdidumm Jun 11, 2021
c5d9a25
changeset
Jun 11, 2021
83dde9d
fix packagejson types field
Jun 12, 2021
cf9c298
split types option in two
Jun 12, 2021
e1f0258
docs
Jun 12, 2021
a14619d
fix tests
Jun 12, 2021
be8dc9c
Update documentation/docs/14-configuration.md
dummdidumm Jun 14, 2021
86a3a66
wording
dummdidumm Jun 14, 2021
4e743eb
docs cleanup
dummdidumm Jun 14, 2021
b8adea5
Merge branch 'master' into package-type-defs
Jun 20, 2021
dc127ea
rename, bump svelte2tsx
Jun 20, 2021
2fa060b
Merge branch 'master' into package-type-defs
Rich-Harris Jun 29, 2021
a8d5edc
doing blind commits on the phone
dummdidumm Jun 29, 2021
0f11b53
ok, laptop it is
Jun 29, 2021
a8842f6
Merge branch 'master' into package-type-defs
Jun 29, 2021
e7f3929
remove types option
Jun 30, 2021
9937137
fix generation of JSdoc->dts
Jun 30, 2021
7e0d3f6
do type generation first
Jun 30, 2021
bf250b4
add tests, fix uncovered bugs
Jul 2, 2021
1f67bf9
ignore test folder for eslint
Jul 2, 2021
b018c95
ignore
Jul 2, 2021
4d2d72f
remove optional chaining
Jul 2, 2021
8692afd
wtf these ordering problems
Jul 2, 2021
a3190dd
bye bye nullish coalescing
Jul 2, 2021
c859d69
Add warning when file will be overwritten
Jul 2, 2021
ccf76c9
Merge branch 'package-type-defs' of github.com:sveltejs/kit into pack…
Rich-Harris Jul 2, 2021
5e278b1
force some compileroptions, now jsconfig works, too
Jul 4, 2021
7aa7108
moved emitDts functionality into svelte2tsx, adjust accordingly
Jul 4, 2021
24768fb
update config.md
Jul 4, 2021
22c57ca
cleanup
Jul 4, 2021
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
5 changes: 5 additions & 0 deletions .changeset/odd-islands-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

Add types generation to svelte-kit package command
9 changes: 3 additions & 6 deletions documentation/docs/12-packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ A SvelteKit component library has the exact same structure as a SvelteKit app, e

Running `svelte-kit package` will take the contents of `src/lib` and generate a `package` directory (which can be [configured](#configuration-package)) containing the following:

- All the files in `src/lib`, unless you [configure](#configuration-package) custom `include`/`exclude` options. Svelte components will be preprocessed (but note the [caveats](#packaging-caveats) below)
- All the files in `src/lib`, unless you [configure](#configuration-package) custom `include`/`exclude` options. Svelte components will be preprocessed, TypeScript files will be transpiled to JavaScript.
- Type definitions (`d.ts` files) which are generated for Svelte, JavaScript and TypeScript files. You need to install `typescript >= 4.0.0` and `svelte2tsx >= 0.4.1` for this. Type definitions are placed next to their implementation, hand-written `d.ts` files are copied over as is.
- A `package.json` that copies the `name`, `version`, `description`, `keywords`, `homepage`, `bugs`, `license`, `author`, `contributors`, `funding`, `repository`, `dependencies`, `private` and `publishConfig` fields from the root of the project, and adds a `"type": "module"` and an `"exports"` field

The `"exports"` field contains the package's entry points. By default, all files in `src/lib` will be treated as an entry point unless they start with (or live in a directory that starts with) an underscore, but you can [configure](#configuration-package) this behaviour. If you have a `src/lib/index.js` or `src/lib/index.svelte` file, it will be treated as the package root.
Expand All @@ -37,8 +38,4 @@ The `package` above is referring to the directory name generated, change accordi

### Caveats

This is a relatively experimental feature and is not yet fully implemented:

- if a preprocessor is specified, `.svelte` files are transformed (meaning they can contain TypeScript, for example), but `.d.ts` files are not generated
- `.ts` files are not currently transformed, and will cause the process to fail
- all other files are copied across as-is
This is a relatively experimental feature and is not yet fully implemented. All files except Svelte files (preprocessed) and TypeScript files (transpiled to JavaScript) are copied across as-is.
19 changes: 19 additions & 0 deletions documentation/docs/14-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ const config = {
ssr: true,
target: null,
trailingSlash: 'never',
package: {
dir: 'package',
exports: {
include: ['**'],
exclude: ['_*', '**/_*']
},
files: {
include: ['**'],
exclude: []
}
},
vite: () => ({})
},

Expand Down Expand Up @@ -149,6 +160,14 @@ Whether to remove, append, or ignore trailing slashes when resolving URLs to rou

> Ignoring trailing slashes is not recommended — the semantics of relative paths differ between the two cases (`./y` from `/x` is `/y`, but from `/x/` is `/x/y`), and `/x` and `/x/` are treated as separate URLs which is harmful to SEO. If you use this option, ensure that you implement logic for conditionally adding or removing trailing slashes from `request.path` inside your [`handle`](#hooks-handle) function.

### package

Options related to [creating a package](#packaging).

- `dir` - output directory
- `exports` - contains a `includes` and a `excludes` array which specifies which files to mark as exported from the `exports` field of the `package.json`
- `files` - contains a `includes` and a `excludes` array which specifies which files to process and copy over when packaging

### vite

A [Vite config object](https://vitejs.dev/config), or a function that returns one. Not all configuration options can be set, since SvelteKit depends on certain values being configured internally.
7 changes: 4 additions & 3 deletions packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"selfsigned": "^1.10.11",
"sirv": "^1.0.12",
"svelte": "^3.38.2",
"svelte-check": "^1.5.2",
"svelte2tsx": "~0.4.1",
"svelte-check": "^2.2.0",
"tiny-glob": "^0.2.8",
"typescript": "^4.2.4",
"uvu": "^0.5.1"
Expand All @@ -53,8 +54,8 @@
"scripts": {
"build": "rollup -c",
"dev": "rollup -cw",
"lint": "eslint --ignore-path .gitignore \"src/**/*.{ts,mjs,js,svelte}\" && npm run check-format",
"check": "tsc && svelte-check",
"lint": "eslint --ignore-path .gitignore --ignore-pattern \"src/core/make_package/test/**\" \"src/**/*.{ts,mjs,js,svelte}\" && npm run check-format",
"check": "tsc && svelte-check --ignore \"src/core/make_package/test\"",
"format": "prettier --write . --config ../../.prettierrc --ignore-path .gitignore",
"check-format": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore",
"prepublishOnly": "npm run build",
Expand Down
3 changes: 2 additions & 1 deletion packages/kit/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const external = [].concat(
Object.keys(pkg.dependencies || {}),
Object.keys(pkg.peerDependencies || {}),
Object.keys(process.binding('natives')),
'typescript'
'typescript',
'svelte2tsx'
);

export default [
Expand Down
6 changes: 3 additions & 3 deletions packages/kit/src/core/config/options.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const noop = () => {};
const identity = (/** @type {any} */ id) => id;

/** @typedef {import('./types').ConfigDefinition} ConfigDefinition */

Expand All @@ -7,7 +7,7 @@ const options = {
compilerOptions: {
type: 'leaf',
default: null,
validate: noop
validate: identity
},

extensions: {
Expand Down Expand Up @@ -169,7 +169,7 @@ const options = {
preprocess: {
type: 'leaf',
default: null,
validate: noop
validate: identity
}
};

Expand Down
56 changes: 50 additions & 6 deletions packages/kit/src/core/make_package/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as fs from 'fs';
import globrex from 'globrex';
import { createRequire } from 'module';
import * as path from 'path';
import { preprocess } from 'svelte/compiler';
import globrex from 'globrex';
import { mkdirp, rimraf } from '../filesystem';
import { mkdirp, rimraf } from '../filesystem/index.js';

/**
* @param {import('types/config').ValidatedConfig} config
Expand All @@ -11,8 +12,14 @@ import { mkdirp, rimraf } from '../filesystem';
export async function make_package(config, cwd = process.cwd()) {
rimraf(path.join(cwd, config.kit.package.dir));

// Generate type definitions first so hand-written types can overwrite generated ones
await emit_dts(config);

const files_filter = create_filter(config.kit.package.files);
const exports_filter = create_filter(config.kit.package.exports);
const exports_filter = create_filter({
...config.kit.package.exports,
exclude: [...config.kit.package.exports.exclude, '*.d.ts']
});

const files = walk(config.kit.files.lib);

Expand Down Expand Up @@ -57,12 +64,22 @@ export async function make_package(config, cwd = process.cwd()) {

if (svelte_ext) {
// it's a Svelte component
// TODO how to emit types?
out_file = file.slice(0, -svelte_ext.length) + '.svelte';
out_contents = config.preprocess
? (await preprocess(source, config.preprocess, { filename })).code
: source;
} else if (ext === '.ts' && !file.endsWith('.d.ts')) {
} else if (ext === '.ts' && file.endsWith('.d.ts')) {
// TypeScript's declaration emit won't copy over the d.ts files, so we do it here
out_file = file;
out_contents = source;
if (fs.existsSync(path.join(cwd, config.kit.package.dir, out_file))) {
console.warn(
'Found already existing file from d.ts generation for ' +
out_file +
'. This file will be overwritten.'
);
}
} else if (ext === '.ts') {
out_file = file.slice(0, -'.ts'.length) + '.js';
out_contents = await transpile_ts(filename, source);
} else {
Expand Down Expand Up @@ -134,7 +151,7 @@ function load_tsconfig(filename, ts) {
const { error, config } = ts.readConfigFile(tsconfig_filename, ts.sys.readFile);

if (error) {
throw new Error('Malformed tsconfig');
throw new Error('Malformed tsconfig\n' + JSON.stringify(error, null, 2));
}

// Do this so TS will not search for initial files which might take a while
Expand Down Expand Up @@ -200,3 +217,30 @@ function write(file, contents) {
mkdirp(path.dirname(file));
fs.writeFileSync(file, contents);
}

/**
* @param {import('types/config').ValidatedConfig} config
*/
export async function emit_dts(config) {
const require = createRequire(import.meta.url);
const emit = await try_load_svelte2tsx();
emit({
libRoot: config.kit.files.lib,
svelteShimsPath: require.resolve('svelte2tsx/svelte-shims.d.ts'),
declarationDir: config.kit.package.dir
});
}

async function try_load_svelte2tsx() {
try {
const svelte2tsx = (await import('svelte2tsx')).emitDts;
if (!svelte2tsx) {
throw new Error('Old svelte2tsx version');
}
return svelte2tsx;
} catch (e) {
throw new Error(
'You need to install svelte2tsx >=0.4.1 if you want to generate type definitions'
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script>
import { createEventDispatcher } from 'svelte';
/**
* @type {string}
*/
export const astring;

const dispatch = createEventDispatcher();
dispatch('event', true);
</script>

<slot {astring} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/** @typedef {typeof __propDef.props} TestProps */
/** @typedef {typeof __propDef.events} TestEvents */
/** @typedef {typeof __propDef.slots} TestSlots */
export default class Test extends SvelteComponentTyped<
{
astring: string;
},
{
event: CustomEvent<any>;
} & {
[evt: string]: CustomEvent<any>;
},
{
default: {
astring: string;
};
}
> {
get astring(): string;
}
export type TestProps = typeof __propDef.props;
export type TestEvents = typeof __propDef.events;
export type TestSlots = typeof __propDef.slots;
import { SvelteComponentTyped } from 'svelte';
declare const __propDef: {
props: {
astring: string;
};
events: {
event: CustomEvent<any>;
} & {
[evt: string]: CustomEvent<any>;
};
slots: {
default: {
astring: string;
};
};
};
export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script>
/**
* @type {import('./foo').Foo}
*/
export let foo;
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/** @typedef {typeof __propDef.props} Test2Props */
/** @typedef {typeof __propDef.events} Test2Events */
/** @typedef {typeof __propDef.slots} Test2Slots */
export default class Test2 extends SvelteComponentTyped<
{
foo: boolean;
},
{
[evt: string]: CustomEvent<any>;
},
{}
> {}
export type Test2Props = typeof __propDef.props;
export type Test2Events = typeof __propDef.events;
export type Test2Slots = typeof __propDef.slots;
import { SvelteComponentTyped } from 'svelte';
declare const __propDef: {
props: {
foo: import('./foo').Foo;
};
events: {
[evt: string]: CustomEvent<any>;
};
slots: {};
};
export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type Foo = boolean;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Test } from './Test.svelte';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Test } from './Test.svelte';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "javascript",
"version": "1.0.0",
"description": "package-javascript-test",
"type": "module",
"exports": {
"./package.json": "./package.json",
"./index.js": "./index.js",
"./Test.svelte": "./Test.svelte",
"./Test2.svelte": "./Test2.svelte",
".": "./index.js"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"checkJs": true,
"paths": {
"$lib/*": ["src/lib/*"]
}
},
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "javascript",
"version": "1.0.0",
"description": "package-javascript-test"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%svelte.head%
</head>
<body>
<div id="svelte">%svelte.body%</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script>
import { createEventDispatcher } from 'svelte';
/**
* @type {string}
*/
export const astring;

const dispatch = createEventDispatcher();
dispatch('event', true);
</script>

<slot {astring} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script>
/**
* @type {import('./foo').Foo}
*/
export let foo;
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type Foo = boolean;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Test } from './Test.svelte';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
export const astring;
const dispatch = createEventDispatcher();
dispatch('event', true);
</script>

<slot {astring} />
Loading