Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Hyperscript Tagged Markup: JSX alternative using standard tagged templates (github.com/developit)
146 points by truth_seeker on Nov 22, 2018 | hide | past | favorite | 86 comments


The nice thing about React, vs others like Vue and Angular, is that we've learned not to write code in strings. You'd think this would be a fundamental law of software engineering, but we keep seeing things like

    v-bind:id="'list-' + id"
or

    <button [style.color]="isSpecial ? 'red' : 'green'">
or this.

Writing code, not strings, means you can do everything to it that you can do to code. You can type check code. You can lint it. You can optimize it, compile it, validate it, syntax highlight it, format it with tools like Prettier, tree shake it...

That's why I like JSX, it's Javascript all the way down. Everything is code. It's a very well designed and thought out DSL.


> Writing code, not strings, means you can do everything to it that you can do to code.

These are tagged templates, not strings. They work as code at runtime, and it already has support to optimize it, compile it, etc. This is JS all the way down. And spankalee mentioned below they're adding typescript support to type check the very similar lit-html, so that's coming too.

> That's why I like JSX, it's Javascript all the way down. Everything is code. It's a very well designed and thought out DSL.

It's also kind of a weird to highlight all this for a DSL that actually has to be parsed from a string to get it to be Javascript. Everything else you're talking about is tooling that had to be built special for it, not the language itself.

Why not just say "I like the style of jsx embedding and existing tools better" :)


+1. It's all strings (of source code) in the end. It's just a matter of how well the tools support those strings.

There exists a medium-sized ecosystem of tools which supports static checks on strings inside angle brackets, a la JSX. There is a smaller-but-growing ecosystem of tools that supports static checks on strings inside backticks, a la HTM/lit-html/etc. There's no fundamental difference here.


The fundamental difference is what you have in the end. With JSX you have function calls (JSX is a very thin DSL after all).

With tagged literals you end up with runtime string parsing and concatenation.


> The fundamental difference is what you have in the end. With JSX you have function calls

Check out the output of the Babel plugin mentioned in the readme

> // input:

> html`<div id="foo">hello ${you}</div>`

> // output:

> React.createElement("div", { id: "foo" }, "hello ", you) options

https://www.npmjs.com/package/babel-plugin-htm


It's (marginally) better than having it at runtime. Poor man's macros. Much like JSX in a sense, though JSX is a much thinner abstraction IMO.


and i think this is the point; runtime vs compile time! similar reasons that people love the popular new type systems... if it compiles, it probably works or you get an error rather than pushing that later and possibly not catching it


> These are tagged templates, not strings

Tagged templates are strings.


> These are tagged templates, not strings. They work as code at runtime, and it already has support to optimize it, compile it, etc.

Optimize, compile etc. what?

f`string` is literally a function call f("string"). What exactly are you "compiling and optimizing"?


No, that's incorrect. Interpolations (${}) are preserved. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


Let's return to the original statement, shall we?

> These are tagged templates, not strings. They work as code at runtime, and it already has support to optimize it, compile it, etc.

So. How exactly do they "work as code" and "have support to optimize it, compile it, etc."?

Here's HTM's main function https://github.com/developit/htm/blob/master/src/index.mjs#L...

It: concatenates strings, parses (via regexes), then concatenates again. Can someone enlighten me as to why this is awesome, is code, is optimized and compiled?


I was responding to your false assertion that f`s` is the exact equivalent of f("s"), which only holds true without interpolations. f`a${b}c`) becomes f("a", b, "c").

Secondly, the answer is Babel. Since the string's components are part of the AST, it can optimize and compile it as the original poster claimed.


> which only holds true without interpolations. f`a${b}c`) becomes f("a", b, "c").

The difference is so subtle as to be irrelevant. You still need to manually parse, process and assemble strings. There’s no magic. There’s no code. It’s just strings (with some values here and there).

It’s enough to see the implementation of HTM (or lit-html or any other library using tagged literals) to see that.

> the answer is Babel

Which is only mentioned once the bullshit claims about tagged literals are refuted.

Babel is not exactly the answer, but as poor man’s macros it will do.


They're just talking about https://www.npmjs.com/package/babel-plugin-htm.

> A Babel plugin that compiles htm syntax to hyperscript, React.createElement, or just plain objects.


I agree with the general sentiment.

The difference is even more pronounced once you start typing your code.

TS/TSX = 100% typed

arbitrary template DSL = 0% typed

and now these string template based on interpolation = 5% typed: Only the dynamic values inside ${} expressions are "typed", but there are no coherence checks. DOM attribute names and values remain untyped

In the absence of any real advantage for string templates, why even bother?


I came to write... this.

Sure, stringly typed programming is sometimes a quick and dirty solution - and that's fine, even permanently, if you just don't want to invest much in whatever you're doing - which too, is fine.

But it's not a good thing. It's cheap and easy, which is sometimes something you need to settle for.


to give a clear answer here, tagged templates don't actually perform string concat. the function being used as the tag receives an array of the string literals, and the rest of the arguments as the values passed in templates, and can perform any processing it wishes at that point. I've seen some js sql libraries actually using this to prevent people from having the exact problem you're complaining about.


However, the main darling of tagged templates, lit-html does exactly that: parsing, concatenating, then doing an element.innerHtml

So does HTM we discuss here.

I quickly googled. sql-tag and node-sql-template-strings do a lot of string concatenation (well, of course, they have to create a SQL statement out of values passed in). sql-template parses and concatenates.

Because it's really basically all you can do, be cause tag`string` is just a function call tag("string"). There's no magic.


its actually more like tag`string ${x}` is tag(['string', ''], x)


Thanks, I was coming here to say how a bad idea is this thing. Totally agree with you, I can't understand why people still like write magic. I like your comparison strings Vs code. I'll steal that!


JSX is not Javascript, you need to transpile, and not everybody wants to mess with webpack+babel or create-react-app, or anything other what involves downloading 1000+ packages from npm just to do some quick PoC. Sometimes you want to stay light (and fast), and incrementally build your stack, just you like can do with vue.

So I actaully like this project, thanks for this :-)

BTW: it's similar to lit-html, t7 and hyperx


Then don't do that.

Write a method to get the list id.

And FWIW I honestly think JSX is misstep.


If I find myself doing something like that in Vue, I immediately make it a computed property. Granted, v-bind:id="something" is still markup that has little chance of being well understood by the IDE, but at least there isn't much to get wrong, and I don't have to remember if I need ', ", \', or \".


When you write JavaScript, you can only optimize it..as JavaScript. It still needs to be parsed, compiled, and optimized as JavaScript.

When you write templates, you get more room for optimization. Glimmer engine compiles templates to binary bytecodes to bypass JavaScript parsing. Vue3 inlines component fragments as part of the compilation step.


Not sure I follow.

Glimmer compiles to bytecode, but I am not sure how that “bypass[es] JavaScript parsing” as the bytecode is evaluated by a runtime written in JS (which is obviously parsed) and that translates the bytecode into JS calls. Basically one type of parsing has been replaced with another, which is fine though not obviously faster.

Likewise, I don’t see the point with Vue since again vue templates are translated into JS calls.

Or you could just make JS calls, like React does, no runtime translation required.


Yes, the runtime needs to be JavaScript.

It’s not obviously faster, but it’s been benchmarked to be faster. https://youtu.be/nXCSloXZ-wc

You can skip to 29:34 for the performance demo.


JSX isn't JavaScript. It's JSX.


no, but it BECOMES javascript after transpile time


Yep. Just like C code isn't binary code.


Why is it not JavaScript? The Ecmascript spec explicitly allows syntax extensions.


Is this valid Javascript: %%$$#~~~~##

What if I make it my special js "syntax extension"?


I'm not javascript syntax, especially if you're being pedantic, but focusing on that is kind of missing the point, which is that it's tool friendly in the way string interpolation is not.


Writing code, not strings, means you can do everything to it that you can do to code. You can type check code. You can lint it. You can optimize it, compile it, validate it, syntax highlight it, format it with tools like Prettier, tree shake it...

Both intellij and emacs do all of these things for Vue code. That seems to cover >90% of developers' tooling needs.

Also, code itself is a string. Code is data.


Code may be a string, but tools that treat it as such are universally abhorred, like the C preprocessor or eval in interpreted languages.


I wrote React for a couple years and was pretty dogmatic against the whole "stringy code" thing. Obviously and generally, we'd rather write code not in strings. But guess what? Moving to Vue from React was a major productivity boost for us - "stringy code" and all.


I'm calling bullshit on all these claims of "massive productivity boosts" when switching to Vue. What on earth were you doing in React that was so unproductive? Maybe if you're bogging yourself down with a bunch of absolute garbage like immutable and redux-saga, but you can hardly blame React then.


What's wrong with redux-saga? What alternatives do you offer to solve problems redux-saga helps to solve?


I assume the point is that some people bog themselves down with tools that make them less productive just because they read that it's best practice, without deeper understanding.

For example, in most threads about Vue on Reddit, the fact that you can just add Vue as a script tag and start working with it is it's most frequently cited advantage.

A small app, especially for most newbies just starting out, doesn't need webpack. But obviously they didn't read the docs and didn't even try the same with React. They just went straight for webpack, even though they shouldn't have. Not to mention CRA exists.

Same with immutable and saga. Their benefits are apparent in much bigger applications, and even then there are arguably simpler and better tools like Immer, update-immutable, redux-thunk, redux-promise-middleware or even rolling out your own middleware.


Why beginner would try that with React? Official documentation does not even offer this approach. Meanwhile Vue.js official documentation gives basics first with this approach.


that as the case may be, id say that was in spite of the “stringy code”. the point (i think) is that this, and JSX are functionally the same thing, but there are some very large issues with doing this in a string.

*edit: clarity


what if it being more productive for you has nothing to do with these magic strings? :)


I’ve written tooling to support lit-html and styled-components in editors. Template strings are sort of nice to write but complete pain to support in editors. They are just like a macro language, so you end up with stuff like: `<${tag}>...<${tag}${slash}>` which is not possible in jsx. Even just detecting syntax errors is a pain

Obviously if deciding between making life easy for editor implementers or making life easier for coders, the coders win, but if something is unnecessarily difficult to build good tooling for, that’s a sign of poor design in my book. And coders will ultimately suffer too because their tooling will suck and because their code will make use of all the crazy flexibility the library allows.


Thanks for all your work on the lit-html plugin!

I think though that the concern here is a little misplaced given the constraints that HTM and lit-html work under - which is that the template strings themselves, without the values, have to be well-formed because they're passed to the HTML parser without values.

In HTM, `<${tag}>...<${tag}${slash}>` is not possible to interpret as a possibly self-closing tag because self-closing tags are expanded before values are written into VDOM.

In lit-html `<${tag}>` is not allowed at all. So we know what the tag name will be, and what attributes and properties are bound. The same amount of type-checking is possible as with JSX.


This is lovely for using React or Preact in Observable!

https://beta.observablehq.com/@mbostock/hello-htm

We have our own lit-html-inspired html tagged template literals that render directly to DOM, but it’s nice to see HTM’s improved support for function attributes (e.g., onclick), spread attributes, etc.


I love that work!

Eventually we'll get some great parts of this approach standardized, starting with Template Instantiation, and we'll finally have a great way to create and update lots of DOM from data right in the platform.

A future d3 based on that would be very awesome too :)


I remember when everyone suggested their own way of s-expressions (basically) to avoid having to write superfluous end-tags and other XML/HTML tchotchkes, now we're getting multiple ways to get all the markup boilerplate into our programming language.

Sometimes it's weird how tastes change. Same thing happened with the nigh universal hacker-ish dislike of "bondage and discipline" languages, now it's enforced formatting wherever you look.


There's so much money in web apps now that your average team is flooded with mediocre devs. These joints need "bondage and discipline" languages and frameworks to be productive. It's better business value to have your 50 work-a-day devs productive than your 2 rockstars.


Can we go back to Jinja now?


Maybe it's just me being an old man at this point but I still dislike doing templating in JavaScript. It's not natural. I want Jinja2 and server side rendering. If you absolutely must do JavaScript you should keep it separate of your HTML and other stuff... again maybe I'm just getting old.

Honestly I don't find neither JSX, or this new alternative particularly readable.


You can use Jinja-style templates client-side too if you want (see nunchucks).

But client-side rendering and server-side rendering have different use-cases. It doesn't come down to just which is 'particularly readable'.

> If you absolutely must do JavaScript you should keep it separate of your HTML and other stuff

Why? This argument has been lost. If you're writing a rich client-side UI that makes sense to be "component-oriented", there is literally zero value in splitting each component into HTML and JS, it is a completely arbitrary 'separation by technology'.


Young is just learning it hardway, we all have been there. Creating walls of abstractions as well as enduser's greed for edgy feel that is turning into easy profit.


Did we leave jinja? I mean, I know I know, but we still do a lot of jinja on our smaller projects.


Why go back?


I wanted to phrase it as a circle, just waiting to see how far SPAs can go.


> Improvements over JSX

With the exception of html comments, these are invariably antifeatures. Optional quotes/closing tags save zero to negligible typing time in exchange for added complexity for tooling and reduced readibility in many circumstances.

I can understand why these features were added to HTML5 (backward compatibility is of more benefit than prescriptivism) but I would never have thought anyone would argue that having these features in HTML1 was a good idea in retrospect!?

Adding them to a new syntax that isn't burdened with backward compat requirements seems daft.


How is this different from lit-html?

https://github.com/Polymer/lit-html


HTM is React-compatible, and has all the React semantics: e.g. you can interpolate attributes (with ...${foo} instead of React's ...foo) and tag names (with <${foo}> instead of React's capitalization-dependent <Foo>).

In other words, HTM is a drop-in replacement for JSX that avoids the need for a compiler by using the corresponding language features. (At the cost of a few extra ${}s.)

lit-html and its ilk render directly to the DOM. They give similar developer ergonomics to React/JSX, but don't integrate with that ecosystem. They aren't a drop-in replacement for a JSX compiler within your React app; they're more of a competitor to the entire React paradigm.

Also of note is that, as a drop-in replacement, HTM is properties-first, like React/JSX. lit-html has you use different syntax for properties and for attributes.

Both are cool!


I wouldn't describe it as a "drop-in replacement". It's very similar in the way that it allows one to use html syntax within javascript, and I appreciate the fact that it doesn't require and extra compilation step. But it does appear to be lacking what I see as the main draw of %eact -- the ability to compose and remix components. (Correct me if I'm wrong?)

Edit: Composability does appear to be a feature. I agree with your description.


lit-html can't be used server side.


The original project that google ripped off can do SSR: https://github.com/WebReflection/viperHTML


TIL https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

I'm not a JavaScript developer so this is news to me, very neat feature!


Here's a list of tagged template literals:

https://github.com/declandewet/common-tags


Which brings up a question: Is it possible to have multiple tags on a given string? Composing smaller helpers would be nice, but I don't see how the syntax would work.


You'd just need to create a meta tagged litteral function. tagged litteral can accept parameters.


IMHO the name choice (htm) is really bad.


We've arrived at the point where the majority of developers have never seen a .htm file!


This is a neat syntax, but it doesn’t seem at all typesafe (typesafety was the main benefit of JSX over the various string templates that came before it).


JSX is only type-checked because someone wrote a type-checker for it.

It's completely possible to write a type-checker for HTML templates like lit-html. In TypeScript we even have access to third-party extensible interfaces for all the elements with the HTMLElementTagNameMap, so we can check buildings to properties.

We'll be working on a lit-html type-checker for TypeScript early next year.


I think type safety was a core design constraint for JSX (since XHP — where JSX came from — is typechecked, after all). It’s also nice to be able to do this checking with the type system you already have (TS, Flow, Reason), rather than having to install extra tooling that has to then interoperable with each of these type systems.


Well, you can skip JSX, and write functions only, and you'll have full support. https://reactjs.org/docs/react-without-jsx.html It's functions all the way down, after all.

How do you write a type checker for something that is a string (HTM, lit-html)?


You parse the string literal, just like you parse the string that is the contents of a JavaScript file.


You could definitely do that (Angular recently started doing this too!). I think once there is a way to enforce compile time safety for HTM, it’ll be a lot more appealing for TypeScript/Flow folks.


Yeah, I'll let Javascript engines parse and convert my Javascript to bytecode for me. It's definitely not the same as manually parsing strings at runtime. And equating the two is disingenuous at best


Type-checking happens at compile time, and even the JS is parsed by a user-land library and not the JS engine.

At runtime these templates are parsed by the browser's built-in HTML parser.


> Type-checking happens at compile time, and even the JS is parsed by a user-land library and not the JS engine.

True. Still, it's functions and regular JS, not strings manually processed at runtime

> At runtime these templates are parsed by the browser's built-in HTML parser.

It's not the first time I've seen this ... bullshit (no other word for it). Where does it come from?

The only time it's parsed by browser's built-in HTML parser is when you manually call something like `element.innerHtml = "you string that you manually assembled in your function at runtime"`.

The browser has no idea what is contained in your tagged string. Which is painfully obvious when you actually look at the implementations of HTM: https://github.com/developit/htm/blob/master/src/index.mjs#L... or lit-html: https://github.com/Polymer/lit-html/blob/master/src/lib/temp...


well no... it’s type checked because it’s transpiled to a function


> typesafety was the main benefit of JSX

Do you have a source for that?


My opinion, not a fact.

Before JSX came out, there were tons of templating systems that let you embed HTMLish things in JS: Handlebars, Mustache, EJS, Pug, Angular templates.

JSX’s big innovation was taking the way XHP promoted HTML to first class syntax and applying it to JS. The big difference between that and the template systems that came before it is JSX is statically analyzable (by your type checker, or by other tools), which means you can scale it to FB scale.


What I was thinking is that you're backdating your impressions. JSX came out in 2013, when TypeScript was at it's infancy, and also predates flow; support for JSX in the TS compiler was only added in late 2015.

You're right that when it came out it's main innovation was separation of concerns and composability, but types or static analysis were not part of the value proposition at the time.


You know, I think that’s a really fair way to look at it. I would be surprised, though, if down the line type safety wasn’t a design goal. It did come from the same company that built XHP around 2010, and the same dude that built the first React prototypes in OCaml. I can ask around after the holidays and see if anyone remembers :)


What kind of source do you have in mind? The arguments types that are valid will vary based on the content of the template string, and no type system (well a dependently typed language such as Idris could do it) for JS (flow, typescript) supports that.

On the other hand, both TypeScript and Flow support type checking for JSX attributes (and child elements to some extent)


I don't know if there is a primary source but it is one of the main benefits for me.


Why would you need an external source for a personal opinion?


So,I guess mixing presentation and logic is OK now? 'Cos as a PHP programmer I used to get shat on all the time for that and I'd like a chance to, you know, unload on someone else for a change.

NM, I'll just go back to my cave now.


Good thing "logic" isn't a concern and separating view code from view code doesn't make sense ;)


There's presentation logic vs business logic.

Tracking whether a button is disabled or not is presentation logic.

Projecting a snapshot of business state onto a presentation is also presentation logic.

"Old school PHP application" style seldom made a distinction.

Also, you must add indirection (bad) to split things apart, and indirection doesn't necessarily pay its own rent. An example would be putting an ajax api request (a client's business concern) into a component where the <button> exists that triggers it. Collating that logic with the UI is nice until you want to reuse the logic, and it's usually cheap to extract it from a component, so you can often add indirection on an as-needed basis as you scale.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: