Next.js: Cannot use declared variables in import inside dynamic callback

Created on 18 Sep 2020  路  10Comments  路  Source: vercel/next.js

Bug report

Describe the bug

I'm currently using v9.5.0 and when attempting to use a variable defined inside the callback of dynamic it throws an error: ReferenceError: locale is not defined.
Although if I move the variable definition to outside the function it works fine.

To Reproduce

Code snippet:

Failed example:

const DynamicComponent = dynamic(
  () => {
    const locale = getUserLocale();
    console.log(locale); // It doesn't even reach here ...
    return import(`~/translations/${locale}.json`)
      .then(handleFileContent)
  },
  { loading: () => <p>Loading...</p>, ssr: false }
)

// error output: "ReferenceError: locale is not defined"

Working example:

const locale = getUserLocale();
const DynamicComponent = dynamic(
  () => {
    console.log(locale); // console.log works as expected
    return import(`~/translations/${locale}.json`)
      .then(handleFileContent)
  },
  { loading: () => <p>Loading...</p>, ssr: false }
)

Expected behavior

Variables declared inside the callback of dynamic should be allowed to use during import call.

System information

  • OS: macOs
  • Version of Next.js: 9.5.0
  • Version of Node.js: 13.14.0
good first issue

All 10 comments

Forgot to mention but the following code example also works fine:

const locale = getUserLocale();
const DynamicComponent = dynamic(
  () => {
    console.log(locale); // console.log works as expected
    const foo = 'bar';
    console.log(bar); // this also works just fine
    return import(`~/translations/${locale}.json`)
      .then(handleFileContent)
  },
  { loading: () => <p>Loading...</p>, ssr: false }
)

Just wanted to demonstrate that variables inside the function work as expected, the only failing case is when calling import with those variables.

I looked into this issue and this seems to be an issue on Webpack side. It seems Webpack probably limits you and you can not use variable inside the import at the build time. Would be happy to take a further look at it but not sure how to proceed.

Also, found this one related question on StackOverflow.

@MihirGH but you can use variables in the import() call, I have a working example on the issue description so I'm not sure webpack is the one to blame here.

The problem comes from the execution of the callback of dynamic() from what I understood.

Let me know if you want to investigate further, I don't know where to start either.
If someone could share some knowledge it would be great 馃槈

So I did the debugging on my chrome and not on the server side. Let me check it again on the server what's happening.

@thegiantbeast you can build the Next repo locally and use that locally build version of Next, inside your own app. In the local build, you can put logs or jump through debugger.

You can refer to the Contributing guide. Did the same today and setting it up locally is really easy.

So, this is my setup, I have forked the with-dynamic-export example locally to replicate the above scenario:

const getComponentName = (index) => `hello${index}`

const DynamicComponent1 = dynamic(() => import('../components/hello1'))

const DynamicComponent2WithCustomLoading = dynamic(
  () => {
    const componentName = getComponentName(2)
    console.log('componentName', componentName)
    return import(`../components/${componentName}`)
  },
  { loading: () => <p>Loading caused by client page transition ...</p> }
)

When I go and look at the index.js inside .next folder, these two components are _resolved_ differently. That's why I think something is wrong with Webpack when using variables inside import (have added new lines for readability)

eval("__webpack_require__.r(__webpack_exports__);

/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"react\");

* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);

/* harmony import */ var _components_Header__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../components/Header */ \"./components/Header.js\");\n

/* harmony import */ var next_dynamic__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! next/dynamic */ \"next/dynamic\");

/* harmony import */ var next_dynamic__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(next_dynamic__WEBPACK_IMPORTED_MODULE_2__);\nvar _jsxFileName = \"/Users/mihirshah/FrontEndLearning/next.js/examples/with-dynamic-import/pages/index.js\";

var __jsx = react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement;

const getComponentName = index => `hello${index}`;

const DynamicComponent1 = next_dynamic__WEBPACK_IMPORTED_MODULE_2___default()(() => __webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ../components/hello1 */ \"./components/hello1.js\")), {\n  loadableGenerated: {\n    webpack: () => [/*require.resolve*/(/*! ../components/hello1 */ \"./components/hello1.js\")],\n    modules: ['../components/hello1']\n  }\n});

const DynamicComponent2WithCustomLoading = next_dynamic__WEBPACK_IMPORTED_MODULE_2___default()(() => {\n  const componentName = getComponentName(2);\n  console.log('componentName', componentName);\n  return __webpack_require__(\"./components lazy recursive ^\\\\.\\\\/.*$\")(`./${componentName}`);\n}, {\n  loading: () => __jsx(\"p\", {\n    __self: undefined,\n    __source: {\n      fileName: _jsxFileName,\n      lineNumber: 15,\n      columnNumber: 20\n    }\n  }, \"Loading caused by client page transition ...\"),\n  loadableGenerated: {\n    webpack: () => [/*require.resolve*/(__webpack_require__(\"./components weak recursive ^\\\\.\\\\/.*$\").resolve(`./${componentName}`))],\n    modules: [`../components/${componentName}`]\n  }\n});")

In the first case, it is using something called __webpack_require__.e(/*! import() */ 0)

In the second case, it basically generated its own helper function and using that __webpack_require__(\"./components lazy recursive ^\\\\.\\\\/.*$\") and calling it with ./${componentName}

I am not much of an expert with Webpack so I don't know if this is helpful or not, but this is what I was able to find.

@MihirGH while you're at it can you output the following example:

const component2 = `hello2`

const DynamicComponent2WithCustomLoading = dynamic(
  () => {
    console.log('componentName', component2)
    return import(`../components/${component2}`)
  },
  { loading: () => <p>Loading caused by client page transition ...</p> }
)

Because this example is working for me and maybe we can compare with your previous one and see if we can see any major difference.

Interesting! This works even though inside index.js the code is similar to above 馃く

For this case also, the output is similar to what I just posted above:

eval("const DynamicComponent2WithCustomLoading = next_dynamic__WEBPACK_IMPORTED_MODULE_2___default()(() => {
console.log('componentName', componentName);
return __webpack_require__(\"./components lazy recursive ^\\\\.\\\\/.*$\")(`./${componentName}`)}, {
  loading: () => __jsx(\"p\", {\n    __self: undefined,\n    __source: {\n      fileName: _jsxFileName,\n      lineNumber: 15,\n      columnNumber: 20\n    }\n  }, \"Loading caused by client page transition ...\"),\n  loadableGenerated: {\n    webpack: () => [/*require.resolve*/(__webpack_require__(\"./components weak recursive ^\\\\.\\\\/.*$\").resolve(`./${componentName}`))],\n    modules: [`../components/${componentName}`]\n  }\n})")

I think now only @timneutkens can guide us on this journey :D

Was this page helpful?
0 / 5 - 0 ratings

Related issues

formula349 picture formula349  路  3Comments

sospedra picture sospedra  路  3Comments

olifante picture olifante  路  3Comments

rauchg picture rauchg  路  3Comments

timneutkens picture timneutkens  路  3Comments