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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emit events for closing elements #85

Closed
evanderkoogh opened this issue Mar 25, 2021 · 2 comments · Fixed by #121
Closed

Emit events for closing elements #85

evanderkoogh opened this issue Mar 25, 2021 · 2 comments · Fixed by #121

Comments

@evanderkoogh
Copy link

A scenario that is currently impossible to do is adding child nodes to an element based on the content of the existing child nodes. What I am looking for is similar to the SAX Parser endElement.

As an example, I am currently trying to add certain <head> tags if they aren't already present in the head, but there is no way to signal that we are at the closing </head> tag so I can insert the missing tags before the closing tag is sent.

If all the content was known ahead it would be possible to just output them straight away and remove them from the stream when I encounter the stream versions. But that won't work if there are defaults, but the content is variable.

My current work-around is to swallow all <head> tags and all their child nodes and save them in memory. Then when the body element event comes, I figure out the missing tags, add them & serialise the entire <head> before the entire body.

This makes for brittle, harder to understand code and unnecessarily delays the delivery of the <head>, which potentially contains very performance sensitive information like preload and preconnect headers.

@bembleton
Copy link

@evanderkoogh I would like an end method added to ElementHandler as well. But in the meantime, instead of saving and rewriting the entire head, you can use removeAndKeepContent to strip the start and close tags and write them back in yourself as needed. This trick only works if you can detect the start of the next element, but this seems to work pretty well for the <head>. You can also add content to <body> in the same way, but you have to handle the <html> element as well, and use a document end handler to rewrite the body closing tag:

let headContent = '';
let bodyContent = '';

const stripEndTag = (el) => {
  const attrs = [...el.attributes].map(([k, v]) => `${k}="${v}"`);
  const tag = attrs.length > 0
    ? `<${el.tagName} ${attrs.join(' ')}>`
    : `<${el.tagName}>`;
  el.prepend(tag, { html: true });
  el.removeAndKeepContent();
};

const resp = new HTMLRewriter()
  .on('html', {
    element: (html) => {
      stripEndTag(html);
    }
  })
  .on('head', {
    element: (head) => {
      stripEndTag(head);
    }
  })
  .on('head > link', {
    element: () => {
      headContent = '<!-- has some links -->';
    }
  })
  .on('body', {
    element: (body) => {
      body.before(`${headContent}</head>`, { html: true });
      stripEndTag(body);
    }
  })
  .on('body *', {
    element: () => {
      bodyContent = "<!-- extra content -->";
    }
  })
  .onDocument({
    end: (doc) => {
      doc.append(`${bodyContent}</body></html>`, { html: true });
    }
  });

@mitsuhiko
Copy link
Contributor

I ran into similar issue in a slightly different setup. What I'm trying to do is to "ignore" all content outside of CSS selectors. That can in theory be accomplished by communicating between HtmlRewriter and OutputSink. The entire setup isn't even that ugly. However the current solution involving on_end_tag runs my callback before the end tag is written (#108) which means it's not possible to implement this correctly. The flipping of the flag happens before the sink is instructed to write the end tag so I'm generally losing out on it.

I believe there is a need for two end tag handlers: one for where you still have the chance to modify the tag, and one where you already exited it. The latter is needed for properly maintaining a stack of elements of interest if that is needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants