Repository: parcel-bundler/parcel
Pull request #4993 (2020-08-08 08:33:16)

Don't inline env vars & fs
# ↪️ Pull Request

1. Don't inline env vars except `NODE_ENV` with `inlineEnvironment: false`  (this is `boolean | Array<Glob>`)
2. Don't inline readFileSync with `inlineFS: false`
3. Always Disallow readFileSync from outside of the project root

Part of https://github.com/parcel-bundler/parcel/issues/4991

## Questions

- ~Not sure if we want two separate options for these (or what this single flag should be called). Should there really be a CLI flag?~
- ~Should env vars from `.env` be allowed? I don't see why not (from a security standpoint). Though this would be harder to implement as `options.env` is not per target, should that be moved to `Environment`?~ Followup

## 💻 Examples

<img width="1050" alt="Bildschirmfoto 2020-08-08 um 10 19 09" src="https://user-images.githubusercontent.com/4586894/89705885-30159f80-d961-11ea-9ec0-f51ad25a3fa2.png">

## ✔️ PR Todo

- [x] Add test
- [ ] What about `__dirname`, `__filename`?
- [x] Make this opt-in in package.json in `@parcel/transformer-js`, turn off by default (so `unsafeInline: true`) until we have a solution for env files


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 | 6.26s | +40.00ms |
| Cached | 2.21s | +27.00ms |

#### Cold Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/legacy/index.html | 701.00b | +0.00b | 619.00ms | -104.00ms 🚀 |
| dist/modern/index.html | 701.00b | +0.00b | 618.00ms | -108.00ms 🚀 |
| dist/legacy/index.0e158b6b.css | 77.00b | +0.00b | 1.59s | +151.00ms ⚠️ |
| dist/modern/index.e480862d.css | 77.00b | +0.00b | 1.23s | -73.00ms 🚀 |

#### Cached Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/legacy/parcel.d5807e82.webp | 102.94kb | +0.00b | 198.00ms | +13.00ms ⚠️ |
| dist/modern/parcel.d5807e82.webp | 102.94kb | +0.00b | 197.00ms | +13.00ms ⚠️ |
| dist/legacy/index.42516b4a.js | 1.10kb | +0.00b | 368.00ms | -21.00ms 🚀 |
| dist/modern/index.8a1ced31.js | 1.10kb | +0.00b | 367.00ms | -19.00ms 🚀 |
| dist/modern/index.html | 701.00b | +0.00b | 367.00ms | -19.00ms 🚀 |
| dist/legacy/index.0e158b6b.css | 77.00b | +0.00b | 368.00ms | -21.00ms 🚀 |
| dist/modern/index.e480862d.css | 77.00b | +0.00b | 367.00ms | -19.00ms 🚀 |

</p></details>

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

#### Timings

| Description | Time | Difference |
| --- | --- | --- |
| Cold | 29.23s | +1.84s ⚠️ |
| Cached | 2.52s | +191.00ms ⚠️ |

#### Cold Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/index.js | 479.25kb | +211.00b ⚠️ | 11.39s | +181.00ms |
| dist/PermalinkedComment.c85d137d.js | 4.22kb | +0.00b | 5.81s | -5.37s 🚀 |
| dist/UserProfile.30e15baf.js | 1.62kb | +0.00b | 5.81s | -5.37s 🚀 |
| dist/NotFound.5aa6b041.js | 387.00b | +0.00b | 5.81s | -5.37s 🚀 |
| dist/logo.24c8bf9e.png | 274.00b | +0.00b | 181.00ms | +26.00ms ⚠️ |

#### Cached Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/index.js | 479.25kb | +211.00b ⚠️ | 362.00ms | +13.00ms |
| dist/logo.24c8bf9e.png | 274.00b | +0.00b | 309.00ms | +37.00ms ⚠️ |

</p></details>

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

#### Timings

| Description | Time | Difference |
| --- | --- | --- |
| Cold | 2.85m | +711.00ms |
| Cached | 5.12s | -2.00ms |

#### Cold Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/index.e8555081.js | 2.73mb | -117.00b 🚀 | 1.63m | +4.03s |
| dist/component.59edff1f.js | 30.78kb | +0.00b | 2.72s | +566.00ms ⚠️ |
| dist/workerHasher.65b8a1b7.js | 11.78kb | +0.00b | 1.62m | +46.24s ⚠️ |
| dist/component.11476a92.js | 6.05kb | +0.00b | 6.24s | +1.68s ⚠️ |
| dist/index.393afe59.css | 3.46kb | +0.00b | 58.96s | -8.89s 🚀 |
| dist/dropzone.2f1a1e17.js | 3.23kb | +0.00b | 30.60s | -19.88s 🚀 |
| dist/16.d1b8e6f7.js | 2.38kb | +0.00b | 2.72s | +566.00ms ⚠️ |
| dist/ResourcedEmojiComponent.d9a49606.js | 2.00kb | +0.00b | 5.36s | +906.00ms ⚠️ |
| dist/16.86a8c7d4.js | 1.66kb | +0.00b | 7.55s | +415.00ms ⚠️ |
| dist/workerHasher.f66f00c7.js | 1.54kb | +0.00b | 56.82s | -36.76s 🚀 |
| dist/16.c1ed07f8.js | 1.33kb | +0.00b | 7.55s | +415.00ms ⚠️ |
| dist/16.a4b392c2.js | 1.32kb | +0.00b | 2.72s | +566.00ms ⚠️ |
| dist/16.ab5c0088.js | 1.27kb | +0.00b | 8.33s | +2.01s ⚠️ |
| dist/16.e60b4d77.js | 1.21kb | +0.00b | 2.72s | +566.00ms ⚠️ |
| dist/media-card-analytics-error-boundary.6a25b970.js | 1.15kb | +0.00b | 30.60s | -19.88s 🚀 |
| dist/16.2096b9f3.js | 1.14kb | +0.00b | 2.72s | +566.00ms ⚠️ |
| dist/16.ecf8a702.js | 1.13kb | +0.00b | 6.13s | -1.11s 🚀 |
| dist/16.da43aa07.js | 1.13kb | +0.00b | 7.55s | +415.00ms ⚠️ |
| dist/16.dd07505e.js | 1.10kb | +0.00b | 5.36s | +907.00ms ⚠️ |
| dist/component.40194190.js | 1.07kb | +0.00b | 5.36s | +907.00ms ⚠️ |
| dist/EmojiPickerComponent.a968dafe.js | 535.00b | +0.00b | 11.43s | +1.62s ⚠️ |
| dist/index.html | 119.00b | +0.00b | 3.95s | +1.69s ⚠️ |

#### Cached Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/index.e8555081.js | 2.73mb | -117.00b 🚀 | 537.00ms | -16.00ms |
| dist/pdfRenderer.cd742f5e.js | 1.11mb | +4.00b ⚠️ | 524.00ms | -27.00ms 🚀 |
| dist/popup.4a66e15e.js | 170.62kb | +0.00b | 524.00ms | -27.00ms 🚀 |
| dist/media-viewer.a8f3a476.js | 72.02kb | +0.00b | 524.00ms | -27.00ms 🚀 |
| dist/card.ce67dc35.js | 55.09kb | +0.00b | 524.00ms | -27.00ms 🚀 |
| dist/ui.cee430a3.js | 13.97kb | +0.00b | 524.00ms | -27.00ms 🚀 |
| dist/card.4ac5c1b2.js | 5.61kb | +0.00b | 524.00ms | -27.00ms 🚀 |
| dist/media-viewer.bc661bce.js | 3.92kb | +0.00b | 524.00ms | -27.00ms 🚀 |
| dist/dropzone.2f1a1e17.js | 3.23kb | +0.00b | 524.00ms | -27.00ms 🚀 |
| dist/clipboard.0244488f.js | 2.75kb | +0.00b | 524.00ms | -27.00ms 🚀 |
| dist/card.cadccd96.js | 2.03kb | +0.00b | 524.00ms | -27.00ms 🚀 |
| dist/status.cccf04be.js | 1.46kb | +0.00b | 525.00ms | -27.00ms 🚀 |
| dist/heading3.93606b9c.js | 1.29kb | +0.00b | 525.00ms | -27.00ms 🚀 |
| dist/media-card-analytics-error-boundary.6a25b970.js | 1.15kb | +0.00b | 524.00ms | -27.00ms 🚀 |
| dist/heading2.e219309a.js | 1.11kb | +0.00b | 525.00ms | -27.00ms 🚀 |
| dist/mention.b1828447.js | 1.07kb | +0.00b | 525.00ms | -27.00ms 🚀 |
| dist/quote.f4e09143.js | 1.02kb | +0.00b | 525.00ms | -27.00ms 🚀 |
| dist/panel-warning.68b04096.js | 1008.00b | +0.00b | 525.00ms | -27.00ms 🚀 |
| dist/list.057cb664.js | 978.00b | +0.00b | 525.00ms | -27.00ms 🚀 |
| dist/heading1.fa66b375.js | 976.00b | +0.00b | 525.00ms | -27.00ms 🚀 |
| dist/panel-error.c4b05f6f.js | 906.00b | +0.00b | 525.00ms | -27.00ms 🚀 |
| dist/panel.9dbc048e.js | 897.00b | +0.00b | 525.00ms | -27.00ms 🚀 |
| dist/table.b35545be.js | 889.00b | +0.00b | 525.00ms | -27.00ms 🚀 |
| dist/panel-success.7eb0809a.js | 845.00b | +0.00b | 525.00ms | -27.00ms 🚀 |
| dist/panel-note.ec8386b7.js | 841.00b | +0.00b | 525.00ms | -27.00ms 🚀 |
| dist/media-picker-analytics-error-boundary.553055f6.js | 837.00b | +0.00b | 524.00ms | -27.00ms 🚀 |
| dist/media-card-analytics-error-boundary.964206cc.js | 833.00b | +0.00b | 524.00ms | -27.00ms 🚀 |
| dist/media-viewer-analytics-error-boundary.bbe40a02.js | 757.00b | +0.00b | 524.00ms | -27.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/273279286276784648)**
parcel-benchmark: ## Benchmark Results
<details><summary>Kitchen Sink ✅</summary><p>

#### Timings

| Description | Time | Difference |
| --- | --- | --- |
| Cold | 6.64s | +298.00ms |
| Cached | 2.41s | +103.00ms |

#### Cold Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/legacy/parcel.d5807e82.webp | 102.94kb | +0.00b | 247.00ms | +17.00ms ⚠️ |
| dist/modern/parcel.d5807e82.webp | 102.94kb | +0.00b | 247.00ms | +17.00ms ⚠️ |
| dist/legacy/index.html | 701.00b | +0.00b | 647.00ms | -40.00ms 🚀 |
| dist/modern/index.html | 701.00b | +0.00b | 644.00ms | -41.00ms 🚀 |

#### Cached Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/legacy/parcel.d5807e82.webp | 102.94kb | +0.00b | 262.00ms | +73.00ms ⚠️ |
| dist/modern/parcel.d5807e82.webp | 102.94kb | +0.00b | 262.00ms | +73.00ms ⚠️ |
| dist/legacy/index.42516b4a.js | 1.10kb | +0.00b | 558.00ms | +140.00ms ⚠️ |
| dist/modern/index.8a1ced31.js | 1.10kb | +0.00b | 550.00ms | +133.00ms ⚠️ |
| dist/legacy/index.html | 701.00b | +0.00b | 560.00ms | +140.00ms ⚠️ |
| dist/modern/index.html | 701.00b | +0.00b | 550.00ms | +133.00ms ⚠️ |
| dist/legacy/index.0e158b6b.css | 77.00b | +0.00b | 559.00ms | +141.00ms ⚠️ |
| dist/modern/index.e480862d.css | 77.00b | +0.00b | 550.00ms | +133.00ms ⚠️ |

</p></details>

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

#### Timings

| Description | Time | Difference |
| --- | --- | --- |
| Cold | 31.39s | +612.00ms |
| Cached | 2.48s | +49.00ms |

#### Cold Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/index.js | 479.25kb | +211.00b ⚠️ | 11.69s | -136.00ms |
| dist/logo.24c8bf9e.png | 274.00b | +0.00b | 163.00ms | +13.00ms ⚠️ |

#### Cached Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/index.js | 479.25kb | +211.00b ⚠️ | 281.00ms | -97.00ms 🚀 |
| dist/PermalinkedComment.c85d137d.js | 4.22kb | +0.00b | 277.00ms | -99.00ms 🚀 |
| dist/UserProfile.30e15baf.js | 1.62kb | +0.00b | 277.00ms | -98.00ms 🚀 |
| dist/NotFound.5aa6b041.js | 387.00b | +0.00b | 277.00ms | -98.00ms 🚀 |
| dist/logo.24c8bf9e.png | 274.00b | +0.00b | 214.00ms | -44.00ms 🚀 |

</p></details>

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

#### Timings

| Description | Time | Difference |
| --- | --- | --- |
| Cold | 2.98m | +1.69s |
| Cached | 5.81s | +646.00ms ⚠️ |

#### Cold Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/index.e8555081.js | 2.73mb | -117.00b 🚀 | 1.65m | +1.67s |
| dist/popup.4a66e15e.js | 170.62kb | +0.00b | 54.97s | +20.61s ⚠️ |
| dist/card.ce67dc35.js | 55.09kb | +0.00b | 14.39s | +816.00ms ⚠️ |
| dist/card.1007a95f.js | 52.86kb | +0.00b | 1.65m | +39.74s ⚠️ |
| dist/Modal.e3bd77d0.js | 41.40kb | +0.00b | 7.17s | +1.13s ⚠️ |
| dist/component.59edff1f.js | 30.78kb | +0.00b | 2.67s | +195.00ms ⚠️ |
| dist/component.37867f50.js | 22.51kb | +0.00b | 7.17s | +1.13s ⚠️ |
| dist/dropzone.812039de.js | 17.27kb | +0.00b | 1.65m | +39.77s ⚠️ |
| dist/smartMediaEditor.c0ff7bfa.js | 16.87kb | +0.00b | 57.01s | -40.43s 🚀 |
| dist/js.48be70a4.js | 16.40kb | +0.00b | 7.17s | +1.13s ⚠️ |
| dist/ui.cee430a3.js | 13.97kb | +0.00b | 14.35s | +778.00ms ⚠️ |
| dist/workerHasher.65b8a1b7.js | 11.78kb | +0.00b | 13.59s | -40.51s 🚀 |
| dist/EmojiTypeAheadComponent.9af6e5fc.js | 8.67kb | +0.00b | 12.66s | -41.38s 🚀 |
| dist/component.11476a92.js | 6.05kb | +0.00b | 7.17s | +1.12s ⚠️ |
| dist/card.4ac5c1b2.js | 5.61kb | +0.00b | 15.60s | +845.00ms ⚠️ |
| dist/EmojiPickerComponent.624bf3f4.js | 3.58kb | +0.00b | 12.18s | +1.71s ⚠️ |
| dist/png-chunks-extract.8d15a914.js | 3.41kb | +0.00b | 7.17s | +1.13s ⚠️ |
| dist/16.d1b8e6f7.js | 2.38kb | +0.00b | 2.67s | +195.00ms ⚠️ |
| dist/EmojiUploadComponent.88a444df.js | 2.18kb | +0.00b | 12.66s | -41.38s 🚀 |
| dist/date.d49e64f8.js | 1.73kb | +0.00b | 7.81s | +1.08s ⚠️ |
| dist/16.f40c1329.js | 1.73kb | +0.00b | 7.63s | +1.09s ⚠️ |
| dist/images.80270b1f.js | 1.67kb | +0.00b | 7.92s | +1.04s ⚠️ |
| dist/feedback.8b7c7928.js | 1.64kb | +0.00b | 8.29s | +897.00ms ⚠️ |
| dist/workerHasher.9bb425b1.js | 1.54kb | +0.00b | 12.66s | -41.38s 🚀 |
| dist/list-number.4632b459.js | 1.46kb | +0.00b | 53.20s | +46.14s ⚠️ |
| dist/status.cccf04be.js | 1.46kb | +0.00b | 8.15s | +869.00ms ⚠️ |
| dist/16.cff410ff.js | 1.38kb | +0.00b | 7.38s | +1.05s ⚠️ |
| dist/code.af9ca2b3.js | 1.38kb | +0.00b | 7.81s | +1.09s ⚠️ |
| dist/16.6603f2f3.js | 1.33kb | +0.00b | 7.38s | +1.05s ⚠️ |
| dist/16.a4b392c2.js | 1.32kb | +0.00b | 2.65s | +178.00ms ⚠️ |
| dist/16.249c644a.js | 1.31kb | +0.00b | 7.81s | +1.09s ⚠️ |
| dist/link.d12cc130.js | 1.30kb | +0.00b | 53.20s | +46.14s ⚠️ |
| dist/heading6.4c4017db.js | 1.30kb | +0.00b | 8.28s | +885.00ms ⚠️ |
| dist/heading3.93606b9c.js | 1.29kb | +0.00b | 8.28s | +1.00s ⚠️ |
| dist/16.ab5c0088.js | 1.27kb | +0.00b | 7.63s | +1.09s ⚠️ |
| dist/emoji.e603e70e.js | 1.23kb | +0.00b | 7.92s | +1.04s ⚠️ |
| dist/16.afee1494.js | 1.23kb | +0.00b | 7.63s | +1.09s ⚠️ |
| dist/16.e60b4d77.js | 1.21kb | +0.00b | 2.65s | +179.00ms ⚠️ |
| dist/16.7c7a40c9.js | 1.19kb | +0.00b | 7.63s | +1.09s ⚠️ |
| dist/16.06d5a67b.js | 1.18kb | +0.00b | 7.81s | +1.09s ⚠️ |
| dist/heading5.dde15cec.js | 1.17kb | +0.00b | 8.28s | +885.00ms ⚠️ |
| dist/expand.6889aead.js | 1.16kb | +0.00b | 10.90s | +3.51s ⚠️ |
| dist/16.2096b9f3.js | 1.14kb | +0.00b | 2.65s | +178.00ms ⚠️ |
| dist/16.f040d62d.js | 1.14kb | +0.00b | 7.38s | -600.00ms 🚀 |
| dist/16.ecf8a702.js | 1.13kb | +0.00b | 7.93s | +1.60s ⚠️ |
| dist/16.1b342686.js | 1.13kb | +0.00b | 7.38s | +1.05s ⚠️ |
| dist/16.2e7f52c6.js | 1.13kb | +0.00b | 7.38s | +1.04s ⚠️ |
| dist/heading2.e219309a.js | 1.11kb | +0.00b | 8.15s | +872.00ms ⚠️ |
| dist/mention.b1828447.js | 1.07kb | +0.00b | 8.02s | +962.00ms ⚠️ |
| dist/heading4.5316f6f2.js | 1.06kb | +0.00b | 8.28s | +885.00ms ⚠️ |
| dist/16.317b9164.js | 1.05kb | +0.00b | 7.63s | +1.09s ⚠️ |
| dist/layout.ac6734f9.js | 1.05kb | +0.00b | 7.92s | +1.04s ⚠️ |
| dist/divider.d2be55a1.js | 1.03kb | +0.00b | 7.92s | +1.04s ⚠️ |
| dist/quote.f4e09143.js | 1.02kb | +0.00b | 8.15s | +973.00ms ⚠️ |
| dist/action.cb287eb0.js | 1.00kb | +0.00b | 7.81s | +1.09s ⚠️ |
| dist/decision.91f3af14.js | 1010.00b | +0.00b | 7.93s | +1.05s ⚠️ |
| dist/panel-warning.68b04096.js | 1008.00b | +0.00b | 8.02s | +851.00ms ⚠️ |
| dist/list.057cb664.js | 978.00b | +0.00b | 53.20s | +46.14s ⚠️ |
| dist/heading1.fa66b375.js | 976.00b | +0.00b | 8.15s | +869.00ms ⚠️ |
| dist/panel-error.c4b05f6f.js | 906.00b | +0.00b | 8.02s | +962.00ms ⚠️ |
| dist/panel.9dbc048e.js | 897.00b | +0.00b | 53.20s | +46.02s ⚠️ |
| dist/table.b35545be.js | 889.00b | +0.00b | 8.15s | +869.00ms ⚠️ |
| dist/panel-success.7eb0809a.js | 845.00b | +0.00b | 8.02s | +848.00ms ⚠️ |
| dist/panel-note.ec8386b7.js | 841.00b | +0.00b | 8.02s | +849.00ms ⚠️ |
| dist/media-picker-analytics-error-boundary.553055f6.js | 837.00b | +0.00b | 54.80s | +20.63s ⚠️ |
| dist/media-card-analytics-error-boundary.964206cc.js | 833.00b | +0.00b | 14.35s | +777.00ms ⚠️ |
| dist/EmojiPickerComponent.a968dafe.js | 535.00b | +0.00b | 12.66s | +2.19s ⚠️ |
| dist/simpleHasher.f8e69464.js | 528.00b | +0.00b | 14.35s | +773.00ms ⚠️ |
| dist/simpleHasher.32d9352e.js | 528.00b | +0.00b | 57.01s | -40.43s 🚀 |
| dist/index.html | 119.00b | +0.00b | 1.00s | -1.59s 🚀 |

#### Cached Bundles

| Bundle | Size | Difference | Time | Difference |
| --- | --- | --- | --- | --- |
| dist/index.e8555081.js | 2.73mb | -117.00b 🚀 | 581.00ms | +33.00ms ⚠️ |
| dist/pdfRenderer.cd742f5e.js | 1.11mb | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/popup.4a66e15e.js | 170.62kb | +0.00b | 577.00ms | +32.00ms ⚠️ |
| dist/EmojiPickerComponent.bab709db.js | 97.89kb | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/media-viewer.a8f3a476.js | 72.02kb | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/card.ce67dc35.js | 55.09kb | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/Modal.e3bd77d0.js | 41.40kb | +0.00b | 580.00ms | +33.00ms ⚠️ |
| dist/component.59edff1f.js | 30.78kb | +0.00b | 581.00ms | +34.00ms ⚠️ |
| dist/component.37867f50.js | 22.51kb | +0.00b | 580.00ms | +33.00ms ⚠️ |
| dist/DatePicker.cbe44a9b.js | 21.17kb | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/EmojiPickerComponent.898d8be6.js | 18.23kb | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/smartMediaEditor.c0ff7bfa.js | 16.87kb | +0.00b | 569.00ms | +30.00ms ⚠️ |
| dist/js.48be70a4.js | 16.40kb | +0.00b | 580.00ms | +33.00ms ⚠️ |
| dist/ui.cee430a3.js | 13.97kb | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/workerHasher.65b8a1b7.js | 11.78kb | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/EmojiTypeAheadComponent.9af6e5fc.js | 8.67kb | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/component.11476a92.js | 6.05kb | +0.00b | 580.00ms | +33.00ms ⚠️ |
| dist/card.4ac5c1b2.js | 5.61kb | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/media-viewer.bc661bce.js | 3.92kb | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/EmojiPickerComponent.624bf3f4.js | 3.58kb | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/png-chunks-extract.8d15a914.js | 3.41kb | +0.00b | 580.00ms | +33.00ms ⚠️ |
| dist/dropzone.2f1a1e17.js | 3.23kb | +0.00b | 577.00ms | +32.00ms ⚠️ |
| dist/Modal.2835744c.js | 3.08kb | +0.00b | 581.00ms | +34.00ms ⚠️ |
| dist/16.d1b8e6f7.js | 2.38kb | +0.00b | 581.00ms | +34.00ms ⚠️ |
| dist/EmojiUploadComponent.88a444df.js | 2.18kb | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/card.cadccd96.js | 2.03kb | +0.00b | 577.00ms | +32.00ms ⚠️ |
| dist/ResourcedEmojiComponent.d9a49606.js | 2.00kb | +0.00b | 581.00ms | +33.00ms ⚠️ |
| dist/date.d49e64f8.js | 1.73kb | +0.00b | 578.00ms | +31.00ms ⚠️ |
| dist/16.f40c1329.js | 1.73kb | +0.00b | 579.00ms | +32.00ms ⚠️ |
| dist/images.80270b1f.js | 1.67kb | +0.00b | 579.00ms | +33.00ms ⚠️ |
| dist/16.86a8c7d4.js | 1.66kb | +0.00b | 580.00ms | +33.00ms ⚠️ |
| dist/feedback.8b7c7928.js | 1.64kb | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/workerHasher.9bb425b1.js | 1.54kb | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/list-number.4632b459.js | 1.46kb | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/status.cccf04be.js | 1.46kb | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/16.cff410ff.js | 1.38kb | +0.00b | 580.00ms | +33.00ms ⚠️ |
| dist/code.af9ca2b3.js | 1.38kb | +0.00b | 579.00ms | +32.00ms ⚠️ |
| dist/16.c1ed07f8.js | 1.33kb | +0.00b | 580.00ms | +33.00ms ⚠️ |
| dist/16.6603f2f3.js | 1.33kb | +0.00b | 580.00ms | +33.00ms ⚠️ |
| dist/16.a4b392c2.js | 1.32kb | +0.00b | 581.00ms | +34.00ms ⚠️ |
| dist/16.249c644a.js | 1.31kb | +0.00b | 579.00ms | +32.00ms ⚠️ |
| dist/link.d12cc130.js | 1.30kb | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/heading6.4c4017db.js | 1.30kb | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/heading3.93606b9c.js | 1.29kb | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/16.ab5c0088.js | 1.27kb | +0.00b | 579.00ms | +32.00ms ⚠️ |
| dist/emoji.e603e70e.js | 1.23kb | +0.00b | 579.00ms | +33.00ms ⚠️ |
| dist/16.afee1494.js | 1.23kb | +0.00b | 579.00ms | +33.00ms ⚠️ |
| dist/16.e60b4d77.js | 1.21kb | +0.00b | 581.00ms | +34.00ms ⚠️ |
| dist/16.7c7a40c9.js | 1.19kb | +0.00b | 579.00ms | +33.00ms ⚠️ |
| dist/16.06d5a67b.js | 1.18kb | +0.00b | 579.00ms | +32.00ms ⚠️ |
| dist/heading5.dde15cec.js | 1.17kb | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/expand.6889aead.js | 1.16kb | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/media-card-analytics-error-boundary.6a25b970.js | 1.15kb | +0.00b | 577.00ms | +32.00ms ⚠️ |
| dist/16.2096b9f3.js | 1.14kb | +0.00b | 581.00ms | +34.00ms ⚠️ |
| dist/16.f040d62d.js | 1.14kb | +0.00b | 580.00ms | +33.00ms ⚠️ |
| dist/16.ecf8a702.js | 1.13kb | +0.00b | 580.00ms | +33.00ms ⚠️ |
| dist/16.1b342686.js | 1.13kb | +0.00b | 580.00ms | +33.00ms ⚠️ |
| dist/16.2e7f52c6.js | 1.13kb | +0.00b | 580.00ms | +33.00ms ⚠️ |
| dist/16.da43aa07.js | 1.13kb | +0.00b | 580.00ms | +33.00ms ⚠️ |
| dist/heading2.e219309a.js | 1.11kb | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/16.dd07505e.js | 1.10kb | +0.00b | 581.00ms | +34.00ms ⚠️ |
| dist/component.40194190.js | 1.07kb | +0.00b | 581.00ms | +33.00ms ⚠️ |
| dist/mention.b1828447.js | 1.07kb | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/heading4.5316f6f2.js | 1.06kb | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/16.317b9164.js | 1.05kb | +0.00b | 579.00ms | +32.00ms ⚠️ |
| dist/layout.ac6734f9.js | 1.05kb | +0.00b | 579.00ms | +33.00ms ⚠️ |
| dist/divider.d2be55a1.js | 1.03kb | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/quote.f4e09143.js | 1.02kb | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/action.cb287eb0.js | 1.00kb | +0.00b | 579.00ms | +32.00ms ⚠️ |
| dist/decision.91f3af14.js | 1010.00b | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/panel-warning.68b04096.js | 1008.00b | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/list.057cb664.js | 978.00b | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/heading1.fa66b375.js | 976.00b | +0.00b | 579.00ms | +33.00ms ⚠️ |
| dist/panel-error.c4b05f6f.js | 906.00b | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/panel.9dbc048e.js | 897.00b | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/table.b35545be.js | 889.00b | +0.00b | 579.00ms | +33.00ms ⚠️ |
| dist/panel-success.7eb0809a.js | 845.00b | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/panel-note.ec8386b7.js | 841.00b | +0.00b | 578.00ms | +32.00ms ⚠️ |
| dist/media-card-analytics-error-boundary.964206cc.js | 833.00b | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/media-viewer-analytics-error-boundary.bbe40a02.js | 757.00b | +0.00b | 577.00ms | +32.00ms ⚠️ |
| dist/EmojiPickerComponent.a968dafe.js | 535.00b | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/simpleHasher.f8e69464.js | 528.00b | +0.00b | 578.00ms | +33.00ms ⚠️ |
| dist/simpleHasher.32d9352e.js | 528.00b | +0.00b | 569.00ms | +30.00ms ⚠️ |
| dist/index.html | 119.00b | +0.00b | 583.00ms | +33.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/273279296442728968)**
mischnic: Updated to:
1. insert `throw new Error()` instead of inserting an empty string for readFileSync
2. Use config in package.json with 
```js
{
	inlineFS: boolean,
	inlineEnvironment: boolean | Array<Glob>
}
devongovett: If we change the default, we should make sure to support vars declared in `.env` in addition to `NODE_ENV` by default.

> Only open question: __dirname, __filename

I think we should probably also have an option for this. Also probably the node globals (`global`/`process`/`Buffer`) as well. Could be done in separate PRs.
mischnic: > I thought we had decided to disable environment inlining (except NODE_ENV), and fs by default.

> If we change the default, we should make sure to support vars declared in .env in addition to NODE_ENV by default.

Last time, we decided to not change the default until envfiles are handled correctly

Patch:
diff --git a/packages/bundlers/default/src/DefaultBundler.js b/packages/bundlers/default/src/DefaultBundler.js
index e2c88a886..c120a7835 100644
--- a/packages/bundlers/default/src/DefaultBundler.js
+++ b/packages/bundlers/default/src/DefaultBundler.js
@@ -518,6 +518,7 @@ const CONFIG_SCHEMA: SchemaEntity = {
       type: 'number',
     },
   },
+  additionalProperties: false,
 };
 
 async function loadBundlerConfig(options: PluginOptions) {
diff --git a/packages/core/core/src/TargetDescriptor.schema.js b/packages/core/core/src/TargetDescriptor.schema.js
index 010292426..87ced012a 100644
--- a/packages/core/core/src/TargetDescriptor.schema.js
+++ b/packages/core/core/src/TargetDescriptor.schema.js
@@ -97,6 +97,9 @@ export const DESCRIPTOR_SCHEMA: SchemaEntity = {
     minify: {
       type: 'boolean',
     },
+    scopeHoist: {
+      type: 'boolean',
+    },
   },
   additionalProperties: false,
 };
diff --git a/packages/shared/babel-ast-utils/src/index.js b/packages/shared/babel-ast-utils/src/index.js
index b16926a18..ef5c54e7f 100644
--- a/packages/shared/babel-ast-utils/src/index.js
+++ b/packages/shared/babel-ast-utils/src/index.js
@@ -1,7 +1,14 @@
 // @flow strict-local
 
-import type {AST, BaseAsset, PluginOptions} from '@parcel/types';
+import type {
+  AST,
+  BaseAsset,
+  PluginOptions,
+  SourceLocation,
+} from '@parcel/types';
+import type {SourceLocation as BabelSourceLocation} from '@babel/types';
 
+import path from 'path';
 import babelGenerate from '@babel/generator';
 import {parse as babelParse} from '@babel/parser';
 import SourceMap from '@parcel/source-map';
@@ -73,3 +80,23 @@ export async function generate({
     map,
   };
 }
+
+export function convertBabelLoc(loc: ?BabelSourceLocation): ?SourceLocation {
+  if (!loc) return null;
+  let {filename, start, end} = loc;
+  if (filename == null) return null;
+  return {
+    filePath: path.normalize(filename),
+    start: {
+      line: start.line,
+      column: start.column + 1,
+    },
+    // - Babel's columns are exclusive, ours are inclusive (column - 1)
+    // - Babel has 0-based columns, ours are 1-based (column + 1)
+    // = +-0
+    end: {
+      line: end.line,
+      column: end.column,
+    },
+  };
+}
diff --git a/packages/shared/scope-hoisting/package.json b/packages/shared/scope-hoisting/package.json
index 0838af706..b7d861fdf 100644
--- a/packages/shared/scope-hoisting/package.json
+++ b/packages/shared/scope-hoisting/package.json
@@ -25,6 +25,7 @@
     "@babel/template": "^7.2.2",
     "@babel/traverse": "^7.2.3",
     "@babel/types": "^7.3.3",
+    "@parcel/babel-ast-utils": "2.0.0-beta.1",
     "@parcel/babylon-walk": "2.0.0-beta.1",
     "@parcel/diagnostic": "2.0.0-beta.1",
     "@parcel/source-map": "2.0.0-alpha.4.13",
diff --git a/packages/shared/scope-hoisting/src/hoist.js b/packages/shared/scope-hoisting/src/hoist.js
index 1aa95409a..b895f30bc 100644
--- a/packages/shared/scope-hoisting/src/hoist.js
+++ b/packages/shared/scope-hoisting/src/hoist.js
@@ -35,13 +35,9 @@ import traverse from '@babel/traverse';
 import template from '@babel/template';
 import nullthrows from 'nullthrows';
 import invariant from 'assert';
+import {convertBabelLoc} from '@parcel/babel-ast-utils';
 import rename from './renamer';
-import {
-  convertBabelLoc,
-  getName,
-  getIdentifier,
-  getExportIdentifier,
-} from './utils';
+import {getName, getIdentifier, getExportIdentifier} from './utils';
 
 const WRAPPER_TEMPLATE = template.statement<
   {|NAME: LVal, BODY: Array<Statement>|},
diff --git a/packages/shared/scope-hoisting/src/link.js b/packages/shared/scope-hoisting/src/link.js
index 8c737ff2a..30c6707f8 100644
--- a/packages/shared/scope-hoisting/src/link.js
+++ b/packages/shared/scope-hoisting/src/link.js
@@ -36,11 +36,11 @@ import {
   isSequenceExpression,
   isStringLiteral,
 } from '@babel/types';
+import {convertBabelLoc} from '@parcel/babel-ast-utils';
 import traverse from '@babel/traverse';
 import treeShake from './shake';
 import {
   assertString,
-  convertBabelLoc,
   getName,
   getIdentifier,
   getThrowableDiagnosticForNode,
@@ -525,7 +525,7 @@ export function link({
             throw getThrowableDiagnosticForNode(
               "`require.resolve` calls for excluded assets are only supported with outputFormat: 'commonjs'",
               mapped.filePath,
-              path.node.loc,
+              convertBabelLoc(path.node.loc),
             );
           }
 
@@ -536,7 +536,7 @@ export function link({
           throw getThrowableDiagnosticForNode(
             "`require.resolve` calls for bundled modules or bundled assets aren't supported with scope hoisting",
             mapped.filePath,
-            path.node.loc,
+            convertBabelLoc(path.node.loc),
           );
         }
       }
diff --git a/packages/shared/scope-hoisting/src/utils.js b/packages/shared/scope-hoisting/src/utils.js
index 3f8b610fc..54ba3cd7d 100644
--- a/packages/shared/scope-hoisting/src/utils.js
+++ b/packages/shared/scope-hoisting/src/utils.js
@@ -4,7 +4,6 @@ import type {
   BundleGraph,
   MutableAsset,
   NamedBundle,
-  SourceLocation,
 } from '@parcel/types';
 import type {NodePath, Scope, VariableDeclarationKind} from '@babel/traverse';
 import type {
@@ -18,7 +17,6 @@ import type {
   VariableDeclarator,
 } from '@babel/types';
 import type {Diagnostic} from '@parcel/diagnostic';
-import type {SourceLocation as BabelSourceLocation} from '@babel/types';
 
 import {simple as walkSimple} from '@parcel/babylon-walk';
 import ThrowableDiagnostic from '@parcel/diagnostic';
@@ -290,13 +288,7 @@ export function getThrowableDiagnosticForNode(
   if (loc) {
     diagnostic.codeFrame = {
       codeHighlights: {
-        start: {
-          line: loc.start.line,
-          column: loc.start.column + 1,
-        },
-        // - Babel's columns are exclusive, ours are inclusive (column - 1)
-        // - Babel has 0-based columns, ours are 1-based (column + 1)
-        // = +-0
+        start: loc.start,
         end: loc.end,
       },
     };
@@ -305,20 +297,3 @@ export function getThrowableDiagnosticForNode(
     diagnostic,
   });
 }
-
-export function convertBabelLoc(loc: ?BabelSourceLocation): ?SourceLocation {
-  if (!loc || !loc.filename) return null;
-
-  let {filename, start, end} = loc;
-  return {
-    filePath: path.normalize(filename),
-    start: {
-      line: start.line,
-      column: start.column,
-    },
-    end: {
-      line: end.line,
-      column: end.column,
-    },
-  };
-}
diff --git a/packages/transformers/js/package.json b/packages/transformers/js/package.json
index ac820afac..b17ca85aa 100644
--- a/packages/transformers/js/package.json
+++ b/packages/transformers/js/package.json
@@ -33,6 +33,7 @@
     "@parcel/plugin": "2.0.0-beta.1",
     "@parcel/scope-hoisting": "2.0.0-beta.1",
     "@parcel/utils": "2.0.0-beta.1",
+    "micromatch": "^4.0.2",
     "nullthrows": "^1.1.1",
     "semver": "^5.4.1"
   },
diff --git a/packages/transformers/js/src/JSTransformer.js b/packages/transformers/js/src/JSTransformer.js
index 2d623d9e8..038461ba6 100644
--- a/packages/transformers/js/src/JSTransformer.js
+++ b/packages/transformers/js/src/JSTransformer.js
@@ -1,6 +1,7 @@
 // @flow
 
 import type {GlobalsMap} from './visitors/globals';
+import type {SchemaEntity} from '@parcel/utils';
 
 import template from '@babel/template';
 import semver from 'semver';
@@ -14,6 +15,31 @@ import {ancestor as walkAncestor} from '@parcel/babylon-walk';
 import * as babelCore from '@babel/core';
 import {hoist} from '@parcel/scope-hoisting';
 import {generate, parse} from '@parcel/babel-ast-utils';
+import {validateSchema} from '@parcel/utils';
+import {encodeJSONKeyComponent} from '@parcel/diagnostic';
+
+const CONFIG_SCHEMA: SchemaEntity = {
+  type: 'object',
+  properties: {
+    inlineFS: {
+      type: 'boolean',
+    },
+    inlineEnvironment: {
+      oneOf: [
+        {
+          type: 'boolean',
+        },
+        {
+          type: 'array',
+          items: {
+            type: 'string',
+          },
+        },
+      ],
+    },
+  },
+  additionalProperties: false,
+};
 
 const IMPORT_RE = /\b(?:import\b|export\b|require\s*\()/;
 const ENV_RE = /\b(?:process\.env)\b/;
@@ -33,6 +59,26 @@ function canHaveDependencies(code) {
 }
 
 export default (new Transformer({
+  async loadConfig({options, config}) {
+    let result = await config.getConfig([], {
+      packageKey: '@parcel/transformer-js',
+    });
+
+    if (result) {
+      validateSchema.diagnostic(
+        CONFIG_SCHEMA,
+        result.contents,
+        result.filePath,
+        // FIXME
+        await options.inputFS.readFile(result.filePath, 'utf8'),
+        '@parcel/transformer-js',
+        `/${encodeJSONKeyComponent('@parcel/transformer-js')}`,
+        'Invalid config for @parcel/transformer-js',
+      );
+      config.setResult(result.contents);
+    }
+  },
+
   canReuseAST({ast}) {
     return ast.type === 'babel' && semver.satisfies(ast.version, '^7.0.0');
   },
@@ -56,7 +102,7 @@ export default (new Transformer({
     });
   },
 
-  async transform({asset, options, logger}) {
+  async transform({asset, config, options, logger}) {
     // When this asset is an bundle entry, allow that bundle to be split to load shared assets separately.
     // Only set here if it is null to allow previous transformers to override this behavior.
     if (asset.isSplittable == null) {
@@ -69,6 +115,8 @@ export default (new Transformer({
       return [asset];
     }
 
+    let {inlineEnvironment = true, inlineFS = true} = config || {};
+
     let code = asset.isASTDirty() ? null : await asset.getCode();
 
     // Inline process/environment variables
@@ -81,6 +129,7 @@ export default (new Transformer({
         ast,
         env: options.env,
         isNode: asset.env.isNode(),
+        replaceEnv: inlineEnvironment,
         isBrowser: asset.env.isBrowser(),
       });
     }
@@ -100,7 +149,13 @@ export default (new Transformer({
 
         if (!ignore) {
           traverse.cache.clearScope();
-          traverse(ast.program, fsVisitor, null, {asset, logger, ast});
+          traverse(ast.program, fsVisitor, null, {
+            asset,
+            logger,
+            ast,
+            options,
+            inlineFS,
+          });
         }
       }
 
diff --git a/packages/transformers/js/src/visitors/fs.js b/packages/transformers/js/src/visitors/fs.js
index 3a56b45b1..a37c9dd08 100644
--- a/packages/transformers/js/src/visitors/fs.js
+++ b/packages/transformers/js/src/visitors/fs.js
@@ -1,5 +1,5 @@
 // @flow
-import type {AST, MutableAsset} from '@parcel/types';
+import type {AST, MutableAsset, PluginOptions} from '@parcel/types';
 import type {PluginLogger} from '@parcel/logger';
 import type {Visitor, NodePath} from '@babel/traverse';
 import type {CallExpression, Node, StringLiteral} from '@babel/types';
@@ -19,15 +19,24 @@ import {
 } from '@babel/types';
 import invariant from 'assert';
 import Path from 'path';
-import fs from 'fs';
 import template from '@babel/template';
 import {errorToDiagnostic} from '@parcel/diagnostic';
+import {convertBabelLoc} from '@parcel/babel-ast-utils';
 
 const bufferTemplate = template.expression<
   {|CONTENT: StringLiteral|},
   CallExpression,
 >('Buffer.from(CONTENT, "base64")');
 
+const throwErrorTemplate = template.expression<null, CallExpression>(
+  `(function(){
+  let e = new Error("ENOENT: no such file or directory, open '...'")
+  e.code = 'ENOENT';
+  throw e;
+})()`,
+  {syntacticPlaceholders: true},
+);
+
 export default ({
   AssignmentExpression(path) {
     if (!isRequire(path.node.right, 'fs', 'readFileSync')) {
@@ -42,65 +51,109 @@ export default ({
     }
   },
 
-  CallExpression(path, {asset, logger, ast}) {
+  CallExpression(path, {asset, logger, ast, options, inlineFS}) {
     if (referencesImport(path, 'fs', 'readFileSync')) {
       let vars = {
         __dirname: Path.dirname(asset.filePath),
         __filename: Path.basename(asset.filePath),
       };
-      let filename, res, args;
-
-      try {
-        [filename, ...args] = (path
-          .get('arguments')
-          .map(arg => evaluate(arg, vars)): Array<string>);
-
-        filename = Path.resolve(filename);
-        res = fs.readFileSync(filename, ...args);
-      } catch (_err) {
-        let err: Error = _err;
 
-        if (err instanceof NodeNotEvaluatedError) {
-          // Warn using a code frame
-          err.fileName = asset.filePath;
-
-          // $FlowFixMe the actual stack is useless
-          delete err.stack;
-
-          logger.warn(errorToDiagnostic(err));
-          return;
+      let filename, args, res;
+      if (inlineFS) {
+        try {
+          [filename, ...args] = (path
+            .get('arguments')
+            .map(arg => evaluate(arg, vars)): Array<string>);
+
+          filename = Path.resolve(filename);
+          if (!Path.relative(options.projectRoot, filename).startsWith('..')) {
+            res = options.inputFS.readFileSync(filename, ...args);
+          }
+        } catch (_err) {
+          let err: Error = _err;
+
+          if (err instanceof NodeNotEvaluatedError) {
+            // Warn using a code frame
+            err.fileName = asset.filePath;
+
+            // $FlowFixMe the actual stack is useless
+            delete err.stack;
+
+            logger.warn(errorToDiagnostic(err));
+            return;
+          }
+
+          // Add location info so we log a code frame with the error
+          // $FlowFixMe Added in Flow 0.121.0 upgrade in #4381
+          err.loc =
+            path.node.arguments.length > 0
+              ? path.node.arguments[0].loc?.start
+              : path.node.loc?.start;
+          throw err;
         }
-
-        // Add location info so we log a code frame with the error
-        // $FlowFixMe Added in Flow 0.121.0 upgrade in #4381
-        err.loc =
-          path.node.arguments.length > 0
-            ? path.node.arguments[0].loc?.start
-            : path.node.loc?.start;
-        throw err;
       }
 
       let replacementNode;
-      if (Buffer.isBuffer(res)) {
-        invariant(res != null);
-        replacementNode = bufferTemplate({
-          CONTENT: t.stringLiteral(res.toString('base64')),
+      if (filename && res) {
+        if (Buffer.isBuffer(res)) {
+          invariant(res != null);
+          replacementNode = bufferTemplate({
+            CONTENT: t.stringLiteral(res.toString('base64')),
+          });
+        } else {
+          invariant(typeof res === 'string');
+          replacementNode = t.stringLiteral(res);
+        }
+
+        invariant(filename != null);
+        asset.addIncludedFile({
+          filePath: filename,
         });
       } else {
-        invariant(typeof res === 'string');
-        replacementNode = t.stringLiteral(res);
-      }
+        let loc = convertBabelLoc(path.node.loc);
+        if (filename) {
+          let e = {
+            message:
+              'Disallowing fs.readFileSync of file outside project root, replacing with an error',
+            filePath: loc?.filePath ?? asset.filePath,
+            codeFrame: loc
+              ? {
+                  codeHighlights: {start: loc.start, end: loc.end},
+                }
+              : undefined,
+          };
+          if (asset.isSource) {
+            logger.warn(e);
+          } else {
+            logger.verbose(e);
+          }
+        } else {
+          logger.verbose({
+            message: 'Replacing fs.readFileSync with an error',
+            filePath: loc?.filePath ?? asset.filePath,
+            codeFrame: loc
+              ? {
+                  codeHighlights: {start: loc.start, end: loc.end},
+                }
+              : undefined,
+            hints: ['You might want to enable `inlineFS`?'],
+          });
+        }
 
-      invariant(filename != null);
-      asset.addIncludedFile({
-        filePath: filename,
-      });
+        replacementNode = throwErrorTemplate();
+      }
 
       path.replaceWith(replacementNode);
       asset.setAST(ast); // mark dirty
     }
   },
-}: Visitor<{|asset: MutableAsset, ast: AST, logger: PluginLogger|}>);
+}: Visitor<{|
+  asset: MutableAsset,
+  ast: AST,
+  logger: PluginLogger,
+  options: PluginOptions,
+  inlineFS: boolean,
+|}>);
 
 function isRequire(node, name, method) {
   // e.g. require('fs').readFileSync
diff --git a/packages/transformers/js/src/visitors/process.js b/packages/transformers/js/src/visitors/process.js
index 20c6f80cf..c399c7f5d 100644
--- a/packages/transformers/js/src/visitors/process.js
+++ b/packages/transformers/js/src/visitors/process.js
@@ -1,19 +1,51 @@
+//@flow
+import type {AST, Glob, EnvMap, MutableAsset} from '@parcel/types';
+import type {Node, MemberExpression} from '@babel/types';
+
 import * as types from '@babel/types';
+import {isAssignmentExpression, isStringLiteral} from '@babel/types';
+import {isMatch} from 'micromatch';
 import {morph} from './utils';
 
+type State = {|
+  asset: MutableAsset,
+  ast: AST,
+  env: EnvMap,
+  isBrowser: boolean,
+  isNode: boolean,
+  replaceEnv: boolean | Array<Glob>,
+|};
+
 export default {
-  MemberExpression(node, {asset, ast, env, isBrowser, isNode}, ancestors) {
+  MemberExpression(
+    node: MemberExpression,
+    {asset, ast, env, isBrowser, isNode, replaceEnv}: State,
+    ancestors: Array<Node>,
+  ) {
     // Inline environment variables accessed on process.env
     if (!isNode && types.matchesPattern(node.object, 'process.env')) {
+      // $FlowFixMe
       let key = types.toComputedKey(node);
-      if (types.isStringLiteral(key)) {
-        // Try using the value from the passed env (either from new Parcel
+      if (isStringLiteral(key)) {
+        let {value} = key;
+
+        let shouldInline =
+          // If true or matched by glob,
+          replaceEnv === true ||
+          (Array.isArray(replaceEnv) && isMatch(value, replaceEnv)) ||
+          // but always inline NODE_ENV or PARCEL_BUILD_ENV in our tests
+          value === 'NODE_ENV' ||
+          (process.env.PARCEL_BUILD_ENV === 'test' &&
+            value === 'PARCEL_BUILD_ENV');
+
+        // Try using the value from the passed env (either from `new Parcel`
         // options or from dotenv), and fall back to process.env
-        let prop = env[key.value] ?? process.env[key.value];
+        let prop = shouldInline ? env[value] ?? process.env[value] : undefined;
         if (typeof prop !== 'function') {
           let value = types.valueToNode(prop);
           morph(node, value);
           asset.setAST(ast);
+          // TODO bust the cache on changes
           // asset.meta.env[key.value] = process.env[key.value];
         }
       }
@@ -21,11 +53,8 @@ export default {
     } else if (isBrowser && types.matchesPattern(node, 'process.browser')) {
       // the last ancestor is the node itself, the one before may be it's parent
       const parent = ancestors[ancestors.length - 2];
-      const isAssignmentExpression = Boolean(
-        parent && types.isAssignmentExpression(parent) && parent.left === node,
-      );
 
-      if (isAssignmentExpression) {
+      if (parent && isAssignmentExpression(parent) && parent.left === node) {
         parent.right = types.booleanLiteral(true);
       } else {
         morph(node, types.booleanLiteral(true));


Test patch:
diff --git a/packages/core/integration-tests/test/blob-url.js b/packages/core/integration-tests/test/blob-url.js
index e5e23b450..45c1e4baf 100644
--- a/packages/core/integration-tests/test/blob-url.js
+++ b/packages/core/integration-tests/test/blob-url.js
@@ -2,20 +2,11 @@
 
 import assert from 'assert';
 import path from 'path';
-import {bundle, distDir, outputFS, inputFS} from '@parcel/test-utils';
-
-const configPath = path.join(__dirname, '/integration/blob-url/.parcelrc');
-
-const config = {
-  ...JSON.parse(inputFS.readFileSync(configPath, 'utf8')),
-  filePath: configPath,
-};
+import {bundle, distDir, outputFS} from '@parcel/test-utils';
 
 describe('blob urls', () => {
   it('should inline compiled content as a blob url with `blob-url:*` imports', async () => {
-    await bundle(path.join(__dirname, '/integration/blob-url/index.js'), {
-      config,
-    });
+    await bundle(path.join(__dirname, '/integration/blob-url/index.js'));
 
     let bundleContent = await outputFS.readFile(
       path.join(distDir, 'index.js'),
@@ -36,7 +27,6 @@ describe('blob urls', () => {
 
   it('should inline minified content as a blob url with `blob-url:*` imports', async () => {
     await bundle(path.join(__dirname, '/integration/blob-url/index.js'), {
-      config,
       minify: true,
     });
 
diff --git a/packages/core/integration-tests/test/fs.js b/packages/core/integration-tests/test/fs.js
index 483cff0fb..be32c01a9 100644
--- a/packages/core/integration-tests/test/fs.js
+++ b/packages/core/integration-tests/test/fs.js
@@ -1,3 +1,4 @@
+// @flow
 import assert from 'assert';
 import path from 'path';
 import {
@@ -5,6 +6,7 @@ import {
   bundle,
   removeDistDirectory,
   run,
+  overlayFS,
   outputFS,
   distDir,
 } from '@parcel/test-utils';
@@ -15,6 +17,36 @@ describe('fs', function() {
   });
 
   describe('browser environment', function() {
+    it('should not inline a file if disabled via config', async function() {
+      let b = await bundle(
+        path.join(__dirname, '/integration/fs-disabled/index.js'),
+        {
+          inputFS: overlayFS,
+        },
+      );
+
+      // $FlowFixMe
+      await assert.rejects(() => run(b), {
+        message: `ENOENT: no such file or directory, open '...'`,
+        code: 'ENOENT',
+      });
+    });
+
+    it('should not inline a file outside of the project root', async function() {
+      let b = await bundle(
+        path.join(__dirname, '/integration/fs-outside-root/index.js'),
+        {
+          inputFS: overlayFS,
+        },
+      );
+
+      // $FlowFixMe
+      await assert.rejects(() => run(b), {
+        message: `ENOENT: no such file or directory, open '...'`,
+        code: 'ENOENT',
+      });
+    });
+
     it('should inline a file as a string', async function() {
       let b = await bundle(path.join(__dirname, '/integration/fs/index.js'));
       let output = await run(b);
@@ -198,7 +230,7 @@ describe('fs', function() {
   describe.skip('electron environment', function() {
     it('should not inline a file in an Electron environment', async function() {
       let b = await bundle(path.join(__dirname, '/integration/fs/index.js'), {
-        target: 'electron',
+        targets: ['electron'],
       });
 
       assertBundles(b, [
diff --git a/packages/core/integration-tests/test/integration/blob-url/package.json b/packages/core/integration-tests/test/integration/blob-url/package.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/blob-url/package.json
@@ -0,0 +1,1 @@
+{}
diff --git a/packages/core/integration-tests/test/integration/blob-url/yarn.lock b/packages/core/integration-tests/test/integration/blob-url/yarn.lock
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/core/integration-tests/test/integration/env-disabled-glob/index.js b/packages/core/integration-tests/test/integration/env-disabled-glob/index.js
new file mode 100644
index 000000000..163ca69f5
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/env-disabled-glob/index.js
@@ -0,0 +1,3 @@
+module.exports = function () {
+  return process.env.A_1 + ":" + process.env.B_1 + ":" + process.env.B_2;
+};
diff --git a/packages/core/integration-tests/test/integration/env-disabled-glob/package.json b/packages/core/integration-tests/test/integration/env-disabled-glob/package.json
new file mode 100644
index 000000000..8d99190f9
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/env-disabled-glob/package.json
@@ -0,0 +1,5 @@
+{
+  "@parcel/transformer-js": {
+    "inlineEnvironment": ["B_*"]
+  }
+}
diff --git a/packages/core/integration-tests/test/integration/env-disabled/index.js b/packages/core/integration-tests/test/integration/env-disabled/index.js
new file mode 100644
index 000000000..fe2001908
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/env-disabled/index.js
@@ -0,0 +1,7 @@
+module.exports = function () {
+  return process.env.FOOBAR + test(process.env.FOOBAR);
+};
+
+function test(str) {
+  return ':' + str;
+}
diff --git a/packages/core/integration-tests/test/integration/env-disabled/package.json b/packages/core/integration-tests/test/integration/env-disabled/package.json
new file mode 100644
index 000000000..889ecc8dd
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/env-disabled/package.json
@@ -0,0 +1,5 @@
+{
+  "@parcel/transformer-js": {
+    "inlineEnvironment": false
+  }
+}
diff --git a/packages/core/integration-tests/test/integration/env-nodeenv/index.js b/packages/core/integration-tests/test/integration/env-nodeenv/index.js
new file mode 100644
index 000000000..16220625a
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/env-nodeenv/index.js
@@ -0,0 +1,3 @@
+module.exports = function () {
+  return process.env.NODE_ENV + ":" + process.env.FOO;
+};
diff --git a/packages/core/integration-tests/test/integration/env-nodeenv/package.json b/packages/core/integration-tests/test/integration/env-nodeenv/package.json
new file mode 100644
index 000000000..889ecc8dd
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/env-nodeenv/package.json
@@ -0,0 +1,5 @@
+{
+  "@parcel/transformer-js": {
+    "inlineEnvironment": false
+  }
+}
diff --git a/packages/core/integration-tests/test/integration/env-nodeenv/yarn.lock b/packages/core/integration-tests/test/integration/env-nodeenv/yarn.lock
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/core/integration-tests/test/integration/env-prior-transform/insert-env.js b/packages/core/integration-tests/test/integration/env-prior-transform/insert-env.js
index 01682bf0e..a3c638b7c 100644
--- a/packages/core/integration-tests/test/integration/env-prior-transform/insert-env.js
+++ b/packages/core/integration-tests/test/integration/env-prior-transform/insert-env.js
@@ -1,5 +1,5 @@
 module.exports = function insertEnv(babel) {
-  const exportFoo = babel.template('module.exports = process.env.foo;');
+  const exportFoo = babel.template('module.exports = process.env.NODE_ENV;', {syntacticPlaceholders: true});
 
   return {
     visitor: {
diff --git a/packages/core/integration-tests/test/integration/fs-disabled/index.js b/packages/core/integration-tests/test/integration/fs-disabled/index.js
new file mode 100644
index 000000000..ba3e5981e
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/fs-disabled/index.js
@@ -0,0 +1,2 @@
+var fs = require('fs');
+module.exports = fs.readFileSync(__dirname + '/test.txt', 'utf8');
diff --git a/packages/core/integration-tests/test/integration/fs-disabled/package.json b/packages/core/integration-tests/test/integration/fs-disabled/package.json
new file mode 100644
index 000000000..8357e4adc
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/fs-disabled/package.json
@@ -0,0 +1,5 @@
+{
+  "@parcel/transformer-js": {
+    "inlineFS": false
+  }
+}
diff --git a/packages/core/integration-tests/test/integration/fs-disabled/test.txt b/packages/core/integration-tests/test/integration/fs-disabled/test.txt
new file mode 100644
index 000000000..b6fc4c620
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/fs-disabled/test.txt
@@ -0,0 +1,1 @@
+hello
\ No newline at end of file
diff --git a/packages/core/integration-tests/test/integration/fs-disabled/yarn.lock b/packages/core/integration-tests/test/integration/fs-disabled/yarn.lock
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/core/integration-tests/test/integration/fs-outside-root/index.js b/packages/core/integration-tests/test/integration/fs-outside-root/index.js
new file mode 100644
index 000000000..5e544f848
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/fs-outside-root/index.js
@@ -0,0 +1,4 @@
+var fs = require('fs');
+
+fs.readFileSync(__dirname + '../../../../../../../package.json', 'utf8')
+
diff --git a/packages/core/integration-tests/test/javascript.js b/packages/core/integration-tests/test/javascript.js
index 301ef5f88..9e4658d81 100644
--- a/packages/core/integration-tests/test/javascript.js
+++ b/packages/core/integration-tests/test/javascript.js
@@ -1238,10 +1238,10 @@ describe('javascript', function() {
     );
 
     let jsBundle = b.getBundles()[0];
-    let contents = await outputFS.readFile(jsBundle.filePath);
+    let contents = await outputFS.readFile(jsBundle.filePath, 'utf8');
 
     assert(!contents.includes('process.env'));
-    assert.equal(await run(b), 42);
+    assert.equal(await run(b), 'test');
   });
 
   it('should not insert environment variables in node environment', async function() {
@@ -1250,7 +1250,7 @@ describe('javascript', function() {
     );
 
     let output = await run(b);
-    assert.ok(output.toString().indexOf('process.env') > -1);
+    assert.ok(output.toString().includes('process.env'));
     assert.equal(output(), 'test:test');
   });
 
@@ -1265,7 +1265,7 @@ describe('javascript', function() {
     });
 
     let output = await run(b);
-    assert.ok(output.toString().indexOf('process.env') > -1);
+    assert.ok(output.toString().includes('process.env'));
     assert.equal(output(), 'test:test');
   });
 
@@ -1280,16 +1280,59 @@ describe('javascript', function() {
     });
 
     let output = await run(b);
-    assert.ok(output.toString().indexOf('process.env') > -1);
+    assert.ok(output.toString().includes('process.env'));
     assert.equal(output(), 'test:test');
   });
 
-  it('should insert environment variables in browser environment', async function() {
-    let b = await bundle(path.join(__dirname, '/integration/env/index.js'));
+  it('should inline NODE_ENV environment variable in browser environment even if disabled', async function() {
+    let b = await bundle(
+      path.join(__dirname, '/integration/env-nodeenv/index.js'),
+      {
+        env: {
+          FOO: 'abc',
+        },
+      },
+    );
 
     let output = await run(b);
-    assert.ok(output.toString().indexOf('process.env') === -1);
-    assert.equal(output(), 'test:test');
+    assert.ok(!output.toString().includes('process.env'));
+    assert.equal(output(), 'test:undefined');
+  });
+
+  it('should not insert environment variables in browser environment if disabled', async function() {
+    let b = await bundle(
+      path.join(__dirname, '/integration/env-disabled/index.js'),
+      {
+        env: {FOOBAR: 'abc'},
+      },
+    );
+
+    let output = await run(b);
+    assert.ok(!output.toString().includes('process.env'));
+    assert.equal(output(), 'undefined:undefined');
+  });
+
+  it('should only insert environment variables in browser environment matching the glob', async function() {
+    let b = await bundle(
+      path.join(__dirname, '/integration/env-disabled-glob/index.js'),
+      {
+        env: {A_1: 'abc', B_1: 'def', B_2: 'ghi'},
+      },
+    );
+
+    let output = await run(b);
+    assert.ok(!output.toString().includes('process.env'));
+    assert.equal(output(), 'undefined:def:ghi');
+  });
+
+  it('should be able to inline environment variables in browser environment', async function() {
+    let b = await bundle(path.join(__dirname, '/integration/env/index.js'), {
+      env: {NODE_ENV: 'abc'},
+    });
+
+    let output = await run(b);
+    assert.ok(!output.toString().includes('process.env'));
+    assert.equal(output(), 'abc:abc');
   });
 
   it("should insert the user's NODE_ENV as process.env.NODE_ENV if passed", async function() {
@@ -1300,7 +1343,7 @@ describe('javascript', function() {
     });
 
     let output = await run(b);
-    assert.ok(output.toString().indexOf('process.env') === -1);
+    assert.ok(!output.toString().includes('process.env'));
     assert.equal(output(), 'production:production');
   });
 
diff --git a/packages/core/test-utils/src/utils.js b/packages/core/test-utils/src/utils.js
index 9eb9961f5..ca98e626e 100644
--- a/packages/core/test-utils/src/utils.js
+++ b/packages/core/test-utils/src/utils.js
@@ -245,7 +245,7 @@ export async function run(
   bundleGraph: BundleGraph<NamedBundle>,
   globals: mixed,
   opts: RunOpts = {},
-): Promise<mixed> {
+): Promise<any> {
   let bundles = bundleGraph.getBundles();
   let bundle = nullthrows(
     bundles.find(b => b.type === 'js' || b.type === 'html'),