vladmandic / human

Human: AI-powered 3D Face Detection & Rotation Tracking, Face Description & Recognition, Body Pose Tracking, 3D Hand & Finger Tracking, Iris Analysis, Age & Gender & Emotion Prediction, Gaze Tracking, Gesture Recognition
https://vladmandic.github.io/human/demo/index.html
MIT License
2.31k stars 320 forks source link

Can not import ESM bundle for browser with NextJS 14 #418

Closed tembra closed 7 months ago

tembra commented 7 months ago

Issue Description

When importing Human on a NextJS 14 project, the framework tries to import the node bundle instead of ESM for browsers, despite adding 'use client' on top of the page file.

Since node bundle requires a tfjs backend, the error below comes in:

./node_modules/@vladmandic/human/dist/human.node.js:7:1678
Module not found: Can't resolve '@tensorflow/tfjs-node'

I know that this is not an issue within the Human package at all and it's about NextJS SSR. However I guess you may help me to achieve the desired solution.

I already tried to load Human within a separated dynamic loaded component with dynamic(() => import('@/components/Human'), { ssr: false }), but it also did not work.

I also tried to import it inside a useEffect of the Human component created as you did in your class constructor in NextJS demo, but it also did not work.

I also tried to import Human explicitly from @vladmandic/human/dist/human.esm but it did not found the path.

Everything works fine if I use Vite instead of NextJS to create my project.

Steps to Reproduce

  1. Create a new project with npx create-next-app app-name and choose all default configs.
  2. Run npm i
  3. Change app/page.tsx to add 'use client' on top the file and add import to Human import { Human } from '@vladmandic/human.
  4. Run npm run dev
  5. Access the URL on your browser

Expected Behavior

NextJS identify that I'm using a client component on that page and load ESM bundle of Human package instead of node bundle.

Environment

Diagnostics

Additional

jpieters commented 7 months ago

can you share the code? I had some issues at first with Next.js 14, but got it working. Ensure that you are explicitly using the "use client"; where appropriate.

tembra commented 7 months ago

@jpieters You can check the issue/example of the code in this repo: https://github.com/tembra/human-nextjs-14

There I tried many ways to load Human, each one in a specific intuitive named route, and they all fail.

Notice that even on /load-on-button-click the error occurs during the build time. It seems that TypeScript/NextJS is executing the code inside the callback function that is assigned to a variable, but never called.

Even if NextJS is pre-rendering client components/pages on the server for the initial page load, it does not makes sense that this function is being executed.

Said that I think that import keyword is the problem, with TypeScript/NextJS trying to immediately find/import the module during the build time. Thinking this way I also tried to use (and also change/adapt) the webpack configuration from next.config.js of the human-next demo.

If you can share how you got it working it will be awesome. I'm really out of ideas.

tembra commented 7 months ago

After digging deeper on webpack configuration I managed to get it working using this next.config.mjs configuration:

/** @type {import('next').NextConfig} */
const nextConfig = {
    webpack: (config, { webpack, isServer }) => {
        if (isServer) {
            config.plugins.push(
                new webpack.IgnorePlugin({
                    checkResource(resource) {
                        if (resource === '@vladmandic/human') {
                            // ignore human package on server
                            return true
                        }

                        return false
                    },
                })
                // or a shorter (but not too explicit) version
                // new webpack.IgnorePlugin({ resourceRegExp: /^@vladmandic\/human$/ })
            )
        }

        return config
    }
}

export default nextConfig

With this configuration all my examples of the shared code work.

However I obviously got the error below during page compiling. So this is not the correct approach.

$ npm run dev

> human-nextjs-14@0.1.0 dev
> next dev

   ▲ Next.js 14.1.0
   - Local:        http://localhost:3000

 ✓ Ready in 6s
 ○ Compiling / ...
 ✓ Compiled / in 8.4s (477 modules)
 ⨯ Error: Cannot find module '@vladmandic/human'
    at webpackMissingModule (./app/page.tsx:7:50)
    at eval (./app/page.tsx:7:142)
    at (ssr)/./app/page.tsx (.../human-nextjs-14/.next/server/app/page.js:162:1)
    at __webpack_require__ (.../human-nextjs-14/.next/server/webpack-runtime.js:33:42)
    at JSON.parse (<anonymous>)
 ✓ Compiled in 243ms (230 modules)

I also still want to know WHY the module is being imported/resolved by webpack on server rendering if it is lazy loaded inside a callback.

Anyone has any thoughts/explanations or am I missing something in my code?

vladmandic commented 7 months ago

since this is not an issue with human itself, this is better suited for discussions. also, i'm not a next.js user, so really cannot help much. i've experimented with next long time ago and while i liked the concept, i really did not like how its build/packaging process and weird webpack worked.