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 RSC loading unnecessary bundles #10104

Open
wants to merge 1 commit into
base: v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/bundlers/default/src/DefaultBundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -1575,7 +1575,6 @@ function createIdealGraph(
asset.env.context !== bundle.env.context
? false
: bundle.needsStableName,
bundleBehavior: bundle.bundleBehavior,
type: asset.type,
target: bundle.target,
env: asset.env,
Expand Down
38 changes: 28 additions & 10 deletions packages/core/core/src/BundleGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -1503,8 +1503,15 @@ export default class BundleGraph {

getBundlesInBundleGroup(
bundleGroup: BundleGroup,
opts?: {|includeInline: boolean|},
opts?: {|
recursive?: boolean,
includeInline?: boolean,
includeIsolated?: boolean,
|},
): Array<Bundle> {
let recursive = opts?.recursive ?? true;
let includeInline = opts?.includeInline ?? false;
let includeIsolated = opts?.includeIsolated ?? true;
let bundles: Set<Bundle> = new Set();
for (let bundleNodeId of this._graph.getNodeIdsConnectedFrom(
this._graph.getNodeIdByContentKey(getBundleGroupId(bundleGroup)),
Expand All @@ -1514,16 +1521,17 @@ export default class BundleGraph {
invariant(bundleNode.type === 'bundle');
let bundle = bundleNode.value;
if (
opts?.includeInline ||
bundle.bundleBehavior !== BundleBehavior.inline
bundle.bundleBehavior == null ||
(includeInline && bundle.bundleBehavior === BundleBehavior.inline) ||
(includeIsolated && bundle.bundleBehavior === BundleBehavior.isolated)
) {
bundles.add(bundle);
}

for (let referencedBundle of this.getReferencedBundles(bundle, {
includeInline: opts?.includeInline,
})) {
bundles.add(referencedBundle);
if (recursive) {
for (let referencedBundle of this.getReferencedBundles(bundle, opts)) {
bundles.add(referencedBundle);
}
}
}

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

getReferencedBundles(
bundle: Bundle,
opts?: {|recursive?: boolean, includeInline?: boolean|},
opts?: {|
recursive?: boolean,
includeInline?: boolean,
includeIsolated?: boolean,
|},
): Array<Bundle> {
let recursive = opts?.recursive ?? true;
let includeInline = opts?.includeInline ?? false;
let includeIsolated = opts?.includeIsolated ?? true;
let referencedBundles = new Set();
this._graph.dfs({
visit: (nodeId, _, actions) => {
Expand All @@ -1549,10 +1562,15 @@ export default class BundleGraph {
}

if (
includeInline ||
node.value.bundleBehavior !== BundleBehavior.inline
node.value.bundleBehavior == null ||
(includeInline &&
node.value.bundleBehavior === BundleBehavior.inline) ||
(includeIsolated &&
node.value.bundleBehavior === BundleBehavior.isolated)
) {
referencedBundles.add(node.value);
} else if (node.value.bundleBehavior === BundleBehavior.isolated) {
actions.skipChildren();
}

if (!recursive) {
Expand Down
12 changes: 10 additions & 2 deletions packages/core/core/src/public/BundleGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,11 @@ export default class BundleGraph<TBundle: IBundle>

getReferencedBundles(
bundle: IBundle,
opts?: {|recursive?: boolean, includeInline?: boolean|},
opts?: {|
recursive?: boolean,
includeInline?: boolean,
includeIsolated?: boolean,
|},
): Array<TBundle> {
return this.#graph
.getReferencedBundles(bundleToInternalBundle(bundle), opts)
Expand Down Expand Up @@ -193,7 +197,11 @@ export default class BundleGraph<TBundle: IBundle>

getBundlesInBundleGroup(
bundleGroup: IBundleGroup,
opts?: {|includeInline: boolean|},
opts?: {|
recursive?: boolean,
includeInline?: boolean,
includeIsolated?: boolean,
|},
): Array<TBundle> {
return this.#graph
.getBundlesInBundleGroup(
Expand Down
98 changes: 98 additions & 0 deletions packages/core/integration-tests/test/react-ssg.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import assert from 'assert';
import path from 'path';
import {bundle, overlayFS, fsFixture, assertBundles} from '@parcel/test-utils';
import nullthrows from 'nullthrows';

describe('react static', function () {
let count = 0;
Expand Down Expand Up @@ -466,4 +467,101 @@ describe('react static', function () {
let output = await overlayFS.readFile(b.getBundles()[0].filePath, 'utf8');
assert(output.includes('<link rel="stylesheet"'));
});

it('should support nested server entries', async function () {
await fsFixture(overlayFS, dir)`
index.jsx:
import {A} from './a';
import {B} from './b';
import './bootstrap';
export default async function Index() {
return (
<html>
<body>
<Switch>
<A />
<B />
</Switch>
</body>
</html>
);
}

function Switch({children}) {
return children[0];
}

a.jsx:
"use server-entry";
import {Client1} from './client1';
export function A() {
return <Client1 />;
}

b.jsx:
"use server-entry";
import {Client2} from './client2';
export function B() {
return <Client2 />;
}

client1.jsx:
"use client";
export function Client1() {
return <span>Client 1</span>;
}

client2.jsx:
"use client";
export function Client2() {
return <span>Client 2</span>;
}

bootstrap.js:
"use client-entry";
`;

let b = await bundle(path.join(dir, '/index.jsx'), {
inputFS: overlayFS,
});

let output = await overlayFS.readFile(b.getBundles()[0].filePath, 'utf8');
let clientBundles = b
.getBundles()
.filter(b => b.env.isBrowser() && b.type === 'js');
let client1, client2, bootstrap;
b.traverse(node => {
if (
node.type === 'asset' &&
node.value.filePath.endsWith('client1.jsx')
) {
client1 = node.value;
} else if (
node.type === 'asset' &&
node.value.filePath.endsWith('client2.jsx')
) {
client2 = node.value;
} else if (
node.type === 'asset' &&
node.value.filePath.endsWith('bootstrap.js')
) {
bootstrap = node.value;
}
});
let client1Bundle = nullthrows(
clientBundles.find(b => b.hasAsset(client1)),
);
let client2Bundle = nullthrows(
clientBundles.find(b => b.hasAsset(client2)),
);
let bootstrapBundle = nullthrows(
clientBundles.find(b => b.hasAsset(bootstrap)),
);
let scripts = Array.from(output.matchAll(/<script.*?src="(.*?)"/g)).map(
b => b[1],
);
assert(scripts.includes('/' + path.basename(client1Bundle.filePath)));
assert(scripts.includes('/' + path.basename(bootstrapBundle.filePath)));
assert(!scripts.includes('/' + path.basename(client2Bundle.filePath)));
});
});
12 changes: 10 additions & 2 deletions packages/core/types-internal/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1547,7 +1547,11 @@ export interface BundleGraph<TBundle: Bundle> {
/** Returns a list of bundles that load together in the given bundle group. */
getBundlesInBundleGroup(
bundleGroup: BundleGroup,
opts?: {|includeInline: boolean|},
opts?: {|
recursive?: boolean,
includeInline?: boolean,
includeIsolated?: boolean,
|},
): Array<TBundle>;
/** Returns a list of bundles that this bundle loads asynchronously. */
getChildBundles(bundle: Bundle): Array<TBundle>;
Expand All @@ -1558,7 +1562,11 @@ export interface BundleGraph<TBundle: Bundle> {
/** Returns a list of bundles that are referenced by this bundle. By default, inline bundles are excluded. */
getReferencedBundles(
bundle: Bundle,
opts?: {|recursive?: boolean, includeInline?: boolean|},
opts?: {|
recursive?: boolean,
includeInline?: boolean,
includeIsolated?: boolean,
|},
): Array<TBundle>;
/** Returns a list of bundles that reference this bundle. */
getReferencingBundles(bundle: Bundle): Array<TBundle>;
Expand Down
31 changes: 22 additions & 9 deletions packages/packagers/react-static/src/ReactStaticPackager.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export default (new Packager({
let entry;
for (let b of bundleGraph.getReferencedBundles(bundle, {
includeInline: false,
includeIsolated: false,
})) {
if (b.type === 'css') {
resources.push(
Expand Down Expand Up @@ -238,7 +239,7 @@ async function loadBundleUncached(
) => Async<{|contents: Blob|}>,
) {
// Load all asset contents.
let queue = new PromiseQueue<Array<[string, [Asset, string]]>>({
let queue = new PromiseQueue<Array<[string, [NamedBundle, Asset, string]]>>({
maxConcurrent: 32,
});
bundle.traverse(node => {
Expand All @@ -262,7 +263,7 @@ async function loadBundleUncached(
return [
[
entryBundle.id,
[nullthrows(entryBundle.getMainEntry()), contents],
[entryBundle, nullthrows(entryBundle.getMainEntry()), contents],
],
];
});
Expand All @@ -278,23 +279,36 @@ async function loadBundleUncached(
}
} else if (node.type === 'asset') {
let asset = node.value;
queue.add(async () => [[asset.id, [asset, await asset.getCode()]]]);
queue.add(async () => [
[asset.id, [bundle, asset, await asset.getCode()]],
]);
}
});

let assets = new Map<string, [Asset, string]>(
for (let b of bundleGraph.getReferencedBundles(bundle)) {
queue.add(async () => {
let {assets: subAssets} = await loadBundle(
b,
bundleGraph,
getInlineBundleContents,
);
return Array.from(subAssets);
});
}

let assets = new Map<string, [NamedBundle, Asset, string]>(
(await queue.run()).flatMap(v => v),
);
let assetsByFilePath = new Map<string, string>();
let assetsByPublicId = new Map<string, string>();
for (let [asset] of assets.values()) {
for (let [, asset] of assets.values()) {
assetsByFilePath.set(getCacheKey(asset), asset.id);
assetsByPublicId.set(bundleGraph.getAssetPublicId(asset), asset.id);
}

// Load an asset into the module system by id.
let loadAsset = (id: string) => {
let [asset, code] = nullthrows(assets.get(id));
let [bundle, asset, code] = nullthrows(assets.get(id));
let cacheKey = getCacheKey(asset);
let cachedModule = moduleCache.get(cacheKey);
if (cachedModule) {
Expand Down Expand Up @@ -376,9 +390,9 @@ async function loadBundleUncached(
bundleGraph,
getInlineBundleContents,
);
for (let [id, [asset, code]] of subAssets) {
for (let [id, [bundle, asset, code]] of subAssets) {
if (!assets.has(id)) {
assets.set(id, [asset, code]);
assets.set(id, [bundle, asset, code]);
assetsByFilePath.set(getCacheKey(asset), asset.id);
assetsByPublicId.set(bundleGraph.getAssetPublicId(asset), asset.id);
}
Expand Down Expand Up @@ -456,7 +470,6 @@ function runModule(
require: (id: string) => any,
parcelRequire: (id: string) => any,
) {
// code = code.replace(/import\((['"].*?['"])\)/g, (_, m) => `parcelRequire.load(${m[0] + m.slice(3)})`);
let moduleFunction = vm.compileFunction(
code,
[
Expand Down
9 changes: 7 additions & 2 deletions packages/runtimes/rsc/src/RSCRuntime.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,13 @@ export default (new Runtime({
let bundles;
let async = bundleGraph.resolveAsyncDependency(node.value, bundle);
if (async?.type === 'bundle_group') {
bundles = bundleGraph.getBundlesInBundleGroup(async.value);
bundles = bundleGraph.getBundlesInBundleGroup(async.value, {
includeIsolated: false,
});
} else {
bundles = bundleGraph.getReferencedBundles(bundle);
bundles = bundleGraph.getReferencedBundles(bundle, {
includeIsolated: false,
});
}

let importMap = {};
Expand Down Expand Up @@ -208,6 +212,7 @@ export default (new Runtime({
if (asyncResolution?.type === 'bundle_group') {
let bundles = bundleGraph.getBundlesInBundleGroup(
asyncResolution.value,
{includeIsolated: false},
);
let resources = [];
let js = [];
Expand Down
Loading