Skip to content

Proposal: embedded syntax block #3022

@tinganho

Description

@tinganho

I have watched this PR #2673 and this #296 discussion for a while. But I somehow don't think it is the right approach to accept JSX directly into TS. There seem to be a demand of embedding HTML and even CSS in JS. Facebook might be the pioneer in embedding HTML in JS and therefore gotten a lot of attention recently with JSX/React. If JSX got accepted into TS, it would probably send a clear message to the JS and TS community that the TS team thinks JSX is the right way(and the only way) to go in terms of rendering views. I think it is more a correct way to accept an "embedded syntax block", so different frameworks have a chance to compete with JSX. It is a more generalized approach as oppose to accepting JSX directly into the TS.

Also remember that @ahejlsberg said "A framework is created nearly every day". So who knows if React will survive the next 2 years? Just remember how fast Angular 1 was popular and very quickly perceived as outdated(roughly a year?). That's how fast the web moves today.

So in rough terms here is my thoughts on this "embedded syntax block". I will take JSX as an example:

First, we refer to a JSX to TS transpiler. So we can transpile JSX to TS in our source code:

///<transpiler name="jsx" path="../node_modules/jsx-ts-transpiler/transpiler.ts"/>

Now, we define a JSX block we want transpiled into TS using the syntax syntax 'jsx' {...}:

let HelloMessage = React.createClass({
  render: function() {
    return syntax 'jsx' { <div>Hello {this.props.name}</div> }
  }
});

The TS's scanner stops at syntax 'jsx' { and feeds the string that reaches the closing brace } of the syntax block to the JSX-TS transpiler in this case: <div>Hello {this.props.name}</div>. The JSX-TS transpiler emits the correct TS code back along with a source map so we can map any lines and columns in the emitted TS back to our JSX. Here is how the transpiler looks like:

export transpile(source: string): Output {
  // Do something with the source
  return { output, sourceMap, diagnostics };
}

Now the TS scanner goes ahead an parse it as if it was a normal TS file.

let HelloMessage = React.createClass({
  render: function() {
    return React.createElement("div", null, "Hello", this.props.name);
  }
});

Now, we want all AST nodes outside of any "embedded syntax block" to have the same positions(pos and end) as if they where without any line and column shifts. So that we can have the correct positions on errors and warnings autocomplete etc. This is easily done by just offsetting a node's pos and end by keeping track of how much the emitted JSX-TS output shifts in character positions. For the emitted AST nodes we would marked them coming from a particular embedded syntax block(the embedded syntax block itself is an AST node with pos and end). The emitted TS nodes will be assigned pos and end beginning from 0 and not the start of its' syntax block.

Now, we have all the information we need to output the correct positions in source map and diagnostics. The diagnostics and the source map needs to be rewritten to take into consideration the new embedded syntax blocks along with its' emitted TS code.

I think this is a more open approach. If you accepts JSX directly into TS, then you are practically closing the door for any future syntax embeds and letting Facebook decide the future of syntax embeds.


Updates 1:

Since the TS scanner can't decide where a syntax embed block ends we would need the transpiler to help us so here is the updated transpiler:

let output = "";
let sourceMap;
let diagnostics;
let statements;

export function pushChar(ch: number): boolean {
  // Do something that produces an AST

  // Return false if it is the end of a syntax embed block else return true.
  return true;
}

export function transpile(): Output {
  // Take the AST and produce the output, source map and diagnostics.
  return { output, sourceMap, diagnostics};
}

The TS scanner pushes characters to our transpiler with pushChar() and receives true or false, whether to proceed with pushing characters to our transpiler or get the transpiled output with transpile().

The syntax for defining syntax embed could drop the syntax keyword:

'jsx' {  <div>Hello {{this.props.name}}</div> }

Metadata

Metadata

Assignees

No one assigned

    Labels

    Out of ScopeThis idea sits outside of the TypeScript language design constraintsSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions