@@ -1435,6 +1435,158 @@ export default class BundleGraph {
14351435 } ) ;
14361436 }
14371437
1438+ // New method: Fast checks only (no caching of results)
1439+ isAssetReferencedFastCheck ( bundle : Bundle , asset : Asset ) : boolean | null {
1440+ // Fast Check #1: If asset is in multiple bundles in same target, it's referenced
1441+ let bundlesWithAsset = this . getBundlesWithAsset ( asset ) . filter (
1442+ ( b ) =>
1443+ b . target . name === bundle . target . name &&
1444+ b . target . distDir === bundle . target . distDir ,
1445+ ) ;
1446+
1447+ if ( bundlesWithAsset . length > 1 ) {
1448+ return true ;
1449+ }
1450+
1451+ // Fast Check #2: If asset is referenced by any async/conditional dependency, it's referenced
1452+ let assetNodeId = nullthrows ( this . _graph . getNodeIdByContentKey ( asset . id ) ) ;
1453+
1454+ if (
1455+ this . _graph
1456+ . getNodeIdsConnectedTo ( assetNodeId , bundleGraphEdgeTypes . references )
1457+ . map ( ( id ) => this . _graph . getNode ( id ) )
1458+ . some (
1459+ ( node ) =>
1460+ node ?. type === 'dependency' &&
1461+ ( node . value . priority === Priority . lazy ||
1462+ node . value . priority === Priority . conditional ) &&
1463+ node . value . specifierType !== SpecifierType . url ,
1464+ )
1465+ ) {
1466+ return true ;
1467+ }
1468+
1469+ // Fast checks failed - return null to indicate expensive computation needed
1470+ return null ;
1471+ }
1472+
1473+ getReferencedAssets ( bundle : Bundle ) : Set < Asset > {
1474+ let referencedAssets = new Set < Asset > ( ) ;
1475+
1476+ // Build a map of all assets in this bundle with their dependencies
1477+ // This allows us to check all assets in a single traversal
1478+ let assetDependenciesMap = new Map < Asset , Array < Dependency > > ( ) ;
1479+
1480+ this . traverseAssets ( bundle , ( asset ) => {
1481+ // Always do fast checks (no caching)
1482+ let fastCheckResult = this . isAssetReferencedFastCheck ( bundle , asset ) ;
1483+
1484+ if ( fastCheckResult === true ) {
1485+ referencedAssets . add ( asset ) ;
1486+ return ;
1487+ }
1488+
1489+ // Fast checks failed (fastCheckResult === null), need expensive computation
1490+ // Check if it's actually referenced via traversal
1491+
1492+ // Store dependencies for later batch checking
1493+ let dependencies = this . _graph
1494+ . getNodeIdsConnectedTo (
1495+ nullthrows ( this . _graph . getNodeIdByContentKey ( asset . id ) ) ,
1496+ )
1497+ . map ( ( id ) => nullthrows ( this . _graph . getNode ( id ) ) )
1498+ . filter ( ( node ) => node . type === 'dependency' )
1499+ . map ( ( node ) => {
1500+ invariant ( node . type === 'dependency' ) ;
1501+ return node . value ;
1502+ } ) ;
1503+
1504+ if ( dependencies . length > 0 ) {
1505+ assetDependenciesMap . set ( asset , dependencies ) ;
1506+ }
1507+ } ) ;
1508+
1509+ // If no assets need the expensive check, return early
1510+ if ( assetDependenciesMap . size === 0 ) {
1511+ return referencedAssets ;
1512+ }
1513+
1514+ // Get the assets we need to check once
1515+ let assetsToCheck = Array . from ( assetDependenciesMap . keys ( ) ) ;
1516+
1517+ // Helper function to check if all assets from assetDependenciesMap are in referencedAssets
1518+ const allAssetsReferenced = ( ) : boolean =>
1519+ assetsToCheck . length <= referencedAssets . size &&
1520+ assetsToCheck . every ( ( asset ) => referencedAssets . has ( asset ) ) ;
1521+
1522+ // Do ONE traversal to check all remaining assets
1523+ // We can share visitedBundles across all assets because we check every asset
1524+ // against every visited bundle, which matches the original per-asset behavior
1525+ let siblingBundles = new Set (
1526+ this . getBundleGroupsContainingBundle ( bundle ) . flatMap ( ( bundleGroup ) =>
1527+ this . getBundlesInBundleGroup ( bundleGroup , { includeInline : true } ) ,
1528+ ) ,
1529+ ) ;
1530+
1531+ let visitedBundles : Set < Bundle > = new Set ( ) ;
1532+
1533+ // Single traversal from all referencers
1534+ for ( let referencer of siblingBundles ) {
1535+ this . traverseBundles ( ( descendant , _ , actions ) => {
1536+ if ( descendant . id === bundle . id ) {
1537+ return ;
1538+ }
1539+
1540+ if ( visitedBundles . has ( descendant ) ) {
1541+ actions . skipChildren ( ) ;
1542+ return ;
1543+ }
1544+
1545+ visitedBundles . add ( descendant ) ;
1546+
1547+ if (
1548+ descendant . type !== bundle . type ||
1549+ fromEnvironmentId ( descendant . env ) . context !==
1550+ fromEnvironmentId ( bundle . env ) . context
1551+ ) {
1552+ // Don't skip children - they might be the right type!
1553+ return ;
1554+ }
1555+
1556+ // Check ALL assets at once in this descendant bundle
1557+ for ( let [ asset , dependencies ] of assetDependenciesMap ) {
1558+ // Skip if already marked as referenced
1559+ if ( referencedAssets . has ( asset ) ) {
1560+ continue ;
1561+ }
1562+
1563+ // Check if this descendant bundle references the asset
1564+ if (
1565+ ! this . bundleHasAsset ( descendant , asset ) &&
1566+ dependencies . some ( ( dependency ) =>
1567+ this . bundleHasDependency ( descendant , dependency ) ,
1568+ )
1569+ ) {
1570+ referencedAssets . add ( asset ) ;
1571+ }
1572+ }
1573+
1574+ // If all assets from assetDependenciesMap are now marked as referenced, we can stop early
1575+ if ( allAssetsReferenced ( ) ) {
1576+ actions . stop ( ) ;
1577+ return ;
1578+ }
1579+ } , referencer ) ;
1580+
1581+ // If all assets from assetDependenciesMap are referenced, no need to check more sibling bundles
1582+ if ( allAssetsReferenced ( ) ) {
1583+ break ;
1584+ }
1585+ }
1586+
1587+ return referencedAssets ;
1588+ }
1589+
14381590 hasParentBundleOfType ( bundle : Bundle , type : string ) : boolean {
14391591 let parents = this . getParentBundles ( bundle ) ;
14401592 return (
0 commit comments