Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: migrate to nanohtml #2548

Closed
wants to merge 2 commits into from
Closed

refactor: migrate to nanohtml #2548

wants to merge 2 commits into from

Conversation

saschanaz
Copy link
Member

@saschanaz saschanaz commented Oct 23, 2019

Fixes #2343

headDlElem.append(...definitionPair.childNodes);
await contentPromise;
const result = await contentPromise;
stats.textContent = "";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we create an emptyElement(el) util? There are evidences of innerHTML being slow (so I suspect same from textContent = '', https://stackoverflow.com/a/3955238) (not sure how slow in our case though)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the performance here would matter as it would just be a few microseconds. fillElement(el, content) would be useful though, as it removes the need to create a separate variable.

data-xref-type="attribute"
data-link-for=${linkFor}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Soo.. it doesn't support not adding undefined/null attributes like hyperHTML? 馃槥
If not, should we raise this issue at nanohtml?

Comment on lines 142 to 144
if (type) {
element.dataset.type = type;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes me sad 馃槥

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A thought:

optdata(html`<var>${varName}</var>`, { type });
// where optdata() assigns `dataset.type` only when the value is not nil

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of this. I like the declarativeness hyperHTML is providing. I think JSX also works the same way.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it doesn't really declare it's potentially nil and thus can be undefined... Everything is currently implicitly nullable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@saschanaz
Copy link
Member Author

saschanaz commented Oct 23, 2019 via email

Copy link
Member

@sidvishnoi sidvishnoi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sidvishnoi
Copy link
Member

Feel free to do so, but not sure the maintainer will like it.

They would like if I send a PR 馃檴

@saschanaz
Copy link
Member Author

saschanaz commented Oct 24, 2019

They would like if I send a PR 馃檴

I like the explicitness in this PR though. Currently it's hard to see which is nullable and which is not, and here it's much easier to see.

If you send a PR, I would expect the feature to be more distinguishable than the hyperhtml way.

@marcoscaceres
Copy link
Member

Although I appreciate that this makes things a little smaller, I'm not sold that we should switch.

On a personal level, I have a really good working relationship with the creator of HyperHTML (@WebReflection) and know he is highly dependable if we hit bugs or ever need help. I also reviewed the first versions of HyperHTML, so I feel comfortable in how it works under the hood.

On a technical level...

No need to remove comments

The comments are annoying, yes... maybe something we can work together with @WebReflection towards removing... or having a single call to have HyperHTML "freeze" (and cleanup).

We do rely on some dynamic aspects of HyperHTML (e.g., in the caniuse module).

Less magic for attributes (No more automatic null attribute removal)

I think this more as a feature than a bug. Otherwise, we need a lot of checks (as shown in the refactor). I personally really like the "null means remove" feature of HyperHTML - but can also appreciate why it might be confusing.

More explicit raw html interpolation by raw()

There might be some alternative we can use, instead of []. It's a little ugly, for sure - but the times we are using raw stuff is usually not a good thing (sometimes potential XSS vectors) so we could look at fixing those.

@saschanaz
Copy link
Member Author

We do rely on some dynamic aspects of HyperHTML (e.g., in the caniuse module).

AFAICT all .bind() and .wire() calls in ReSpec are not strictly needed if not redundant. I think the only valid uses are in UI codes, and even there it doesn't really help too much.

Otherwise, we need a lot of checks (as shown in the refactor).

Agreed. What do you think about my idea to reduce checks and still be explicit about optionality? #2548 (comment)

but the times we are using raw stuff is usually not a good thing

Right, and thus it should be better to be more explicit when using discouraged raw stuff.

@marcoscaceres
Copy link
Member

Spoke to @saschanaz and reviewed the code. I think if it helps get #2187 landed and we don't have any other option, I'd be ok with this. The changes are not very drastic and the APIs are mostly compatible.

@sidvishnoi, you get to be the tie breaker here as the other core maintainer. What say you? Yay or nay?

@WebReflection
Copy link

WebReflection commented Oct 24, 2019

Interesting thread, and also some confusion.

To start with, it looks like all changes would be better off with lighterhtml, as you simply use hyperHTML to create or append content, without reiterating much over it (i.e. just append instead of bind means you don't need bind and any wire would do).

The simplification lighterhtml would bring is that you just:

element.append(html`<a>ny</a> content`)

but the underlying engine is identical to hyperHTML so it'd bring least surprises to this refactoring.

About the list of points this PR would like to solve:

No need to remove comments

I'm not sure what is this about, but if it's about developer comments, you can write those via:

<!--馃懟 won't land on the page -->
<!--/* won't land on the page */-->

If it's instead about the comments needed by HTML in order to work, every reactive, DOM based, library does that, in a way or another, including lit-html, but those comments should never bother anyone.

Less magic for attributes (No more automatic null attribute removal)

This is a feature, as declarative speaking there wouldn't be any other way to remove attributes or events otherwise, not sure why this is a win.

More explicit raw html interpolation by raw()

To have explicit html interpolations you can simply use {html: '<h1>any content</h1>'} as value, or pass an array of strings that will be injected as raw html, so there's literally no gain here, just maybe some need to read the documentation.

Potentially will help #2187 with choojs/nanohtml#158 (I have local patch)

I don't think there's anything hyperHTML cannot do, so it's sad nobody asked me an opinion when devs got stuck on something, if hyperHTML was the reason, but I am not sure I know enough about that issue, yet happy to "unlock" it if anyone can explain what's the issue there.

Creates slightly smaller file. (322 KiB -> 313 KiB)

It's easy to be smaller when the libraries does much less, but if that's what you are after, few Ks out of 300K+ bundle, then I suggest lighterhtml instead.


I understand this refactoring required some effort, so I'm pretty sure I'm late to contribute anyhow, but I'd be more than happy to help migrate to lighterhtml if that solves any real issue you have.

Regarding hyperHTML, it's been used in production for over 2 years now, and we target roughly 100M users with their browsers. Now, I don't mean to say nanohtml is worse than hyperHTML, 'cause I don't even know what nanohtml is so I wouldn't dare judging it, but when it comes to cross browser and legacy compatibility, I can guarantee even after transpilation hyperHTML is a pretty battle tested and robust solution, and so is indirectly its little brother lighterhtml, since it shares 80% of the core code.

As summary, I'm not sure this comment was useful at all, at this point, but I think it was worth writing it.

Best Regards

@sidvishnoi
Copy link
Member

(on mobile)

If it's instead about the comments needed by HTML in order to work, every reactive, DOM based, library does that, in a way or another, including lit-html, but those comments should never bother anyone.

Unfortunately, this is the primary issue we've here as far as I know. ReSpec supports an export to HTML functionality. Those comments kinda uglify the output HTML, so we had to add a post processing step to remove those comments.

The other part is, we're not using much of reactivity features that hyperHTML provides - hence looking for a less powerful alternative.

Would it be possible that we can use some internal part of hyperHTML et al, that only converts template string to DOM nodes, without those comments and reactivity features?

Sidenote: I love how hyperHTML made use of comments for reactivity 鉂わ笍, it's a clever idea.

@saschanaz
Copy link
Member Author

saschanaz commented Oct 24, 2019

First of all, thank you for your precious opinions!

If it's instead about the comments needed by HTML in order to work, every reactive library does that, in a way or another, including lit-html, but those comments should never bother anyone.

It's completely okay on apps where the DOM only appears in runtime, but it matters when ReSpec stores the tree into a static HTML file. The comment appears more and more frequent as we use the template feature heavily, so we had to filter all those comments out before saving the tree into a file. nanohtml somehow does not emit any comments, so I consider that as a win.

This is a feature, as declarative speaking there wouldn't be any other way to remove attributes or events otherwise, not sure why this is a win.

Currently it's harder to see which is nullable and which is not because the maintainers just put the nullable attribute inline and not bother to indicate it's nullable. Being less magic forces maintainers to make them more distinguishable.

To have explicit html interpolations you can simply use {html: '<h1>any content</h1>'} as value

Riiight... Some part of ReSpec already uses that pattern, it's just that initially we decided to use string array to use raw interpolation and that made things indistinguishable. Here all raw strings are forced to be distinguishable, so that's a win for code maintaining sake.

I don't think there's anything hyperHTML cannot do, so it's sad nobody asked me an opinion when devs got stuck on something

Actually we had a short discussion in #1469 (comment), and I didn't like the global namespace mutation-unmutation hack. IMO that sort of hack shouldn't be in a production code... It doesn't harm, it's just me being uncomfortable with it 馃檮.

It's easy to be smaller when the libraries does much less, but if that's what you are after, few Ks out of 300K+ bundle, then I suggest lighterhtml instead.

Definitely an option 馃憤

I should admit that hyperhtml is more battle hardened than nanohtml as I found some luckily-non-blocking bugs on it. By this PR I'm not saying that hyperhtml is any worse, it's just that I like nanohtml's characteristics that I described above.

@WebReflection
Copy link

Would it be possible that we can use some internal part of hyperHTML et al, that only converts template string to DOM nodes, without those comments and reactivity features?

that's domtagger, but you need a definition that replaces always the same node with latest content received (re-addressing same node each time, 'cause without comments pinned on the tree you have only one slot to deal with).

If interested, I might try to come up with the least amount of logic around a tagger and create an utility that just creates HTML nodes, without any smart logic in place.

but it matters when ReSpec stores the tree into a static HTML file

I understand this, but I also think using a simple reg exp on document.documentElement.outerHTML that drops those comments should be sufficient, although I am not sure what you folks do for storing that, so I am not sure that's an easy option.

The comment is basically domconstants.UICD and it's a module a part, so you can use that anywhere you like, as long as it's the same module version hyperHTML is using (due random()).

Currently it's harder to see which is nullable and which is not

they are all nullable. If you pass null or undefined you don't want an atribute containing the string null or undefined, I guess, and this makes the final soterd tree even cleaner, so I am still not sure why this is an issue.

initially we decided to use string array to use raw interpolation and that made things indistinguishable.

But then instead of revisiting that decision, since {html: ...} would make it explicit too, you refactor everything to have raw(html) ... I would've solved in other ways but ... hey, everyone is free to do what they prefer.

I didn't like the global namespace mutation-unmutation hack. IMO that sort of hack shouldn't be in a production code

but that's not hyperHTML, that's basicHTML, which is for developing DOM on the server, where you inevitably need Node.ELEMENT_NODE and stuff like that on the global, if you want to be able to use any client side library.

So you are uncomfortable about something unrelated to hyperHTML, like ... a completely different library that has nothing to do with browsers and surfing users.

Whatever hack I've suggested, was to move forward on the Node.js side, I've never suggested to put that live, 'casue it's not needed for any browser.

I like nanohtml's characteristics that I described above.

It's a matter of chosing the right tool for the job.

If you don't need anything hyperHTML provides, then you are better off with another libarry, but if the only thing that really bothers you is explicit html, then use {html: ...} instead, and if it's comments, then maybe let's just find a solution to it?

@saschanaz
Copy link
Member Author

they are all nullable. If you pass null or undefined you don't want an atribute containing the string null or undefined, I guess, and this makes the final soterd tree even cleaner, so I am still not sure why this is an issue.

Ah, I mean the inputs. It's hard to see which input argument is nullable and which is not because we mixed it all and passed them to hyperhtml.

html`
  <div data-arbitrary=${value}>
`;
// is `value` nullable? no one was bothered to indicate nullableness 馃槢

But this is admittedly an hypothetical issue that I only found in this PR.

But then instead of revisiting that decision, since {html: ...} would make it explicit too, you refactor everything to have raw(html) ... I would've solved in other ways but ... hey, everyone is free to do what they prefer.

Well, I wouldn't have been able to catch and refactor them without nanohtml behavior that requires raw() for raw interpolation. 馃槄 But now that I caught them all, maybe I can do {html: ...} now and consciously discourage string arrays.

but that's not hyperHTML, that's basicHTML, which is for developing DOM on the server, where you inevitably need Node.ELEMENT_NODE and stuff like that on the global, if you want to be able to use any client side library.

Node.ELEMENT_NODE is also available in any element instances (el.ELEMENT_NODE) so that specific issue is not a blocker. In #2187 I could do nearly everything without global things. ("Near" because it's not complete yet.)

So you are uncomfortable about something unrelated to hyperHTML

Uhm, I'm confused, do you mean there is non-hacky way to use hyperHTML and DOM APIs on Node.js without the global namespace hack?

@sidvishnoi
Copy link
Member

If interested, I might try to come up with the least amount of logic around a tagger and create an utility that just creates HTML nodes, without any smart logic in place.

If you believe that can be useful for other users also, that would be great. I would love to help if possible.

I played with domtagger just now. I think that is something we're looking for!

@WebReflection
Copy link

WebReflection commented Oct 24, 2019

Ah, I mean the inputs. It's hard to see which input argument is nullable

I am still not sure I am following. All attributes are nullable, except those that are acessors, which will be removed from the outerhtml anyway, unless there is a non null value.

If you go to this lighterhtml playground and type the following:

const {render, html} = lighterhtml;
render(document.body, () => html`<div data-arbitrary=${null}/>`);
console.log(document.body.firstElementChild.outerHTML);
// <div></div>

you'll see attributes are nullable, even data-attributes.

consciously discourage string arrays

FWIW, lighterhtml never injects arrays, differently from hyperHTML (one of the very few differences), but I actually don't even understand why you need raw(...) at all, as that's never safe.

Anyway, another way to use raw in hyperHTML is via intents

hyperHTML.define('raw-html', (node, value) => {
  // node is the node with the defined attribute
  // value is any value passed as attribute (not just strings)
  node.innerHTML = value;
  // return whatever you want to see on the attribute
  // if you return 'test', you'll have raw-html="test" as attribute
  return '';
});

Once you have that intent, you can write wires, or bound content, as such:

wire()`<div raw-html=${htmlContent}/>`

in that case you can easily separate nodes that want HTML from those that don't

Once again I have the feeling before deciding to drop the library maybe a bit more of documentation reading would've helped.

Node.ELEMENT_NODE is also available in any element instances

Sure, but you need a document to start with, or even a window object to reach such document.

basicHTML has that, you can pass arbitrary window object, but how would hyperHTML know what to use? where does it start? It's a chicken/egg problem, and yet it's not hyperHTML fault, as hyperHTML is a library for the browser.

I use heresy and heresy-ssr for anything else, both based on lighterhtml, and previously viperHTML for SSR based on hyperHTML.

If you believe that can be useful for other users also, that would be great.

It's useful for this project already, so if it doesn't require too much effort, I'd be happy to help as I can.

However, I need to understand your requirements:

  1. is it just one-off creation of elements?
  2. are template literals holes update required?

These and any other direction could help me define the minimal amount of features the tagger used for this project would look like.

At that point, you might have just the html utility, and nothing else ... that seems worth it, or usefull, for others too, when no updates are needed, and not all the rest of the features ... just to safely create nodes as meant by developers 馃憤

@WebReflection
Copy link

P.S. now that I have explained how hyperHTML intent works, I believe all you need to do, for a one-off content creation, is just this:

// the html utility to use right away
const html = (template, ...values) => wire()(template, ...values);

// the render utility if you don't like bind
const render = (node, content) => {
  // cleanup
  node.textContent = '';
  // and append
  node.append(content);
  // if needed ...
  return node
};

Once you have these, and you define an explicit raw-html intent, your code would look like:

render(document.body, html`<main>thing with <div raw-html=${content}/></main>`)

and call it a day.

Yeah, hyperHTML weights around 8 or 9K, I don't even remember, but it has everything to control everything you land on the page.

At that point, if you don't want to see comments, you can either instrument your html wire wrapper to remove these once the node has been created, before returning it, or instrument the render utility to do the same, after appending anything.

That would be:

const cleanUp = node => {
  for (let i = 0, {childNodes} = node, {length} = childNodes; i < length; i++) {
    const {nodeType} = childNodes[i];
    if (nodeType === 8) {
      node.removeChild(childNodes[i]);
      i--; length--;
    }
    else if (nodeType === 1)
      cleanUp(childNodes[i]);
  }
  return node;
};

const html = (template, ...values) => cleanUp(wire()(template, ...values));

Since each html would create a new node, it's safe to go this way, so that almost every problem I understand would disappear, and you won't need to change library.

The day you'll need more though, hyperHTML will be there to give you that more just adjusting few things, as I've done right now.

@saschanaz
Copy link
Member Author

I am still not sure I am following. All attributes are nullable, except those that are acessors, which will be removed from the outerhtml anyway, unless there is a non null value.

Yes, all attrs are nullable and can implicitly be not defined when the value is null. And I prefer this "not defining" behavior more explicit so that any code reader can easily see some attributes can be not defined and others are always defined. Again, I'm not saying the existing feature is bad or buggy, it's great and battle-hardened indeed.

FWIW, lighterhtml never injects arrays, differently from hyperHTML (one of the very few differences),

Does that mean lighterhtml does not support injecting array of elements? I don't see any array injection thing in the difference list...

but I actually don't even understand why you need raw(...) at all, as that's never safe.

raw() clearly shows the intention is to insert raw HTML string if and only if it appears, which is a win for code reading and debugging. (A reader can make sure we are using escaped string anywhere else, unless we are directly inserting text nodes.)

Sure, but you need a document to start with, or even a window object to reach such document.

#2187 passes a document object so it's not an issue. (In this way we can process multiple documents in parallel, which will be useful in Reffy.)

That said, if I can use hyperhtml without global things then I'm all for it, as it's the main reason I decided to open this PR. Anything else are good things to have but not musts.

@WebReflection
Copy link

WebReflection commented Oct 25, 2019

I prefer this "not defining" behavior more explicit

how about <div data-thing=${value || ''} /> then ? that's explicit, and will set an empty string when value is falsy.

Does that mean lighterhtml does not support injecting array of elements?

exactly, lighterhtml does not give you primitives to inject via innerHTML, 'cause it's considered 1. never necessary and 2. shenanigans prone (just use html to create nodes, and if strings come form eslewhere, just html([content])) then.

raw() clearly shows the intention is to insert raw HTML string if and only if it appears

same for my solution, only if the node has raw-html=${content} attribute, it injects it as innerHTML.

If you want to escape that, you can do it within the raw-html intent definition.

Please note no HTML is visible in the attribute, only what you returns from the intent would be visible, including empty strings, or null.

if I can use hyperhtml without global things then I'm all for it

beside the incredible amount of runtime features detections that hyperHTML needs to work well in every of its target browsers, whenever you pass a template, it needs to create DOM nodes.

Where would it find a document to start with? A document is indeed likely the only global needed by hyperHTML to work in Node.js, but that's true for every DOM based library out there.

If nanohtml has a whole DOM parser and never creates nodes in the wild, then it means it's not DOM based, so it doesn't need DOM primitives to create nodes.

Beside that, hyperHTML itself does never even use the document, but its internal polyfills, features detection, and primitives, need one: @ungap/create-content, as example, does feature detection right away.

I could bump a major version so that features detections are done lazily, but once I do that, how are you planning to pass the document down code created exclusively for the browsers?

The whole thing uses node.ownerDocument whenever it's possible, but some code (imho rightly) assumes there is a global document.

Such document is also never touched, it's just its primitives to create elements or fragments, that are used, so that swapping a single document shouldn't really be an issue, and having a document to deal with DOM is, still imho, just natural.

The create-content is just the first one that comes to mind that might rely in document, but I am sure other dependencies use it here and there, so that robustness and its "battle tested plus" might fade away if I refactor all those FE libraries to not assume document exists.

And yet, even if I'll do that, it's not clear how is hyperHTML supposed to pass along a document, but maybe this one can be solved somehow, but it will still need to swap such document internally, and per each library that needs one to work.

@saschanaz
Copy link
Member Author

how about <div data-thing=${value || ''} /> then ? that's explicit, and will set an empty string when value is falsy.

Hmm. It works, yes, while I would prefer the reversed way, be more verbose when potentially undefined, because I think always-defined attributes are more frequent and adding || '' everywhere would be too verbose...

exactly, lighterhtml does not give you primitives to inject via innerHTML, 'cause it's considered 1. never necessary and 2. shenanigans prone (just use html to create nodes, and if strings come form eslewhere, just html([html])) then.

That's about string arrays, right? I mean node/element arrays for example:

const nodes = getNodes();
html`<div some-attribute>${nodes}</div>`

This pattern exists in ReSpec and IMO useful enough.

same for my solution, only if the node has raw-html=${content} attribute, it injects it as innerHTML.

That would work if we decide to use lighterhtml so that we can make sure implicit raw interpolation by string won't work. Then readers can make sure only raw-html is raw html and others are definitely not. (But that's a bit sad, it would be more ideal if string arrays are considered as escaped text nodes, just like element.append(...nodes) does.)

For the global document things, if you think it's too hard to implement and you are not convinced to do it, I think that's okay. Admittedly it's a niche use case to generate and manipulate DOM in both browser and Node.js environment, and we don't need any polyfill (as we already only target latest browsers), so... 馃槓

@WebReflection
Copy link

I think always-defined attributes are more frequent and adding || '' everywhere would be too verbose...

I respectfulyl disagree. When you pass a non value to an attribute, not having the attribute at all is the least surprise to me. Having attributes containing null or undefined makes no sense.

I like knowing what I am passing through, so if there was no value to show, don't show the attribute, as it might affect CSS selectors too.

That's about string arrays, right? I mean node/element arrays for example:

That doesn't produce the same in lighterhtml, there is never innerHTML in lighterhtml.

That would work if we decide to use lighterhtml so that we can make sure implicit raw interpolation by string won't work.

unfortunately lighterhtml has no intents definitions, it's in the list of differences, it's lighter because it dropped quite few features from hyperHTML.

Again, there's never a good reason to inject HTML, so lighterhtml doesn't bother with that.

Admittedly it's a niche use case to generate and manipulate DOM in both browser and Node.js environment

I do that daily with heresy-ssr, so it's not niche, but I don't care about a global document in Node.js, it's me putting it there, nobody else.

The issue here is how to pass the current used document down each dependency, which means there should be a way to update such document too, 'cause indeed your case is that you want N documents, a use case I never needed, so it'd be a hell of a refactoring.

@WebReflection
Copy link

P.S. if that would seal a deal, I might implement a raw(...) utility in lighterhtml to fulfill this requirement, whenever it's needed.

@saschanaz
Copy link
Member Author

I respectfulyl disagree. When you pass a non value to an attribute, not having the attribute at all is the least surprise to me. Having attributes containing null or undefined makes no sense.

Reasonable.

That doesn't produce the same in lighterhtml, there is never innerHTML in lighterhtml.

But inserting node/element objects doesn't need innerHTML, does it?

unfortunately lighterhtml has no intents definitions, it's in the list of differences, it's lighter because it dropped quite few features from hyperHTML.

Oops! You're right. In that case using raw-html in hyperhtml still doesn't make sure that non-raw-html are all escaped, am I right?

We want to remove raw interpolations anyway, so ideally we won't need raw-html and everything will be escaped! 馃槅

The issue here is how to pass the current used document down each dependency, which means there should be a way to update such document too, 'cause indeed your case is that you want N documents, a use case I never needed, so it'd be a hell of a refactoring.

I see the difficulty. Does lighterhtml have same dependencies for feature detection etc.?

@WebReflection
Copy link

WebReflection commented Oct 25, 2019

But inserting node/element objects doesn't need innerHTML, does it?

it doesn't, all I am saying is that there's no way to inject raw HTML in lighterhtml, and the array in hyperHTML has been more than once considered an hazard ... but after 3 years of usage, it'd be hazardous to remove such functionality now.

In that case using raw-html in hyperhtml still doesn't make sure that non-raw-html are all escaped, am I right?

correct, but the thing is that, if in lighterhtml you pass raw HTML and you expect that to be visualized as such, you'd be disappointed, so it moves from feature to developer mistake.

You never need to use innerHTML in lighterhtml, 'cause it's a function, and it can create one-off content simply via html([content]), where content is the HTML you'd like to inject otherwise, but it makes it explicit you want to use a predefined string, similar to raw, except it won't use innerHTML anyway, as it'd pass through the template and do the DOM dance (well, yes, the template needs to use innerHTML to create its content, but that's not exposed to users).

We want to remove raw interpolations anyway, so ideally we won't need raw-html and everything will be escaped! laughing

that's a good thing

I see the difficulty. Does lighterhtml have same dependencies for feature detection etc.?

80% of the core is shared, so yes, it has similar issues. The main problem is not setting up a document neither, it's the ability to swap such document, once explicitly defined.

But maybe that's an unnecessary complication, but reading "we use many documents" makes me think it'll be an issue.

@WebReflection
Copy link

WebReflection commented Oct 25, 2019

P.S. in case it's anyhow useful to play around with a raw in lighterhtml, this is some code that should work out of the box:

const fakeplate = Object.craete(null);

const fake = HTML => {
  const template = [HTML];
  Object.defineProperty(template, 'raw', {value: template});
  return fakeplate[HTML] = Object.freeze(template);
};

const unique = HTML => fakeplate[HTML] || fake(HTML);

// the utility to use, `html` is the one exported by lighterhtml
const raw = HTML => html(unique(HTML));

@WebReflection
Copy link

WebReflection commented Oct 25, 2019

P.S.2 ... the str to template can be handy, so I've just crated a module called fakeplate, and my previous playground would now look like this:

import fakeplate from 'fakeplate';
const raw = HTML => html(fakeplate(HTML));

that's it, and it's granted (via code coverage) to pass template-literal checks to establish when a browser is capable of it or not.

@marcoscaceres
Copy link
Member

Reflecting on this, I'd feel better if we switched to lighterhtml. On export, we always need to clean up the document irrespective (e.g., removing the ReSpec pill and friends + "no-export"), so cleaning up the comments is just a tiny part of that. I still really like that null removes the attributes, and having the occasional "" seems reasonable (e.g., data-export=""), as almost all ReSpec specs get processed through HTML5 Tidy (which always adds ="" to empty attributes).

@WebReflection
Copy link

If it could be of any help, I've tried to export a raw utility and realized, without blowing the library for something very edge case, all you meed to have raw is:

const raw = html => ({html});

That's enough to have a raw behavior, and what I could do eventually, in order to speedup raw operations, is to avoid recreating exact same tree if the raw content is identical to the previous one, making {html: content} a no-op when content is the same as the previous one.

I might explore this optimization possibility regardless, but I wanted to let you know what are my latest thoughts on raw(...).

@saschanaz
Copy link
Member Author

saschanaz commented Nov 3, 2019

Note that I also consider this as low priority as now we don't expect to do #1469 in any near future. This PR is kept open so that we can easily identify raw string interpolations and remove them (#2551).

@saschanaz saschanaz added the Low priority Suggestions with no good driving force label Nov 3, 2019
@marcoscaceres
Copy link
Member

I'm going to go ahead and close this. I think the path of least resistance is still for us to eventually move to lighterhtml. However, it's not super high priority as HyperHTML is working great, plus it's easy to understand and use.

@saschanaz saschanaz deleted the nanohtml branch February 16, 2021 02:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Low priority Suggestions with no good driving force
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Explore alternative templating engine
4 participants