从create-react-app转换到NextJS,ThreeJS有问题



我在react中有一个可用的三维模型查看器,我现在正试图将其移植到Next。我需要GLTFLoader和OrbitControls,它们给了我反应中的问题。我加载了这样的:

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

不能这么做,因为我会在下一篇文章中得到这个错误:

SyntaxError: Cannot use import statement outside a module
This error happened while generating the page. Any console logs will be displayed in the terminal window.
Source
.nextserverpagesindex.js (1:0) @ Object.three/examples/jsm/loaders/GLTFLoader
> 1 | module.exports = require("three/examples/jsm/loaders/GLTFLoader");

然后我尝试使用three-std库,并从那里导入控件和加载程序。同样的错误。然后我尝试使用require("(导入它,但再次出现相同的错误。我在谷歌上发现了一些几乎类似的问题,但没有容易理解的解决方案。

完整代码:

import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
// import Model3d from '../assets/centered.gltf'
// import Model3d from '../assets/3dmodel.gltf'
import D3d from '../assets/images/3d.svg'
import React from 'react'
// let GLTFLoader
// let OrbitControls
const VisA = () => {
// GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader').GLTFLoader
// OrbitControls = require('three/examples/js/controls/OrbitControls')
//   .OrbitControls
const { useRef, useEffect } = React
const mount = useRef(null)
const hitbox = useRef(null)
const controls = useRef(null)
useEffect(() => {
let width = mount.current.clientWidth
let height = mount.current.clientHeight
let frameId
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
const loader = new GLTFLoader()
const camcontrols = new OrbitControls(camera, hitbox.current)

// loader.load(Model3d, function (gltf) {
loader.load('./3dmodel/3dmodel.gltf', function (gltf) {
gltf.scene.scale.set(14, 14, 14)
var box = new THREE.Box3().setFromObject(gltf.scene)
var center = new THREE.Vector3()
box.getCenter(center)
gltf.scene.position.sub(center)
scene.add(gltf.scene)
})
const hemiLight = new THREE.HemisphereLight(0xffeeb1, 0x80820, 20)
const light2 = new THREE.DirectionalLight(0xfdeee1, 20)
const spotLight = new THREE.SpotLight(0xfdeee1, 4)
const spotLight2 = new THREE.SpotLight(0xfdeee1, 4)
scene.add(hemiLight)
scene.add(spotLight)
scene.add(spotLight2)
light2.position.set(10, -50, 500)
scene.add(light2)
scene.rotation.y = -1.65
camera.position.z = 4
// scene.add(cube)
// renderer.setClearColor('#FFFFFF')
renderer.setSize(width, height)
const renderScene = () => {
renderer.render(scene, camera)
}
const handleResize = () => {
width = mount.current.clientWidth
height = mount.current.clientHeight
renderer.setSize(width, height)
camera.aspect = width / height
camera.updateProjectionMatrix()
renderScene()
}
const animate = () => {
spotLight.position.set(
camera.position.x + 10,
camera.position.y + 10,
camera.position.z + 10
)
spotLight2.position.set(
camera.position.x - 10,
camera.position.y - 10,
camera.position.z - 10
)
renderScene()
frameId = window.requestAnimationFrame(animate)
}
const start = () => {
if (!frameId) {
frameId = requestAnimationFrame(animate)
}
}
const stop = () => {
cancelAnimationFrame(frameId)
frameId = null
}
mount.current.appendChild(renderer.domElement)
window.addEventListener('resize', handleResize)
start()
controls.current = { start, stop }

return () => {
stop()
window.removeEventListener('resize', handleResize)
mount.current.removeChild(renderer.domElement)

}
}, [])
return (
<div
className="relative flex items-center justify-center w-full h-full "
ref={mount}
// onClick={() => setAnimating(!isAnimating)}
>
<div className="absolute w-full h-full">
<D3d className="w-10 h-10 mt-10 text-gray-800 fill-current " />
</div>
<div className="absolute w-3/5 h-4/5" ref={hitbox}></div>
</div>
)
}
export default VisA

问题

您正在导入以ESM格式编写的模块,当NextJS试图在服务器端渲染时,您的代码节点不理解ESM——node.js通常需要CJS(CommonJS(。

解决方案

这取决于情况

1.是否希望使用three的代码在服务器端运行

(这假设three可以在节点中运行,我不能这样说或那样说(

我认为这里有几个选择:

a(使用与Node.js和Browser兼容的three版本。浏览一下这个问题,你可以考虑尝试三个通用(我没有用过(。

b(自定义Webpack行为,将three代码捆绑并转换为服务器的CJS。就我个人而言,我不建议将此作为第一步。自定义Webpack可能会很快变得非常复杂,在NextJS中这样做也会增加复杂性。

2.使用three修改代码,使其仅在客户端运行

使用动态导入。参考文献:

您可能并不总是希望在服务器端包含模块。例如,当模块包含仅在浏览器中工作的库时。

其他反馈

请注意,上面的答案是概念性的。如果你能够通过存储库或其他方式提供复制,我可能会给出一个更具体的解决方案。

在Mcy的帮助下,我发现在我的情况下添加动态导入是最简单的。这很容易,我没有从react中更改查看器组件中的任何内容,并在pages/index.js上加载了该组件:

import dynamic from 'next/dynamic' 
const Dynamic3dViewr = dynamic(() => import('../components/Viewer3d.js'), {
ssr: false,
})

最新更新