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

Support for ESM plugins and configs #8913

Merged
merged 25 commits into from Apr 28, 2023
Merged

Support for ESM plugins and configs #8913

merged 25 commits into from Apr 28, 2023

Conversation

devongovett
Copy link
Member

@devongovett devongovett commented Mar 26, 2023

Fixes #7639. Fixes #7330. Closes #8726.

This adds support for loading plugins and configs distributed using native ESM. It works by resolving the module type (ESM or CJS) based on the file extension (mjs/cjs) or "type": "module" field in package.json. Then it loads ES modules using import.

Since we cannot intercept import like we do require, we have to track dependencies to invalidate the cache differently. This is done using a fork of es-module-lexer, with Rust bindings and support for analyzing require as well as import. We recursively build up a graph of dependencies using this and the Rust resolver, and keep track of all of the invalidations found along the way.

There is basic support for dynamic expressions in import() and require() functions. If the argument starts with a string and contains string concatenations, or is a template literal with interpolations, it will be compiled to a glob and then we attempt to match the glob against the file system to include all of the possible files that could be loaded in the cache key. If this is not possible (i.e. it is not statically analyzable), then we invalidate on startup.

For now, this strategy is only applied to ESM dependencies and we use our old strategy for CJS. Eventually I'd like to switch everything over as it's more stable/predictable, but want to test it out first. ESM support will be marked experimental at first, and will emit a warning saying so.

For configs, we now also automatically add dev dependencies and invalidate on startup for JS files in core so plugins no longer need to do this.

To do

  • Test loading ESM configs

@parcel-benchmark
Copy link

parcel-benchmark commented Mar 27, 2023

Benchmark Results

Kitchen Sink ✅

Timings

Description Time Difference
Cold 1.48s -12.00ms
Cached 376.00ms +31.00ms ⚠️

Cold Bundles

Bundle Size Difference Time Difference
dist/legacy/parcel.7cdb0fad.webp 102.94kb +0.00b 244.00ms +14.00ms ⚠️
dist/legacy/parcel.7cdb0fad.webp 102.94kb +0.00b 245.00ms +14.00ms ⚠️
dist/legacy/index.b8ae99ba.css 94.00b +0.00b 252.00ms +13.00ms ⚠️
dist/modern/index.31cedca9.css 94.00b +0.00b 253.00ms +14.00ms ⚠️

Cached Bundles

Bundle Size Difference Time Difference
dist/legacy/parcel.7cdb0fad.webp 102.94kb +0.00b 258.00ms +15.00ms ⚠️
dist/legacy/parcel.7cdb0fad.webp 102.94kb +0.00b 258.00ms +16.00ms ⚠️
dist/modern/parcel.7cdb0fad.webp 102.94kb +0.00b 259.00ms +16.00ms ⚠️
dist/legacy/index.d20f91ee.js 1.19kb +0.00b 412.00ms +21.00ms ⚠️
dist/legacy/index.b8ae99ba.css 94.00b +0.00b 266.00ms +15.00ms ⚠️
dist/modern/index.31cedca9.css 94.00b +0.00b 265.00ms +15.00ms ⚠️

React HackerNews ✅

Timings

Description Time Difference
Cold 6.88s -182.00ms
Cached 427.00ms -20.00ms

Cold Bundles

No bundle changes detected.

Cached Bundles

Bundle Size Difference Time Difference
dist/index.js 463.02kb +0.00b 1.06s -68.00ms 🚀
dist/PermalinkedComment.60e78a07.js 4.18kb +0.00b 456.00ms -25.00ms 🚀
dist/UserProfile.c18819ee.js 1.57kb +0.00b 456.00ms -25.00ms 🚀
dist/NotFound.cfeedbab.js 427.00b +0.00b 450.00ms -30.00ms 🚀

AtlasKit Editor ✅

Timings

Description Time Difference
Cold 1.02m -454.00ms
Cached 2.15s +8.00ms

Cold Bundles

Bundle Size Difference Time Difference
dist/media-viewer.bd165005.js 542.15kb +0.00b 10.71s +2.40s ⚠️
dist/ConfigPanelFieldsLoader.f06a6b36.js 312.08kb +0.00b 7.78s -535.00ms 🚀
dist/EmojiPickerComponent.a25bd8e7.js 196.67kb +0.00b 10.71s +1.17s ⚠️
dist/card.501ecffa.js 143.52kb +0.00b 7.78s -532.00ms 🚀
dist/ConfigPanelFieldsLoader.e1ae433f.js 83.45kb +0.00b 7.78s -533.00ms 🚀
dist/ElementBrowser.3bcad544.js 65.85kb +0.00b 7.78s -533.00ms 🚀
dist/archive.b919f1ad.js 61.47kb +0.00b 10.70s +2.38s ⚠️
dist/esm.945b66be.js 60.94kb +0.00b 7.78s -532.00ms 🚀
dist/ConfigPanelFieldsLoader.ef739802.js 16.14kb +0.00b 7.78s -533.00ms 🚀
dist/ui.2de0ef21.js 14.88kb +0.00b 7.78s -533.00ms 🚀
dist/ConfigPanelFieldsLoader.c68d84ab.js 14.25kb +0.00b 7.78s -533.00ms 🚀
dist/pdfRenderer.187ba54d.js 12.21kb +0.00b 7.78s -532.00ms 🚀
dist/mobile-upload.136dd5cb.js 8.08kb +0.00b 7.78s -534.00ms 🚀
dist/mobile-upload.0bdb676c.js 8.08kb +0.00b 7.78s -536.00ms 🚀
dist/media-viewer-analytics-error-boundary.e6109a6a.js 3.46kb +0.00b 10.70s +2.39s ⚠️
dist/uk.48c97550.js 2.89kb +0.00b 7.78s -532.00ms 🚀
dist/codeViewerRenderer.915ef6b3.js 2.84kb +0.00b 10.71s +2.40s ⚠️
dist/th.31044730.js 2.73kb +0.00b 7.78s -533.00ms 🚀
dist/vi.d8dcb67a.js 2.22kb +0.00b 7.78s -533.00ms 🚀
dist/tr.46f26598.js 2.16kb +0.00b 7.78s -532.00ms 🚀
dist/sv.13d93533.js 2.10kb +0.00b 7.78s -533.00ms 🚀
dist/zh_TW.afaf6222.js 1.98kb +0.00b 7.78s -533.00ms 🚀
dist/zh.fcdc32bb.js 1.96kb +0.00b 7.78s -534.00ms 🚀
dist/workerHasher.ef49a7fc.js 1.72kb +0.00b 7.78s -533.00ms 🚀
dist/workerHasher.9d5fe27b.js 1.72kb +0.00b 7.78s -536.00ms 🚀
dist/heading5.023a8f1f.js 1.36kb +0.00b 5.95s +419.00ms ⚠️
dist/sk.101f1705.js 786.00b +0.00b 7.78s +1.74s ⚠️
dist/simpleHasher.f1f58b0a.js 687.00b +0.00b 7.78s -534.00ms 🚀
dist/simpleHasher.09f4d713.js 687.00b +0.00b 7.78s -536.00ms 🚀
dist/ro.a6eff34a.js 612.00b +0.00b 7.78s +1.74s ⚠️
dist/index.html 240.00b +0.00b 10.87s +5.87s ⚠️

Cached Bundles

Bundle Size Difference Time Difference
dist/card.501ecffa.js 143.52kb +0.00b 10.90s +3.02s ⚠️
dist/pdfRenderer.187ba54d.js 12.21kb +0.00b 11.02s +3.13s ⚠️
dist/pt_BR.eccfad73.js 2.19kb +0.00b 7.11s +1.01s ⚠️
dist/heading4.05995ed9.js 1.25kb +0.00b 5.92s +409.00ms ⚠️
dist/pt_PT.402f9c4e.js 765.00b +0.00b 7.88s +1.79s ⚠️

Three.js ✅

Timings

Description Time Difference
Cold 4.83s -93.00ms
Cached 266.00ms -18.00ms 🚀

Cold Bundles

No bundle changes detected.

Cached Bundles

No bundle changes detected.

Click here to view a detailed benchmark overview.

let module = lex(&contents)?;
module
.imports()
// .par_bridge()
Copy link
Member Author

Choose a reason for hiding this comment

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

I want to make this run in parallel, but that only works if the file system is the native one and not provided by JS otherwise we'll deadlock. Could work if we make the packageManager.getInvalidations API async but maybe that's a breaking change.

@mischnic
Copy link
Member

This doesn't sound good:

Load Node-API [napi_get_last_error_info] from host runtime failed: GetProcAddress failed
Load Node-API [napi_get_uv_event_loop] from host runtime failed: GetProcAddress failed
Load Node-API [napi_fatal_exception] from host runtime failed: GetProcAddress failed
Load Node-API [napi_create_threadsafe_function] from host runtime failed: GetProcAddress failed

@devongovett
Copy link
Member Author

@mischnic where do you see that?

@mischnic
Copy link
Member

In the windows unit tests, though that is only printed if there are no tests:

     Running unittests src\lib.rs (target\debug\deps\parcel_image-39752e9c761b6e55.exe)
Load Node-API [napi_get_last_error_info] from host runtime failed: GetProcAddress failed
Load Node-API [napi_get_uv_event_loop] from host runtime failed: GetProcAddress failed
Load Node-API [napi_fatal_exception] from host runtime failed: GetProcAddress failed
Load Node-API [napi_create_threadsafe_function] from host runtime failed: GetProcAddress failed

running 0 tests

@devongovett devongovett marked this pull request as ready for review April 19, 2023 04:14
Copy link
Member

@lettertwo lettertwo left a comment

Choose a reason for hiding this comment

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

LGTM! I like how all of the configurable packages lose their own bespoke invalidation logic.

I made an honest attempt to read through the native changes too 😆

packages/utils/node-resolver-core/src/lib.rs Outdated Show resolved Hide resolved
Co-authored-by: Eric Eldredge <lettertwo@gmail.com>
.github/workflows/ci.yml Show resolved Hide resolved
packages/utils/dev-dep-resolver/src/lib.rs Show resolved Hide resolved
@@ -123,7 +125,27 @@ export class NodePackageManager implements PackageManager {
saveDev?: boolean,
|},
): Promise<any> {
let {resolved} = await this.resolve(name, from, opts);
let {resolved, type} = await this.resolve(name, from, opts);
if (type === 2) {
Copy link
Member

Choose a reason for hiding this comment

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

Ideally we'd have an enum somewhere for these magic numbers

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