-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
Parcel 2: BundleGraph and Default Bundler plugin #2401
Conversation
id: 'bundle:' + asset.id, | ||
type: asset.type, | ||
assetGraph: graph, | ||
filePath: 'bundle.' + BUNDLECOUNT++ + '.js' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will be replaced by namer plugins
@@ -220,6 +226,96 @@ export default class AssetGraph extends Graph { | |||
} | |||
} | |||
|
|||
getDependencies(asset: Asset): Array<Dependency> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do we feel about adding all of these helper methods meant for external plugin consumption to the existing AssetGraph class used in core?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not expose the raw AssetGraph. Ideally we'd wrap it and only expose what we want the plugins to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I agree with that. Let's get this PR in first and then discuss what the interface we want to expose is.
@@ -27,6 +29,14 @@ export default class PackagerRunner { | |||
} | |||
|
|||
async writeBundle(bundle: Bundle) { | |||
// deserialize asset graph from JSON |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be awesome to have a better way of serializing and deserializing complex objects from JSON like this...
const getBundleGroupId = (bundleGroup: BundleGroup) => | ||
'bundle_group:' + bundleGroup.dependency.id; | ||
|
||
export default class BundleGraph extends AssetGraph { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Meant to make this extend from Graph
instead of AssetGraph
obviously, but AssetGraph
has the nice dumpGraphViz
function. Will move that to Graph
later.
|
||
traverseAncestors( | ||
startNode: Node, | ||
visit: (node: Node, context?: any, traversal: TraversalContext) => any |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the other traversal methods have visit as the first arg, is there a reason why this one is different?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well... in the other ones it is optional, whereas this one it is required. So I felt like it looked nicer to have the callback at the end in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would probably prefer consistency, but I realize it's a nitpick ;)
All in all, this looks really good! The graph visualizations really help explain what's going on. My one constructive criticism would be that it got a little confusing following how the bundle asset graphs are morphed by the bundler. I think the idea we talked about of exposing a wrapper or a different asset graph class to the bundler plugin would naturally help with this. It might help to strip out nodes that aren't really necessary for bundling, like files and transformer requests. Would be less cognitive load for bundler plugin writers and would be nicer to introspect. |
} | ||
// if (GLOBAL_RE.test(asset.code)) { | ||
// walk.ancestor(asset.ast.program, insertGlobals, asset); | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this be uncommented eventually?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah... that transform is currently broken
Yeah I was thinking about removing transformer requests and file nodes from the asset graphs inside bundles as well. Would definitely make things simpler. Need to figure out how to do the graph transforms for that when creating a bundle. One idea I was also thinking about was how to make bundler plugins more composable so you could use the default one in addition to a third party plugin without the third party one needing to reimplement all of the features of the default. We could possibly do this by making the input to bundler plugins only a bundle graph instead of also the original asset graph. The initial bundle graph passed in would contain a single bundle containing all of the assets, and then the bundler plugins would take that bundle graph and modify it to include other bundles. This way you could chain multiple bundlers together by passing the bundle graph to each one to modify. Thoughts? |
Hmm, do you have an example of when that would be useful? Seems like it would help if you want to break the bundles apart even more, but not very helpful if you want to rearrange what is in the bundles. |
Yeah that's true. If we broke apart the default bundler slightly into its three phases so they could be composed together that might make it easier to keep parts and overrides parts. e.g. keep the dynamic import handling, but override the bundle splitting behavior. idk, I haven't fully thought it through yet. |
Going to merge this to get some stuff in. Will go back and refactor some of this later. |
This is an implementation of the default bundler plugin for Parcel 2, along with the infrastructure in core necessary to support it. It implements code splitting, splitting by asset type, asset deduping, and bundle splitting.
Closes #2139. Related to #885.
BundleGraph
to core, and changes the Bundler plugin API to return one. Bundler plugins accept the complete asset graph, along with an empty bundle graph instance to fill in. This way we don't need to expose the constructor to plugins. ABundleGraph
contains nodes of typebundle_group
andbundle
.Bundle
object now has anAssetGraph
in it instead of just an array of assets. This gives packagers access to the dependencies and other information in the graph for that bundle.BundleGraph
andAssetGraph
, along with some traversal methods toGraph
itself.asset.getOutput()
rather than beforehand in the PackagerRunner.The default bundler traverses the asset graph and creates a node of type
bundle_group
for every entry point and async dependency. Connected to these arebundle
nodes, which each contain a subset of the full asset graph. The point of thebundle_group
nodes is to group things that would be loaded in parallel, e.g. JS + CSS, or JS and other bundle split JS.Child bundle groups can share dependencies with their parents, so assets that would be duplicated are removed from children. When a dependency with an isolated environment (e.g. a web worker) is hit where it would be impossible to access parent dependencies, it is connected to the root of the bundle graph instead. This way it will have no parents and thus not share anything with other bundles.
Within each
bundle
node is a sub asset graph containing only the assets in that bundle. These asset graphs inside bundles start from one or more assets and point to the rest of the subgraph for those assets. When an async dependency is hit that creates a separate bundle, the dependency resolution is replaced with thebundle_group
node from the bundle graph for this dependency, which gives packagers access to the referenced bundles that should be loaded.The above example shows three async dependencies, two of which depend on react and lodash. Since those are large libraries and they are shared, they are extracted out into their own separate bundle which is added to the original bundle groups to be loaded in parallel. This allows them to be cached independently of the original bundles, decreasing the overall application size.
Within those async bundles, the separated assets which are no longer in the original bundles are replaced with
asset_reference
nodes, which give packagers access to the original assets without including them or their dependencies in the bundle.The full rules for the default bundler are as follows:
Feedback on the API for AssetGraph and BundleGraph would be appreciated! One idea I had was to make bundler plugins composable, but I will explore that in a future PR. 😄