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

Use canvas in gatsby #17661

Closed
sayjeyhi opened this issue Sep 16, 2019 · 17 comments
Closed

Use canvas in gatsby #17661

sayjeyhi opened this issue Sep 16, 2019 · 17 comments
Labels
status: awaiting author response Additional information has been requested from the author status: needs reproduction This issue needs a simplified reproduction of the bug for further troubleshooting.

Comments

@sayjeyhi
Copy link

Recently I was working on a project , which uses node-canvas ,
and it is working fine in development mode. but if I want to build package and run gatsby build canvas will got some error on last steps with message :

Generating SSR bundle failed

Unexpected character '' (1:0)

File: node_modules/canvas/build/Release/canvas.node:1

⠹ Building static HTML for pages

I used loadImage function from node canvas in an async function like :

import { loadImage } from 'canvas';

/**
 * Load image source to use in canvas
 * @param src
 * @returns {Promise<Image | void>}
 */
export default async function getImage(src) {
  return loadImage(src)
    .then(image => image)
    .catch(err => console.log(err));
}
// etc.

and use it in some componenet to load image in react-konva :

import React, { useEffect, useState } from 'react';
import { Image, Group, Rect } from 'react-konva';
import { Motion, spring } from 'react-motion';
import { getImage, getPlayerCoordinates } from './../../selectors/utility';
import avatars from './../../images/avatars/avatars';

const Player = props => {
  const {
    player: { id, pos, avatar, boxPosition },
    current: { id: currentPlayerId },
    grid: {
      box: { width },
    },
    grid,
  } = props;
  
  const { x, y } = getPlayerCoordinates(pos, grid, boxPosition);
  const isCurrent = id === currentPlayerId;

  let [playerSource, setPlayerSource] = useState();

  useEffect(() => {
    getImage(avatars[avatar - 1]).then(image => setPlayerSource(image));

    /* eslint-disable-next-line */
  }, []);

  const minifiedWidth = width - 10;
  return (
    <Motion style={{ x: spring(x), y: spring(y), stiffness: 211, dumping: 33 }}>
      {({ x, y }) => (
        <Group>
          <Rect
            x={x - minifiedWidth / 2}
            y={y - minifiedWidth / 2}
            width={minifiedWidth}
            height={minifiedWidth}
            cornerRadius={10}
            scale={{ x: 0.92, y: 0.92 }}
            fill={isCurrent ? 'rgba(255,255,255,0.9)' : 'transparent'}
          />
          <Image
            x={x - minifiedWidth / 2}
            y={y - minifiedWidth / 2}
            width={minifiedWidth}
            height={minifiedWidth}
            image={playerSource}
          />
        </Group>
      )}
    </Motion>
  );
};

export default Player;

There was a issue in node-canvas repo , which says add this configuration to webpack , and I did it , but it pass build level and make some errors on browser console :

exports.onCreateWebpackConfig = ({
  stage,
  rules,
  loaders,
  plugins,
  actions,
}) => {
  actions.setWebpackConfig({
  externals: {
    canvas: "commonjs canvas" // Important (2)
    }
  })
}

after build , we got this error on console :

external "canvas":1 Uncaught (in promise) ReferenceError: require is not defined
    at Object.<anonymous> (external "canvas":1)
    at u (bootstrap:84)
    at Module.<anonymous> (component---src-web-pages-index-js-edb51a559818a5f7befe.js:1)
    at u (bootstrap:84)

My project is in this repo :
https://github.com/jafar-rezaei/snakeAndLadders.

File contents (if changed)

gatsby-config.js: N/A
package.json: N/A
gatsby-node.js: N/A
gatsby-browser.js: N/A
gatsby-ssr.js: N/A

@jonniebigodes
Copy link

Gatsby is a ssr(server side framework), meaning that all the build process is done in node. With that won't have access to certain apis, like canvas. And it generates that error. As a workaround you could probably use react- loadable, for instance, let me see if I can make a reproduction of both packages and report back, do you mind waiting a bit?

@sayjeyhi
Copy link
Author

@jonniebigodes Yeah , thatswhy I used node-canvas , to bring canvas to SSR , but I could not make it to build the gatsby static files..
Sure, thanks for your feedback 💯

@LekoArts
Copy link
Contributor

Hi!

Sorry to hear you're running into an issue. To help us best begin debugging the underlying cause, it is incredibly helpful if you're able to create a minimal reproduction. This is a simplified example of the issue that makes it clear and obvious what the issue is and how we can begin to debug it.

If you're up for it, we'd very much appreciate if you could provide a minimal reproduction and we'll be able to take another look.

Thanks for using Gatsby! 💜

@lannonbr lannonbr added the status: needs reproduction This issue needs a simplified reproduction of the bug for further troubleshooting. label Sep 16, 2019
@sayjeyhi
Copy link
Author

@LekoArts I translate the game and add it on a sandbox here :
https://codesandbox.io/s/snakeandladders-ricvr

@jonniebigodes
Copy link

@jafar-rezaei i've cloned your codesandbox and it seems that i have a solution for you. Going to test out a couple of things and report back.

@jonniebigodes jonniebigodes added the status: awaiting author response Additional information has been requested from the author label Sep 17, 2019
@jonniebigodes
Copy link

@jafar-rezaei i think i have a solution for your issue. Below are the steps i took to try and solve it.

  • Grabbed your codesandbox code.
  • Installed the dependencies.
  • Issued gatsby build && gatsby serve and i was able to confirm your issue, the same message popped up.
  • Checked gatsby-node.js modified it to the following:
exports.onCreateWebpackConfig = ({
  stage,
  rules,
  loaders,
  plugins,
  actions,
}) => {
  if (stage === "build-html") {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /canvas/,
            use: loaders.null(),
          },
        ],
      },
    })
  }
};

With this small change once the build enters the build-html stage, the package in question, namely canvas will be "silenced" and returns a empty module. This is due to the fact that this package will try to use apis that are not in the node side like window for instance. But will cause no loss in functionality as you can see below.

  • Issued gatsby build && gatsby serve to create a production build and i'm presented with a error regarding redux, the way it's implemented it's not gatsby/ssr optimized.
  • With that in mind i changed the code inside src/store/index.js to the following:
const windowGlobal = typeof window !== 'undefined' && window;

const devtools =
  process.env.NODE_ENV === 'development' && windowGlobal.devToolsExtension
    ? window.__REDUX_DEVTOOLS_EXTENSION__ &&
      window.__REDUX_DEVTOOLS_EXTENSION__()
    : f => f;

const store = createStore(
  rootReducer,
  compose(
    applyMiddleware(logger, sagaMiddleware),
    devtools
  )
);
sagaMiddleware.run(rootSaga) 
export default store;

This small change will check for both mode(development/production) and window and load the devtools accordingly.

  • Issued gatsby clean && gatsby build && gatsby serve, to set a a clean slate on the build, issue a production build and emulate a production server and i'm presented with the following:
    canvas_build_1

  • Opened up a new browser window to http://localhost:9000 and started a new game and i'm presented with the following:
    canvas_build_2

  • Issued gatsby clean && gatsby develop, once again purge the folders associated to the build process(.cache and public) to generate a development build, just to check if the redux devtools as being loaded correctly.

  • Opened up http://localhost:8000 and started a new game and i'm presented with the following:
    canvas_build_3

The devtools are being loaded and also the package aswell.

Feel free to provide feedback so that we can close this issue or continue to work on it until we find a suitable solution.

@sayjeyhi
Copy link
Author

Big thanks @jonniebigodes , the problem solved

@jonniebigodes
Copy link

@jafar-rezaei no need to thank. Glad that i was able to solve your issue.

@LinusU
Copy link

LinusU commented Oct 2, 2019

@jafar-rezaei @jonniebigodes

The canvas package is meant to work both in the browser and on Node.js, with the use of separate files for each platform. In our package.json, we've added a line to tell bundlers to use the file browser.js when building for browsers, and index.js when running on Node.js.

https://github.com/Automattic/node-canvas/blob/7baaecfe13638fb8406f5277e97bb99e26afef51/package.json#L7

I thought that WebPack would automatically pick up on this 🤔 are you using any configuration that would make that not work?

@jonniebigodes
Copy link

@LinusU i don't know how familiar you are with gatsby but all of the build is done on the node side of things, from my testing and based on the code @jafar-rezaei supplied the bundler/webpack configuration is the default that gatsby offers out of the box, with a small caveat and that is to ignore the canvas library during the build process, as outlined in these lines:

exports.onCreateWebpackConfig = ({
  stage,
  rules,
  loaders,
  plugins,
  actions,
}) => {
  if (stage === "build-html") {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /canvas/,
            use: loaders.null(),
          },
        ],
      },
    })
  }
};

It could be that the property browser you mentioned isn't being picked up by gatsby's webpack out of the box. I'm not knowledgeable of the webpack workflow of gatsby, but it could be that with some work and some changes this can work with gatsby. I'll ping the @gatsbyjs/core for some insight on this, on a personal level if the two, with some work can be better integrated i think that the community would benefit a bit more.

@larryg727
Copy link

Thank for this. As a side note we also had this issue during gatsby develop as well. When running gatsby develop the stage is develop-html. So if you are getting this error during development you can always add this as an or conditional in the stage check in the above fix.

@jonniebigodes
Copy link

@larryg727 when this was written it was intended for production build with Gatsby, with that i ommited any configurations relevant to that. But nonetheless nice touch to include it and expand the response.

@JGeiser9
Copy link

Thank for this. As a side note we also had this issue during gatsby develop as well. When running gatsby develop the stage is develop-html. So if you are getting this error during development you can always add this as an or conditional in the stage check in the above fix.

@larryg727 you saved me a huge headache. Thank you

@Ubanna
Copy link

Ubanna commented Jul 11, 2020

I have an eraser effect with canvas on my Gatsby application. On the home page users can erase the canvas to see the video underneath the canvas.

Everything works as normal on my local machine but when I serve the app, the canvas and video do not load initially. However, if I navigate to any page within the app and back to home page, the canvas and video load and work properly.

I am new to Gatsby, please how can I resolve this.

Please see below code:

const HomePage = () => {
  let canvas = useRef(null)
  const size = useWindowSize()
  const { currentTheme } = useGlobalStateContext()

  useEffect(() => {
    let renderingElement = canvas.current
    let drawingElement = renderingElement.cloneNode()

    let drawingCtx = drawingElement.getContext("2d")
    let renderingCtx = renderingElement.getContext("2d")

    let lastX
    let lastY

    let moving = false

    renderingCtx.globalCompositeOperation = "source-over"
    renderingCtx.fillStyle = currentTheme === "dark" ? "#000000" : "#ffffff"
    renderingCtx.fillRect(0, 0, size.width, size.height)

    renderingElement.addEventListener("mouseover", e => {
      moving = true
      lastX = e.pageX - renderingElement.offsetLeft
      lastY = e.pageY - renderingElement.offsetTop
    })

    renderingElement.addEventListener("mouseup", e => {
      moving = false
      lastX = e.pageX - renderingElement.offsetLeft
      lastY = e.pageY - renderingElement.offsetTop
    })

    renderingElement.addEventListener("mousemove", e => {
      if (moving) {
        drawingCtx.globalCompositeOperation = "source-over"
        renderingCtx.globalCompositeOperation = "destination-out"
        let currentX = e.pageX - renderingElement.offsetLeft
        let currentY = e.pageY - renderingElement.offsetTop
        drawingCtx.lineJoin = "round"
        drawingCtx.moveTo(lastX, lastY)
        drawingCtx.lineTo(currentX, currentY)
        drawingCtx.closePath()
        drawingCtx.lineWidth = 120
        drawingCtx.stroke()
        lastX = currentX
        lastY = currentY

        renderingCtx.drawImage(drawingElement, 0, 0)
      }
    })
  }, [currentTheme])

  return (
    <Container>
      <Video>
        <video
          height="100%"
          width="100%"
          loop
          autoPlay
          muted
          src={require("../assets/somevideo.mp4")}
        />
      </Video>
      <Canvas
        width={size.width}
        height={size.height}
        ref={canvas}
      />
    </Container>
  )
}
@nerdess
Copy link

nerdess commented Jul 29, 2020

i got the same error as @sayjeyhi and just like @larryg727 it happens during gatsby develop...

@jonniebigodes so your approach is to mute the canvas plugin, did i understand this correctly? with

 actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /canvas/,
            use: loaders.null(),
          },
        ],
      },
    })

your are essentially doing the same as writing

 actions.setWebpackConfig({
      externals: ['canvas']
    })

but with canvas muted, an error is thrown in the browser now.

i get a TypeError: Object(...) is not a function because this import: import { createCanvas } from 'canvas'; is importing nothing...which makes sense, since canvas is muted. but then again this is bad because i actually want to use it :D

it would be great if someone could nudge me in the right direction how to fix this paradox...

@nerdess
Copy link

nerdess commented Jul 31, 2020

I am sorry, should've read the whole thread more thouroughly. I ended up with this Webpack config in gatsby-node.js which works just fine (the part with the resolve/alias is not relevant to this issue, the if-condition is the relevant thing):

exports.onCreateWebpackConfig = ({
  stage,
  rules,
  loaders,
  plugins,
  actions
}) => {

  actions.setWebpackConfig({
    resolve: {
      alias: {
        '~components': path.resolve(__dirname, 'src/components'),
        '~images': path.resolve(__dirname, 'src/images'),
        '~hooks': path.resolve(__dirname, 'src/lib/hooks')
      },
    }
  });

  if (stage === "develop-html") {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /canvas/,
            use: loaders.null(),
          },
        ],
      },
    })
  }
};
@martinjuhasz
Copy link

weird but why does the proposed workaround not work for me?
onCreateWebpackConfig gets called, stage is build-html, still it get this error:

exports.onCreateWebpackConfig = ({
  stage,
  rules,
  loaders,
  plugins,
  actions,
}) => {
  console.log("onCreateWebpackConfig", { stage })
  // see: https://github.com/gatsbyjs/gatsby/issues/17661
  if (stage === "build-html") {
    actions.setWebpackConfig({
      module: {
        rules: [
          {
            test: /canvas/,
            use: loaders.null(),
          },
        ],
      },
    })
  }
}
success Writing page-data.json files to public directory - 0.187s - 381/381 2033.69/s
onCreateWebpackConfig { stage: 'build-html' }

 ERROR #98124  WEBPACK

Generating SSR bundle failed

Can't resolve 'canvas' in '/dir/node_modules/konva/lib'

anyone has an idea why this is still happening here?

gatsby: 3.14.6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: awaiting author response Additional information has been requested from the author status: needs reproduction This issue needs a simplified reproduction of the bug for further troubleshooting.
10 participants