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

When using SWC, code wrapped in if (typeof window === 'undefined') is not removed from the browser bundle. #30892

Open
rgoldfinger-quizlet opened this issue Nov 3, 2021 · 7 comments
Labels
SWC Related to minification/transpilation in Next.js.

Comments

@rgoldfinger-quizlet
Copy link

What version of Next.js are you using?

12.0.2

What version of Node.js are you using?

16.8.0

What browser are you using?

Chrome / N/A

What operating system are you using?

macOS

How are you deploying your application?

other

Describe the Bug

When using SWC, code wrapped in if (typeof window === 'undefined') is not removed from the browser bundle.

Without a .babelrc in my project (and confirming that it's using SWC via lack of warn - Disabled SWC as replacement for Babel warning), I see that server-side libraries are loaded in the browser bundle.

See repro.

Expected Behavior

Code wrapped in if (typeof window === 'undefined') is not included in the browser bundle.

To Reproduce

Original code:

import { getDocumentData } from 'utils/data/documentData';
import type { QGetServerSidePropsContext } from 'utils/serverSideProps/types';

export async function withServerSideTranslations(
  context: QGetServerSidePropsContext
) {
  if (typeof window === 'undefined') {
    const serverSideTranslations = (
      await import('next-i18next/serverSideTranslations')
    ).serverSideTranslations;
    const data = await getDocumentData(context.req);
    return {
      props: await serverSideTranslations(
        data.localePreference || context.locale!
      ),
    };
  }
  return { props: {} };
}

Browser output using SWC:

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
  /* harmony export */ withServerSideTranslations: function () {
    return /* binding */ withServerSideTranslations;
  },
  /* harmony export */
});
/* harmony import */ var regenerator_runtime__WEBPACK_IMPORTED_MODULE_0__ =
  __webpack_require__(
    /*! regenerator-runtime */ "../../node_modules/regenerator-runtime/runtime.js"
  );
/* harmony import */ var regenerator_runtime__WEBPACK_IMPORTED_MODULE_0___default =
  /*#__PURE__*/ __webpack_require__.n(
    regenerator_runtime__WEBPACK_IMPORTED_MODULE_0__
  );
/* harmony import */ var utils_data_documentData__WEBPACK_IMPORTED_MODULE_1__ =
  __webpack_require__(
    /*! utils/data/documentData */ "./utils/data/documentData.ts"
  );
/* module decorator */ module = __webpack_require__.hmd(module);
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try {
    var info = gen[key](arg);
    var value = info.value;
  } catch (error) {
    reject(error);
    return;
  }
  if (info.done) {
    resolve(value);
  } else {
    Promise.resolve(value).then(_next, _throw);
  }
}
function _asyncToGenerator(fn) {
  return function () {
    var self = this,
      args = arguments;
    return new Promise(function (resolve, reject) {
      var gen = fn.apply(self, args);
      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
      }
      function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
      }
      _next(undefined);
    });
  };
}
function _withServerSideTranslations() {
  _withServerSideTranslations = _asyncToGenerator(
    regenerator_runtime__WEBPACK_IMPORTED_MODULE_0___default().mark(
      function _callee(context) {
        var serverSideTranslations, data;
        return regenerator_runtime__WEBPACK_IMPORTED_MODULE_0___default().wrap(
          function _callee$(_ctx) {
            while (1)
              switch ((_ctx.prev = _ctx.next)) {
                case 0:
                  if (true) {
                    _ctx.next = 11;
                    break;
                  }
                  _ctx.next = 3;
                  return __webpack_require__
                    .e(
                      /*! import() */ "node_modules_next-i18next_serverSideTranslations_js"
                    )
                    .then(
                      __webpack_require__.t.bind(
                        __webpack_require__,
                        /*! next-i18next/serverSideTranslations */ "../../node_modules/next-i18next/serverSideTranslations.js",
                        23
                      )
                    );
                case 3:
                  serverSideTranslations = _ctx.sent.serverSideTranslations;
                  _ctx.next = 6;
                  return (0,
                  utils_data_documentData__WEBPACK_IMPORTED_MODULE_1__.getDocumentData)(
                    context.req
                  );
                case 6:
                  data = _ctx.sent;
                  _ctx.next = 9;
                  return serverSideTranslations(
                    data.localePreference || context.locale
                  );
                case 9:
                  _ctx.t0 = _ctx.sent;
                  return _ctx.abrupt("return", {
                    props: _ctx.t0,
                  });
                case 11:
                  return _ctx.abrupt("return", {
                    props: {},
                  });
                case 12:
                case "end":
                  return _ctx.stop();
              }
          },
          _callee
        );
      }
    )
  );
  return _withServerSideTranslations.apply(this, arguments);
}

function withServerSideTranslations(context) {
  return _withServerSideTranslations.apply(this, arguments);
}
var _a, _b;
// Legacy CSS implementations will `eval` browser code in a Node.js context
// to extract CSS. For backwards compatibility, we need to check we're in a
// browser context before continuing.
if (
  typeof self !== "undefined" &&
  // AMP / No-JS mode does not inject these helpers:
  "$RefreshHelpers$" in self
) {
  var currentExports = module.__proto__.exports;
  var prevExports =
    (_b =
      (_a = module.hot.data) === null || _a === void 0
        ? void 0
        : _a.prevExports) !== null && _b !== void 0
      ? _b
      : null;
  // This cannot happen in MainTemplate because the exports mismatch between
  // templating and execution.
  self.$RefreshHelpers$.registerExportsForReactRefresh(
    currentExports,
    module.id
  );
  // A module can be accepted automatically based on its exports, e.g. when
  // it is a Refresh Boundary.
  if (self.$RefreshHelpers$.isReactRefreshBoundary(currentExports)) {
    // Save the previous exports on update so we can compare the boundary
    // signatures.
    module.hot.dispose(function (data) {
      data.prevExports = currentExports;
    });
    // Unconditionally accept an update to this module, we'll check if it's
    // still a Refresh Boundary later.
    module.hot.accept();
    // This field is set when the previous version of this module was a
    // Refresh Boundary, letting us know we need to check for invalidation or
    // enqueue an update.
    if (prevExports !== null) {
      // A boundary can become ineligible if its exports are incompatible
      // with the previous exports.
      //
      // For example, if you add/remove/change exports, we'll want to
      // re-execute the importing modules, and force those components to
      // re-render. Similarly, if you convert a class component to a
      // function, we want to invalidate the boundary.
      if (
        self.$RefreshHelpers$.shouldInvalidateReactRefreshBoundary(
          prevExports,
          currentExports
        )
      ) {
        module.hot.invalidate();
      } else {
        self.$RefreshHelpers$.scheduleUpdate();
      }
    }
  } else {
    // Since we just executed the code for the module, it's possible that the
    // new exports made it ineligible for being a boundary.
    // We only care about the case when we were _previously_ a boundary,
    // because we already accepted this update (accidental side effect).
    var isNoLongerABoundary = prevExports !== null;
    if (isNoLongerABoundary) {
      module.hot.invalidate();
    }
  }
}

Browser output using Babel:

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
  /* harmony export */ withServerSideTranslations: function () {
    return /* binding */ withServerSideTranslations;
  },
  /* harmony export */
});
/* harmony import */ var _opt_projects_quizlet_node_modules_next_node_modules_babel_runtime_helpers_esm_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__ =
  __webpack_require__(
    /*! ../../node_modules/next/node_modules/@babel/runtime/helpers/esm/asyncToGenerator */ "../../node_modules/next/node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js"
  );
/* harmony import */ var core_js_modules_es_promise_js__WEBPACK_IMPORTED_MODULE_1__ =
  __webpack_require__(
    /*! core-js/modules/es.promise.js */ "../../node_modules/core-js/modules/es.promise.js"
  );
/* harmony import */ var core_js_modules_es_promise_js__WEBPACK_IMPORTED_MODULE_1___default =
  /*#__PURE__*/ __webpack_require__.n(
    core_js_modules_es_promise_js__WEBPACK_IMPORTED_MODULE_1__
  );
/* harmony import */ var utils_data_documentData__WEBPACK_IMPORTED_MODULE_2__ =
  __webpack_require__(
    /*! utils/data/documentData */ "./utils/data/documentData.ts"
  );
/* module decorator */ module = __webpack_require__.hmd(module);
function withServerSideTranslations(_x) {
  return _withServerSideTranslations.apply(this, arguments);
}
function _withServerSideTranslations() {
  _withServerSideTranslations = (0,
  _opt_projects_quizlet_node_modules_next_node_modules_babel_runtime_helpers_esm_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__[
    "default"
  ])(function* (context) {
    if (false) {
      var data, serverSideTranslations;
    }
    return {
      props: {},
    };
  });
  return _withServerSideTranslations.apply(this, arguments);
}
var _a, _b;
// Legacy CSS implementations will `eval` browser code in a Node.js context
// to extract CSS. For backwards compatibility, we need to check we're in a
// browser context before continuing.
if (
  typeof self !== "undefined" &&
  // AMP / No-JS mode does not inject these helpers:
  "$RefreshHelpers$" in self
) {
  var currentExports = module.__proto__.exports;
  var prevExports =
    (_b =
      (_a = module.hot.data) === null || _a === void 0
        ? void 0
        : _a.prevExports) !== null && _b !== void 0
      ? _b
      : null;
  // This cannot happen in MainTemplate because the exports mismatch between
  // templating and execution.
  self.$RefreshHelpers$.registerExportsForReactRefresh(
    currentExports,
    module.id
  );
  // A module can be accepted automatically based on its exports, e.g. when
  // it is a Refresh Boundary.
  if (self.$RefreshHelpers$.isReactRefreshBoundary(currentExports)) {
    // Save the previous exports on update so we can compare the boundary
    // signatures.
    module.hot.dispose(function (data) {
      data.prevExports = currentExports;
    });
    // Unconditionally accept an update to this module, we'll check if it's
    // still a Refresh Boundary later.
    module.hot.accept();
    // This field is set when the previous version of this module was a
    // Refresh Boundary, letting us know we need to check for invalidation or
    // enqueue an update.
    if (prevExports !== null) {
      // A boundary can become ineligible if its exports are incompatible
      // with the previous exports.
      //
      // For example, if you add/remove/change exports, we'll want to
      // re-execute the importing modules, and force those components to
      // re-render. Similarly, if you convert a class component to a
      // function, we want to invalidate the boundary.
      if (
        self.$RefreshHelpers$.shouldInvalidateReactRefreshBoundary(
          prevExports,
          currentExports
        )
      ) {
        module.hot.invalidate();
      } else {
        self.$RefreshHelpers$.scheduleUpdate();
      }
    }
  } else {
    // Since we just executed the code for the module, it's possible that the
    // new exports made it ineligible for being a boundary.
    // We only care about the case when we were _previously_ a boundary,
    // because we already accepted this update (accidental side effect).
    var isNoLongerABoundary = prevExports !== null;
    if (isNoLongerABoundary) {
      module.hot.invalidate();
    }
  }
}

Babel config for reference:

{
  "presets": ["next/babel"],
  "plugins": [
    "@babel/plugin-proposal-logical-assignment-operators",
    ["@babel/plugin-proposal-private-methods", { "loose": false }],
    ["@babel/plugin-proposal-private-property-in-object", { "loose": false }]
  ]
}
@rgoldfinger-quizlet rgoldfinger-quizlet added the bug Issue was opened via the bug report template. label Nov 3, 2021
@timneutkens timneutkens added the SWC Related to minification/transpilation in Next.js. label Nov 4, 2021
@SirensOfTitan
Copy link

SirensOfTitan commented Dec 6, 2021

It'd be amazing to this fixed, we're blocked on upgrading to next 12+SWC because of this. I'm super happy to attempt a PR if it would be helpful. Still seeing this on 12.0.7.

Thank you for an amazing project!

@balazsorban44
Copy link
Member

Hi, since the reproduction is somewhat obscure, could any of you add a minimal reproduction so that I can investigate, but also test out next@canary first if it might have been resolved already? Thanks. 🙏

@SirensOfTitan
Copy link

@balazsorban44: Here's a simple reproduction: https://github.com/SirensOfTitan/next-30892

  1. Run the project in dev mode: npm run dev and hit http://localhost:3000/, notice a blank page and an error related to fs not being found (in browser context)
  2. Move .babelrc.works to .babelrc and rerun project, notice that index renders successfully.

@balazsorban44 balazsorban44 added kind: bug and removed bug Issue was opened via the bug report template. labels Feb 8, 2022
@saiichihashimoto
Copy link

Is there any update on this? Isomorphic code is a huge plus side of next.js and being unable to fork code in these specific cases creates convoluted code.

@timneutkens
Copy link
Member

Looked at this and it's probably the same as this: #36514 (comment).

@AndersDJohnson
Copy link
Contributor

Had the same issue. Seems like a possible workaround is to refactor code to instead use typeof window !== 'object'. I think perhaps this might even work with both Terser and SWC, but to be determined.

@adanperez
Copy link

We are running into this issue with latest Next.js 14/15 with our typeof window !== 'object' that worked with babel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
SWC Related to minification/transpilation in Next.js.
Projects
None yet
Development

No branches or pull requests

8 participants