[RFC] Support for tagged template literals

Resurfacing from this thread. Binding to Tagged template function - #37 by zth

Tagged Template RFC

I’ve compiled this thread’s discussions into a single post here for renewed discussion. Github issue to come.

Popular JS libraries implement tagged templates (MDN) to provide some really ergonomic APIs. Rescript should make their use feel easy, whether directly supporting the syntax or compiling it into the desugared version of tagFun(expressionsArr, ...args)

Common use cases for both frontend and backend

  1. Common SQL libraries, like prisma.$rawQuery use the sql-template-tag library to form prepared statements.

    sql`select * from table where id = ${val1}` // val1 is escaped
    

    Great improvement over the typical

    sql("select * from table where id = ?", [val1]) 
    
  2. Frontend css-in-js libraries heavily use tag template literals, i.e. styled-components.

    const Button = styled.button`
      color: grey;
    `;
    
  3. graphql-tag uses tagged templates

    gql`
       {
         user(id: 5) {
         firstName
         lastName
       }
      }
     `
    

Current solutions

  1. Use raw to directly directly inline the tagged template. referenced post.

    %%raw("require('gql')` your query `")
    
  2. Create bindings that map to the desugared form (terrible, and I’ve yet to do this myself). referenced post

    @val @variadic
    external gql:  array<string>  => array<'a> => int = "gql"
    
    let result = max([5, -2, 6, 1])
    

Prior art - what’s been done so far

@kevinbarabash 's done alot of exploration and have submitted PRs 1 and 2 to have Rescript compile Rescript tagged templates into the js desugared form (see post).

The main blocker to this possible solution is to have the rescript syntax understand when a tagged template function is called. See @Maxim 's response.

Where to go from here? Can we add native support into rescript?

The team and community should determine the best solution and outline a proper implementation plan with dev costs.

10 Likes

My desire for this feature is relatively moderate as it’d let me use our prisma and sql in a team-appetizing, but not enough for me to dive into the compiler just yet. :slight_smile:

There’s alot of work done already, but no consensus on what’s actually acceptable.

2 Likes

We can all probably agree that this is a desired feature. It will however most likely need substantial amounts of work still, even with the exploration done. Let’s see if someone can pick this up at some point.

1 Like

I’m interested in doing this myself too. But am hacking on another ocaml project atm.

I guess a good starting point here for anyone is to introduce the tag template syntax to the syntax repo.

3 Likes

The syntax repo is now merged with the compiler, so working on this should be a lot easier.

2 Likes

Ty. I’ll likely be able to take this up late March. Unless someone else decides to do it

2 Likes

Let us know if you’re still interested in looking into this.

I’m unable to pick this work up :face_with_peeking_eye:

Some excitement - @tsnobip has revived the great base work done for this feature by @kevinbarabash, and has gotten quite far in finishing it: GitHub - rescript-lang/rescript-compiler at tagged_templates

7 Likes

ok, so let’s enumerate here the use cases we have for tagged templates in rescript so we can have a better understanding of this feature and its priority level.

I’ll start: use rescript to write GraphQL servers backed by a Postgres DB using postgraphile.

This makes me query the DB quite a lot, and hence, write SQL queries. You have basically two ways to do it,
you either use parameters like this:

query(
  pgClient,
  ~statement="select * from app_private.create_media($1, $2, $3, $4, $5)",
  ~params=[uuid(productId), string(path), string(filename), float(size), string(sha256)],
)

but it’s very brittle when you start having a significant number of arguments and start modifying them, so you could instead use tagged templates like this:

query(
  pgClient,
  sql`select * from app_private.create_media(${uuid(productId)}, ${string(path)}, ${string(filename)}, ${float(size)}, ${string(sha256)})`
)

This solution is way more robust but is not doable right now in rescript.

I also write GraphQL schema language using a gql tagged template function, but using tagged templates manually is so complex and error prone I just make it a single string and don’t use parameters, copy pasting blocks instead of using parameters, which is of course not ideal.

6 Likes