Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeError: Cannot use 'in' operator to search for '__next_img_default' in undefined #63803

Closed
ojj1123 opened this issue Mar 28, 2024 · 9 comments · Fixed by #64036
Closed

TypeError: Cannot use 'in' operator to search for '__next_img_default' in undefined #63803

ojj1123 opened this issue Mar 28, 2024 · 9 comments · Fixed by #64036
Labels
bug Issue was opened via the bug report template. Image (next/image) Related to Next.js Image Optimization. locked

Comments

@ojj1123
Copy link
Contributor

ojj1123 commented Mar 28, 2024

Link to the code that reproduces this issue

https://github.com/ojj1123/next-custom-loader-issue-repro

To Reproduce

  1. pnpm install
  2. pnpm dev
  3. and then you can see the issue below:

Current vs. Expected behavior

Expected behavior

I want to configure the image loader globally.
Nextjs docs describes that I can configure the image loader at next.config.js:

const nextConfig = {
  images: {
    loader: 'custom',
    loaderFile: '/custom-loader.ts',
  },
};

export default nextConfig;

Current

But currently, nextjs occurs this error:
It is shown at either App router or Page router

TypeError: Cannot use 'in' operator to search for '__next_img_default' in undefined
스크린샷 2024-03-28 16 10 53

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.1.0: Mon Oct  9 21:28:12 PDT 2023; root:xnu-10002.41.9~6/RELEASE_ARM64_T8103
Binaries:
  Node: 18.19.0
  npm: 10.2.3
  Yarn: 1.22.21
  pnpm: 8.15.4
Relevant Packages:
  next: 14.1.4
  eslint-config-next: 14.1.4
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.4.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

App Router, Image optimization (next/image, next/legacy/image)

Which stage(s) are affected? (Select all that apply)

next dev (local), next build (local)

Additional context

This error occurs at getImgProps

let loader: ImageLoaderWithConfig = rest.loader || defaultLoader
// Remove property so it's not spread on <img> element
delete rest.loader
delete (rest as any).srcSet
// This special value indicates that the user
// didn't define a "loader" prop or "loader" config.
const isDefaultLoader = '__next_img_default' in loader

rest.loader is what the user pass loader function at Image's loader prop. But I didn't pass the custom loader at Image component so then the value of loader variable above would be defaultLoader. __next_img_default property is only in defaultLoader for checking whether or not the user pass the custom loader. I don't know why the value is undefined

Update

If setting images.loaderFile at next.config.js, defaultLoader module is resolved as config.images.loaderFile during build. So we have to export loader function as default. #63803 (comment)

...(config.images.loaderFile
? {
'next/dist/shared/lib/image-loader': config.images.loaderFile,
...(isEdgeServer && {
'next/dist/esm/shared/lib/image-loader': config.images.loaderFile,
}),
}
: undefined),

Nextjs internally import/export defaultLoader as a default function

// @ts-ignore - This is replaced by webpack alias
import defaultLoader from 'next/dist/shared/lib/image-loader'

After build, the build assets is like:

// next.config.js
const nextConfig = {
  images: {
    loader: 'custom',
    loaderFile: '/custom-loader.js'
  }
}

// custom-loader.js
// ⚠️ named export
export function customLoader({ src }) {
  return src
}

Since custom loader is exported as a named, defaultLoader.default is undefined

// build asset for getImgProps.js
 import defaultLoader from '/custom-loader.js' // Resolve through webpack alias

...
 let loader: ImageLoaderWithConfig = rest.loader || defaultLoader.default // ⚠️ defaultLoader.default is undefined
  
 // Remove property so it's not spread on <img> element 
 delete rest.loader 
 delete (rest as any).srcSet 
  
 // This special value indicates that the user 
 // didn't define a "loader" prop or "loader" config. 
 const isDefaultLoader = '__next_img_default' in loader 
...
@ojj1123 ojj1123 added the bug Issue was opened via the bug report template. label Mar 28, 2024
@github-actions github-actions bot added the Image (next/image) Related to Next.js Image Optimization. label Mar 28, 2024
@ojj1123
Copy link
Contributor Author

ojj1123 commented Mar 29, 2024

https://github.com/vercel/next.js/tree/c9439b5654432df6488e178e5ade6f4ad2d1cf6a/test/integration/next-image-new/loader-config

But why this test pass?

Next.js version for test environment : Next.js 14.2.0-canary.48

스크린샷 2024-03-29 13 40 26

Repro

https://github.com/ojj1123/next-custom-loader-issue-repro/tree/with-next-canary

So I try to upgrade nextjs version to Next.js 14.2.0-canary.48 and then reproduce it, but the error occurs.

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.1.0: Mon Oct  9 21:28:12 PDT 2023; root:xnu-10002.41.9~6/RELEASE_ARM64_T8103
  Available memory (MB): 8192
  Available CPU cores: 8
Binaries:
  Node: 18.19.0
  npm: 10.2.3
  Yarn: 1.22.21
  pnpm: 8.15.5
Relevant Packages:
  next: 14.2.0-canary.48 // Latest available version is detected (14.2.0-canary.48).
  eslint-config-next: 14.1.4
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.4.3
Next.js Config:
  output: N/A

@ojj1123
Copy link
Contributor Author

ojj1123 commented Mar 29, 2024

I found the reason.
Nextjs docs describes that the loader file must ONLY export a default function
so I did export as a default and then resolve this issue.

https://github.com/ojj1123/next-custom-loader-issue-repro/tree/export-default-loader

diff --git a/custom-loader.ts b/custom-loader.ts
index 461b1e3..d0af4f9 100644
--- a/custom-loader.ts
+++ b/custom-loader.ts
@@ -2,6 +2,10 @@

 import { ImageLoaderProps } from 'next/image';

-export function customLoader({ src, width, quality }: ImageLoaderProps) {
+export default function customLoader({
+  src,
+  width,
+  quality,
+}: ImageLoaderProps) {
   return `${src}?url=${encodeURIComponent(src)}&w=${width}&q=${quality || 75}`;
 }

Suggestion

BTW, How about supporting a named export?
some people(also me) use the eslint rule, import/no-default-export. Currenly whenever using the custom loader, I have to turn off this rule manually. For flexibility, I suggest supporting a named export for loader.

@devjiwonchoi
Copy link
Member

devjiwonchoi commented Mar 29, 2024

Hi, I'd say it is similar to how the next.config.js is imported, where it also requires a default export.
Since Next.js doesn't support multiple loaders, it is hard to target which one to import with named exports.
Glad you found out the cause by your own!

@ojj1123
Copy link
Contributor Author

ojj1123 commented Mar 29, 2024

@devjiwonchoi

Since Next.js doesn't support multiple loaders, it is hard to target which one to import with named exports.

Yeah, I agree with you.
Nextjs replaces defaultLoader with custom loader (images.loaderFile) through webpack config.resolve.alias

...(config.images.loaderFile
? {
'next/dist/shared/lib/image-loader': config.images.loaderFile,
...(isEdgeServer && {
'next/dist/esm/shared/lib/image-loader': config.images.loaderFile,
}),
}
: undefined),

// @ts-ignore - This is replaced by webpack alias
import defaultLoader from 'next/dist/shared/lib/image-loader'

But I think it would be good to warn users not to use named exports. Is it possible?

@devjiwonchoi
Copy link
Member

Yeah, an accurate error message is always preferred. Mind if I open a PR? Feel free to open one if you'd like to!

@ojj1123
Copy link
Contributor Author

ojj1123 commented Mar 29, 2024

Oh, so Could I try to PR? I want to investigate and resolve this issue!

@devjiwonchoi
Copy link
Member

devjiwonchoi commented Mar 29, 2024

Yeah, for sure! It's up to you if you'd like to contribute. Ping me if you want to discuss anything. 😄

@ojj1123
Copy link
Contributor Author

ojj1123 commented Apr 3, 2024

@devjiwonchoi

UPDATE
It's easier to check it during runtime. Because if checking it during buildtime, i should parse the code of a loader file so it's a bit complex. So I've decided to deal with it runtime.


I try to resolve this issue but I want to discuss about this issue.
My tries were to detect during runtime and buildtime

1. During runtime

After build (dev or build script), Webpack replaces defaultLoader with custom loader module. And then If exporting custom loader as named, _imageLoader.default(build result) is undefined. So I try to write this condition statements.

import defaultLoader from '.....'

if(typeof defaultLoader === 'undefined') {
  throw new Error('you should export the loader function as default');
}

It was working well. But I thought it's a bit hacky, because if doing that, I write that code wherever Next.js import defaultLoader. (Of course, just two now)
So I try to check it and throw the error during build.

2. During buildtime

I try to make the custom webpack loader for allowing our to use a default function.

// /webpack/loader/next-image-loader-file-loader.ts
const nextImageLoaderFileLoader: webpack.LoaderDefinitionFunction = function (
  this,
  source
) {
  const hasDefaultExport = /export default/.test(source)
  if (!hasDefaultExport) {
    throw new Error('you should export the loader function as default')
  }

  return source
}

// webpack.config.ts
...
module: {
  rules: [
      ...(config.images.loaderFile
      ? [
          {
            test: config.images.loaderFile,
            use: ['next-image-loader-file-loader'],
          },
        ]
      : []),
  ]
}
...

It was throwing the error well. But I thought it has some edge case:

function customLoader() {...}

export default customLoader; // correct
export customLoader; // throw error
export { customLoader as default } // throw error.. but this case is also default export so it should have not to throw the error!!

It's a bit difficult to check whether or not default function during either runtime or buildtime.
How do you think? Do you come up with another solution?

styfle added a commit that referenced this issue Apr 10, 2024
…efault function (#64036)

<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:

## For Contributors

### Fixing a bug

- Related issues linked using `fixes #number`
- Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md

-->

fixes #63803 

## What I do?

- If the loader file export the function as `named`, Next.js throws the
error.
- But this error is a bit confusing for the developers.
- So I open this PR for showing the accurate error message.

## AS-IS / TO-BE

### AS-IS

```
TypeError: Cannot use 'in' operator to search for '__next_img_default' in undefined
```
<img width="1202" alt="스크린샷 2024-03-28 16 10 53"
src="https://github.com/vercel/next.js/assets/33178048/e7c81cb5-7976-46ff-b86f-9c8fd9a7a681">

### TO-BE

```
Error: The loader file must export a default function that returns a string.
See more info here: https://nextjs.org/docs/messages/invalid-images-config
```
<img width="500" alt="스크린샷 2024-03-28 16 10 53"
src="https://github.com/vercel/next.js/assets/33178048/c391e61b-6a44-4f85-8600-28ab6cb5b0eb">

---------

Co-authored-by: Steven <steven@ceriously.com>
Copy link
Contributor

This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 26, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Issue was opened via the bug report template. Image (next/image) Related to Next.js Image Optimization. locked
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants