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

Error when using html-react-parser #2517

Open
bgonzalez-thestable opened this issue Sep 11, 2024 · 8 comments
Open

Error when using html-react-parser #2517

bgonzalez-thestable opened this issue Sep 11, 2024 · 8 comments

Comments

@bgonzalez-thestable
Copy link

bgonzalez-thestable commented Sep 11, 2024

What is the location of your example repository?

No response

Which package or tool is having this issue?

Hydrogen

What version of that package or tool are you using?

2024.7.4

What version of Remix are you using?

2.10.1

Steps to Reproduce

  1. Setup a fresh Hydrogen instance
  2. Install html-react-parser as a dependency
  3. Go to any component or route and import the package:
import parse from 'html-react-parser';

//...

export function Foo() {
  const parsedHTML = parse('<p>bar</p>');
}

Expected Behavior

Server should not try to execute DOM methods, the html-react-parser package supports SSR.

Actual Behavior

The server crashes with the error: Error: This browser does not support document.implementation.createHTMLDocument.

@bgonzalez-thestable
Copy link
Author

@wizardlyhel
Copy link
Contributor

wizardlyhel commented Sep 16, 2024

The issue it that the package itself expects to have a window or document to be available in the SSR side. Hydrogen local server uses Cloudflare workerd instance. This is to ensure that the local environment that you have is the same as the one that will be running on production. A cloudflare worker doesn't have access to a window or document.

Instead of trying to run the html parser in SSR, run it on client side. I tried this package locally.

import * as parser from 'html-react-parser';

const parse = parser.default;

export default function Homepage() {
  const data = useLoaderData<typeof loader>();
  const [parsedHtml, setParsedHtml] = useState<ReturnType<typeof parse>>();

  useEffect(() => {
    setParsedHtml(parse('<p>bar</p>'));
  });

  return (
    <div className="home">
      {parsedHtml && <div>{parsedHtml}</div>}
    </div>
  );
}

Make sure to add the parser package in the ssr.optimizeDeps in the vite.config.ts

 ssr: {
    optimizeDeps: {
      /**
       * Include dependencies here if they throw CJS<>ESM errors.
       * For example, for the following error:
       *
       * > ReferenceError: module is not defined
       * >   at /Users/.../node_modules/example-dep/index.js:1:1
       *
       * Include 'example-dep' in the array below.
       * @see https://vitejs.dev/config/dep-optimization-options
       */
      include: ['html-react-parser'],
    },
  },

@bgonzalez-thestable
Copy link
Author

Thanks @wizardlyhel

Moving this to client-side would be problematic because in our case we receive markup or markdown from a CMS response. This would require making the entire page client-side, as individual pieces of the structured data received from the CMS may o may not have HTML.

Additionally, this works on Hydrogen Remix classic (no Vite), and it also works on Vite + Remix (no Hydrogen/Oxygen).

We're currently unable to migrate to Vite + Hydrogen because this is blocking the implementation.

@wizardlyhel
Copy link
Contributor

wizardlyhel commented Sep 17, 2024

Then I would consider using dompurify + jsdom.

  • dompurify - gives you the html sanitization method
  • jsdom - provides the window object that dompurify needs on the server side

I don't know what other limitations these 2 package can run into when executed on the server side. They could be:

  • Bundle size limitation: these 2 packages is huge
  • Execution time limitation: don't know the CPU requirement of these 2 packages

The core of any html sanitizer is that they relies on there is a DOM object that they can parse strings into html nodes and run queries to manipulate the node elements. Instead of writing a DOM object, they just reuse the ones from browser. But often, package like jsdom provides way more than just a simple parse and query dom nodes since these packages are used for unit testing. So these dom packages are really large in size because they try to mimic everything, including downloading scripts, that a browser dom does.

@bgonzalez-thestable
Copy link
Author

I tried using dompurify as well and it had a similar error, I will try it with jsdom.

That said, I still do not understand why this works as expected on all other SSR frameworks out there, this seems to be a problem specific to Oxygen/Hydrogen. As I mentioned, we currently have sites deployed to production running on Hydrogen + Remix classic and there's no errors.

The server should not be trying to parse these into HTML, I tried using this other plugin https://www.npmjs.com/package/isomorphic-dompurify to no avail. And as described in kkomelin/isomorphic-dompurify#214 (comment) it looks like Hydrogen is loading the wrong file?

@wizardlyhel
Copy link
Contributor

wizardlyhel commented Sep 18, 2024

It depends on the hosting environment you are on. Some hosting platform supply a global.windows object and some don't. For example, Vercel and Cloudflare workers (what Oxygen is using) doesn't have a global.windows object. The reason why windows often isn't defined is due to performance.

To be honest, the fact that I have to do this kinda import workaround when using html-react-parser is already strange.

import * as parser from 'html-react-parser';

const parse = parser.default;

This already tells me that the library export is set up oddly. Most likely the difference between es modules setup and cjs setup (what html-react-parser most likely is using).

Since you are saying that this package "should" work on SSR, the file is there and it's just using the wrong one. Why not look into the option of patching the package so that it returns the correct file? https://www.npmjs.com/package/patch-package

@wizardlyhel
Copy link
Contributor

Another option is just copy the entire package content locally into your project and see if that works

@bgonzalez-thestable
Copy link
Author

That's odd, I'm not sure why on your environment you have to import the package that way, for us to works as a normal import.

For example, a simplified hook that parses HTML:

import parse from 'html-react-parser';

export const useHTMLParser = (html) => {
  return typeof html === 'string' ? parse(html) : html;
};

The production environments we have are hosted in Oxygen/Cloudflare and the parser works as expected.

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

No branches or pull requests

2 participants