Last update: Dec 2021
Status: Proposal (Draft)
This proposal outlines the format and usage of the dependencies
optional
section as a part of a Web Bundle.
- Hayato Ito (hayato@google.com)
Subresource loading with Web Bundles provides a way to load a large number of resources efficiently using a Web Bundle. However, it's not always appropriate for a web site to deliver their resources in one giant web bundle.
There are several reasons to split their resources into several bundles. Code Splitting, splitting code into various bundles which can be loaded on demand or in parallel, instead of having one giant bundle, if used correctly, has many advantages, such as:
- To prevent downloading ginormous files on initial load.
- On demand lazy-loading only when a user requests it.
- Separate common code into a separate bundle so that they can be reloaded from a browser's cache, instead of from the network, on page re-loading or page navigation.
Some bundlers like Webpack and Browserify support Code Splitting, however, Subresource loading with Web Bundles doesn't provide any support for Code Splitting yet.
This proposal explain a new dependencies
section, which provides a way to
express dependencies between this bundle and other bundles, aiming more
flexibility of how resources are grouped together as bundles. Bundlers can
express a dependency graph explicitly in bundles themselves.
In actual loading time, a browser reads the dependencies
section in a web
bundle and can get benefits. A browser can preload dependent bundles,
recursively, or fetch dependent bundles on-demand basics. Behaviors can be
specified in dependencies
section in a declarative way. No configuration is
required outside of a bundle, thus a bundle becomes more self-contained.
TODO(hayato): Define a dependencies
section format in CDDL. Meanwhile,
this section explains a non-normative conceptual format.
The dependencies
section maps a resource URL
to a tuple of
(web bundle URL
, load type
). That declares that resource URL
should be
loaded from an external bundle, web bundle url
.
For example,
resource URL | web bundle URL | load type |
---|---|---|
./a.js | ./a.wbn | preload |
./b.js | ./b.wbn | preload |
./optional.js | ./optional.wbn | lazy |
https://cdn.example.com/util.js | https://cdn.example.com/util.wbn | preload |
-
load type
is eitherpreload
orlazy
. -
If
resource URL
is absolute,web bundle URL
must be absolute. -
If
resource URL
is relative,web bundle URL
must be relative. -
resource URL
and its mappedweb bundle URL
must follow a path restriction rule.For example, the following is fine.
resource URL web bundle URL load type ./dir/foo.js ./foo.wbn preload The following is illegal.
resource URL web bundle URL load type ./foo.js ./dir/foo.wbn preload -
We probably want to support a glob pattern, such as
dir/**/*.js
, as aresource URL
.
Suppose that a web site has two top pages, main1.html
and main2.html
. They
use several resources.
The resource dependency graph is:
Note that each *.js
file here might be the result of combining one-or-more
fine-grained raw ES module files used in development time. This proposal doesn't
care the content. It's totally up to a web site how they decide granularity of
each resource, considering various factors.
Considering the usage patterns, they split their resources into the following five bundles:
The bundle, a.wbn
, has the following resources, which are required for FCP
(First Contentful Paint), for main1.html
:
URL |
---|
./a1.js |
./a2.js |
./a3.js |
./a.png |
./a.css |
and the following dependencies
section:
resource URL | web bundle URL | load type |
---|---|---|
./b1.js | ./b.wbn | preload |
./c1.js | ./c.wbn | preload |
The bundle, b.wbn
, has the following resources:
URL |
---|
./b1.js |
./b2.js |
./b3.js |
The bundle, c.wbn
, has the following resources:
URL |
---|
./c1.js |
./c2.js |
./c3.js |
and the following dependencies
section:
resource URL | web bundle URL | load type |
---|---|---|
./d1.js | ./d.wbn | lazy |
The bundle, d.wbn
, has the following resources:
URL |
---|
./d1.js |
./d2.js |
./d3.js |
The bundle, e.wbn
, has the following resources, which are required for FCP
(First Contentful Paint), for main2.html
:
URL |
---|
./e1.js |
./e.png |
./e.css |
and the following dependencies
section:
resource URL | web bundle URL | load type |
---|---|---|
./c1.js | ./c.wbn | preload |
The main page, main1.html
includes a bootstrap <script type=webbundle>
declaration.
<script type="webbundle">
{
source: "a.wbn",
resources: ["a1.js", "a.css", "a.png"]
}
</script>
....
<link rel="style" href="a.css" />
...
<img src="a.png" />
...
<script type="module" src="a1.js" />
-
After parsing
<script type=webbundle>
, a browser updates its internalresource-to-webbundle
map as follows, and starts to preloada.wbn
.resource URL web bundle URL ./a1.js ./a.wbn (preload) ./a.css ./a.wbn ./a.png ./a.wbn -
When fetching
a.wbn
is done, the map is updated:resource URL web bundle URL ./a1.js ./a.wbn ./a.css ./a.wbn ./a.png ./a.wbn ./a2.js ./a.wbn ./a3.js ./a.wbn ./b1.js ./b.wbn (preload) ./c1.js ./c.wbn (preload) Note that
a2.js
anda3.js
are also added. The current Subresource Loading with Web Bundles doesn't add that. This proposal extends the current behavior. -
Since
load type
ofb.wbn
andc.wbn
are bothpreload
, a browser starts to preloadb.wbn
andc.wbn
.TODO: There is an optimization opportunity to fetching
b.wbn
andc.wbn
in one HTTP GET request. That's currently out of scope in this proposal. We will explore this idea later. -
When fetching
b.wbn
andc.wbn
is done, the map is updated:resource URL web bundle URL ./a1.js ./a.wbn ./a.css ./a.wbn ./a.png ./a.wbn ./a2.js ./a.wbn ./a3.js ./a.wbn ./b1.js ./b.wbn ./c1.js ./c.wbn ./b2.js ./b.wbn ./b3.js ./b.wbn ./c2.js ./c.wbn ./c3.js ./c.wbn ./d1.js ./d.wbn (lazy) -
Although
d1.js
->d.wbn
mapping is added to the map, a browser doesn't start to preloadd.wbn
because itsload type
is lazy.A browser start to fetch
d.wbn
only when a user actually requestsd1.js
, such as dynamic import.For example, when
userAction()
function, which is defined inc2.js
, is called,function userAction() { const d1 = import("d1.js"); // ... }
a browser starts to fetch
d.wbn
and loadsd1.js
from the fetched bundle.
Suppose that a user visits another main page main2.html
after a user visits
main1.html
:
main2.html
:
<script type="webbundle">
{
source: "e.wbn",
resources: ["e1.js", "e.css", "e.png"]
}
</script>
....
<link rel="style" href="e.css" />
...
<img src="e.png" />
...
<script type="module" src="e1.js" />
-
A browser updates the internal
resource-to-webbundle
map as follows:resource URL web bundle URL ./e1.js ./e.wbn (preload) ./e1.css ./e.wbn ./e2.png ./e.wbn -
When fetching
e.wbn
is done, the map is updated:resource URL web bundle URL ./e1.js ./e.wbn ./e1.css ./e.wbn ./e2.png ./e.wbn ./c1.js ./c.wbn (preload) -
Since a browser caches
c.wbn
when a user visitsmain1.html
, a browser retrievesc.wbn
from the cache. A network request doesn't happen.resource URL web bundle URL ./e1.js ./e.wbn ./e1.css ./e.wbn ./e2.png ./e.wbn ./c1.js ./c.wbn (from cache) ./c2.js ./c.wbn ./c3.js ./c.wbn ./d1.js ./d.wbn (lazy) -
Similarly, a browser retrieves
d.wbn
, ifd1.js
is requested, from the cache.resource URL web bundle URL ./e1.js ./e.wbn ./e1.css ./e.wbn ./e2.png ./e.wbn ./c1.js ./c.wbn (from cache) ./c2.js ./c.wbn ./c3.js ./c.wbn ./d1.js ./d.wbn (from cache) ./d2.js ./d.wbn ./d3.js ./d.wbn
Yes. For example, in the following cases:
There can be 5 network round-trips,
[main.html -> A], [A -> C], [C -> F], [F -> G], [G -> H]
.
If a web site wants to keep their dependency graph, and wants to reduce the
number of network round-trip, they can let A
's dependencies
section have all
descendant bundles, including indirect dependencies as well as direct
dependencies.
For example, A (a.wbn)
can have the following dependencies
section:
resource URL | web bundle URL | load type |
---|---|---|
./b1.js | ./b.wbn | preload |
./c1.js | ./c.wbn | preload |
./d1.js | ./d.wbn | preload |
./f1.js | ./f.wbn | preload |
./g1.js | ./g.wbn | preload |
./h1.js | ./h.wbn | preload |
./i1.js | ./i.wbn | preload |
This is feasible because a server knows the dependency graph statically.
As previously mentioned, there is an optimization opportunity to fetch all dependent bundles in one HTTP GET request, however, that's out of scope of this proposal.