Repository: parcel-bundler/parcel
Pull request #4994 (2020-08-08 12:29:24)

Packager: loadConfig
# ↪️ Pull Request

Based on cache-key (like the bundler) until it's made a request. However: should the loading happen in a worker? Two options:
- currently: main thread: load config, send config over to worker on a cache miss
- all in a worker: load config, calculate cache key, return key to main thread

## ✔️ PR Todo

- [x] ~Determine where to run `loadConfig`~ Run in worker
	- Consideration: can we prevent sending over the bundle graph to a work if it's preventable?
- [x] Test Perf of cached builds?

height[bot]: > Link [Height](https://parcel.height.app) 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.
<!-- Sent by Height -->
parcel-benchmark: ## Benchmark Results
<details><summary>Kitchen Sink ✅</summary><p>

#### Timings

| Description | Time | Difference |
| --- | --- | --- |
| Cold | 5.75s | -279.00ms |
| Cached | 2.10s | +51.00ms |

#### Cold Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/legacy/parcel.d5807e82.webp | 102.94kb | +0.00b | 256.00ms | -28.00ms 🚀 |
| dist/modern/parcel.d5807e82.webp | 102.94kb | +0.00b | 256.00ms | -31.00ms 🚀 |
| dist/legacy/index.42516b4a.js | 1.10kb | +0.00b | 1.55s | -89.00ms 🚀 |
| dist/modern/index.8a1ced31.js | 1.10kb | +0.00b | 1.55s | -90.00ms 🚀 |
| dist/legacy/index.html | 701.00b | +0.00b | 715.00ms | +40.00ms ⚠️ |
| dist/modern/index.html | 701.00b | +0.00b | 711.00ms | +38.00ms ⚠️ |
| dist/modern/index.e480862d.css | 77.00b | +0.00b | 1.43s | -98.00ms 🚀 |

#### Cached Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/legacy/parcel.d5807e82.webp | 102.94kb | +0.00b | 196.00ms | +47.00ms ⚠️ |
| dist/modern/parcel.d5807e82.webp | 102.94kb | +0.00b | 195.00ms | +45.00ms ⚠️ |
| dist/legacy/index.42516b4a.js | 1.10kb | +0.00b | 408.00ms | +41.00ms ⚠️ |
| dist/modern/index.8a1ced31.js | 1.10kb | +0.00b | 407.00ms | +41.00ms ⚠️ |
| dist/legacy/index.html | 701.00b | +0.00b | 409.00ms | +41.00ms ⚠️ |
| dist/modern/index.html | 701.00b | +0.00b | 407.00ms | +41.00ms ⚠️ |
| dist/legacy/index.0e158b6b.css | 77.00b | +0.00b | 408.00ms | +41.00ms ⚠️ |
| dist/modern/index.e480862d.css | 77.00b | +0.00b | 407.00ms | +41.00ms ⚠️ |

</p></details>

<details><summary>React HackerNews ✅</summary><p>

#### Timings

| Description | Time | Difference |
| --- | --- | --- |
| Cold | 26.59s | +601.00ms |
| Cached | 2.19s | -5.00ms |

#### Cold Bundles

*No bundle changes detected.*

#### Cached Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/logo.24c8bf9e.png | 274.00b | +0.00b | 202.00ms | +17.00ms ⚠️ |

</p></details>

<details><summary>AtlasKit Editor ✅</summary><p>

#### Timings

| Description | Time | Difference |
| --- | --- | --- |
| Cold | 2.61m | -4.58s |
| Cached | 4.55s | -56.00ms |

#### Cold Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/index.e8555081.js | 2.73mb | +9.00b ⚠️ | 1.43m | -3.65s |
| dist/card.ce67dc35.js | 55.09kb | +0.00b | 12.94s | -739.00ms 🚀 |
| dist/component.59edff1f.js | 30.78kb | +0.00b | 2.59s | +287.00ms ⚠️ |
| dist/dropzone.812039de.js | 17.27kb | +0.00b | 52.07s | -37.65s 🚀 |
| dist/ui.cee430a3.js | 13.97kb | +0.00b | 12.91s | -769.00ms 🚀 |
| dist/workerHasher.65b8a1b7.js | 11.78kb | +0.00b | 12.24s | -622.00ms 🚀 |
| dist/card.4ac5c1b2.js | 5.61kb | +0.00b | 14.01s | -1.03s 🚀 |
| dist/dropzone.2f1a1e17.js | 3.23kb | +0.00b | 28.54s | -1.45s 🚀 |
| dist/Modal.2835744c.js | 3.08kb | +0.00b | 4.84s | +1.06s ⚠️ |
| dist/16.d1b8e6f7.js | 2.38kb | +0.00b | 2.58s | +300.00ms ⚠️ |
| dist/card.cadccd96.js | 2.03kb | +0.00b | 15.57s | -14.29s 🚀 |
| dist/ResourcedEmojiComponent.d9a49606.js | 2.00kb | +0.00b | 4.84s | +1.06s ⚠️ |
| dist/16.86a8c7d4.js | 1.66kb | +0.00b | 7.14s | +452.00ms ⚠️ |
| dist/list-number.4632b459.js | 1.46kb | +0.00b | 7.01s | -39.64s 🚀 |
| dist/status.cccf04be.js | 1.46kb | +0.00b | 46.98s | +40.02s ⚠️ |
| dist/16.c1ed07f8.js | 1.33kb | +0.00b | 7.14s | +452.00ms ⚠️ |
| dist/16.a4b392c2.js | 1.32kb | +0.00b | 2.57s | +289.00ms ⚠️ |
| dist/link.d12cc130.js | 1.30kb | +0.00b | 7.01s | -39.64s 🚀 |
| dist/16.e60b4d77.js | 1.21kb | +0.00b | 2.57s | +289.00ms ⚠️ |
| dist/media-card-analytics-error-boundary.6a25b970.js | 1.15kb | +0.00b | 28.54s | -1.45s 🚀 |
| dist/16.2096b9f3.js | 1.14kb | +0.00b | 2.57s | +289.00ms ⚠️ |
| dist/16.ecf8a702.js | 1.13kb | +0.00b | 7.14s | +452.00ms ⚠️ |
| dist/16.da43aa07.js | 1.13kb | +0.00b | 7.13s | +451.00ms ⚠️ |
| dist/heading2.e219309a.js | 1.11kb | +0.00b | 46.98s | +40.02s ⚠️ |
| dist/16.dd07505e.js | 1.10kb | +0.00b | 4.84s | +1.06s ⚠️ |
| dist/component.40194190.js | 1.07kb | +0.00b | 4.84s | +1.06s ⚠️ |
| dist/mention.b1828447.js | 1.07kb | +0.00b | 7.01s | -39.64s 🚀 |
| dist/list.057cb664.js | 978.00b | +0.00b | 7.01s | -39.64s 🚀 |
| dist/heading1.fa66b375.js | 976.00b | +0.00b | 46.98s | +40.02s ⚠️ |
| dist/table.b35545be.js | 889.00b | +0.00b | 46.98s | +40.02s ⚠️ |
| dist/media-picker-analytics-error-boundary.553055f6.js | 837.00b | +0.00b | 30.53s | -20.19s 🚀 |
| dist/media-card-analytics-error-boundary.964206cc.js | 833.00b | +0.00b | 12.91s | -769.00ms 🚀 |
| dist/simpleHasher.f8e69464.js | 528.00b | +0.00b | 12.90s | -773.00ms 🚀 |
| dist/simpleHasher.32d9352e.js | 528.00b | +0.00b | 1.43m | +33.91s ⚠️ |
| dist/index.html | 119.00b | +0.00b | 766.00ms | -88.00ms 🚀 |

#### Cached Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/index.e8555081.js | 2.73mb | +0.00b | 479.00ms | +67.00ms ⚠️ |
| dist/pdfRenderer.cd742f5e.js | 1.11mb | -3.00b 🚀 | 456.00ms | +47.00ms ⚠️ |
| dist/editorView.ae59b49f.js | 585.98kb | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/popup.4a66e15e.js | 170.62kb | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/EmojiPickerComponent.bab709db.js | 97.89kb | +0.00b | 457.00ms | +47.00ms ⚠️ |
| dist/Toolbar.33a83ae3.js | 92.34kb | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/media-viewer.a8f3a476.js | 72.02kb | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/card.ce67dc35.js | 55.09kb | +0.00b | 456.00ms | +46.00ms ⚠️ |
| dist/card.1007a95f.js | 52.86kb | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/Modal.e3bd77d0.js | 41.40kb | +0.00b | 478.00ms | +67.00ms ⚠️ |
| dist/component.59edff1f.js | 30.78kb | +0.00b | 478.00ms | +67.00ms ⚠️ |
| dist/component.37867f50.js | 22.51kb | +0.00b | 478.00ms | +66.00ms ⚠️ |
| dist/DatePicker.cbe44a9b.js | 21.17kb | +0.00b | 457.00ms | +47.00ms ⚠️ |
| dist/EmojiPickerComponent.898d8be6.js | 18.23kb | +0.00b | 457.00ms | +47.00ms ⚠️ |
| dist/dropzone.812039de.js | 17.27kb | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/smartMediaEditor.c0ff7bfa.js | 16.87kb | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/js.48be70a4.js | 16.40kb | +0.00b | 478.00ms | +67.00ms ⚠️ |
| dist/ui.cee430a3.js | 13.97kb | +0.00b | 456.00ms | +46.00ms ⚠️ |
| dist/workerHasher.65b8a1b7.js | 11.78kb | +0.00b | 456.00ms | +46.00ms ⚠️ |
| dist/EmojiTypeAheadComponent.9af6e5fc.js | 8.67kb | +0.00b | 457.00ms | +47.00ms ⚠️ |
| dist/component.11476a92.js | 6.05kb | +0.00b | 478.00ms | +66.00ms ⚠️ |
| dist/card.4ac5c1b2.js | 5.61kb | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/media-viewer.bc661bce.js | 3.92kb | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/EmojiPickerComponent.624bf3f4.js | 3.58kb | +0.00b | 457.00ms | +47.00ms ⚠️ |
| dist/index.393afe59.css | 3.46kb | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/png-chunks-extract.8d15a914.js | 3.41kb | +0.00b | 478.00ms | +67.00ms ⚠️ |
| dist/dropzone.2f1a1e17.js | 3.23kb | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/Modal.2835744c.js | 3.08kb | +0.00b | 478.00ms | +67.00ms ⚠️ |
| dist/clipboard.0244488f.js | 2.75kb | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/16.d1b8e6f7.js | 2.38kb | +0.00b | 478.00ms | +67.00ms ⚠️ |
| dist/EmojiUploadComponent.88a444df.js | 2.18kb | +0.00b | 457.00ms | +47.00ms ⚠️ |
| dist/card.cadccd96.js | 2.03kb | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/ResourcedEmojiComponent.d9a49606.js | 2.00kb | +0.00b | 479.00ms | +68.00ms ⚠️ |
| dist/date.d49e64f8.js | 1.73kb | +0.00b | 477.00ms | +66.00ms ⚠️ |
| dist/16.f40c1329.js | 1.73kb | +0.00b | 478.00ms | +66.00ms ⚠️ |
| dist/images.80270b1f.js | 1.67kb | +0.00b | 458.00ms | +47.00ms ⚠️ |
| dist/16.86a8c7d4.js | 1.66kb | +0.00b | 478.00ms | +66.00ms ⚠️ |
| dist/feedback.8b7c7928.js | 1.64kb | +0.00b | 457.00ms | +47.00ms ⚠️ |
| dist/browser.16c438d4.js | 1.61kb | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/workerHasher.9bb425b1.js | 1.54kb | +0.00b | 457.00ms | +47.00ms ⚠️ |
| dist/workerHasher.f66f00c7.js | 1.54kb | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/list-number.4632b459.js | 1.46kb | +0.00b | 456.00ms | +45.00ms ⚠️ |
| dist/status.cccf04be.js | 1.46kb | +0.00b | 457.00ms | +46.00ms ⚠️ |
| dist/16.cff410ff.js | 1.38kb | +0.00b | 478.00ms | +66.00ms ⚠️ |
| dist/code.af9ca2b3.js | 1.38kb | +0.00b | 477.00ms | +66.00ms ⚠️ |
| dist/16.c1ed07f8.js | 1.33kb | +0.00b | 478.00ms | +66.00ms ⚠️ |
| dist/16.6603f2f3.js | 1.33kb | +0.00b | 478.00ms | +66.00ms ⚠️ |
| dist/16.a4b392c2.js | 1.32kb | +0.00b | 478.00ms | +67.00ms ⚠️ |
| dist/16.249c644a.js | 1.31kb | +0.00b | 477.00ms | +66.00ms ⚠️ |
| dist/link.d12cc130.js | 1.30kb | +0.00b | 456.00ms | +45.00ms ⚠️ |
| dist/heading6.4c4017db.js | 1.30kb | +0.00b | 457.00ms | +47.00ms ⚠️ |
| dist/heading3.93606b9c.js | 1.29kb | +0.00b | 457.00ms | +47.00ms ⚠️ |
| dist/16.ab5c0088.js | 1.27kb | +0.00b | 478.00ms | +67.00ms ⚠️ |
| dist/emoji.e603e70e.js | 1.23kb | +0.00b | 458.00ms | +47.00ms ⚠️ |
| dist/16.afee1494.js | 1.23kb | +0.00b | 478.00ms | +66.00ms ⚠️ |
| dist/16.e60b4d77.js | 1.21kb | +0.00b | 479.00ms | +68.00ms ⚠️ |
| dist/16.7c7a40c9.js | 1.19kb | +0.00b | 478.00ms | +67.00ms ⚠️ |
| dist/16.06d5a67b.js | 1.18kb | +0.00b | 477.00ms | +66.00ms ⚠️ |
| dist/heading5.dde15cec.js | 1.17kb | +0.00b | 457.00ms | +47.00ms ⚠️ |
| dist/expand.6889aead.js | 1.16kb | +0.00b | 457.00ms | +47.00ms ⚠️ |
| dist/media-card-analytics-error-boundary.6a25b970.js | 1.15kb | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/16.2096b9f3.js | 1.14kb | +0.00b | 479.00ms | +68.00ms ⚠️ |
| dist/16.f040d62d.js | 1.14kb | +0.00b | 478.00ms | +66.00ms ⚠️ |
| dist/16.ecf8a702.js | 1.13kb | +0.00b | 478.00ms | +66.00ms ⚠️ |
| dist/16.1b342686.js | 1.13kb | +0.00b | 478.00ms | +66.00ms ⚠️ |
| dist/16.2e7f52c6.js | 1.13kb | +0.00b | 478.00ms | +66.00ms ⚠️ |
| dist/16.da43aa07.js | 1.13kb | +0.00b | 478.00ms | +66.00ms ⚠️ |
| dist/heading2.e219309a.js | 1.11kb | +0.00b | 457.00ms | +46.00ms ⚠️ |
| dist/16.dd07505e.js | 1.10kb | +0.00b | 479.00ms | +68.00ms ⚠️ |
| dist/component.40194190.js | 1.07kb | +0.00b | 479.00ms | +68.00ms ⚠️ |
| dist/mention.b1828447.js | 1.07kb | +0.00b | 457.00ms | +46.00ms ⚠️ |
| dist/heading4.5316f6f2.js | 1.06kb | +0.00b | 457.00ms | +47.00ms ⚠️ |
| dist/16.317b9164.js | 1.05kb | +0.00b | 478.00ms | +67.00ms ⚠️ |
| dist/layout.ac6734f9.js | 1.05kb | +0.00b | 457.00ms | +46.00ms ⚠️ |
| dist/divider.d2be55a1.js | 1.03kb | +0.00b | 457.00ms | +46.00ms ⚠️ |
| dist/quote.f4e09143.js | 1.02kb | +0.00b | 457.00ms | +46.00ms ⚠️ |
| dist/action.cb287eb0.js | 1.00kb | +0.00b | 477.00ms | +66.00ms ⚠️ |
| dist/decision.91f3af14.js | 1010.00b | +0.00b | 477.00ms | +66.00ms ⚠️ |
| dist/panel-warning.68b04096.js | 1008.00b | +0.00b | 457.00ms | +46.00ms ⚠️ |
| dist/list.057cb664.js | 978.00b | +0.00b | 456.00ms | +45.00ms ⚠️ |
| dist/heading1.fa66b375.js | 976.00b | +0.00b | 457.00ms | +46.00ms ⚠️ |
| dist/panel-error.c4b05f6f.js | 906.00b | +0.00b | 456.00ms | +45.00ms ⚠️ |
| dist/panel.9dbc048e.js | 897.00b | +0.00b | 457.00ms | +46.00ms ⚠️ |
| dist/table.b35545be.js | 889.00b | +0.00b | 457.00ms | +46.00ms ⚠️ |
| dist/panel-success.7eb0809a.js | 845.00b | +0.00b | 457.00ms | +46.00ms ⚠️ |
| dist/panel-note.ec8386b7.js | 841.00b | +0.00b | 457.00ms | +46.00ms ⚠️ |
| dist/media-picker-analytics-error-boundary.553055f6.js | 837.00b | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/media-card-analytics-error-boundary.964206cc.js | 833.00b | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/media-card-analytics-error-boundary.57503222.js | 833.00b | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/media-viewer-analytics-error-boundary.bbe40a02.js | 757.00b | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/EmojiPickerComponent.a968dafe.js | 535.00b | +0.00b | 457.00ms | +47.00ms ⚠️ |
| dist/simpleHasher.f8e69464.js | 528.00b | +0.00b | 456.00ms | +46.00ms ⚠️ |
| dist/simpleHasher.32d9352e.js | 528.00b | +0.00b | 456.00ms | +47.00ms ⚠️ |
| dist/index.html | 119.00b | +0.00b | 481.00ms | +55.00ms ⚠️ |

</p></details>

<details><summary>Three.js x4 🚨</summary><p>

#### 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...*
</p></details>

**[Click here to view a detailed benchmark overview.](https://benchmark.parceljs.org/benchmark/273293860736598540)**
padmaia: Originally I was thinking that I implemented checking the cache key in the main process in order to avoid serializing and sending the graph to the worker if it wasn't going to end up using it, but it looks like we create the shared reference regardless 🤷‍♀️ We could easily do that conditionally but ideally we will be able to avoid the serialization cost by implementing the graph in Rust (being explored right now but looks promising). Now that we are incorporating the config into the cache key it's probably best to do all that in the worker where the config would be used so we don't have to load it twice which can be costly in some cases (e.g. Babel since it not only loads the static config but all of the plugins too). I doubt it hurt performance but let's keep an eye on the performance tests to make sure. 
padmaia: Also if we are checking the cache keys in the workers it makes sense to just write the bundles from there too.
mischnic: > Also if we are checking the cache keys in the workers it makes sense to just write the bundles from there too.

I think this doesn't work because we need to call `assignComplexNameHashes` between writing to cache and writing to disk.
mischnic: ak-editor, cached build with no changes:

```
v2
build . --no-minify --no-source-maps  8.11s user 1.39s system 178% cpu 5.318 total
build . --no-minify --no-source-maps  7.46s user 1.19s system 182% cpu 4.743 total
build . --no-minify --no-source-maps  8.14s user 1.38s system 182% cpu 5.205 total
build . --no-minify --no-source-maps  8.16s user 1.18s system 180% cpu 5.165 total


packager-loadConfig
build . --no-minify --no-source-maps  7.68s user 1.10s system 190% cpu 4.594 total
build . --no-minify --no-source-maps  8.79s user 1.31s system 193% cpu 5.214 total
build . --no-minify --no-source-maps  8.32s user 1.26s system 192% cpu 4.979 total
build . --no-minify --no-source-maps  8.36s user 1.29s system 192% cpu 5.017 total
```

Patch:
diff --git a/packages/core/core/src/PackagerRunner.js b/packages/core/core/src/PackagerRunner.js
index 82b08b500..fde7275eb 100644
--- a/packages/core/core/src/PackagerRunner.js
+++ b/packages/core/core/src/PackagerRunner.js
@@ -8,6 +8,8 @@ import type {
   BundleGraph as BundleGraphType,
   NamedBundle as NamedBundleType,
   Async,
+  ConfigOutput,
+  ConfigResult,
 } from '@parcel/types';
 import type SourceMap from '@parcel/source-map';
 import type WorkerFarm, {SharedReference} from '@parcel/workers';
@@ -54,6 +56,7 @@ export type BundleInfo = {|
   +hash: string,
   +hashReferences: Array<string>,
   +time?: number,
+  +cacheKeys: CacheKeyMap,
 |};
 
 type CacheKeyMap = {|
@@ -78,7 +81,6 @@ export default class PackagerRunner {
     bundle: InternalBundle,
     bundleGraphReference: SharedReference,
     configRef: SharedReference,
-    cacheKeys: CacheKeyMap,
     optionsRef: SharedReference,
   |}) => Promise<BundleInfo>;
 
@@ -155,32 +157,41 @@ export default class PackagerRunner {
   |}> {
     let start = Date.now();
 
-    let cacheKey = await this.getCacheKey(bundle, bundleGraph);
-    let cacheKeys = {
-      content: getContentKey(cacheKey),
-      map: getMapKey(cacheKey),
-      info: getInfoKey(cacheKey),
-    };
-    let {type, size, hash, hashReferences} =
-      (await this.getBundleInfoFromCache(cacheKeys.info)) ??
-      (await this.getBundleInfoFromWorker({
+    return {
+      ...(await this.getBundleInfoFromWorker({
         bundle,
         bundleGraphReference,
-        cacheKeys,
         optionsRef: nullthrows(this.optionsRef),
         configRef: nullthrows(this.configRef),
-      }));
-
-    return {
-      type,
-      size,
+      })),
       time: Date.now() - start,
-      hash,
-      hashReferences,
-      cacheKeys,
     };
   }
 
+  async loadConfig(
+    bundleGraph: InternalBundleGraph,
+    bundle: InternalBundle,
+  ): Promise<?ConfigOutput> {
+    let config: ?ConfigOutput;
+
+    let {plugin} = await this.config.getPackager(nullthrows(bundle.filePath));
+    if (plugin.loadConfig != null) {
+      try {
+        config = await nullthrows(plugin.loadConfig)({
+          bundle: NamedBundle.get(bundle, bundleGraph, this.options),
+          options: this.pluginOptions,
+          logger: new PluginLogger({origin: this.config.getBundlerName()}),
+        });
+      } catch (e) {
+        throw new ThrowableDiagnostic({
+          diagnostic: errorToDiagnostic(e, this.config.getBundlerName()),
+        });
+      }
+    }
+
+    return config;
+  }
+
   getBundleInfoFromCache(infoKey: string): Async<?BundleInfo> {
     if (this.options.disableCache) {
       return;
@@ -193,8 +204,13 @@ export default class PackagerRunner {
     bundle: InternalBundle,
     bundleGraph: InternalBundleGraph,
     cacheKeys: CacheKeyMap,
+    config: ConfigResult,
   ): Promise<BundleInfo> {
-    let {type, contents, map} = await this.getBundleResult(bundle, bundleGraph);
+    let {type, contents, map} = await this.getBundleResult(
+      bundle,
+      bundleGraph,
+      config,
+    );
 
     return this.writeToCache(cacheKeys, type, contents, map);
   }
@@ -202,6 +218,7 @@ export default class PackagerRunner {
   async getBundleResult(
     bundle: InternalBundle,
     bundleGraph: InternalBundleGraph,
+    config: ConfigResult,
   ): Promise<{|
     type: string,
     contents: Blob,
@@ -209,7 +226,7 @@ export default class PackagerRunner {
   |}> {
     await initSourcemaps;
 
-    let packaged = await this.package(bundle, bundleGraph);
+    let packaged = await this.package(bundle, bundleGraph, config);
     let type = packaged.type ?? bundle.type;
     let res = await this.optimize(
       bundle,
@@ -243,6 +260,7 @@ export default class PackagerRunner {
   async package(
     internalBundle: InternalBundle,
     bundleGraph: InternalBundleGraph,
+    config: ConfigResult,
   ): Promise<BundleResult> {
     let bundle = NamedBundle.get(internalBundle, bundleGraph, this.options);
     this.report({
@@ -251,9 +269,10 @@ export default class PackagerRunner {
       bundle,
     });
 
-    let packager = await this.config.getPackager(bundle.filePath);
+    let {name, plugin} = await this.config.getPackager(bundle.filePath);
     try {
-      return await packager.plugin.package({
+      return await plugin.package({
+        config,
         bundle,
         bundleGraph: new BundleGraph<NamedBundleType>(
           bundleGraph,
@@ -264,7 +283,7 @@ export default class PackagerRunner {
           return this.getSourceMapReference(bundle, map);
         },
         options: this.pluginOptions,
-        logger: new PluginLogger({origin: packager.name}),
+        logger: new PluginLogger({origin: name}),
         getInlineBundleContents: async (
           bundle: BundleType,
           bundleGraph: BundleGraphType<NamedBundleType>,
@@ -286,7 +305,7 @@ export default class PackagerRunner {
       });
     } catch (e) {
       throw new ThrowableDiagnostic({
-        diagnostic: errorToDiagnostic(e, packager.name),
+        diagnostic: errorToDiagnostic(e, name),
       });
     }
   }
@@ -413,6 +432,7 @@ export default class PackagerRunner {
   async getCacheKey(
     bundle: InternalBundle,
     bundleGraph: InternalBundleGraph,
+    configResult: ?ConfigResult,
   ): Promise<string> {
     let filePath = nullthrows(bundle.filePath);
     // TODO: include packagers and optimizers used in inline bundles as well
@@ -429,6 +449,7 @@ export default class PackagerRunner {
       optimizers,
       opts: {sourceMaps},
       hash: bundleGraph.getHash(bundle),
+      configResult,
     });
   }
 
@@ -438,8 +459,8 @@ export default class PackagerRunner {
     contents: Readable,
     map: ?Readable,
   |}> {
-    let contentKey = getContentKey(cacheKey);
-    let mapKey = getMapKey(cacheKey);
+    let contentKey = PackagerRunner.getContentKey(cacheKey);
+    let mapKey = PackagerRunner.getMapKey(cacheKey);
 
     let contentExists = await this.options.cache.blobExists(contentKey);
     if (!contentExists) {
@@ -561,10 +582,28 @@ export default class PackagerRunner {
     if (map != null) {
       await this.options.cache.setStream(cacheKeys.map, blobToStream(map));
     }
-    let info = {type, size, hash: hash.digest('hex'), hashReferences};
+    let info = {
+      type,
+      size,
+      hash: hash.digest('hex'),
+      hashReferences,
+      cacheKeys,
+    };
     await this.options.cache.set(cacheKeys.info, info);
     return info;
   }
+
+  static getContentKey(cacheKey: string): string {
+    return md5FromString(`${cacheKey}:content`);
+  }
+
+  static getMapKey(cacheKey: string): string {
+    return md5FromString(`${cacheKey}:map`);
+  }
+
+  static getInfoKey(cacheKey: string): string {
+    return md5FromString(`${cacheKey}:info`);
+  }
 }
 
 function writeFileStream(
@@ -618,18 +657,6 @@ function replaceStream(hashRefToNameHash) {
   });
 }
 
-function getContentKey(cacheKey: string) {
-  return md5FromString(`${cacheKey}:content`);
-}
-
-function getMapKey(cacheKey: string) {
-  return md5FromString(`${cacheKey}:map`);
-}
-
-function getInfoKey(cacheKey: string) {
-  return md5FromString(`${cacheKey}:info`);
-}
-
 function assignComplexNameHashes(hashRefToNameHash, bundles, bundleInfoMap) {
   for (let bundle of bundles) {
     if (hashRefToNameHash.get(bundle.hashReference) != null) {
diff --git a/packages/core/core/src/worker.js b/packages/core/core/src/worker.js
index 6898ea005..350898832 100644
--- a/packages/core/core/src/worker.js
+++ b/packages/core/core/src/worker.js
@@ -96,13 +96,12 @@ export async function runValidate(
   }).run();
 }
 
-export function runPackage(
+export async function runPackage(
   workerApi: WorkerApi,
   {
     bundle,
     bundleGraphReference,
     configRef,
-    cacheKeys,
     optionsRef,
   }: {|
     bundle: Bundle,
@@ -123,16 +122,31 @@ export function runPackage(
     configRef,
     // $FlowFixMe
   ): any): ProcessedParcelConfig);
-  let config = new ParcelConfig(
+  let parcelConfig = new ParcelConfig(
     processedConfig,
     options.packageManager,
     options.inputFS,
     options.autoinstall,
   );
 
-  return new PackagerRunner({
-    config,
+  let runner = new PackagerRunner({
+    config: parcelConfig,
     options,
     report: reportWorker.bind(null, workerApi),
-  }).getBundleInfo(bundle, bundleGraph, cacheKeys);
+  });
+
+  let config = await runner.loadConfig(bundleGraph, bundle);
+  // TODO: add invalidations in `config?.files` once packaging is a request
+
+  let cacheKey = await runner.getCacheKey(bundle, bundleGraph, config?.config);
+  let cacheKeys = {
+    content: PackagerRunner.getContentKey(cacheKey),
+    map: PackagerRunner.getMapKey(cacheKey),
+    info: PackagerRunner.getInfoKey(cacheKey),
+  };
+
+  return (
+    (await runner.getBundleInfoFromCache(cacheKeys.info)) ??
+    runner.getBundleInfo(bundle, bundleGraph, cacheKeys, config?.config)
+  );
 }
diff --git a/packages/core/types/index.js b/packages/core/types/index.js
index 3299ca0fd..75db34a72 100644
--- a/packages/core/types/index.js
+++ b/packages/core/types/index.js
@@ -1091,11 +1091,17 @@ export type Runtime = {|
  * @section packager
  */
 export type Packager = {|
+  loadConfig?: ({|
+    bundle: NamedBundle,
+    options: PluginOptions,
+    logger: PluginLogger,
+  |}) => Async<?ConfigOutput>,
   package({|
     bundle: NamedBundle,
     bundleGraph: BundleGraph<NamedBundle>,
     options: PluginOptions,
     logger: PluginLogger,
+    config: ?ConfigResult,
     getInlineBundleContents: (
       Bundle,
       BundleGraph<NamedBundle>,


Test patch:
diff --git a/packages/core/integration-tests/test/integration/packager-loadConfig/.parcelrc b/packages/core/integration-tests/test/integration/packager-loadConfig/.parcelrc
new file mode 100644
index 000000000..8d13690ba
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/packager-loadConfig/.parcelrc
@@ -0,0 +1,9 @@
+{
+  "extends": "@parcel/config-default",
+  "transformers": {
+    "*.txt": ["@parcel/transformer-raw"]
+  },
+  "packagers": {
+    "*.txt": "parcel-packager-config"
+  }
+}
diff --git a/packages/core/integration-tests/test/integration/packager-loadConfig/foo.config.json b/packages/core/integration-tests/test/integration/packager-loadConfig/foo.config.json
new file mode 100644
index 000000000..0839b395e
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/packager-loadConfig/foo.config.json
@@ -0,0 +1,3 @@
+{
+  "contents": "1234"
+}
diff --git a/packages/core/integration-tests/test/integration/packager-loadConfig/index.txt b/packages/core/integration-tests/test/integration/packager-loadConfig/index.txt
new file mode 100644
index 000000000..4bbffde10
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/packager-loadConfig/index.txt
@@ -0,0 +1,1 @@
+module.exports = 2;
diff --git a/packages/core/integration-tests/test/integration/packager-loadConfig/node_modules/parcel-packager-config/index.js b/packages/core/integration-tests/test/integration/packager-loadConfig/node_modules/parcel-packager-config/index.js
new file mode 100644
index 000000000..77dcddefe
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/packager-loadConfig/node_modules/parcel-packager-config/index.js
@@ -0,0 +1,26 @@
+// @flow strict-local
+
+/*::
+import type {ConfigOutput} from '@parcel/types';
+*/
+
+const invariant = require('assert');
+const path = require('path');
+const {Packager} = require('@parcel/plugin');
+const {loadConfig} = require('@parcel/utils');
+
+module.exports = (new Packager({
+  async loadConfig({options}) {
+    let result = await loadConfig(
+      options.inputFS,
+      path.join(options.entryRoot, 'index'),
+      ['foo.config.json'],
+    );
+
+    return result;
+  },
+  package({bundle, config}) {
+    invariant(config);
+    return {contents: config.contents};
+  },
+}) /*: Packager */);
diff --git a/packages/core/integration-tests/test/integration/packager-loadConfig/node_modules/parcel-packager-config/package.json b/packages/core/integration-tests/test/integration/packager-loadConfig/node_modules/parcel-packager-config/package.json
new file mode 100644
index 000000000..3eda97b42
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/packager-loadConfig/node_modules/parcel-packager-config/package.json
@@ -0,0 +1,13 @@
+{
+  "name": "parcel-packager-config",
+  "version": "1.0.0",
+  "private": true,
+  "main": "index.js",
+  "engines": {
+    "parcel": "^2.0.0-beta.1"
+  },
+  "dependencies": {
+    "@parcel/plugin": "^2.0.0-beta.1",
+    "@parcel/utils": "^2.0.0-beta.1"
+  }
+}
diff --git a/packages/core/integration-tests/test/integration/packager-loadConfig/yarn.lock b/packages/core/integration-tests/test/integration/packager-loadConfig/yarn.lock
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/core/integration-tests/test/plugin.js b/packages/core/integration-tests/test/plugin.js
index 8da04240e..45317a4fd 100644
--- a/packages/core/integration-tests/test/plugin.js
+++ b/packages/core/integration-tests/test/plugin.js
@@ -4,7 +4,13 @@ import type {Dependency} from '@parcel/types';
 import assert from 'assert';
 import path from 'path';
 import nullthrows from 'nullthrows';
-import {bundle, outputFS as fs, distDir, run} from '@parcel/test-utils';
+import {
+  bundle,
+  outputFS as fs,
+  distDir,
+  run,
+  overlayFS,
+} from '@parcel/test-utils';
 
 describe('plugin', function() {
   it("continue transformer pipeline on type change that doesn't change the pipeline", async function() {
@@ -60,4 +66,30 @@ parcel-transformer-b`,
     assert(!b.isDependencyDeferred(nullthrows(depB)));
     assert(b.isDependencyDeferred(nullthrows(depC)));
   });
+
+  it('invalidate the cache based on loadConfig in a packager', async function() {
+    let fixture = path.join(__dirname, '/integration/packager-loadConfig');
+    let entry = path.join(fixture, 'index.txt');
+    let config = path.join(fixture, 'foo.config.json');
+    let b = await bundle(entry, {
+      inputFS: overlayFS,
+      disableCache: false,
+    });
+
+    assert.strictEqual(
+      await overlayFS.readFile(b.getBundles()[0].filePath, 'utf8'),
+      '1234',
+    );
+
+    await overlayFS.writeFile(config, JSON.stringify({contents: 'xyz'}));
+
+    b = await bundle(entry, {
+      inputFS: overlayFS,
+      disableCache: false,
+    });
+    assert.strictEqual(
+      await overlayFS.readFile(b.getBundles()[0].filePath, 'utf8'),
+      'xyz',
+    );
+  });
 });