Binding to Tagged template function

I made some progress on this today and was able to get the following code:

type sqlQuery
type sqlFragment = string
@module("sql") @variadic external sql: (array<string>, array<sqlFragment>) => sqlQuery = "default"

let table = "users"
let id = "5"

let query = sql`SELECT * FROM ${table} WHERE id = ${id}`
let jsStr = `SELECT * FROM ${table} WHERE id = ${id}`
let jStr = j`SELECT * FROM $table WHERE id = $id`

to compile to:

// Generated by ReScript, PLEASE EDIT WITH CARE
'use strict';

var Sql = require("sql").default;

var table = "users";

var id = "5";

var query = Sql([
      "SELECT * FROM ",
      " WHERE id = ",
      ""
    ], table, id);

var jsStr = "SELECT * FROM " + table + " WHERE id = " + id + "";

var jStr = "SELECT * FROM " + table + " WHERE id = " + id;

exports.table = table;
exports.id = id;
exports.query = query;
exports.jsStr = jsStr;
exports.jStr = jStr;
/* query Not a pure module */

Interestingly, I only had to make changes to src/res_core.ml in the syntax submodule.

There’s still some work to do (ensuring parse tree nodes have the proper locations, adding tests, etc.). I’ll post a link to the PR here once it’s ready.

7 Likes

I ran the tests in the syntax repo for the first time with these changes I ran into an issue with the printer tests. I have to update the printer to know how to convert the generate function calls back into tagged template strings. This should be possible by setting the res.template attribute on apply nodes generated from tagged template strings and then teaching the printer how to handle them.

1 Like

This is exciting @kevinbarabash , nice work!

Here’s a link to the PR: https://github.com/rescript-lang/syntax/pull/471. It’s still a work-in-progress.

3 Likes

Waiting so hard for this one :sweat_smile:

I juste love Styled-components :smiley:

Great work !!

I made some more progress today. There’s one failing test in the “roundtrip” tests that needs to be addressed but it shouldn’t be too much longer before the PR is done.

That being said, I would like to update the codegen in the compiler repo to produce tagged template literals in the JS output. As it stands right now and function call will be output. In order to simplify changes across the two repos, it would be best if we merged the syntax repo into the rescript-compiler repo. I’ll be investigating that next.

All of the tests are passing now including the “roundtrip” tests. :slightly_smiling_face:

5 Likes

I’ve replaced my original PR with a new one: https://github.com/kevinbarabash/rescript-compiler/pull/2. This one includes changes to the frontend so that the JS we output uses a tagged template literal instead of a function call. The output now looks like:

// Generated by ReScript, PLEASE EDIT WITH CARE
'use strict';

var Sql = require("sql").default;

var table = "users";

var id = "5";

var query = Sql`SELECT * FROM ${table} WHERE id = ${id}`;

exports.table = table;
exports.id = id;
exports.query = query;
/* query Not a pure module */
10 Likes

@kevinbarabash Hello, do you have any news about this PR ? I would really like to use tag template literals for producing my CSS (it’s way better than using ReactDomStyle :sweat_smile:).
And do you know, if this PR is merged in a repo, how can I use the repo containing this PR instead of the current one ?

Best regards, and thx for your hard work :wink:

I ended up closing the PR. Maybe someone else can pick up where I left off. The change required changes to the syntax so merging the syntax repo into rescript-compiler would make these kinds of changes easier.

2 Likes

Can we revive this if we make an RFC for the tagged template syntax? I believe merging the syntax repo into the compiler isn’t palatable to the core team.

2 Likes

I personally think supporting this would be very valuable. An RFC and some thorough discussion will probably be a great foundation for any contributor who might be interested in implementing the actual feature. Would you be up for formulating an RFC?

3 Likes

I’m willing to put one together. Thinking a few things at minimum are required to have a decent RFC ready for a healthy discussion.

  1. Enumerate a handful of use cases and examples to catch any edge cases
  2. Catch up on the exploration Kevin and members here have done and discussed. Eg: Binding to Tagged template function - #12 by Maxim. Identify path forward.
  3. Explore any alternative solutions and identify blockers, technical and otherwise
  4. ???
  5. Profit

Besides my list written in the rather early morning, is there a good RFC template? Looked at a few others RFCs shared in the forums. I’ll start the work in a GitHub issue, and reshare here. Like that many of the rfcs aren’t super formal.

I’ve never done any compiler work, so my mind begins at what the syntactical inputs are and what the generated code should be, and how the rescript AST can support differentiation between a tagged template literal and a normal template literal.

1 Like

Is this variadic design to account for typechecking somehow? Is it simpler and possible to compile it directly to JavaScript’s native tag template syntax?

I think you capture the important points. First we need to focus on what pain/problem we’re solving, how that problem looks in the wild, if there are current acceptable workarounds, etc. No need to discuss a solution just yet imo.

1 Like

It’s variadic because it’s the way it is in JS independently of the way we compile it in JS.

Compiling it to JS native tag template is likely doable but more complicated since it means we would need to change the compiler to make it output a new kind of code while otherwise it’s mostly a syntax issue.

Aren’t native template literals incompatible with ES5? Provided anyone still cares about ES5 output, of course.

I guess indeed it’s incompatible with ES5, but like you I’d be in favor of stop caring about this anymore, if you really want to be compatible, just use babel or something.

2 Likes

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?

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

7 Likes

Great write up! I think you should move this to its own post, so it can get a little bit more attention.