Skip to content

Commit c1c4c1b

Browse files
committed
Fix RSC loading unnecessary bundles
1 parent a4249c5 commit c1c4c1b

File tree

7 files changed

+175
-26
lines changed

7 files changed

+175
-26
lines changed

packages/bundlers/default/src/DefaultBundler.js

-1
Original file line numberDiff line numberDiff line change
@@ -1575,7 +1575,6 @@ function createIdealGraph(
15751575
asset.env.context !== bundle.env.context
15761576
? false
15771577
: bundle.needsStableName,
1578-
bundleBehavior: bundle.bundleBehavior,
15791578
type: asset.type,
15801579
target: bundle.target,
15811580
env: asset.env,

packages/core/core/src/BundleGraph.js

+28-10
Original file line numberDiff line numberDiff line change
@@ -1503,8 +1503,15 @@ export default class BundleGraph {
15031503

15041504
getBundlesInBundleGroup(
15051505
bundleGroup: BundleGroup,
1506-
opts?: {|includeInline: boolean|},
1506+
opts?: {|
1507+
recursive?: boolean,
1508+
includeInline?: boolean,
1509+
includeIsolated?: boolean,
1510+
|},
15071511
): Array<Bundle> {
1512+
let recursive = opts?.recursive ?? true;
1513+
let includeInline = opts?.includeInline ?? false;
1514+
let includeIsolated = opts?.includeIsolated ?? true;
15081515
let bundles: Set<Bundle> = new Set();
15091516
for (let bundleNodeId of this._graph.getNodeIdsConnectedFrom(
15101517
this._graph.getNodeIdByContentKey(getBundleGroupId(bundleGroup)),
@@ -1514,16 +1521,17 @@ export default class BundleGraph {
15141521
invariant(bundleNode.type === 'bundle');
15151522
let bundle = bundleNode.value;
15161523
if (
1517-
opts?.includeInline ||
1518-
bundle.bundleBehavior !== BundleBehavior.inline
1524+
bundle.bundleBehavior == null ||
1525+
(includeInline && bundle.bundleBehavior === BundleBehavior.inline) ||
1526+
(includeIsolated && bundle.bundleBehavior === BundleBehavior.isolated)
15191527
) {
15201528
bundles.add(bundle);
15211529
}
15221530

1523-
for (let referencedBundle of this.getReferencedBundles(bundle, {
1524-
includeInline: opts?.includeInline,
1525-
})) {
1526-
bundles.add(referencedBundle);
1531+
if (recursive) {
1532+
for (let referencedBundle of this.getReferencedBundles(bundle, opts)) {
1533+
bundles.add(referencedBundle);
1534+
}
15271535
}
15281536
}
15291537

@@ -1532,10 +1540,15 @@ export default class BundleGraph {
15321540

15331541
getReferencedBundles(
15341542
bundle: Bundle,
1535-
opts?: {|recursive?: boolean, includeInline?: boolean|},
1543+
opts?: {|
1544+
recursive?: boolean,
1545+
includeInline?: boolean,
1546+
includeIsolated?: boolean,
1547+
|},
15361548
): Array<Bundle> {
15371549
let recursive = opts?.recursive ?? true;
15381550
let includeInline = opts?.includeInline ?? false;
1551+
let includeIsolated = opts?.includeIsolated ?? true;
15391552
let referencedBundles = new Set();
15401553
this._graph.dfs({
15411554
visit: (nodeId, _, actions) => {
@@ -1549,10 +1562,15 @@ export default class BundleGraph {
15491562
}
15501563

15511564
if (
1552-
includeInline ||
1553-
node.value.bundleBehavior !== BundleBehavior.inline
1565+
node.value.bundleBehavior == null ||
1566+
(includeInline &&
1567+
node.value.bundleBehavior === BundleBehavior.inline) ||
1568+
(includeIsolated &&
1569+
node.value.bundleBehavior === BundleBehavior.isolated)
15541570
) {
15551571
referencedBundles.add(node.value);
1572+
} else if (node.value.bundleBehavior === BundleBehavior.isolated) {
1573+
actions.skipChildren();
15561574
}
15571575

15581576
if (!recursive) {

packages/core/core/src/public/BundleGraph.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,11 @@ export default class BundleGraph<TBundle: IBundle>
113113

114114
getReferencedBundles(
115115
bundle: IBundle,
116-
opts?: {|recursive?: boolean, includeInline?: boolean|},
116+
opts?: {|
117+
recursive?: boolean,
118+
includeInline?: boolean,
119+
includeIsolated?: boolean,
120+
|},
117121
): Array<TBundle> {
118122
return this.#graph
119123
.getReferencedBundles(bundleToInternalBundle(bundle), opts)
@@ -193,7 +197,11 @@ export default class BundleGraph<TBundle: IBundle>
193197

194198
getBundlesInBundleGroup(
195199
bundleGroup: IBundleGroup,
196-
opts?: {|includeInline: boolean|},
200+
opts?: {|
201+
recursive?: boolean,
202+
includeInline?: boolean,
203+
includeIsolated?: boolean,
204+
|},
197205
): Array<TBundle> {
198206
return this.#graph
199207
.getBundlesInBundleGroup(

packages/core/integration-tests/test/react-ssg.js

+98
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import assert from 'assert';
33
import path from 'path';
44
import {bundle, overlayFS, fsFixture, assertBundles} from '@parcel/test-utils';
5+
import nullthrows from 'nullthrows';
56

67
describe('react static', function () {
78
let count = 0;
@@ -466,4 +467,101 @@ describe('react static', function () {
466467
let output = await overlayFS.readFile(b.getBundles()[0].filePath, 'utf8');
467468
assert(output.includes('<link rel="stylesheet"'));
468469
});
470+
471+
it('should support nested server entries', async function () {
472+
await fsFixture(overlayFS, dir)`
473+
index.jsx:
474+
import {A} from './a';
475+
import {B} from './b';
476+
import './bootstrap';
477+
export default async function Index() {
478+
return (
479+
<html>
480+
<body>
481+
<Switch>
482+
<A />
483+
<B />
484+
</Switch>
485+
</body>
486+
</html>
487+
);
488+
}
489+
490+
function Switch({children}) {
491+
return children[0];
492+
}
493+
494+
a.jsx:
495+
"use server-entry";
496+
import {Client1} from './client1';
497+
export function A() {
498+
return <Client1 />;
499+
}
500+
501+
b.jsx:
502+
"use server-entry";
503+
import {Client2} from './client2';
504+
export function B() {
505+
return <Client2 />;
506+
}
507+
508+
client1.jsx:
509+
"use client";
510+
export function Client1() {
511+
return <span>Client 1</span>;
512+
}
513+
514+
client2.jsx:
515+
"use client";
516+
export function Client2() {
517+
return <span>Client 2</span>;
518+
}
519+
520+
bootstrap.js:
521+
"use client-entry";
522+
`;
523+
524+
let b = await bundle(path.join(dir, '/index.jsx'), {
525+
inputFS: overlayFS,
526+
});
527+
528+
let output = await overlayFS.readFile(b.getBundles()[0].filePath, 'utf8');
529+
let clientBundles = b
530+
.getBundles()
531+
.filter(b => b.env.isBrowser() && b.type === 'js');
532+
let client1, client2, bootstrap;
533+
b.traverse(node => {
534+
if (
535+
node.type === 'asset' &&
536+
node.value.filePath.endsWith('client1.jsx')
537+
) {
538+
client1 = node.value;
539+
} else if (
540+
node.type === 'asset' &&
541+
node.value.filePath.endsWith('client2.jsx')
542+
) {
543+
client2 = node.value;
544+
} else if (
545+
node.type === 'asset' &&
546+
node.value.filePath.endsWith('bootstrap.js')
547+
) {
548+
bootstrap = node.value;
549+
}
550+
});
551+
let client1Bundle = nullthrows(
552+
clientBundles.find(b => b.hasAsset(client1)),
553+
);
554+
let client2Bundle = nullthrows(
555+
clientBundles.find(b => b.hasAsset(client2)),
556+
);
557+
let bootstrapBundle = nullthrows(
558+
clientBundles.find(b => b.hasAsset(bootstrap)),
559+
);
560+
let scripts = Array.from(output.matchAll(/<script.*?src="(.*?)"/g)).map(
561+
b => b[1],
562+
);
563+
assert(scripts.includes('/' + path.basename(client1Bundle.filePath)));
564+
assert(scripts.includes('/' + path.basename(bootstrapBundle.filePath)));
565+
assert(!scripts.includes('/' + path.basename(client2Bundle.filePath)));
566+
});
469567
});

packages/core/types-internal/src/index.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -1547,7 +1547,11 @@ export interface BundleGraph<TBundle: Bundle> {
15471547
/** Returns a list of bundles that load together in the given bundle group. */
15481548
getBundlesInBundleGroup(
15491549
bundleGroup: BundleGroup,
1550-
opts?: {|includeInline: boolean|},
1550+
opts?: {|
1551+
recursive?: boolean,
1552+
includeInline?: boolean,
1553+
includeIsolated?: boolean,
1554+
|},
15511555
): Array<TBundle>;
15521556
/** Returns a list of bundles that this bundle loads asynchronously. */
15531557
getChildBundles(bundle: Bundle): Array<TBundle>;
@@ -1558,7 +1562,11 @@ export interface BundleGraph<TBundle: Bundle> {
15581562
/** Returns a list of bundles that are referenced by this bundle. By default, inline bundles are excluded. */
15591563
getReferencedBundles(
15601564
bundle: Bundle,
1561-
opts?: {|recursive?: boolean, includeInline?: boolean|},
1565+
opts?: {|
1566+
recursive?: boolean,
1567+
includeInline?: boolean,
1568+
includeIsolated?: boolean,
1569+
|},
15621570
): Array<TBundle>;
15631571
/** Returns a list of bundles that reference this bundle. */
15641572
getReferencingBundles(bundle: Bundle): Array<TBundle>;

packages/packagers/react-static/src/ReactStaticPackager.js

+22-9
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export default (new Packager({
138138
let entry;
139139
for (let b of bundleGraph.getReferencedBundles(bundle, {
140140
includeInline: false,
141+
includeIsolated: false,
141142
})) {
142143
if (b.type === 'css') {
143144
resources.push(
@@ -238,7 +239,7 @@ async function loadBundleUncached(
238239
) => Async<{|contents: Blob|}>,
239240
) {
240241
// Load all asset contents.
241-
let queue = new PromiseQueue<Array<[string, [Asset, string]]>>({
242+
let queue = new PromiseQueue<Array<[string, [NamedBundle, Asset, string]]>>({
242243
maxConcurrent: 32,
243244
});
244245
bundle.traverse(node => {
@@ -262,7 +263,7 @@ async function loadBundleUncached(
262263
return [
263264
[
264265
entryBundle.id,
265-
[nullthrows(entryBundle.getMainEntry()), contents],
266+
[entryBundle, nullthrows(entryBundle.getMainEntry()), contents],
266267
],
267268
];
268269
});
@@ -278,23 +279,36 @@ async function loadBundleUncached(
278279
}
279280
} else if (node.type === 'asset') {
280281
let asset = node.value;
281-
queue.add(async () => [[asset.id, [asset, await asset.getCode()]]]);
282+
queue.add(async () => [
283+
[asset.id, [bundle, asset, await asset.getCode()]],
284+
]);
282285
}
283286
});
284287

285-
let assets = new Map<string, [Asset, string]>(
288+
for (let b of bundleGraph.getReferencedBundles(bundle)) {
289+
queue.add(async () => {
290+
let {assets: subAssets} = await loadBundle(
291+
b,
292+
bundleGraph,
293+
getInlineBundleContents,
294+
);
295+
return Array.from(subAssets);
296+
});
297+
}
298+
299+
let assets = new Map<string, [NamedBundle, Asset, string]>(
286300
(await queue.run()).flatMap(v => v),
287301
);
288302
let assetsByFilePath = new Map<string, string>();
289303
let assetsByPublicId = new Map<string, string>();
290-
for (let [asset] of assets.values()) {
304+
for (let [, asset] of assets.values()) {
291305
assetsByFilePath.set(getCacheKey(asset), asset.id);
292306
assetsByPublicId.set(bundleGraph.getAssetPublicId(asset), asset.id);
293307
}
294308

295309
// Load an asset into the module system by id.
296310
let loadAsset = (id: string) => {
297-
let [asset, code] = nullthrows(assets.get(id));
311+
let [bundle, asset, code] = nullthrows(assets.get(id));
298312
let cacheKey = getCacheKey(asset);
299313
let cachedModule = moduleCache.get(cacheKey);
300314
if (cachedModule) {
@@ -376,9 +390,9 @@ async function loadBundleUncached(
376390
bundleGraph,
377391
getInlineBundleContents,
378392
);
379-
for (let [id, [asset, code]] of subAssets) {
393+
for (let [id, [bundle, asset, code]] of subAssets) {
380394
if (!assets.has(id)) {
381-
assets.set(id, [asset, code]);
395+
assets.set(id, [bundle, asset, code]);
382396
assetsByFilePath.set(getCacheKey(asset), asset.id);
383397
assetsByPublicId.set(bundleGraph.getAssetPublicId(asset), asset.id);
384398
}
@@ -456,7 +470,6 @@ function runModule(
456470
require: (id: string) => any,
457471
parcelRequire: (id: string) => any,
458472
) {
459-
// code = code.replace(/import\((['"].*?['"])\)/g, (_, m) => `parcelRequire.load(${m[0] + m.slice(3)})`);
460473
let moduleFunction = vm.compileFunction(
461474
code,
462475
[

packages/runtimes/rsc/src/RSCRuntime.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,13 @@ export default (new Runtime({
5252
let bundles;
5353
let async = bundleGraph.resolveAsyncDependency(node.value, bundle);
5454
if (async?.type === 'bundle_group') {
55-
bundles = bundleGraph.getBundlesInBundleGroup(async.value);
55+
bundles = bundleGraph.getBundlesInBundleGroup(async.value, {
56+
includeIsolated: false,
57+
});
5658
} else {
57-
bundles = bundleGraph.getReferencedBundles(bundle);
59+
bundles = bundleGraph.getReferencedBundles(bundle, {
60+
includeIsolated: false,
61+
});
5862
}
5963

6064
let importMap = {};
@@ -208,6 +212,7 @@ export default (new Runtime({
208212
if (asyncResolution?.type === 'bundle_group') {
209213
let bundles = bundleGraph.getBundlesInBundleGroup(
210214
asyncResolution.value,
215+
{includeIsolated: false},
211216
);
212217
let resources = [];
213218
let js = [];

0 commit comments

Comments
 (0)