vasturiano / react-globe.gl

React component for Globe Data Visualization using ThreeJS/WebGL
https://vasturiano.github.io/react-globe.gl/example/world-population/
MIT License
817 stars 150 forks source link

Next.js ES Module Loading Error #90

Open arulandu opened 2 years ago

arulandu commented 2 years ago

Describe the bug I was trying to use react-globe.gl in a next.js project. I simply created a component, imported import Globe from 'react-globe.gl', and instantiated the component with default properties return <Globe>. However, I get the following compile error:

error - Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: D:\CalebArulandu\Projects\Startups\Glocal\proj\app\node_modules\three\examples\jsm\renderers\CSS2DRenderer.js
require() of ES modules is not supported.
require() of D:\CalebArulandu\Projects\Startups\Glocal\proj\app\node_modules\three\examples\jsm\renderers\CSS2DRenderer.js from D:\CalebArulandu\Projects\Startups\Glocal\proj\app\node_modules\globe.gl\dist\globe.gl.common.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package 
scope as ES modules.
Instead rename CSS2DRenderer.js to end in .cjs, change the requiring code 
to use import(), or remove "type": "module" from D:\CalebArulandu\Projects\Startups\Glocal\proj\app\node_modules\three\examples\jsm\package.json.   

Ironically, if I comment the line and then uncomment, hot reloading displays the globe. But, when I manually reload, I get the same error again.

To Reproduce Steps to reproduce the behavior:

  1. Make a next.js app with typescript template.
  2. Create a component that returns the <Globe> component from react-globe.gl.
  3. Add this component to the index.tsx page.
  4. See error

Expected behavior A globe should be displayed during hot reload and after manual reloads.

Desktop (please complete the following information):

Additional context Add any other context about the problem here.

tsconfig.json

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*":["src/*"],
      "~/*": ["./*"]
    },
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": false,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

package.json

{
  "name": "app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "next": "12.1.6",
    "react": "18.1.0",
    "react-dom": "18.1.0",
    "react-globe.gl": "^2.22.1"
  },
  "devDependencies": {
    "@types/node": "17.0.42",
    "@types/react": "18.0.12",
    "@types/react-dom": "18.0.5",
    "eslint": "8.17.0",
    "eslint-config-next": "12.1.6",
    "typescript": "4.7.3"
  }
}
vasturiano commented 2 years ago

@Claeb101 please see: https://github.com/vasturiano/react-globe.gl/issues/1 https://github.com/vasturiano/react-globe.gl/issues/15

arulandu commented 2 years ago

Hello @vasturiano . After using the dynamic import method described in the above issues, I was able to get the globe working. However, for some reason, the points layer does not seem to be working. When I serve the following test.html, the points layer displays, but when I do the same thing in globe.tsx the points do not render though the textured globe does.

globe.tsx

import dynamic from "next/dynamic";
const Globe = dynamic(() => import('react-globe.gl'), { ssr: false })

const World = () => {
  const volcanoes = [
    {
      "name": "Abu",
      "country": "Japan",
      "type": "Shield",
      "lat": 34.5,
      "lon": 131.6,
      "elevation": 641
    },
    {
      "name": "Acamarachi",
      "country": "Chile",
      "type": "Stratovolcano",
      "lat": -23.3,
      "lon": -67.62,
      "elevation": 6046
    }]

  return (
    <Globe

      globeImageUrl="//unpkg.com/three-globe/example/img/earth-night.jpg"
      backgroundImageUrl="//unpkg.com/three-globe/example/img/night-sky.png"

      pointsData={volcanoes}
      pointLat="lat"
      pointLng="lon"

    />
  );
}

export default World

test.html

<head>
  <style> body { margin: 0; } </style>

  <script src="//unpkg.com/react/umd/react.production.min.js"></script>
  <script src="//unpkg.com/react-dom/umd/react-dom.production.min.js"></script>
  <script src="//unpkg.com/babel-standalone"></script>

  <script src="//unpkg.com/react-globe.gl"></script>
  <!--<script src="../../dist/react-globe.gl.js"></script>-->
</head>

<body>
<div id="globeViz"></div>

<script type="text/jsx">
  const World = () => {
    const volcanoes = [
    {
      "name": "Abu",
      "country": "Japan",
      "type": "Shield",
      "lat": 34.5,
      "lon": 131.6,
      "elevation": 641
    },
    {
      "name": "Acamarachi",
      "country": "Chile",
      "type": "Stratovolcano",
      "lat": -23.3,
      "lon": -67.62,
      "elevation": 6046
    }]

    return <Globe
      globeImageUrl="//unpkg.com/three-globe/example/img/earth-night.jpg"
      backgroundImageUrl="//unpkg.com/three-globe/example/img/night-sky.png"

      pointsData={volcanoes}
      pointLat="lat"
      pointLng="lon"
    />;
  };

  ReactDOM.render(
    <World />,
    document.getElementById('globeViz')
  );
</script>
</body>

Do you have any advice on how to troubleshoot this? I can send my zipped project if needed. Thank you!

arulandu commented 2 years ago

Weirdly, when I change the dynamic import to the following (as commented in #15), the points layer shows (during hot reload), but on manual reloads I get a hydration error.

let Globe = () => null;
if (typeof window !== "undefined") Globe = require("react-globe.gl").default;
OkuruchiDan commented 1 year ago

Hello! You can try to render globe with dynamic import like this: const GlobeComponent = dynamic(() => import("@/components/Globe"), { ssr: false }) and make sure you have "three": "^0.140.0", dependency.

ronalddas commented 1 year ago

"three": "^0.140.0"

I have set "three": "^0.140.0" dependency, but am still unable to useRef to change controls. Example Code

useEffect(() => {
        if(globeEl.current){
            globeEl.current.controls().enableZoom = false;
            globeEl.current.controls().autoRotate = true;
            globeEl.current.controls().autoRotateSpeed = 5;
        }

    }, []);

Am still getting ref undefined error

Unhandled Runtime Error
TypeError: globeEl.current.controls is not a function
DavidDolyak commented 1 year ago

Any update on this? Still having the issue.

OkuruchiDan commented 1 year ago

Any update on this? Still having the issue.

Just use any of lazy loading modes with React or dynamic import with Next.js. The reason you getting undefined error, that 'three.js' lib not rendered 3d object at all and you trying to access them, to prevent this situations put you object into component and lazy load it, so you will be sure that component rendered object and returned it.