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 modules with --no-bundle #2769

Merged
merged 27 commits into from
Mar 27, 2023
Merged

Support modules with --no-bundle #2769

merged 27 commits into from
Mar 27, 2023

Conversation

penalosa
Copy link
Contributor

@penalosa penalosa commented Feb 20, 2023

What this PR solves / how to test:

When the --no-bundle flag is set, this PR adds support for traversing the module graph with esbuild, to figure out what additional modules should be uploaded alongside the entrypoint to support runtime imports. It also bumps esbuild to 16.5 to support packages: external.

Associated docs issues/PR:

  • TBD

Author has included the following, where applicable:

  • Tests
  • Changeset

Reviewer has performed the following, where applicable:

  • Checked for inclusion of relevant tests
  • Checked for inclusion of a relevant changeset
  • Checked for creation of associated docs updates
  • Manually pulled down the changes and spot-tested

Fixes #2697.

@penalosa penalosa requested a review from a team as a code owner February 20, 2023 21:03
@changeset-bot
Copy link

changeset-bot bot commented Feb 20, 2023

🦋 Changeset detected

Latest commit: b7e71a6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
wrangler Minor
no-bundle-import Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Contributor

github-actions bot commented Feb 20, 2023

A wrangler prerelease is available for testing. You can install this latest build in your project with:

npm install --save-dev https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/4533143746/npm-package-wrangler-2769

You can reference the automatically updated head of this PR with:

npm install --save-dev https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/prs/2769/npm-package-wrangler-2769

Or you can use npx with this latest build directly:

npx https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/4533143746/npm-package-wrangler-2769 dev path/to/script.js
Additional artifacts:
npm install https://prerelease-registry.devprod.cloudflare.dev/workers-sdk/runs/4533143746/npm-package-cloudflare-pages-shared-2769

Note that these links will no longer work once the GitHub Actions artifact expires.

@codecov
Copy link

codecov bot commented Feb 20, 2023

Codecov Report

Merging #2769 (be45c79) into main (191b23f) will increase coverage by 0.51%.
The diff coverage is 85.33%.

❗ Current head be45c79 differs from pull request most recent head b7e71a6. Consider uploading reports for the commit b7e71a6 to get more accurate results

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2769      +/-   ##
==========================================
+ Coverage   73.49%   74.01%   +0.51%     
==========================================
  Files         166      168       +2     
  Lines       10427    10358      -69     
  Branches     2791     2761      -30     
==========================================
+ Hits         7663     7666       +3     
+ Misses       2764     2692      -72     
Impacted Files Coverage Δ
...ckages/wrangler/src/pages/functions/buildPlugin.ts 19.35% <ø> (ø)
...ckages/wrangler/src/pages/functions/buildWorker.ts 64.55% <ø> (ø)
packages/wrangler/src/config/validation.ts 90.58% <54.54%> (-0.56%) ⬇️
packages/wrangler/src/module-collection.ts 91.91% <79.31%> (-5.45%) ⬇️
packages/wrangler/src/bundle.ts 93.02% <100.00%> (-0.10%) ⬇️
packages/wrangler/src/dev/start-server.ts 65.60% <100.00%> (+0.63%) ⬆️
packages/wrangler/src/entry.ts 98.40% <100.00%> (+0.02%) ⬆️
packages/wrangler/src/publish/publish.ts 86.80% <100.00%> (-0.64%) ⬇️
packages/wrangler/src/traverse-module-graph.ts 100.00% <100.00%> (ø)

... and 21 files with indirect coverage changes

Copy link
Contributor

@IgorMinar IgorMinar left a comment

Choose a reason for hiding this comment

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

This looks awesome! I've left one comment, but overall I think this is looking great at a high level.

I'm also however not the most qualified to review this as I'm not deeply familiar with internals of Wrangler so I'll leave defer to someone on the team to do an in-depth review and approve this change.

But thank you for putting this together! I appreciate it.

const resp = await worker.fetch("/dynamic-var");
const text = await resp.text();
expect(text).toMatchInlineSnapshot(
'"Error: No such module \\"dynamic-var.js\\"."'
Copy link
Contributor

Choose a reason for hiding this comment

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

is this the right error message? oh.. wait.. are we throwing this error because we failed to upload that file and then runtime couldn't find it, hence this error message?

would it be possible for the --no-bundle code to fail if we can't resolve dynamic imports? As a developer I'd rather see a build error than runtime error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, this gets all the way to the runtime. Unfortunately, esbuild doesn't provide any hooks for dynamic variable imports—it seems to just ignore them (see https://esbuild.github.io/api/#non-analyzable-imports), so I'm not sure there's much we can do here. This might be possible to support in future somehow with https://www.npmjs.com/package/es-module-lexer though

Copy link
Contributor

Choose a reason for hiding this comment

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

If we can't find a way for --no-bundle to fail when dynamic imports can't be resolved, could this error message read something like:

"Error: The dynamically imported ES module \"dynamic-var.js"\ could not be found. Run wrangler --dry-run --outdir --no-bundle and verify that this module was generated when building your worker"

...such that we give people instructions on how to debug? (not sure if I've got the instructions quite right above, but you get the gist). How can we give people an error message they can act on and debug on their own?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure—this error comes from the runtime, so we may not want to call out Wrangler specifically. Maybe something like "Error: The dynamically imported ES module "dynamic-var.js"\ could not be found. Make sure it was uploaded when you published your Worker"?

Copy link
Contributor

Choose a reason for hiding this comment

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

I was going to ask about [es-module-lexer](https://www.npmjs.com/package/es-module-lexer)... could you please do a lightweight exploration if implementing this feature via es-module-lexer would allow us to turn the runtime error into build time error?

If that was possible and there were no other major blockers, I'd be inclined to going down that path as runtime errors are really really nasty and we should avoid them whenever we can even if it means a little more work for us — it will save many times more work to developers that would otherwise scramble to deal with runtime errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've pushed up an additional commit that adds a build-time error for non string-literal dynamic imports. It uses es-module-lexer in addition to esbuild, and so has a slight performance cost over just using esbuild (but that should be minimal). They're both needed in order to be able to run Wrangler's module collection (which is implemented as an esbuild plugin), and to avoid re-implementing esbuild's module resolution (reading tsconfig, for instance)

Copy link
Contributor

Choose a reason for hiding this comment

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

I see. Interesting. How significant is the overhead? Do you have any estimates? <5ms for an average project, or more? Unless it's overhead measured in hundreds of ms, I think it's totally worth it. I am hoping for single or at most double digit ms...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's pretty minimal. es-module-lexer has benchmarks which estimate 5-10ms as an upper bound for parsing a MB of JS, but it will scale with the number of modules. For a large worker (let's say 25 modules at 200kb, which is at the 5MB limit for workers), es-module-lexer will probably add about 50ms of latency in the worst case (with a fairly pessimistic reading of their benchmarks).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For an average project (let's say 5 modules at 100kb), the overhead would be closer to ~5ms

Copy link
Contributor

Choose a reason for hiding this comment

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

Great. Then this overhead is totally worth it. Thanks

Copy link
Contributor

@mrbbot mrbbot left a comment

Choose a reason for hiding this comment

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

Nice! 😃 Added a few comments...

fixtures/no-bundle-import/src/index.test.ts Outdated Show resolved Hide resolved
packages/wrangler/src/publish/publish.ts Outdated Show resolved Hide resolved
packages/wrangler/src/traverse-module-graph.ts Outdated Show resolved Hide resolved
packages/wrangler/src/traverse-module-graph.ts Outdated Show resolved Hide resolved
packages/wrangler/src/traverse-module-graph.ts Outdated Show resolved Hide resolved
packages/wrangler/src/traverse-module-graph.ts Outdated Show resolved Hide resolved
@mrbbot
Copy link
Contributor

mrbbot commented Feb 21, 2023

Oh, and also this needs a changeset! (and probably a small docs update?)

@penalosa penalosa requested a review from mrbbot February 21, 2023 19:15
Copy link
Contributor

@JacobMGEvans JacobMGEvans left a comment

Choose a reason for hiding this comment

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

I have an inquiry mostly. This is really great though!

fixtures/no-bundle-import/src/data.txt Show resolved Hide resolved
@@ -105,7 +105,7 @@
"@miniflare/durable-objects": "2.12.1",
"blake3-wasm": "^2.1.5",
"chokidar": "^3.5.3",
"esbuild": "0.16.3",
"esbuild": "0.16.5",
Copy link
Contributor

Choose a reason for hiding this comment

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

Was the "external" feature from ESBuild needed for the feature to be complete? Just trying to understand the ESBuild bump and "external" feature interaction with the --no-bundle feature 😁

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, the external feature is needed. This PR uses esbuild to try and figure out which additional modules should be uploaded with your entrypoint file. Without packages: external we'd try and upload your package imports as well as just file imports. This is pretty much only relevant for node:* node-compat imports right now, but more might be added in future.

We could try and figure out which package imports could be safely uploaded, to support runtime importing of npm modules, but the right solution for that is bundling.

Copy link
Contributor

Choose a reason for hiding this comment

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

Appreciate the clarification!! 🎆

Copy link
Contributor

@JacobMGEvans JacobMGEvans Feb 27, 2023

Choose a reason for hiding this comment

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

the right solution for that is bundling.

So like https://www.npmjs.com/package/rollup lol 😆

packages/wrangler/src/entry.ts Outdated Show resolved Hide resolved
).map(([name, m]) => ({ name, ...m }));

if (collectedModules.length > 0) {
logger.info(`Detected data modules. Uploading additional data modules:`);
Copy link
Contributor

Choose a reason for hiding this comment

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

Would the user understand what "data modules" means here? I am not sure I do. From looking at the code, I think they are modules that matched the wrangler.toml "rules" for including imported files?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How about "Detected non-javascript module rules. Uploading additional modules:"?

@penalosa penalosa closed this Mar 22, 2023
@penalosa penalosa reopened this Mar 22, 2023
@penalosa
Copy link
Contributor Author

@petebacondarwin CI should be green now, if you have a chance to take another look!

@petebacondarwin petebacondarwin self-requested a review March 24, 2023 10:27
Copy link
Contributor

@petebacondarwin petebacondarwin left a comment

Choose a reason for hiding this comment

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

A few nits but looks good to me. Nice work.

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

Successfully merging this pull request may close these issues.

🚀 Feature Request: support --no-bundle with static and dynamic ES Module imports
7 participants