cognitedata / reveal

Cognite Reveal 3D viewer
https://cognitedata.github.io/reveal
Apache License 2.0
76 stars 18 forks source link

Receiving Error: "ReferenceError: Image is not defined" when instantiating Cognite3DViewer using NextJS #4449

Closed joao-mbn closed 1 week ago

joao-mbn commented 2 weeks ago

I have a NextJS app that is making use of Reveal, pretty much following along the examples in the docs. Right in the first one, for loading a CAD model, I face an issue when trying to create an instance of the Cognite3DViewer class, in a simple component, as follows:


import { useCognite } from '@/common/hooks'
import { Cognite3DViewer } from '@cognite/reveal'
import { Grid } from '@mui/material'
import { FC, useEffect } from 'react'

const SiteView: FC = () => {
    const { cogniteClient: client } = useCognite()

    useEffect(() => {
        if (!client) return

        const wrapper = document.getElementById('3d-wrapper')
        if (!wrapper) return

        const viewer = new Cognite3DViewer({ sdk: client, domElement: wrapper })
        viewer.addModel({ modelId: 7131182391004713, revisionId: 1328881511555769 }).then((model) => {
            viewer.loadCameraFromModel(model)
        })

        return () => {
            viewer.dispose()
        }
    }, [client])

    return (
        <Grid container gap={2} overflow="hidden">
            <div
                id="3d-wrapper"
                style={{
                    height: '600px',
                    width: '600px',
                    position: 'absolute',
                    top: 0,
                    left: 0,
                }}
            />
        </Grid>
    )
}

export default SiteView

This component is meant to be rendered when the initial page loads, but it doesn't even get to that point, it fails at compile step. No breakpoint anywhere inside the component is ever hit.

The following error is shown: "ReferenceError: Image is not defined"

The stack trace is the following:

index.js:578 Uncaught ReferenceError: Image is not defined at <unknown> (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:561:10) at <unknown> (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:880:4) at <unknown> (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:880:10) at <unknown> (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:1:81) at Object.<anonymous> (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:1:217) at Module._compile (node:internal/modules/cjs/loader:1256:14) at Module._extensions..js (node:internal/modules/cjs/loader:1310:10) at Module.load (node:internal/modules/cjs/loader:1119:32) at Module._load (node:internal/modules/cjs/loader:960:12) at Module.require (node:internal/modules/cjs/loader:1143:19) at require (node:internal/modules/cjs/helpers:121:18) at @cognite/reveal (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\.next\server\pages\index.js:1063:18) at __webpack_require__ (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\.next\server\webpack-runtime.js:33:42) at eval (webpack-internal:///./src/features/siteView/siteView.tsx:9:73) at __webpack_require__.a (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\.next\server\webpack-runtime.js:97:13) at eval (webpack-internal:///./src/features/siteView/siteView.tsx:1:21) at ./src/features/siteView/siteView.tsx (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\.next\server\pages\index.js:944:1) at __webpack_require__ (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\.next\server\webpack-runtime.js:33:42) at eval (webpack-internal:///./src/pages/index.tsx:9:85) at __webpack_require__.a (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\.next\server\webpack-runtime.js:97:13) at eval (webpack-internal:///./src/pages/index.tsx:1:21) at ./src/pages/index.tsx (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\.next\server\pages\index.js:966:1) at __webpack_require__ (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\.next\server\webpack-runtime.js:33:42) at __webpack_exec__ (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\.next\server\pages\index.js:1513:39) at <unknown> (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\.next\server\pages\index.js:1514:28) at Object.<anonymous> (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\.next\server\pages\index.js:1517:3) at Module._compile (node:internal/modules/cjs/loader:1256:14) at Module._extensions..js (node:internal/modules/cjs/loader:1310:10) at Module.load (node:internal/modules/cjs/loader:1119:32) at Module._load (node:internal/modules/cjs/loader:960:12) at Module.require (node:internal/modules/cjs/loader:1143:19) at require (node:internal/modules/cjs/helpers:121:18) at requirePage (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\next\dist\server\require.js:156:12) at <unknown> (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\next\dist\server\load-components.js:68:84) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async loadComponentsImpl (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\next\dist\server\load-components.js:68:26) at async DevServer.findPageComponentsImpl (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\next\dist\server\next-server.js:755:36) at async DevServer.findPageComponents (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\next\dist\server\dev\next-dev-server.js:1297:20) at async DevServer.renderPageComponent (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\next\dist\server\base-server.js:1298:24) at async DevServer.renderToResponseImpl (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\next\dist\server\base-server.js:1341:32) at async DevServer.pipeImpl (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\next\dist\server\base-server.js:627:25) at async Object.fn (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\next\dist\server\next-server.js:1134:21) at async Router.execute (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\next\dist\server\router.js:315:32) at async DevServer.runImpl (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\next\dist\server\base-server.js:601:29) at async DevServer.run (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\next\dist\server\dev\next-dev-server.js:922:20) at async DevServer.handleRequestImpl (file://C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\next\dist\server\base-server.js:533:20)

This are the versions of the related packages I'm using.

"dependencies": {
  "@cognite/reveal": "^4.4.0",
  "@cognite/sdk": "^8.2.0",
  "react": "18.2.0",
  "react-dom": "18.2.0",
  "next": "13.4.0",
  "three": "^0.158.0"
}

The current workaround for the problem is to dynamically import the Cognite3DViewer inside an async IIFE in the useEffect, like this:

useEffect(() => {
    if (!client) return

    const wrapper = document.getElementById('3d-wrapper')
    if (!wrapper) return

    let viewer: Cognite3DViewer // Note that importing the class just for static typing is not a problem.
    ;(async () => {
        const Cognite3DViewer = (await import('@cognite/reveal')).Cognite3DViewer

        viewer = new Cognite3DViewer({ sdk: client, domElement: wrapper })
        viewer.addModel({ modelId: 7131182391004713, revisionId: 1328881511555769 }).then((model) => {
            viewer.loadCameraFromModel(model)
        })
    })()

    return () => {
        viewer?.dispose()
    }
}, [client])

Can you shed some light to what is happening here, whether this is a bug or a library misuse? Thank you.

christjt commented 2 weeks ago

Hey, Thanks for testing it out. Have not used Reveal with Nextjs so I am certainly interested in seeing if we can get this to work. Your error message is referring to this https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image. Which is strictly part of a Web API.

In general, Reveal does not support being server side rendered as it requires Web native APIs. From the code, it is not clear if this component is server component or client. Can you ensure that the component is client only, and see if that relieves anything?

I suspect the reason for the dynamic import working is because the effect is done on the client and hence the import is done on the client, which does not seem to be the case in the initial example.

christjt commented 2 weeks ago

It may also simply be that the types are missing in your tsconfig for specific DOM apis. What does your tsconfig look like?

joao-mbn commented 1 week ago

Hey Chris, thanks for the Reply. As for the first comment, yes, this is client side rendered. We're using the pages router for Next and, according to its docs, we have:

... In Next.js, there are two ways you can implement client-side rendering:

Using React's useEffect() hook inside your pages instead of the server-side rendering methods getStaticProps and getServerSideProps ...

Link: https://nextjs.org/docs/pages/building-your-application/rendering/client-side-rendering

Besides that, I'm using the document to get the viewer parent element by its ID, which is also client-only, and I'm able to use it just fine.

joao-mbn commented 1 week ago

It may also simply be that the types are missing in your tsconfig for specific DOM apis. What does your tsconfig look like?

This is how it looks:

{
    "compilerOptions": {
        "target": "es6",
        "lib": ["dom", "dom.iterable", "esnext"],
        "allowJs": true,
        "skipLibCheck": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "noEmit": true,
        "esModuleInterop": true,
        "module": "esnext",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "jsx": "preserve",
        "incremental": true,
        "paths": {
            "@/*": ["./src/*"],
            "react": ["./node_modules/@types/react"]
        }
    },
    "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
    "exclude": ["node_modules"]
}
christjt commented 1 week ago

The Image type is declared in the dom lib, which I see you have already added. The Image type here is initialized during import, which, from I can tell, is not happening client side. Only what is inside the useEffect is executed on the client. I can try to wrap the logic such that it isn't executed during import as a side-effect (which is probably a good idea anyway), but I would like to verify that it would actually work for your case before merging anything. Are you able to build Reveal locally, and link it to your project?

christjt commented 1 week ago

Are you able to test a potential fix I made? This is the branch: https://github.com/cognitedata/reveal/tree/christjt/matcap-no-side-effect

joao-mbn commented 1 week ago

Hey Chris, I think you're on the right track.

Using your branch's build the error disappeared, but another one showed up, that seem to be of the same cause as the Image error: Error [ReferenceError]: navigator is not defined.

Error [ReferenceError]: navigator is not defined at eval (webpack-internal:///./packages/utilities/src/events/getWheelEventDelta.ts:8:20) at ./packages/utilities/src/events/getWheelEventDelta.ts (C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:3259:1) at webpack_require (C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:5845:41) at eval (webpack-internal:///./packages/utilities/index.ts:73:88) at ./packages/utilities/index.ts (C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:2973:1) at webpack_require (C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:5845:41) at eval (webpack-internal:///./packages/api/src/public/migration/Cognite3DViewer.ts:15:75) at ./packages/api/src/public/migration/Cognite3DViewer.ts (C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:364:1) at webpack_require (C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:5845:41) at eval (webpack-internal:///./packages/api/index.ts:11:95)
at ./packages/api/index.ts (C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:320:1) at
webpack_require
(C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:5845:41) at eval (webpack-internal:///./api-entry-points/core.ts:51:72)
at ./api-entry-points/core.ts (C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:89:1) at webpack_require (C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:5845:41) at eval (webpack-internal:///./index.ts:64:80) at ./index.ts (C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:122:1)
at
webpack_require
(C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:5845:41) at C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:5909:37 at C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:5912:12 at webpackUniversalModuleDefinition (C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:11:20) at Object. (C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room\node_modules\@cognite\reveal\dist\index.js:18:3) at Module._compile (node:internal/modules/cjs/loader:1256:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1310:10) at Module.load (node:internal/modules/cjs/loader:1119:32) at Module._load (node:internal/modules/cjs/loader:960:12) at Module.require (node:internal/modules/cjs/loader:1143:19)
at require (node:internal/modules/cjs/helpers:121:18) at @cognite/reveal (C:\Users\joao.batista\Desktop\Repos-RDX\CLN\ControlTower\control-room.next\server\pages\index.js:1074:18)

It looks like that the fix is to map these client-only objects down and apply the same fix as you did for the Image instance.

EDIT: I edited the source code to fix this last error, by passing a const that used the navigator to inside the function that used it, but there are other instances needing this fix. I can take a look on that myself in another moment, for I'm currently unable to clone the repo due to some network issues and having to rely on the zip download.

christjt commented 1 week ago

Ah, I see. Let me know how it goes. Regardless of this issue, it would be great to get rid of any side effects we have during import. Feel free to open a PR if you are able to get it working, and if not, please share how far you got.

joao-mbn commented 1 week ago

There you go. I've created the PR #4469. Can you take a look at it?

christjt commented 1 week ago

Awesome, thanks! Looks good 👍