xeokit / xeokit-sdk

Open source JavaScript SDK for viewing high-detail, full-precision 3D BIM and AEC models in the Web browser.
https://xeokit.io
Other
728 stars 287 forks source link

Import issues when using NextJS 12 #819

Closed lukvermeulen closed 2 years ago

lukvermeulen commented 2 years ago

Describe the bug Trying to use import Xeokit with React from Next.js throws ReferenceError: navigator is not defined or unexpected token 'export'

To Reproduce Steps to reproduce the behavior:

import {
  Viewer,
  XKTLoaderPlugin,
} from "@xeokit/xeokit-sdk/dist/xeokit-sdk.cjs"; 

-> throws navigator is not defined

import {
  Viewer,
  XKTLoaderPlugin,
} from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es"; 

-> throws unexpected token 'export'

Importing the package from a CDN (https://...) yields the same result.

The full component looks like this:

import { Viewer, XKTLoaderPlugin } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es";

type ViewerProps = {
  canvasId: string;
  models: { id: string; src: string }[];
};

const XktViewer: React.FC<ViewerProps> = (props) => {
  const viewer = new Viewer({
    canvasId: props.canvasId,
  });

  const { camera } = viewer.scene;

  camera.eye = [0, 0, 1];
  camera.look = [1, 0, 0];
  camera.up = [0, 0, 1];
  camera.project.far = 100000; // Make sure we don't clip the model

  const models = props.models;

  const xktLoader = new XKTLoaderPlugin(viewer);

  const loadedModels = models.map((model) =>
    xktLoader.load({ id: model.id, src: model.src })
  );

  return <canvas id={props.canvasId}></canvas>;
};

export default XktViewer;

Expected behavior I expect the import to work and do its magic so I can create a viewer.

Additional context

Any hints on how I could use this package with NextJS or what else I could try to get this working are greatly appreciated.

simplylogicninjas commented 2 years ago

@lukvermeulen Try to use dynamic imports with "SSR" mode set on false. By default all imports are also resolved for server side rendering. And the window object is then not available.

https://nextjs.org/docs/advanced-features/dynamic-import

Maybe this helps with importing Xeokit.

lukvermeulen commented 2 years ago

@lukvermeulen Try to use dynamic imports with "SSR" mode set on false. By default all imports are also resolved for server side rendering. And the window object is then not available.

https://nextjs.org/docs/advanced-features/dynamic-import

Maybe this helps with importing Xeokit.

Thanks for the Suggestion! I did try that already, but the navigator is not defined error remained. Maybe that has to do with my implementation though?

lukvermeulen commented 2 years ago

@simplylogicninjas I had a look at it again and came up with this solution now:

const XktViewer: React.FC<ViewerProps> = (props) => {
  const [viewer, setViewer] = useState();
  const [camera, setCamera] = useState();
  const [xktLoader, setXktLoader] = useState();
  const { classes } = useStyles();

  useEffect(() => {
    async function loadXeokit() {
      const Xeokit = await import("@xeokit/xeokit-sdk/dist/xeokit-sdk.min.es");

      const viewer = new Xeokit.Viewer({
        canvasId: props.canvasId,
      });

      setViewer(viewer);

      const { camera } = viewer.scene;
      setCamera(camera);

      const xktLoader = new Xeokit.XKTLoaderPlugin(viewer);
      setXktLoader(xktLoader);

      camera.eye = [0, 0, 1];
      camera.look = [1, 0, 0];
      camera.up = [0, 0, 1];
      camera.project.far = 100000; // Make sure we don't clip the model

      const models = props.models;
      const loadedModels = models.map((model) =>
        xktLoader.load({ id: model.id, src: model.src })
      );
    }
    loadXeokit();
  }, []);

  return <canvas id={props.canvasId} className={classes.viewer}></canvas>;
};

It appears to be working like this with NextJS / React, though I'd love some comments on how this could be solved cleaner!

It feels quite weird to put everything in state and create everything anew with every time this component updates, since Xeokit probably has its own object lifecycles that I don't want React to interfer with... but hey, it works for now.