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

fix: mixing namespace import and named import client components #64809

Merged
merged 8 commits into from Apr 22, 2024

Conversation

huozhi
Copy link
Member

@huozhi huozhi commented Apr 20, 2024

What

Reported by @MaxLeiter, when you mixing named import and wildcard import to a client component, and if you clone the module it will missed others exports except the named ones. This lead to an issue that rendered React element type is undefined.

Why

We're using a tree-shaking strategy that collects the imported identifiers from client components on server components side. But in our code connection.dependency.ids can be undefined when you're using import *. So for that case we include all the exports.

In the flight client entry plugin, if we found there's named imports that collected later, and the module is already being marked as namespace import *, we merge the ids into "*", so the whole module and all exports are respected.

Now there're few possible cases for a client component import:

During webpack build, in the outout going connections, there're connection with empty imported ids ([]), cannot unable to detect the imported ids (['*']) and detected named imported ids (['a', 'b', ..]). First two represnt to include the whole module and all exports, but we might collect the named imports could come later than the whole module. So if we found the existing collection already has ['*'] then we keep using that regardless the collected named imports. This can avoid the collected named imports cover "exports all" case, where we will expose less exports for that collection module lead to the undefined component error.

Closes NEXT-3177

@ijjk
Copy link
Member

ijjk commented Apr 20, 2024

Failing test suites

Commit: 6bc015b

TURBOPACK=1 pnpm test-start test/production/app-dir-edge-runtime-with-wasm/index.test.ts (turbopack)

  • app-dir edge runtime with wasm > should have built
Expand output

● app-dir edge runtime with wasm › should have built

next build failed with code/signal 1

  89 |           if (code || signal)
  90 |             reject(
> 91 |               new Error(`next build failed with code/signal ${code || signal}`)
     |               ^
  92 |             )
  93 |           else resolve()
  94 |         })

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:91:15)

● Test suite failed to run

TypeError: Cannot read properties of undefined (reading 'destroy')

  50 |     })
  51 |   })
> 52 |   afterAll(() => next.destroy())
     |                       ^
  53 |
  54 |   it('should have built', async () => {
  55 |     const html = await renderViaHTTP(next.url, '/')

  at Object.destroy (production/app-dir-edge-runtime-with-wasm/index.test.ts:52:23)

Read more about building and testing Next.js in contributing.md.

TURBOPACK=1 pnpm test-start test/e2e/prerender.test.ts (turbopack)

  • Prerender > should on-demand revalidate for fallback: blocking with onlyGenerated if generated
Expand output

● Prerender › should on-demand revalidate for fallback: blocking with onlyGenerated if generated

expect(received).toMatch(expected)

Expected pattern: /(HIT|STALE)/
Received string:  "MISS"

  2296 |
  2297 |         expect(initialTime).toBe($2('#time').text())
> 2298 |         expect(res2.headers.get('x-nextjs-cache')).toMatch(/(HIT|STALE)/)
       |                                                    ^
  2299 |
  2300 |         const res3 = await fetchViaHTTP(
  2301 |           next.url,

  at Object.toMatch (e2e/prerender.test.ts:2298:52)

Read more about building and testing Next.js in contributing.md.

@ijjk
Copy link
Member

ijjk commented Apr 20, 2024

Stats from current PR

Default Build
General Overall increase ⚠️
vercel/next.js canary vercel/next.js fix/tree-shaking-import-wildcard Change
buildDuration 25.5s 22.9s N/A
buildDurationCached 11.9s 12.3s ⚠️ +403ms
nodeModulesSize 199 MB 199 MB ⚠️ +4.7 kB
nextStartRea..uration (ms) 746ms 674ms N/A
Client Bundles (main, webpack)
vercel/next.js canary vercel/next.js fix/tree-shaking-import-wildcard Change
2453-HASH.js gzip 31.5 kB 31.5 kB N/A
3304.HASH.js gzip 169 B 169 B
3f784ff6-HASH.js gzip 53.7 kB 53.7 kB N/A
8299-HASH.js gzip 5.09 kB 5.1 kB N/A
framework-HASH.js gzip 45.2 kB 45.2 kB
main-app-HASH.js gzip 230 B 227 B N/A
main-HASH.js gzip 29.6 kB 29.6 kB N/A
webpack-HASH.js gzip 1.64 kB 1.65 kB N/A
Overall change 45.4 kB 45.4 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary vercel/next.js fix/tree-shaking-import-wildcard Change
polyfills-HASH.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary vercel/next.js fix/tree-shaking-import-wildcard Change
_app-HASH.js gzip 193 B 194 B N/A
_error-HASH.js gzip 193 B 191 B N/A
amp-HASH.js gzip 510 B 510 B
css-HASH.js gzip 342 B 343 B N/A
dynamic-HASH.js gzip 2.51 kB 2.52 kB N/A
edge-ssr-HASH.js gzip 265 B 265 B
head-HASH.js gzip 365 B 364 B N/A
hooks-HASH.js gzip 389 B 391 B N/A
image-HASH.js gzip 4.28 kB 4.28 kB N/A
index-HASH.js gzip 269 B 268 B N/A
link-HASH.js gzip 2.68 kB 2.69 kB N/A
routerDirect..HASH.js gzip 328 B 326 B N/A
script-HASH.js gzip 395 B 397 B N/A
withRouter-HASH.js gzip 323 B 323 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Overall change 1.2 kB 1.2 kB
Client Build Manifests
vercel/next.js canary vercel/next.js fix/tree-shaking-import-wildcard Change
_buildManifest.js gzip 482 B 484 B N/A
Overall change 0 B 0 B
Rendered Page Sizes
vercel/next.js canary vercel/next.js fix/tree-shaking-import-wildcard Change
index.html gzip 529 B 529 B
link.html gzip 542 B 541 B N/A
withRouter.html gzip 523 B 523 B
Overall change 1.05 kB 1.05 kB
Edge SSR bundle Size
vercel/next.js canary vercel/next.js fix/tree-shaking-import-wildcard Change
edge-ssr.js gzip 94.5 kB 94.5 kB N/A
page.js gzip 3.05 kB 3.04 kB N/A
Overall change 0 B 0 B
Middleware size
vercel/next.js canary vercel/next.js fix/tree-shaking-import-wildcard Change
middleware-b..fest.js gzip 622 B 626 B N/A
middleware-r..fest.js gzip 155 B 156 B N/A
middleware.js gzip 25.6 kB 25.6 kB N/A
edge-runtime..pack.js gzip 839 B 839 B
Overall change 839 B 839 B
Next Runtimes
vercel/next.js canary vercel/next.js fix/tree-shaking-import-wildcard Change
app-page-exp...dev.js gzip 171 kB 171 kB
app-page-exp..prod.js gzip 97.6 kB 97.6 kB
app-page-tur..prod.js gzip 99.4 kB 99.4 kB
app-page-tur..prod.js gzip 93.6 kB 93.6 kB
app-page.run...dev.js gzip 145 kB 145 kB
app-page.run..prod.js gzip 92.1 kB 92.1 kB
app-route-ex...dev.js gzip 21.5 kB 21.5 kB
app-route-ex..prod.js gzip 15.1 kB 15.1 kB
app-route-tu..prod.js gzip 15.1 kB 15.1 kB
app-route-tu..prod.js gzip 14.9 kB 14.9 kB
app-route.ru...dev.js gzip 21.2 kB 21.2 kB
app-route.ru..prod.js gzip 14.9 kB 14.9 kB
pages-api-tu..prod.js gzip 9.55 kB 9.55 kB
pages-api.ru...dev.js gzip 9.82 kB 9.82 kB
pages-api.ru..prod.js gzip 9.55 kB 9.55 kB
pages-turbo...prod.js gzip 21.4 kB 21.4 kB
pages.runtim...dev.js gzip 22.1 kB 22.1 kB
pages.runtim..prod.js gzip 21.4 kB 21.4 kB
server.runti..prod.js gzip 51.6 kB 51.6 kB
Overall change 946 kB 946 kB
build cache
vercel/next.js canary vercel/next.js fix/tree-shaking-import-wildcard Change
0.pack gzip 1.6 MB 1.6 MB N/A
index.pack gzip 107 kB 107 kB N/A
Overall change 0 B 0 B
Diff details
Diff for middleware.js

Diff too large to display

Diff for image-HASH.js
@@ -1,7 +1,7 @@
 (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([
   [8358],
   {
-    /***/ 1552: /***/ (
+    /***/ 4070: /***/ (
       __unused_webpack_module,
       __unused_webpack_exports,
       __webpack_require__
@@ -9,7 +9,7 @@
       (window.__NEXT_P = window.__NEXT_P || []).push([
         "/image",
         function () {
-          return __webpack_require__(5237);
+          return __webpack_require__(396);
         },
       ]);
       if (false) {
@@ -18,7 +18,7 @@
       /***/
     },
 
-    /***/ 2016: /***/ (module, exports, __webpack_require__) => {
+    /***/ 8490: /***/ (module, exports, __webpack_require__) => {
       "use strict";
       /* __next_internal_client_entry_do_not_use__  cjs */
       Object.defineProperty(exports, "__esModule", {
@@ -40,15 +40,15 @@
         __webpack_require__(422)
       );
       const _head = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(6074)
+        __webpack_require__(2457)
       );
-      const _getimgprops = __webpack_require__(9571);
-      const _imageconfig = __webpack_require__(6567);
-      const _imageconfigcontextsharedruntime = __webpack_require__(419);
-      const _warnonce = __webpack_require__(4486);
-      const _routercontextsharedruntime = __webpack_require__(162);
+      const _getimgprops = __webpack_require__(7932);
+      const _imageconfig = __webpack_require__(5706);
+      const _imageconfigcontextsharedruntime = __webpack_require__(9483);
+      const _warnonce = __webpack_require__(9035);
+      const _routercontextsharedruntime = __webpack_require__(4829);
       const _imageloader = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(6996)
+        __webpack_require__(7240)
       );
       // This is replaced by webpack define plugin
       const configEnv = {
@@ -379,7 +379,7 @@
       /***/
     },
 
-    /***/ 9571: /***/ (
+    /***/ 7932: /***/ (
       __unused_webpack_module,
       exports,
       __webpack_require__
@@ -395,9 +395,9 @@
           return getImgProps;
         },
       });
-      const _warnonce = __webpack_require__(4486);
-      const _imageblursvg = __webpack_require__(133);
-      const _imageconfig = __webpack_require__(6567);
+      const _warnonce = __webpack_require__(9035);
+      const _imageblursvg = __webpack_require__(2642);
+      const _imageconfig = __webpack_require__(5706);
       const VALID_LOADING_VALUES =
         /* unused pure expression or super */ null && [
           "lazy",
@@ -772,7 +772,7 @@
       /***/
     },
 
-    /***/ 133: /***/ (__unused_webpack_module, exports) => {
+    /***/ 2642: /***/ (__unused_webpack_module, exports) => {
       "use strict";
       /**
        * A shared function, used on both client and server, to generate a SVG blur placeholder.
@@ -827,7 +827,7 @@
       /***/
     },
 
-    /***/ 4085: /***/ (
+    /***/ 503: /***/ (
       __unused_webpack_module,
       exports,
       __webpack_require__
@@ -854,10 +854,10 @@
         },
       });
       const _interop_require_default = __webpack_require__(7456);
-      const _getimgprops = __webpack_require__(9571);
-      const _imagecomponent = __webpack_require__(2016);
+      const _getimgprops = __webpack_require__(7932);
+      const _imagecomponent = __webpack_require__(8490);
       const _imageloader = /*#__PURE__*/ _interop_require_default._(
-        __webpack_require__(6996)
+        __webpack_require__(7240)
       );
       function getImageProps(imgProps) {
         const { props } = (0, _getimgprops.getImgProps)(imgProps, {
@@ -889,7 +889,7 @@
       /***/
     },
 
-    /***/ 6996: /***/ (__unused_webpack_module, exports) => {
+    /***/ 7240: /***/ (__unused_webpack_module, exports) => {
       "use strict";
 
       Object.defineProperty(exports, "__esModule", {
@@ -924,7 +924,7 @@
       /***/
     },
 
-    /***/ 5237: /***/ (
+    /***/ 396: /***/ (
       __unused_webpack_module,
       __webpack_exports__,
       __webpack_require__
@@ -941,8 +941,8 @@
 
       // EXTERNAL MODULE: ./node_modules/.pnpm/react@18.2.0/node_modules/react/jsx-runtime.js
       var jsx_runtime = __webpack_require__(1527);
-      // EXTERNAL MODULE: ./node_modules/.pnpm/file+..+main-repo+packages+next+next-packed.tgz_react-dom@18.2.0_react@18.2.0/node_modules/next/image.js
-      var next_image = __webpack_require__(1577);
+      // EXTERNAL MODULE: ./node_modules/.pnpm/file+..+diff-repo+packages+next+next-packed.tgz_react-dom@18.2.0_react@18.2.0/node_modules/next/image.js
+      var next_image = __webpack_require__(73);
       var image_default = /*#__PURE__*/ __webpack_require__.n(next_image); // CONCATENATED MODULE: ./pages/nextjs.png
       /* harmony default export */ const nextjs = {
         src: "/_next/static/media/nextjs.cae0b805.png",
@@ -972,12 +972,8 @@
       /***/
     },
 
-    /***/ 1577: /***/ (
-      module,
-      __unused_webpack_exports,
-      __webpack_require__
-    ) => {
-      module.exports = __webpack_require__(4085);
+    /***/ 73: /***/ (module, __unused_webpack_exports, __webpack_require__) => {
+      module.exports = __webpack_require__(503);
 
       /***/
     },
@@ -987,7 +983,7 @@
     /******/ var __webpack_exec__ = (moduleId) =>
       __webpack_require__((__webpack_require__.s = moduleId));
     /******/ __webpack_require__.O(0, [2888, 9774, 179], () =>
-      __webpack_exec__(1552)
+      __webpack_exec__(4070)
     );
     /******/ var __webpack_exports__ = __webpack_require__.O();
     /******/ _N_E = __webpack_exports__;
Diff for 2453-HASH.js

Diff too large to display

Diff for main-HASH.js

Diff too large to display

Commit: 6bc015b

@huozhi huozhi marked this pull request as ready for review April 21, 2024 01:27
@huozhi huozhi changed the title fix: mixing wildcard import and named import client components fix: mixing namespace import and named import client components Apr 21, 2024
@huozhi huozhi requested a review from shuding April 21, 2024 10:08
@huozhi huozhi merged commit 7404ea4 into canary Apr 22, 2024
72 of 80 checks passed
@huozhi huozhi deleted the fix/tree-shaking-import-wildcard branch April 22, 2024 13:05
huozhi added a commit that referenced this pull request Apr 23, 2024
### What

Reported by @MaxLeiter, when you mixing named import and wildcard import
to a client component, and if you clone the module it will missed others
exports except the named ones. This lead to an issue that rendered React
element type is `undefined`.

### Why

We're using a tree-shaking strategy that collects the imported
identifiers from client components on server components side. But in our
code `connection.dependency.ids` can be undefined when you're using
`import *`. So for that case we include all the exports.

In the flight client entry plugin, if we found there's named imports
that collected later, and the module is already being marked as
namespace import `*`, we merge the ids into "*", so the whole module and
all exports are respected.

Now there're few possible cases for a client component import:

During webpack build, in the outout going connections, there're
connection with empty imported ids (`[]`), cannot unable to detect the
imported ids (`['*']`) and detected named imported ids (`['a', 'b',
..]`). First two represnt to include the whole module and all exports,
but we might collect the named imports could come later than the whole
module. So if we found the existing collection already has `['*']` then
we keep using that regardless the collected named imports. This can
avoid the collected named imports cover "exports all" case, where we
will expose less exports for that collection module lead to the
undefined component error.

Closes NEXT-3177
@github-actions github-actions bot added the locked label May 7, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 7, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants