-
-
Notifications
You must be signed in to change notification settings - Fork 5.8k
Description
Feature Request
Problem
Currently @babel/template allows you to supply a placeholderPattern that is applied after the successful parse of the template string. What this means is that it is impossible to create a pattern to unambiguously separate placeholders from common identifiers and string literals. This is fine if you are creating a template literal yourself, but makes things difficult if the templating is happening on user input where you have to communicate these restrictions. If you try a pattern like /^@[a-z]+@$/ the parse will simply fail before it even gets to the replacement phase since @x@ is not valid JavaScript syntax.
To concisely state the problem and its implications:
- It is not currently possible to construct placeholder patterns that unambiguously separate placeholders from normal JavaScript identifiers.
- The default pattern is too loose and too often catches cases not intended to be Placeholders.
- The current state leads to a "forever maintenance" burden for either maintainers or users as new valid global identifiers are introduced to JavaScript runtimes that can be inadvertently caught by the matcher. The new
URLconstructor is a perfect example of this. While one could safely assumeURLis a replacement in the past, it is now built-in on the web and on node. This means that something likeconst NAME = new URL()would surprisingly throw an error. The API then becomes immediately harder to use as you have to look through documentation to fix this. You have no option but to employ a very verbose solution for this very simple snippet because you need to use the full capital constructor. The options are: 1) provide a whitelist containing URL, 2) come up with a new pattern, or 3) pass in URL for URL. All three significantly increase the noise of the operation for a built-in type. - Strings are particularly confusing to deal with in this implementation as seen here.
- This often leads to fracturing of templating patterns in the community as people try to solve the above problems by coming up with their own more restrictive
placeholderPatterns. - It makes it hard (impossible?) to use babel templates for arbitrary user input as opposed to a developer's static templates.
Placeholder AST Nodes Solution
It would be nice to add a new node type,Placeholder, to @babel/parser with the following shape:
interface Placeholder {
type: "Placeholder";
name: string;
}This node can appear anywhere an Expression, Statement (and possibly Pattern?) can. A more rigorous version would have a specific Placeholder${Type} analog for each specific subtype of Expression and Statement (for example, PlaceholderBlockStatement to represent placeholders where BlockStatements are expected, etc.). However, this is largely an (IMO) unnecessary implementation detail as the given replacement can be validated against the parent's NODE_FIELDS. So for example:
function replace(nodePath, field, node)
{
if (NODE_FIELDS[nodePath.node.type][field].validate(node)) {
nodePath.node[field] = node;
}
}I imagine this is probably how the current templating system handles this anyways.
Similarly, we would extend the Statement and Expression, etc. parse rules to support a new, currently invalid, JavaScript syntax. For example:
Placeholder ::
@ IdentifierName @
Expression ::
...
Placeholder
We then would create an additional option in @babel/parse called allowsPlaceholders which would turn on support for Placeholder parsing. Placeholders would then of course be parsed into Placeholder AST Nodes, which can then be easily identified in the AST and replaced. I am not tied to the @@ syntax, just throwing it out as a quick example.
With a system like this, you get the following benefits
-
A templating system that is not dependent on the input template like the current one. Instead the entire community can standardize against one placeholder pattern since it works with any JavaScript source.
-
As in my case, it allows to safely rely on
@babel/templateas my templating system without worrying about user input or having to specify rules to the user. -
There is no longer any need to handle string contents, which currently surprises people, like in this bug. This should also simplify the implementation because strings don't need to be special cased. The reason for this is that
Placeholderscan naturally be used with template literals to achieve the same effect syntactically:const message = `Hi ${@name@}!`;
As you can see, the feature naturally supports the string case, and is much clearer to boot.
-
We can support more expressive templating. For example, the string
class A DEFINITIONwith the expectation of a block would work fine, which currently does not. We may decide we don't want to support this of course, but that decision would be based entirely on our specific desires and not merely a side-effect of the fact that the stringclass A DEFINITIONdoesn't pass initial parse.
Teachability, Documentation, Adoption, Migration Strategy
I think the documentation would be greatly simplified with this solution. A lot of the documentation currently revolves around options like placeholderWhitelist and placeholderPattern that have little use outside of accounting for the short comings of the valid identifier-name string matching strategy currently employed.