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

[Flight Parcel] Pass import maps through client references #32132

Merged
merged 1 commit into from
Jan 27, 2025

Conversation

devongovett
Copy link
Contributor

Corresponding Parcel PR: parcel-bundler/parcel#10073

Parcel avoids cascading cache invalidation by injecting a bundle manifest containing a mapping of stable bundle ids to hashed URLs. When using an HTML entry point, this is done (as of the above PR) via a native import map. This means that if a bundle's hash changes, only that bundle will be invalidated (plus the HTML itself which typically has a short caching policy), not any other bundles that reference it.

For RSCs, we cannot currently use native import maps because of client side navigations, where a new HTML file is not requested. Eventually, multiple <script type="importmap"> elements will be supported (whatwg/html#10528) (coming Chrome 133), at which point React could potentially inject them. In the meantime, I've added some APIs to Parcel to polyfill this. With this change, an import map can be sent along with a client reference, containing a mapping for any dynamic imports and URL dependencies (e.g. images) that are referenced by the JS bundles. On the client, the import map is extended with these new mappings prior to executing the referenced bundles. This preserves the caching advantages described above while supporting client navigations.

@react-sizebot
Copy link

Comparing: 18eaf51...a68e7a1

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.68 kB 6.68 kB +0.05% 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 514.24 kB 514.24 kB = 91.73 kB 91.73 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB +0.05% 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 556.18 kB 556.18 kB = 98.72 kB 98.72 kB
facebook-www/ReactDOM-prod.classic.js = 595.79 kB 595.79 kB = 104.85 kB 104.85 kB
facebook-www/ReactDOM-prod.modern.js = 586.21 kB 586.21 kB = 103.30 kB 103.30 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.production.js +0.37% 85.98 kB 86.29 kB +0.26% 17.96 kB 18.01 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.production.js +0.37% 85.98 kB 86.29 kB +0.26% 17.96 kB 18.01 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.production.js +0.36% 87.42 kB 87.73 kB +0.27% 18.29 kB 18.33 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.production.js +0.36% 87.42 kB 87.73 kB +0.27% 18.29 kB 18.33 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.production.js +0.35% 89.47 kB 89.79 kB +0.29% 18.70 kB 18.75 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.production.js +0.35% 89.47 kB 89.79 kB +0.29% 18.70 kB 18.75 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.production.js +0.35% 90.48 kB 90.80 kB +0.28% 18.69 kB 18.74 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.production.js +0.34% 92.01 kB 92.33 kB +0.26% 19.02 kB 19.07 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.production.js +0.34% 93.93 kB 94.25 kB +0.28% 19.44 kB 19.49 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js +0.29% 130.87 kB 131.25 kB +0.28% 24.41 kB 24.48 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js +0.29% 130.87 kB 131.25 kB +0.28% 24.41 kB 24.48 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js +0.28% 133.64 kB 134.02 kB +0.30% 24.93 kB 25.00 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js +0.28% 133.64 kB 134.02 kB +0.30% 24.93 kB 25.00 kB
oss-stable-semver/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +0.28% 134.86 kB 135.24 kB +0.31% 25.23 kB 25.31 kB
oss-stable/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +0.28% 134.86 kB 135.24 kB +0.31% 25.23 kB 25.31 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.browser.development.js +0.26% 143.84 kB 144.22 kB +0.27% 26.67 kB 26.74 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.edge.development.js +0.26% 148.30 kB 148.68 kB +0.26% 27.41 kB 27.48 kB
oss-experimental/react-server-dom-parcel/cjs/react-server-dom-parcel-server.node.development.js +0.26% 148.57 kB 148.95 kB +0.26% 27.63 kB 27.70 kB

Generated by 🚫 dangerJS against 49287ae

@sebmarkbage
Copy link
Collaborator

I'm not sure why this would need to be attached to each client reference. Can't it be part of the "bundlerConfig"? It's basically what the other manifests are for. Is this map limited to only mapping the resources used by this particular client reference?

If so, can it be part of the bundles array instead? It's really just meta data for how to load something. That way we stick to the same fields as other references uses.

Webpack normally does something similar to this as well. However, a goal of the Next.js versions of this is to explicitly exclude any global manifests of all possible chunks (which is also what Meta's version does). The reason is that 1) it tends to grow unbounded for large apps and ends up loaded upfront 2) it can reveal the entirety of the app to a hacker when ideally they only see what the server sent.

That's why, ideally, everything about how to load a module is the already resolved version. Since an RSC payload can update when the manifest changes without recompiling the chunks. Why do you need both the pre- and post-resolution info on the client?

@devongovett
Copy link
Contributor Author

@sebmarkbage The import map passed here only includes mappings for resources used by the bundles in this client reference. For example if a client component references an image or dynamic imports something, it would include mappings for those. It does not include mappings for client components themselves (the client reference already has those in bundles), only any additional resources that they reference. Many client references won't need a map at all.

It also does not include mappings for the entire application, and the goal is to avoid that since those maps can be very large. This approach allows incrementally loading mappings as needed.

If so, can it be part of the bundles array instead?

It could, I just merged them all into one map here. I could make each bundle in the bundles array a tuple containing the URL of the bundle plus the map for the resources it references. Then call extendImportMap multiple times instead of once and also update prepareDestinationForModule to handle this tuple structure. Happy to change it if you want but it seemed simpler to just merge them into a single map.

@sebmarkbage sebmarkbage merged commit 37906d4 into facebook:main Jan 27, 2025
190 checks passed
@sebmarkbage
Copy link
Collaborator

I guess if there was a more universal ClientReference object for all bundlers the meta data would be a deeply nested object which includes id and bundles and other meta data, but since id and bundles are flat it makes sense that other custom are too for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants