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

Bundle preloading/prefetching #5158

Merged
merged 8 commits into from
Sep 29, 2020
Merged

Conversation

wbinnssmith
Copy link
Contributor

@wbinnssmith wbinnssmith commented Sep 18, 2020

This implements declarative bundle preloading and prefetching in JavaScript using import attributes[0], which is a Stage 2 JavaScript feature.

Usage:

import('./preloaded', {preload: true});

or

import('./prefetched', {prefetch: true});

Effect: When a bundle A containing import(B, {prefetch: true}) is requested (in global mode a script tag for A is added to the page), a link tag with rel="prefetch" for B is also added to the page at the same time.

In the future, we could consider allowing setting the as priority or media constraints through these import attributes as well, which would get added as attributes on the link tag.

Question: Should we prefix or nest these attributes as to not interfere with formalized, standardized keys?


It does so by passing along the attributes object (currently only by statically analyzing pairs that have boolean values) in the dependency's meta as importAttributes when the dependency is parsed by the JSTransformer.

In the JSRuntime, when creating the loader runtime, child bundles are walked to find prefetchable/preloadable resources. Currently the entire bundle group is preloaded, and JS bundles use script priority and css bundles use style priority.

Notes:

  • Does not currently implement a way of imperatively prefetching.
  • Does not currently implement prefetching/preloading when it takes place in an entry bundle.

Test Plan:

  • Added integration test for each
  • Manual test in the kitchen sink example

[0] https://github.com/tc39/proposal-import-assertions

@height
Copy link

height bot commented Sep 18, 2020

Link Height tasks by mentioning a task ID in the pull request title or description, commit messages, or comments.

💡Tip: You can also use "Close T-X" to automatically close a task when the pull request is merged.

let cache = getCache(type);
let bundle = arguments[0];

if (cache[bundle]) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently caching loaders by the bundle path as the only key.

@wbinnssmith wbinnssmith force-pushed the preload-prefetch-module-attributes branch 2 times, most recently from cb6673c to dac7351 Compare September 18, 2020 03:24
@wbinnssmith
Copy link
Contributor Author

cc the proposal-import-assertions folks, @dandclark @xtuc @littledan @MylesBorins

and @jakearchibald, who may have opinions on this 😅

@parcel-benchmark
Copy link

parcel-benchmark commented Sep 18, 2020

Benchmark Results

Kitchen Sink ✅

Timings

Description Time Difference
Cold 6.88s +107.00ms
Cached 1.54s -28.00ms

Cold Bundles

Bundle Size Difference Time Difference
dist/legacy/parcel.d5807e82.webp 102.94kb +0.00b 192.00ms -26.00ms 🚀
dist/modern/parcel.d5807e82.webp 102.94kb +0.00b 325.00ms +58.00ms ⚠️
dist/modern/index.1964d1f6.js 1.10kb +0.00b 2.36s +227.00ms ⚠️
dist/modern/index.html 701.00b +0.00b 2.46s +212.00ms ⚠️
dist/modern/index.7bc22a8b.css 77.00b +0.00b 2.35s +252.00ms ⚠️

Cached Bundles

No bundle changes detected.

React HackerNews ✅

Timings

Description Time Difference
Cold 23.57s +1.08s
Cached 1.56s -9.00ms

Cold Bundles

Bundle Size Difference Time Difference
dist/index.js 478.68kb +140.00b ⚠️ 10.23s +24.00ms
dist/logo.24c8bf9e.png 274.00b +0.00b 181.00ms +46.00ms ⚠️

Cached Bundles

Bundle Size Difference Time Difference
dist/index.js 478.68kb +140.00b ⚠️ 926.00ms -83.00ms 🚀
dist/PermalinkedComment.9c5ad065.js 4.22kb +0.00b 925.00ms -84.00ms 🚀
dist/UserProfile.c1927888.js 1.62kb +0.00b 925.00ms -84.00ms 🚀
dist/NotFound.f8a0616d.js 387.00b +0.00b 925.00ms -62.00ms 🚀
dist/logo.24c8bf9e.png 274.00b +0.00b 881.00ms -62.00ms 🚀

AtlasKit Editor ✅

Timings

Description Time Difference
Cold 2.82m +936.00ms
Cached 5.95s +40.00ms

Cold Bundles

Bundle Size Difference Time Difference
dist/index.85b7cbc2.js 2.72mb +155.00b ⚠️ 1.51m -2.77s
dist/pdfRenderer.15a6a023.js 1.11mb +0.00b 49.00s +5.01s ⚠️
dist/popup.6c453e3c.js 169.96kb +0.00b 29.66s -15.98s 🚀
dist/Toolbar.83ac0b6e.js 92.34kb +0.00b 53.57s +3.82s ⚠️
dist/EmojiPickerComponent.ae601247.js 75.29kb +0.00b 9.57s +652.00ms ⚠️
dist/media-viewer.bcba38b3.js 71.96kb +0.00b 14.39s +1.06s ⚠️
dist/card.645a5c17.js 55.01kb +0.00b 12.06s +1.20s ⚠️
dist/card.0313bac8.js 52.78kb +0.00b 50.57s +3.76s ⚠️
dist/Modal.19ffed32.js 41.27kb +0.00b 5.32s +906.00ms ⚠️
dist/component.d7c29980.js 30.70kb +0.00b 6.36s +1.27s ⚠️
dist/component.54080a26.js 22.50kb +0.00b 5.32s +910.00ms ⚠️
dist/DatePicker.0df05dac.js 21.21kb +0.00b 9.56s +637.00ms ⚠️
dist/EmojiPickerComponent.7dc0ebd0.js 18.23kb +0.00b 9.56s +637.00ms ⚠️
dist/dropzone.7c704bdb.js 17.27kb +0.00b 50.04s +3.74s ⚠️
dist/smartMediaEditor.9623570c.js 16.87kb +0.00b 52.31s +4.49s ⚠️
dist/js.5733a017.js 16.40kb +0.00b 5.32s +910.00ms ⚠️
dist/ui.fee77548.js 13.97kb +0.00b 11.65s +786.00ms ⚠️
dist/workerHasher.1e706e77.js 11.75kb +0.00b 11.65s +786.00ms ⚠️
dist/EmojiTypeAheadComponent.1990d771.js 8.67kb +0.00b 53.05s -3.21s 🚀
dist/component.77c0f682.js 6.05kb +0.00b 5.72s +884.00ms ⚠️
dist/card.383bd9bd.js 5.61kb +0.00b 14.37s +1.04s ⚠️
dist/media-viewer.59c17dad.js 3.92kb +0.00b 14.39s +1.06s ⚠️
dist/EmojiPickerComponent.627aca6b.js 3.58kb +0.00b 52.76s +43.31s ⚠️
dist/index.ac562c80.css 3.46kb +0.00b 1.08m +4.60s ⚠️
dist/png-chunks-extract.a844fdb6.js 3.41kb +0.00b 5.32s +910.00ms ⚠️
dist/dropzone.3c67f0a6.js 3.23kb +0.00b 49.23s +5.22s ⚠️
dist/Modal.0304b9ed.js 3.08kb +0.00b 5.32s +910.00ms ⚠️
dist/clipboard.da112b6e.js 2.75kb +0.00b 49.80s +3.51s ⚠️
dist/16.290b4a43.js 2.38kb +0.00b 1.81s +339.00ms ⚠️
dist/EmojiUploadComponent.d54a1fc6.js 2.18kb +0.00b 53.05s -3.21s 🚀
dist/card.eea6ad8c.js 2.03kb +0.00b 27.72s -16.27s 🚀
dist/ResourcedEmojiComponent.abe8596c.js 2.00kb +0.00b 6.17s +1.18s ⚠️
dist/date.231500d2.js 1.73kb +0.00b 6.40s +1.05s ⚠️
dist/16.d16f14ed.js 1.73kb +0.00b 6.27s -1.96s 🚀
dist/images.80c92f5e.js 1.67kb +0.00b 6.51s +1.07s ⚠️
dist/16.1b6ca79f.js 1.66kb +0.00b 5.72s +884.00ms ⚠️
dist/feedback.d5590b34.js 1.64kb +0.00b 9.56s +637.00ms ⚠️
dist/browser.75813db0.js 1.61kb +0.00b 49.80s +3.50s ⚠️
dist/workerHasher.6d5b22d7.js 1.54kb +0.00b 11.64s +783.00ms ⚠️
dist/workerHasher.fa198233.js 1.54kb +0.00b 51.99s +4.21s ⚠️
dist/list-number.c35f9589.js 1.46kb +0.00b 6.51s +1.07s ⚠️
dist/status.8ab4226b.js 1.46kb +0.00b 6.73s +1.13s ⚠️
dist/16.9ccb5b28.js 1.38kb +0.00b 6.01s +962.00ms ⚠️
dist/code.3dd71280.js 1.38kb +0.00b 6.40s +1.05s ⚠️
dist/16.19c93285.js 1.33kb +0.00b 5.72s +884.00ms ⚠️
dist/16.c7751e34.js 1.33kb +0.00b 6.01s +974.00ms ⚠️
dist/16.51049e2c.js 1.32kb +0.00b 1.81s +339.00ms ⚠️
dist/16.18344f47.js 1.31kb +0.00b 8.41s +3.19s ⚠️
dist/link.2f4427e6.js 1.30kb +0.00b 6.51s +1.07s ⚠️
dist/heading6.fdbbdf4d.js 1.30kb +0.00b 6.84s +1.17s ⚠️
dist/heading3.2113b7a1.js 1.29kb +0.00b 6.84s +1.17s ⚠️
dist/16.2c4c1846.js 1.27kb +0.00b 6.27s +1.05s ⚠️
dist/emoji.93ae61d3.js 1.23kb +0.00b 8.55s +3.21s ⚠️
dist/16.d17ab186.js 1.23kb +0.00b 6.27s -1.96s 🚀
dist/16.436f097f.js 1.21kb +0.00b 1.81s +339.00ms ⚠️
dist/16.b4da0acb.js 1.19kb +0.00b 6.27s +1.05s ⚠️
dist/16.02c68c4f.js 1.18kb +0.00b 8.41s +3.19s ⚠️
dist/heading5.e195aab1.js 1.17kb +0.00b 6.84s +1.17s ⚠️
dist/expand.450f3aea.js 1.16kb +0.00b 9.56s +637.00ms ⚠️
dist/media-card-analytics-error-boundary.2323af57.js 1.15kb +0.00b 27.80s -16.21s 🚀
dist/16.b46ef690.js 1.14kb +0.00b 1.81s +339.00ms ⚠️
dist/16.4e3b0235.js 1.14kb +0.00b 6.01s +963.00ms ⚠️
dist/16.9ba230ea.js 1.13kb +0.00b 5.72s +888.00ms ⚠️
dist/16.a37df53b.js 1.13kb +0.00b 6.01s +962.00ms ⚠️
dist/16.9adb4ec7.js 1.13kb +0.00b 6.01s +963.00ms ⚠️
dist/16.eafe1a11.js 1.13kb +0.00b 5.72s +884.00ms ⚠️
dist/heading2.a2a1be3f.js 1.11kb +0.00b 6.84s +1.17s ⚠️
dist/16.3920766f.js 1.10kb +0.00b 1.81s +340.00ms ⚠️
dist/component.6600f83e.js 1.07kb +0.00b 6.17s +1.18s ⚠️
dist/mention.4d64e059.js 1.07kb +0.00b 6.62s +1.10s ⚠️
dist/heading4.ec980e93.js 1.06kb +0.00b 6.84s +1.17s ⚠️
dist/16.afcb09b6.js 1.05kb +0.00b 6.27s +1.05s ⚠️
dist/layout.52ba15f7.js 1.05kb +0.00b 6.51s +1.07s ⚠️
dist/divider.16defde6.js 1.03kb +0.00b 6.39s +1.05s ⚠️
dist/quote.aa25b83c.js 1.02kb +0.00b 6.73s +1.13s ⚠️
dist/action.9aa72b39.js 1.00kb +0.00b 6.40s -1.92s 🚀
dist/decision.20244046.js 1010.00b +0.00b 6.39s +1.05s ⚠️
dist/panel-warning.97a0e3b3.js 1008.00b +0.00b 6.62s +1.10s ⚠️
dist/list.c9147a62.js 978.00b +0.00b 6.51s +1.07s ⚠️
dist/heading1.7d899ba5.js 976.00b +0.00b 6.73s +1.13s ⚠️
dist/panel-error.8d11e8b7.js 906.00b +0.00b 6.62s +1.10s ⚠️
dist/panel.a28c56ee.js 897.00b +0.00b 6.73s +1.13s ⚠️
dist/table.5a4874ee.js 889.00b +0.00b 6.73s +1.13s ⚠️
dist/panel-success.9b923e58.js 845.00b +0.00b 6.62s +1.10s ⚠️
dist/panel-note.97a4d541.js 841.00b +0.00b 6.62s +1.10s ⚠️
dist/media-picker-analytics-error-boundary.f8ddfee3.js 839.00b +0.00b 49.23s +2.93s ⚠️
dist/media-card-analytics-error-boundary.663369b9.js 835.00b +0.00b 14.37s -1.32m 🚀
dist/media-card-analytics-error-boundary.73ba4074.js 835.00b +0.00b 50.56s +2.79s ⚠️
dist/media-viewer-analytics-error-boundary.0afa7031.js 755.00b +0.00b 27.72s +14.39s ⚠️
dist/EmojiPickerComponent.2e54b9c6.js 535.00b +0.00b 53.05s -3.21s 🚀
dist/simpleHasher.16c8bf41.js 528.00b +0.00b 11.65s +784.00ms ⚠️
dist/simpleHasher.a29b2a8e.js 528.00b +0.00b 51.99s +4.20s ⚠️

Cached Bundles

Bundle Size Difference Time Difference
dist/index.85b7cbc2.js 2.72mb +155.00b ⚠️ 194.00ms -56.00ms 🚀
dist/Modal.19ffed32.js 41.27kb +0.00b 207.00ms -48.00ms 🚀
dist/component.d7c29980.js 30.70kb +0.00b 195.00ms -55.00ms 🚀
dist/component.54080a26.js 22.50kb +0.00b 230.00ms -30.00ms 🚀
dist/js.5733a017.js 16.40kb +0.00b 207.00ms -48.00ms 🚀
dist/component.77c0f682.js 6.05kb +0.00b 230.00ms -30.00ms 🚀
dist/png-chunks-extract.a844fdb6.js 3.41kb +0.00b 207.00ms -48.00ms 🚀
dist/Modal.0304b9ed.js 3.08kb +0.00b 207.00ms -48.00ms 🚀
dist/clipboard.da112b6e.js 2.75kb +0.00b 503.00ms -40.00ms 🚀
dist/16.290b4a43.js 2.38kb +0.00b 207.00ms -48.00ms 🚀
dist/ResourcedEmojiComponent.abe8596c.js 2.00kb +0.00b 195.00ms -54.00ms 🚀
dist/date.231500d2.js 1.73kb +0.00b 326.00ms +18.00ms ⚠️
dist/16.d16f14ed.js 1.73kb +0.00b 278.00ms -18.00ms 🚀
dist/16.1b6ca79f.js 1.66kb +0.00b 231.00ms -29.00ms 🚀
dist/feedback.d5590b34.js 1.64kb +0.00b 407.00ms +36.00ms ⚠️
dist/browser.75813db0.js 1.61kb +0.00b 503.00ms -40.00ms 🚀
dist/workerHasher.fa198233.js 1.54kb +0.00b 527.00ms +32.00ms ⚠️
dist/list-number.c35f9589.js 1.46kb +0.00b 351.00ms +22.00ms ⚠️
dist/status.8ab4226b.js 1.46kb +0.00b 390.00ms +31.00ms ⚠️
dist/16.19c93285.js 1.33kb +0.00b 231.00ms -29.00ms 🚀
dist/16.c7751e34.js 1.33kb +0.00b 254.00ms -13.00ms 🚀
dist/16.51049e2c.js 1.32kb +0.00b 548.00ms +77.00ms ⚠️
dist/link.2f4427e6.js 1.30kb +0.00b 351.00ms +22.00ms ⚠️
dist/heading6.fdbbdf4d.js 1.30kb +0.00b 406.00ms +35.00ms ⚠️
dist/heading3.2113b7a1.js 1.29kb +0.00b 391.00ms +32.00ms ⚠️
dist/16.d17ab186.js 1.23kb +0.00b 279.00ms -17.00ms 🚀
dist/16.436f097f.js 1.21kb +0.00b 548.00ms +77.00ms ⚠️
dist/16.b4da0acb.js 1.19kb +0.00b 279.00ms -17.00ms 🚀
dist/heading5.e195aab1.js 1.17kb +0.00b 406.00ms +35.00ms ⚠️
dist/expand.450f3aea.js 1.16kb +0.00b 407.00ms +36.00ms ⚠️
dist/16.b46ef690.js 1.14kb +0.00b 548.00ms +77.00ms ⚠️
dist/16.4e3b0235.js 1.14kb +0.00b 278.00ms -193.00ms 🚀
dist/16.9ba230ea.js 1.13kb +0.00b 254.00ms -13.00ms 🚀
dist/16.a37df53b.js 1.13kb +0.00b 254.00ms -13.00ms 🚀
dist/16.eafe1a11.js 1.13kb +0.00b 231.00ms -29.00ms 🚀
dist/heading2.a2a1be3f.js 1.11kb +0.00b 390.00ms +31.00ms ⚠️
dist/16.3920766f.js 1.10kb +0.00b 548.00ms +77.00ms ⚠️
dist/component.6600f83e.js 1.07kb +0.00b 195.00ms -54.00ms 🚀
dist/mention.4d64e059.js 1.07kb +0.00b 351.00ms +22.00ms ⚠️
dist/heading4.ec980e93.js 1.06kb +0.00b 406.00ms +35.00ms ⚠️
dist/16.afcb09b6.js 1.05kb +0.00b 279.00ms -17.00ms 🚀
dist/layout.52ba15f7.js 1.05kb +0.00b 350.00ms +23.00ms ⚠️
dist/quote.aa25b83c.js 1.02kb +0.00b 548.00ms +205.00ms ⚠️
dist/panel-warning.97a0e3b3.js 1008.00b +0.00b 371.00ms +28.00ms ⚠️
dist/list.c9147a62.js 978.00b +0.00b 351.00ms +22.00ms ⚠️
dist/heading1.7d899ba5.js 976.00b +0.00b 390.00ms +31.00ms ⚠️
dist/panel-error.8d11e8b7.js 906.00b +0.00b 370.00ms +41.00ms ⚠️
dist/panel.a28c56ee.js 897.00b +0.00b 371.00ms +28.00ms ⚠️
dist/table.5a4874ee.js 889.00b +0.00b 390.00ms +31.00ms ⚠️
dist/panel-success.9b923e58.js 845.00b +0.00b 371.00ms +28.00ms ⚠️
dist/panel-note.97a4d541.js 841.00b +0.00b 371.00ms +28.00ms ⚠️
dist/media-picker-analytics-error-boundary.f8ddfee3.js 839.00b +0.00b 503.00ms -39.00ms 🚀
dist/media-card-analytics-error-boundary.73ba4074.js 835.00b +0.00b 527.00ms +32.00ms ⚠️
dist/index.html 119.00b +0.00b 194.00ms -56.00ms 🚀

Three.js x4 🚨

Timings

Description Time Difference
Cold FAILED -0.00ms
Cached FAILED -0.00ms

Cold Bundles

No bundles found, this is probably a failed build...

Cached Bundles

No bundles found, this is probably a failed build...

Click here to view a detailed benchmark overview.

@wbinnssmith wbinnssmith force-pushed the preload-prefetch-module-attributes branch 2 times, most recently from 1277591 to 6ef0983 Compare September 18, 2020 05:18
@littledan
Copy link

I will have to think about this some more, but to me, at a first glance, it makes sense for this to be neither an assertion not a change in how the module is evaluated (a "with" evaluator attribute). I don't think this feature will make sense to put in browsers as proposed, as it is not statically analyzable, and I guess it doesn't make sense with import statements either (since once you encounter an import statement, you already know that you want to do a fetch). So, personally, I don't have any concerns with this as a tool-only feature at this point, but I will keep thinking on it and get back to you if any issue occurs to me.

@wbinnssmith
Copy link
Contributor Author

Thanks for the feedback @littledan — much appreciated 😄

With this change, Parcel removes all non-assert properties of the object passed as the second argument to dynamic import() calls. This should keep these attributes as tool-only for now.

@wbinnssmith wbinnssmith force-pushed the preload-prefetch-module-attributes branch 2 times, most recently from fe91e49 to 4642762 Compare September 25, 2020 21:57
@littledan
Copy link

Thinking about this more, I wonder if transformation/"evaluator" attributes actually could work here--but with some very different syntax.

What if it was like a static import statement, where you made a transformation to defer it? Then, this could even be implemented in browsers:

import foo from "./foo.mjs" with { load: "prefetch" };

const exports = await foo.get()

So instead of using dynamic import, you do a static import that is specified to defer it. Then, the module is evaluated only when get is done. This would make the set of prefetches determined reliably just by parsing, no partial evaluation required--the key property to enable eventual browser alignment.

@wbinnssmith wbinnssmith force-pushed the preload-prefetch-module-attributes branch from 4642762 to be2a540 Compare September 29, 2020 21:52
@wbinnssmith wbinnssmith merged commit e1e1d8a into v2 Sep 29, 2020
@wbinnssmith
Copy link
Contributor Author

Merged this, with an understanding that this is likely unstable and going to evolve.

As discussed this morning, let's work with @littledan to draft a proposal at a language level that allows us to further explore this space and iterate on ideas, keeping in mind possible implementations in browsers.

@xtuc
Copy link

xtuc commented Sep 30, 2020

I agree with @littledan that it would be a transformation/"evaluator" attribute. One other use case for loading was lazy:

import foo from "./foo.mjs" with { load: "lazy" };

As opposed to tc39/proposal-import-attributes#99 (comment) i think it suits a with attribute because implementation might want to transform the module, especially for lazy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants