📐

Next.js / TypeScriptでThree.js(react-three-fiber)を使うまで

2021/06/25に公開2

TypeScriptを使ったNext.js環境で、Three.jsを使えるようにしていくまでの課程です。
汎用的に使えるようにできるだけ最低限の構成を目指しています。
Three.jsはあまり触れたことがないので私自身よくわかっていないのが正直なところです。
(ご指摘あればお気軽にお願いいたします。)

Three.jsのReactWrapper(react-three-fiber)を使いました。
Hooksなどの用意もあって、オーバーヘッドもない(公式見解)とのことなので、簡潔なコードを書くためにも導入しました。

途中ビルドエラーもあったので、解消方法も記しておきます。

バージョン情報

パッケージ バージョン
next 10.2.3
typescript 4.3.2
@react-three/fiber ^7.0.1
react 17.0.2

パッケージ追加

Next.jsはインストールしておく

yarn add three @types/three @react-three/fiber

React (Typescript) + react-three-fiber + three-vrmでVRMモデルを表示してみる
こちらの記事を参考にサンプルページを作成

ページを作成

three.tsx
import { Canvas, useFrame } from "@react-three/fiber";
import { Mesh } from "three";
import React, { useRef } from "react";
import Controls from "../utils/Controls";

const Thing = () => {
  const ref = useRef({} as Mesh);
  useFrame(() => (ref.current.rotation.z += 0.01));
  return (
    <mesh
      ref={ref}
      onClick={(e) => console.log("click")}
      onPointerOver={(e) => console.log("hover")}
      onPointerOut={(e) => console.log("unhover")}
    >
      <planeBufferGeometry attach="geometry" args={[1, 1]} />
      <meshBasicMaterial
        attach="material"
        color="hotpink"
        opacity={0.5}
        transparent
      />
    </mesh>
  );
};

const Three: React.FC = React.memo(() => {
  return (
    <div style={{ width: "100vw", height: "100vh" }}>
      <Canvas>
        <Thing />
        <Controls />
        <gridHelper />
      </Canvas>
    </div>
  );
});
export default Three;
../utils/Controls.tsx
import React, { useRef } from "react";
import {
  extend,
  ReactThreeFiber,
  useThree,
  useFrame,
} from "@react-three/fiber";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";

extend({ OrbitControls });

declare global {
  namespace JSX {
    interface IntrinsicElements {
      readonly orbitControls: ReactThreeFiber.Object3DNode<
        OrbitControls,
        typeof OrbitControls
      >;
    }
  }
}

export default function Controls(
  props: ReactThreeFiber.Object3DNode<OrbitControls, typeof OrbitControls>
) {
  const {
    camera,
    gl: { domElement },
  } = useThree();
  const controls = useRef({} as OrbitControls);
  useFrame(() => controls.current.update());
  return (
    <orbitControls {...props} ref={controls} args={[camera, domElement]} />
  );
}

ここでちょっとつまづき

OrbitControlマウスコントロールモジュールをimportしてみたが、Next.js / Typescript環境では以下のエラーが出てビルドエラーが発生した

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: ../node_modules/three/examples/jsm/controls/OrbitControls.js
require() of ES modules is not supported.
require() of ../node_modules/three/examples/jsm/controls/OrbitControls.js from ../.next/server/pages/three.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 OrbitControls.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from ../node_modules/three/examples/jsm/package.json.
    at new NodeError (node:internal/errors:363:5)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1126:13)
    at Module.load (node:internal/modules/cjs/loader:989:32)
    at Function.Module._load (node:internal/modules/cjs/loader:829:14)
    at Module.require (node:internal/modules/cjs/loader:1013:19)
    at require (node:internal/modules/cjs/helpers:93:18)
    at Object.three/examples/jsm/controls/OrbitControls (../.next/server/pages/three.js:77:18)
    at __webpack_require__ (../.next/server/webpack-runtime.js:33:42)
    at eval (webpack-internal:///./utils/Controls.tsx:11:99)
    at Object../utils/Controls.tsx (../.next/server/pages/three.js:33:1)
    at __webpack_require__ (../.next/server/webpack-runtime.js:33:42)
    at eval (webpack-internal:///./pages/three.tsx:8:73)
    at Object../pages/three.tsx (../.next/server/pages/three.js:22:1)
    at __webpack_require__ (../.next/server/webpack-runtime.js:33:42)
    at __webpack_exec__ (../.next/server/pages/three.js:87:52)
    at ../.next/server/pages/three.js:88:28 {
  code: 'ERR_REQUIRE_ESM'
}

ビルドエラー解消

ES Moduleで書かれたライブラリをNext.jsでimportすると ERR_REQUIRE_ESM エラーが起こるらしい

しばらく調べると、ESMモジュールをトランスパイルできるライブラリがあるらしいので使ってみる

yarn add -D next-transpile-modules
next.config.js
const withTM = require('next-transpile-modules')(['three'])
module.exports = withTM()

参考 :
https://r3fdocs.vercel.app/getting-started/installation#nextjs

ビルドすると今度は、以下のようなwebpack周りのエラーがでるので、

const isLocal=request.startsWith('.')||// Always check for unix-style path, as webpack sometimes

https://github.com/vercel/next.js/discussions/23498

webpack 5を使うように設定する

next.config.js
const withTM = require('next-transpile-modules')(['three'])
module.exports = withTM({
  future: {
    webpack5: true
  }
})

Next.jsでThreeを使ってOrbitControlを実装して、ビルド出来た

参考記事

React (Typescript) + react-three-fiber + three-vrmでVRMモデルを表示してみる
超楽しくてカッコいい、Reactで始めるThree.js入門

公式ドキュメント

Next.js
Three.js
react-three-fiber

Discussion