Repository navigation
[feature request] Support Custom Types for Tagged Template Expressions #16551
Description
Activity
- addedIn DiscussionNot yet reached consensusNot yet reached consensusSuggestionAn idea for TypeScriptAn idea for TypeScript
on Jun 16, 2017 DanielRosenwasser commented
on Jun 16, 2017 MemberMore actionsThis is very much related to #3136. To be frank, I don't know how ready we are to implement something like this. I think it'd be extremely useful, but it's a fairly large change that I'm not sure we could commit to any time in the near future, but I'd be open to hearing others' thoughts.
ForbesLindesay commented
on Jun 19, 2017 ContributorAuthorMore actionsTrue, this is closely related to #3136, but I think it's a stronger use case. You can always use code generation to generate strongly typed APIs for remote services. The key difference here is that we would be allowing users to stick with the native language.
Seems like this would be best suited to a Language Service Plugin. Matt Bierner (@mjbvz) has been doing a lot of great work recently to provide intellisense and syntax highlighting for tagged template literals.
Matt Bierner (@mjbvz): Do you know of anyway using the current Language Service Plugin API to provide return type safety to tagged template literals? Or would it need to be a completely new feature added to the API?
I'm trying to piece together a much easier (and feature complete) VS Code extension + Language Service Plugin for GraphQL.
Not sure if a plugin can modify TypeScript's typing information. Ryan Cavanaugh (@RyanCavanaugh) would know more about this.
One thing to keep in mind is that plugins are currently focused on enhancing the editor experience. I do not believe they are loaded when you run
tscReacted by Remy Rylan and Max MakarovReacted by Brian KimThanks for the reply Matt Bierner (@mjbvz) -- my current approach is going in the direction of a VS Code plugin that interfaces with the language service to create type definitions and then insert references to them into the current document.
For example:
Foo.tsconst foo = graphql` query { ... } `
The VS Code plugin figures out the type information for the given query, and then creates a new file with the type interface defined in a subdirectory of the original file, so in this case you end up with the following file structure:
Foo.ts __generated__/Foo.queries.d.tsThe original file (Foo.ts) is modified via the VS Code plugin to add the relevant import and reference as such:
import { QueryResult } from './__generated__/Foo.queries' const foo = <QueryResult>graphql` query { ... } `
It's complicated, but the end result does work well for type safety. Hope to polish it all up and release soon.
Reacted by Brian KimReacted by Brian KimI'd like to revisit this. I've used the code generation solutions like @jayrylan mentions, but I find them to be a bit too complicated. It was something of an effort to set up, it's annoying to have to run a watcher process or install an editor plugin and try to figure out what went wrong if the typings are incorrect or fail to generate. The generators I've used also all rely on query and fragment names not colliding, since the generator dumps all of them into the same massive types file. I find it's a speed bump to educate new project contributors about what is going on (why import the query from here, but the typings from there) even if they understand TypeScript itself.
And at the core of it, it just feels a little... wrong... to have the promises of typing from TypeScript, and a strongly typed API from GraphQL, but they just don't work together without a bunch of duct tape.
Before finding this issue to tag along to, my original idea for a solution was to invent a new keyword for types which are dynamically generated from generics.
generatedType GraphQLResult<TDoc extends GraphQLDocument> = (doc: TDoc) => { const ast = parseGraphQLDocument(doc); return walkASTAndConvertToTypeScriptTypes(ast); }; function graphQLRequest<TDoc extends GraphQLDocument>(doc: TDoc): GraphQLResult<TDoc> { // ... }
Obviously there's a lot of difficulties there. Presumably TypeScript would remove the
generatedTypedefinition from the code during processing. If that code references other functions (as mine does), not sure how it would know whether to remove those too. I also included a reference to a parsing function which would probably be from a library. I don't pretend to know how TypeScript works or if it would be able to import that function from that library during type checking in order to resolve this type.However, I do like the approach in general, because the generated type could be bundled up into a library (like one that makes GraphQL requests) and the user would just get full typing without even having to think about it.
I also think the concept may be useful outside of just templates. For instance, typing this function:
function camelCaseKeys<T extends {}>(t: T): CamelCased<T> { /* ... */ } camelCaseKeys({ A: 0, FooBar: 1 }); // { a: 0, fooBar: 1 }
I get that this feature would probably be massively difficult, probably even more so than I'm aware. Perhaps not even possible with the way TS works today. But I could see it also being a huge step forward in seamlessly typing more things.
Reacted by joshjg, Raj Nigam, Daisy Leigh Brenecki, Max Makarov, Benjamin Kniffler and Yoav BalasianoReacted by Brian KimAny news?
Problem Statement
It is common to embed queries in tagged template expressions within typescript. For example, you might use sql:
You also see a similar pattern with graphql:
Currently, there is no way to provide type information about what is returned by
queryGraph/querySQL. A lot of potential type information is lost at the boundary here.I obviously don't expect typescript to read the graphql or SQL schema, as that's way out of scope. What I would like is a way to provide this information as a module author.
Proposal
I believe that all that's needed is a way to write plugins that answer the question "given a tagged template expression, what type does it return". This would only affect the type checker. It would not change generated code or add any new syntax.
A new
compilerConfigoption calledtaggedTemplateHandlerswould be added, that would take an array of paths to typescript files. An example might look something like:You could then define
querySQLlike:Language Feature Checklist
taggedTemplateHandlershas been registered for that tagtaggedTemplateHandlerif one exists.TypeNodereturned by thetaggedTemplateHandlerin place of the default behaviour.I'm happy to do my best to help implement this, but I would need some pointers on where to start.
P.S. would it be possible to pass in the type of the expressions, in place of the actual expressions themselves?