isomorphic-dompurify icon indicating copy to clipboard operation
isomorphic-dompurify copied to clipboard

Can't resolve 'canvas' on next.js serverless app

Open EnricoNapolitan opened this issue 5 years ago • 42 comments

What's the problem

I built a next.js application with serveless option, but simply importing this library it throws this error:

Failed to compile.
ModuleNotFoundError: Module not found: Error: Can't resolve 'canvas' in '/.../node_modules/jsdom/lib/jsdom'
> Build error occurred

I already tried to add canvas library but this doesn't fix the problem and it throws another error.

Step to reproduce

Add to next.config.js:

module.exports = {
  target: 'serverless'
}

Run next build

Basic Info:

  • "isomorphic-dompurify": "^0.12.0",
  • "next": "^10.0.8",
  • "react": "^17.0.1",
  • "react-dom": "^17.0.1"

This issue could be related to #35 , but i don't use umijs.

EnricoNapolitan avatar Apr 26 '21 14:04 EnricoNapolitan

@EnricoNapolitan did you find a solution? I'm encountering something similar with Gatsby.

eharkins avatar Apr 28 '21 21:04 eharkins

Thanks for your detailed bug report @EnricoNapolitan. Since many people experience this issue, I will try to find a solution myself. Linking it here just for reference https://github.com/kkomelin/isomorphic-dompurify/pull/36

kkomelin avatar Apr 29 '21 10:04 kkomelin

I think I've seen this come up with JSDOM before. JSDOM marks canvas as an optional dependency https://github.com/jsdom/jsdom/blob/master/package.json#L58

NicholasEllul avatar Apr 29 '21 12:04 NicholasEllul

Hi @NicholasEllul, Thanks for the info. I'd like to try to add Canvas as an optional dependency too and see if it fixes the issue.

kkomelin avatar Apr 29 '21 16:04 kkomelin

I've reproduced the bug locally. Will try to find a solution.

kkomelin avatar Apr 29 '21 20:04 kkomelin

My hypothesis that adding canvas as an optional dependency to isomporphic-dompurify didn't work out. I'm thinking about adding canvas as a normal dependency. What do you think about it, @NicholasEllul?

How would it affect our existing users? Should we release a major version with that change?

kkomelin avatar Apr 30 '21 11:04 kkomelin

I've tried to add canvas as a normal dependency but it caused new errors on npm run build:

> next build

info  - Creating an optimized production build  
Failed to compile.

./node_modules/canvas/build/Release/canvas.node 1:0
Module parse failed: Unexpected character '' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)

> Build error occurred
Error: > Build failed because of webpack errors
    at [my path]/nextjs-dompurify/node_modules/next/dist/build/index.js:15:918
    at async [my path]/nextjs-dompurify/node_modules/next/dist/build/tracer.js:3:470
npm ERR! code 1
npm ERR! path [my path]/nextjs-dompurify
npm ERR! command failed
npm ERR! command sh -c next build

npm ERR! A complete log of this run can be found in:
npm ERR!     [my path]/.npm/_logs/2021-04-30T11_28_12_323Z-debug.log

Maybe it's my local Ubuntu issue only but we can't be sure that it won't happen with other OSes too.

kkomelin avatar Apr 30 '21 11:04 kkomelin

Just tested on MacOS and building succeeded for me. However, I agree we cant be sure about other environments. There seems to be a reason JSDOM makes canvas optional despite some functionality requiring it

https://github.com/jsdom/jsdom/issues/2603

So if we make it mandatory, we may hit the same issues

NicholasEllul avatar May 11 '21 18:05 NicholasEllul

Thanks @NicholasEllul . If adding canvas as an optional dependency to isomporphic-dompurify doesn't solve the problem and adding it as a normal dependency can potentially break things, I don't have other choices for now to just mention this issue in the documentation - that our project doesn't work for Next.js serverless setup and some other server-side environments. Unless you see any other options.

kkomelin avatar May 13 '21 08:05 kkomelin

I have a more or less similar issue while trying to use isomorphic-dompurify with AWS cdk and esbuild, got following errors the 2 warnings something to do with esbuild, I assume

 > node_modules/jsdom/lib/jsdom/utils.js: error: Could not resolve "canvas" (mark it as external to exclude it from the bundle)
    160 │   const Canvas = require("canvas");
        ╵                          ~~~~~~~~

 > node_modules/jsdom/lib/jsdom/living/xhr/XMLHttpRequest-impl.js: warning: Indirect calls to "require" will not be bundled (surround with a try/catch to silence this warning)
    31 │ const syncWorkerFile = require.resolve ? require.resolve("./xhr-sync-worker.js") : null;
       ╵                        ~~~~~~~

 > node_modules/jsdom/lib/jsdom/living/xhr/XMLHttpRequest-impl.js: warning: "./xhr-sync-worker.js" should be marked as external for use with "require.resolve"
    31 │ const syncWorkerFile = require.resolve ? require.resolve("./xhr-sync-worker.js") : null;

nnduc1994 avatar Jan 25 '22 20:01 nnduc1994

@nnduc1994 Hi, did you resolve this error, please?

zhengminhui avatar Aug 02 '22 08:08 zhengminhui

Just ran into this in Gatsby, any updates? Also receiving the same "Module parse failed..." error if I try to use this package with canvas installed. I'll switch to using sanitize-html for now instead.

DennisRosen avatar Aug 02 '22 13:08 DennisRosen

I was also having this issue with SvelteKit using the Node adapter. Installing canvas solved the issue, but it would be nice if there were also a solution for serverless.

DePasqualeOrg avatar Sep 24 '22 17:09 DePasqualeOrg

Still seeing this issue using gatsby and trying to use the isomorphic-dompurify in a template file

Dinkelborg avatar Jan 23 '23 18:01 Dinkelborg

Still seeing this issue using gatsby and trying to use the isomorphic-dompurify in a template file

Same for me here

libbyberrie avatar Jan 27 '23 01:01 libbyberrie

Noting that this does not work (for me at least) on Next 13 not even on Serverless but normal out of the box Next app

Antonio-Laguna avatar Feb 19 '23 10:02 Antonio-Laguna

@Antonio-Laguna

Noting that this does not work (for me at least) on Next 13 not even on Serverless but normal out of the box Next app

This one is new. Do you use the experimental app/ folder? Could you please give me more info?

kkomelin avatar Feb 19 '23 12:02 kkomelin

@kkomelin yes that was on the experimental app/ folder using use client. It was just a quick test since I'm trying to build a library and we're leveraging that package on a component.

I'm happy to do more testing and even contribute to fixes if I know what could be going on

Antonio-Laguna avatar Feb 19 '23 18:02 Antonio-Laguna

Thank you @Antonio-Laguna. I don't think there is much we can do here at the isomorhic-dompurify level because it's just a tiny wrapper around dompurify and jsdom, and the problem lays in the way jsdom communicates with canvas dependency. But if you have any ideas how we can fix it through this package, please let me know.

kkomelin avatar Feb 19 '23 18:02 kkomelin

Is this tracked at JSDOM level somehow though?

This might be extremely simplistic but shouldn't dompurify leverage the DOM instead of JSDOM which is intended to be leveraged only at Node level?

Antonio-Laguna avatar Feb 19 '23 18:02 Antonio-Laguna

@Antonio-Laguna

Usually the error message looks like this:

Failed to compile. ModuleNotFoundError: Module not found: Error: Can't resolve 'canvas' in '/.../node_modules/jsdom/lib/jsdom' Build error occurred

What's your error message?


As for the server vs client code, modern JS frameworks flirt a lot with the server (SSR, Server Components, etc) and Next is not an exception. Dompurify itself leverages DOM and isomorphic-dompurify utilizes JSDOM to make dompurify work on the server.

kkomelin avatar Feb 19 '23 20:02 kkomelin

Solution

For usage of DOMPurify in Next.js without the wrapper package isomorphic-dompurify, here is a demo of a simple version:

  • https://github.com/vercel/next.js/issues/46893#issuecomment-1458595411

Mainly the code is this (this is a Server Component - if you aren't using the app/ directory, then do the DOMPurify() call in getServerSideProps or an API Route):

import DOMPurify from "dompurify";
import { JSDOM } from "jsdom";

export default function Home() {
  return (
    <div
      dangerouslySetInnerHTML={{
        __html: DOMPurify(new JSDOM("<!DOCTYPE html>").window).sanitize(
          "<img src=x onerror=alert(1)//>"
        ),
      }}
    />
  );
}

There is also some required Next.js configuration for this, to resolve the webpack bundling errors:

next.config.js

/** @type {import("next").NextConfig} */
module.exports = {
  reactStrictMode: true,
+ webpack: (config) => {
+   config.externals = [...config.externals, "canvas", "jsdom"];
+   return config;
+ },
};

karlhorky avatar Mar 07 '23 18:03 karlhorky

Thanks @karlhorky. As I already asked here https://github.com/cure53/DOMPurify/issues/400#issuecomment-1458624807, since isomorphic-dompurify does the same under the hood as you illustrated in the code example, I guess it will work just fine with the webpack configuration you suggested. Can you test it for me?

kkomelin avatar Mar 07 '23 18:03 kkomelin

Should be fine, try modifying the CodeSandbox demo to use your library instead of dompurify:

https://codesandbox.io/p/sandbox/lingering-river-j5fpi7?file=%2Fapp%2Fpage.tsx

karlhorky avatar Mar 07 '23 19:03 karlhorky

Should be fine, try modifying the CodeSandbox demo to use your library instead of dompurify: https://codesandbox.io/p/sandbox/lingering-river-j5fpi7?file=%2Fapp%2Fpage.tsx

Just confirmed. Your webpack fix works for isomorphic-dompurify as well.

kkomelin avatar Mar 07 '23 20:03 kkomelin

For usage of DOMPurify in Next.js without the wrapper package isomorphic-dompurify, here is a demo of a simple version:

Mainly the code is this (this is a Server Component - if you aren't using the app/ directory, then do the DOMPurify() call in getServerSideProps or an API Route):

import DOMPurify from "dompurify";
import { JSDOM } from "jsdom";

export default function Home() {
  return (
    <div
      dangerouslySetInnerHTML={{
        __html: DOMPurify(new JSDOM("<!DOCTYPE html>").window).sanitize(
          "<img src=x onerror=alert(1)//>"
        ),
      }}
    />
  );
}

There is also some required Next.js configuration for this, to resolve the webpack bundling errors:

next.config.js

/** @type {import("next").NextConfig} */
module.exports = {
  reactStrictMode: true,
+ webpack: (config) => {
+   config.externals = [...config.externals, "canvas", "jsdom"];
+   return config;
+ },
};

Holy Crap the next config fix worked for me 🥳

Daggron avatar Mar 14 '23 16:03 Daggron

Should be fine, try modifying the CodeSandbox demo to use your library instead of dompurify:

https://codesandbox.io/p/sandbox/lingering-river-j5fpi7?file=%2Fapp%2Fpage.tsx

@kkomelin @karlhorky Can you put more details into this why webpack changes fix this issue ?

Daggron avatar Mar 14 '23 16:03 Daggron

Don't know too much about it, check out the details I know here:

  • https://github.com/vercel/next.js/issues/46893#issuecomment-1458595411

karlhorky avatar Mar 14 '23 17:03 karlhorky

i still have this error when using react-pdf Basic Info:

    "@types/react-pdf": "^6.2.0",
    "next": "13.2.4",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-pdf": "^6.2.2"

Error Message

./node_modules/pdfjs-dist/build/pdf.js:10759:0
Module not found: Can't resolve 'canvas'

https://nextjs.org/docs/messages/module-not-found

Import trace for requested module:
./node_modules/react-pdf/dist/cjs/entry.js
./component/PDF/index.tsx

when I add "canvas"

Failed to compile
./node_modules/canvas/build/Release/canvas.node
Module parse failed: Unexpected character '�' (1:2)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)

I still didn't update it to the next 13.3 since when I run yarn dev on windows, it crashes and throws errors

abedshaaban avatar Apr 15 '23 12:04 abedshaaban

@abedshaaban I found a way to resolve this when using react-pdf in Next Js 13 (app directory)

  1. The best way In your app directory, whatever/page.tsx have this
"use client"
import { pdfjs } from "react-pdf";
pdfjs.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js';

In your next.config.js have this

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    appDir: true,
  },
  future: { webpack5: true },
  webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
      config.resolve.alias.canvas = false
      config.resolve.alias.encoding = false
      return config
  }
}

module.exports = nextConfig

Let me know if it doesn't work. Happy to help out. This took me waaaay too long to figure out. Hopefully it will help someone get there faster! :)

mephobic avatar Apr 26 '23 14:04 mephobic