-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Generate types for paths #7110
Comments
This would work in the short-term, but it would likely be difficult to make it work longer-term if we allow you to do things like have routes in multiple sub-projects and register routes from a different sub-project |
I agree that it's difficult to implement it before the more advanced routing features drop. But I have a silly script as a workaround. import glob from "fast-glob"
import path from "path"
import fs from "fs"
const rootDir = path.resolve(__dirname, "..")
const routesDir = path.join(rootDir, "src", "routes")
const libDir = path.join(rootDir, "src", "lib")
// look for +page.svelte and +page.ts files in ./src/routes
const files = glob.sync("**/+page.{svelte,ts}", {
cwd: routesDir,
absolute: true,
onlyFiles: true,
ignore: ["**/node_modules/**", "**/.*"]
})
// make url parts out of the file paths
const pathParts = files.map((file) =>
path.relative(routesDir, file).split(path.sep).slice(0, -1)
)
const routeTypesContent = `import { redirect } from "@sveltejs/kit"\n
export type TypedRoute = ${pathParts.map((parts) => {
// in case it's root, we don't want to have an empty string
if (!parts.length) return '"/"'
// remove layout groups
parts = parts.filter((part) => !part.startsWith("("))
//replace params with string type
parts = parts.map((part) => (part.startsWith("[") ? "${string}" : part))
// make the route type
return `\`/${parts.join("/")}\``
}).join(" | ")}\n
export const typedRedirect = (status: number, url: TypedRoute) => redirect(status, url)\n
export const typedHref = (url: TypedRoute) => url
`
// Write out $routes.d.ts
fs.writeFileSync(
path.join(libDir, "$routes.ts"),
routeTypesContent, {
encoding: "utf8",
flag: "w"
}) It generates a // imagine something like /foo/[bar]
import { redirect } from "@sveltejs/kit"
export type TypedRoute = "/" | `/foo` | `/foo/${string}`
export const typedRedirect = (status: number, url: TypedRoute) => redirect(status, url)
export const href = (url: TypedRoute) => url It could be used as so: <script lang="ts">
import { typedRedirect, typedHref, type TypedRoute } from '$lib/$routes'
const home: TypedRoute = '/'
// redirect with
typedRedirect(307, '/foo')
</script>
<a href={typedHref('/foo/bar')}>FooBar</a> I added it to my |
Generate types for requests to +server and responses according to the same schemeFetch will have a response type not
|
I also use a script workaround to provide safe routes. But instead of trying to type-check the path-strings, I generate string constants (for routes without parameters) and functions (for routes with parameters). Then, I make sure that all paths used in my app are either given by these constants/functions or external URLs. Given a directory
the script generates export namespace Routes {
export const Index = "/";
export function Project(id: string): string {
return `/project/${id}/`
}
} In the template... <a href={Routes.Project(project_id)}>{project.title}</a> Having all the routes in a shared namespace makes it quick and easy to import them, however, it comes at the cost of having to import all the routes when you want to use only one. And this is, unfortunately, a serious flaw. For generating typed bindings for calls to // src/routes/!/projects/+server.ts
import type { RequestEvent } from '@sveltejs/kit';
import { type EndpointResponse } from '@endpoint_utils';
type InputBody = { q: string };
type OutputBody = { projects: Project[] };
type ErrorType = void;
export async function POST({ request }: RequestEvent): Promise<EndpointResponse<OutputBody, ErrorType>> {
// tell the compiler that the json body of the request has type InputBody
let input: InputBody = await request.json();
let result = ... // fetch projects from external API or database;
...
// type EndpointResponse<OutputBody, ErrorType> enforces
// that the endpoint returns either a Response with
// body of type OutputBody or ErrorType
} The script inspects export type ProjectsInput = { q: string }
export type ProjectsOutput = { projects: Project[] }
export type ProjectsError = void
export function projects(body: ProjectsInput): { response: Promise<ProjectsOutput | EndpointError<ProjectsError>>, abort: () => void} {
return fetch(`/!/projects/`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
} However, this endpoint-bindings-generating script is just a band-aid. In an ideal world, type JSONPostRequestEvent<InputBody> = { json: InputBody, ... }
type GetRequestEvent<SearchParams> = { search_params: SearchParams, ... } But there is another aspect to this: path parameters, which should be automatically generated from the paths. E.g. Then, import type { JSONPostRequestEvent } from './$types';
type OutputBody = { projects: Project[] };
type ErrorType = void;
export async function json_POST({ request, json }: JSONPostRequestEvent<{ q: string }>)
: Promise<EndpointResponse<OutputBody, ErrorType>> {
// compiler knows that the type of `json` is `{ q: string }`
let result = ... // fetch projects from external API or database;
....
} Then, a script could extract the type arguments of But here comes the big caveat: the approach of generating actual constants/functions for routes and typed endpoint bindings is pretty invasive in the sense that it's not just types. There's actual code being generated which is shipped to production. However, it's also simpler to do than generating types and it's convenient in terms of code editors autocompleting bindings and routes. To specialize / break up the type of Looking at this from the other direction: In the case of pages, it was possible to generate types, as we look at the type of However, with independent endpoints, there's no corresponding entity that requires data to be in a certain shape that we could inspect. Maybe, if we would have to write a function for every endpoint that makes a call to that endpoint, we could annotate that with types, which are then used to generate a type for the handler in |
I stumbled upon this https://github.com/mattpocock/make-route-map By replacing the param matcher For now, I crawl the routes folder and generate an index.ts. It would be great if this could be done by svelte in the |
Closed as duplicate of #647 |
Describe the problem
Example:
Describe the proposed solution
It is interesting:
Alternatives considered
No response
Importance
would make my life easier
Additional Information
No response
The text was updated successfully, but these errors were encountered: