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

Next.js 13.4.9 - SWC minification breaks production builds #54192

Closed
1 task done
jantimon opened this issue Aug 17, 2023 · 4 comments · Fixed by #54368
Closed
1 task done

Next.js 13.4.9 - SWC minification breaks production builds #54192

jantimon opened this issue Aug 17, 2023 · 4 comments · Fixed by #54368
Assignees
Labels
bug Issue was opened via the bug report template. linear: next Confirmed issue that is tracked by the Next.js team. locked

Comments

@jantimon
Copy link
Contributor

jantimon commented Aug 17, 2023

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
      Platform: darwin
      Arch: arm64
      Version: Darwin Kernel Version 22.5.0: Mon Apr 24 20:52:24 PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T6000
    Binaries:
      Node: 18.16.0
      npm: 9.5.1
      Yarn: 1.22.19
      pnpm: 8.6.11
    Relevant Packages:
      next: 13.4.17
      eslint-config-next: N/A
      react: 18.2.0
      react-dom: 18.2.0
      typescript: 5.1.6
    Next.js Config:
      output: N/A

Which area(s) of Next.js are affected? (leave empty if unsure)

SWC minifier (swcMinify: true)

Link to the code that reproduces this issue or a replay of the bug

https://github.com/jantimon/next-swc-minification-reproduction/

To Reproduce

  1. clone the reproduction example
git clone https://github.com/jantimon/next-swc-minification-reproduction.git
npm i
  1. As the SWC minifier runs only during build you have to create a build:
npm run build
npm run start
  1. Open http://localhost:3000/
  2. enter anything into the input field
  3. click the button to cause a rerender
  4. react will unmount the input field and mount it again
  5. the input is empty

shot-BDOkhzCn

Describe the Bug

The bug was introduced in next@13.4.9-canary.2 - all early versions (e.g. next@13.4.9-canary.1) work fine.

The issue seems to be part of this change: v13.4.9-canary.1...v13.4.9-canary.2

ContextExample.tsx

import { ReactNode, createContext } from "react";

const Context = createContext({});

export const ContextProvider = ({ children }: { children: ReactNode}) => {
    return <Context.Provider value={{}}>{children}</Context.Provider>;
}

export const ContextDemo = {
    Provider: ContextProvider
}

Client Component.tsx

"use client";
import { useState } from "react";
import { ContextDemo } from "./ContextExample";

export const ClientCompoenent = () => {
  const [count, setCount] = useState(0);

  return (
    <>

      <ContextDemo.Provider>
        <input />
      </ContextDemo.Provider>

      <button
        onClick={() => {
          console.log("click");
          setCount(count + 1);
        }}
      >
        +1 (rerender)
      </button>
    </>
  );
};

During minifcation SWC will inline the ContextProvider component right into the render function:

"use strict";
      n.r(r),
        n.d(r, {
          ClientCompoenent: function () {
            return u;
          },
        });
      var t = n(7437),
        o = n(2265);
      let s = (0, o.createContext)({}),
        u = () => {
          let [e, r] = (0, o.useState)(0);
          return (0, t.jsxs)(t.Fragment, {
            children: [
              (0, t.jsx)(
                // 🐞 here is the inlined function (scoped to `u`)
                //  therefore the function will recreated for each render: 
                (e) => {    
                  let { children: r } = e;
                  return (0, t.jsx)(s.Provider, { value: {}, children: r });
                },
                { children: (0, t.jsx)("input", {}) }
              ),
              (0, t.jsx)("button", {
                onClick: () => {
                  console.log("click"), r(e + 1);
                },
                children: "+1 (rerender)",
              }),
            ],
          });
        };

It looks like SWC does this minification only if a dot is in the component name.
changing ContextDemo.Provider to ContextDemoProvider fixes the issue.

Expected Behavior

Running npm dev will not trigger the swc minifcation and runs properly.

A rerender will not unmount components and the input will keep its value:

shot-VoV9ZKkO

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

NEXT-1530

@jantimon jantimon added the bug Issue was opened via the bug report template. label Aug 17, 2023
@kdy1 kdy1 self-assigned this Aug 17, 2023
@jantimon jantimon changed the title SWC minification breaks production builds Next.js 13.4.9 - SWC minification breaks production builds Aug 18, 2023
@jantimon jantimon changed the title Next.js 13.4.9 - SWC minification breaks production builds Next.js 13.4.9 - SWC minification breaks production builds after React upgrade Aug 18, 2023
@jantimon jantimon changed the title Next.js 13.4.9 - SWC minification breaks production builds after React upgrade Next.js 13.4.9 - SWC minification breaks production builds Aug 18, 2023
@jantimon
Copy link
Contributor Author

Update:

@kdy1 - it looks like the problem is not with SWC.

This is the generated code with next@13.4.9-canary.1:

    7228: function (e, r, n) {
      "use strict";
      n.r(r),
        n.d(r, {
          ClientCompoenent: function () {
            return u;
          },
        });
      var t = n(9268),
        o = n(6006);
      let s = (0, o.createContext)({}),
        u = () => {
          let [e, r] = (0, o.useState)(0);
          return (0, t.jsxs)(t.Fragment, {
            children: [
              (0, t.jsx)(
                (e) => {
                  let { children: r } = e;
                  return (0, t.jsx)(s.Provider, { value: {}, children: r });
                },
                { children: (0, t.jsx)("input", {}) }
              ),
              (0, t.jsx)("button", {
                onClick: () => {
                  console.log("click"), r(e + 1);
                },
                children: "+1 (rerender)",
              }),
            ],
          });
        };
    },

and this is the broken code with next@13.4.9-canary.2:

    7365: function (e, r, n) {
      "use strict";
      n.r(r),
        n.d(r, {
          ClientCompoenent: function () {
            return u;
          },
        });
      var t = n(9268),
        o = n(6006);
      let s = (0, o.createContext)({}),
        u = () => {
          let [e, r] = (0, o.useState)(0);
          return (0, t.jsxs)(t.Fragment, {
            children: [
              (0, t.jsx)(
                (e) => {
                  let { children: r } = e;
                  return (0, t.jsx)(s.Provider, { value: {}, children: r });
                },
                { children: (0, t.jsx)("input", {}) }
              ),
              (0, t.jsx)("button", {
                onClick: () => {
                  console.log("click"), r(e + 1);
                },
                children: "+1 (rerender)",
              }),
            ],
          });
        };
    },

as you can see (beside the module id) the code is exactly the same as before..

I can only guess but it might be because of the react upgrade from "react-builtin": "npm:react@18.3.0-canary-1cea38448-20230530" to "react-builtin": "npm:react@18.3.0-canary-1fdacbefd-20230630".

@jantimon
Copy link
Contributor Author

@jaggli has just created a new react issue: facebook/react#27248

@jaggli
Copy link

jaggli commented Aug 18, 2023

I boiled down the problem to the following two statements, which are the equivalent of the code being generated through SWC, under certain conditions like shown above.

  1. Create a dynamically created component, by nesting createElement:
    const DynamicComponent = createElement(() => createElement(Demo));
  2. Use <DynamicComponent /> as children
    return createElement("button", null, DynamicComponent);

I'm not sure what de documentation is referring to, when they say

"You must treat React elements and their props as immutable and never change their contents after creation."

https://react.dev/reference/react/createElement

So in case this sentence says: "You can't dynamically nest createElement()" then this in fact is an SWC bug. If nesting is fine, then it's probably a React bug..

@balazsorban44 balazsorban44 added the linear: next Confirmed issue that is tracked by the Next.js team. label Aug 18, 2023
kdy1 added a commit to swc-project/swc that referenced this issue Aug 22, 2023
@kodiakhq kodiakhq bot closed this as completed in #54368 Aug 22, 2023
kodiakhq bot pushed a commit that referenced this issue Aug 22, 2023
### What?

Update swc_core to `v0.79.70`

### Why?

To apply swc-project/swc#7839

### How?

Closes WEB-1420
Fixes #54192
@github-actions
Copy link
Contributor

github-actions bot commented Sep 6, 2023

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 added the locked label Sep 6, 2023
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 6, 2023
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. linear: next Confirmed issue that is tracked by the Next.js team. locked
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants