-
Notifications
You must be signed in to change notification settings - Fork 3.3k
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
webpack/bundler Fix: Update worker JS file import URL to be compatible with bundlers #22165
webpack/bundler Fix: Update worker JS file import URL to be compatible with bundlers #22165
Conversation
8938042
to
f95b7b9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks so much for working on this!
I do think its a bit of a shame that we have to make the code strictly worse (IMHO) because the bundlers can't handle the shorter form. But I it is an important use case.
@RReverser do you think we can/should file this a bug on the webpack side.
@@ -5548,13 +5548,13 @@ def test_error_reporting(self): | |||
def test_webpack(self, es6): | |||
if es6: | |||
shutil.copytree(test_file('webpack_es6'), 'webpack') | |||
self.emcc_args += ['-sEXPORT_ES6'] | |||
self.emcc_args += ['-sEXPORT_ES6', '-pthread', '-sPTHREAD_POOL_SIZE=1'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be enough to instead use -sPROXY_TO_PTHREAD
. That way not only do we ensure that a thread is started but we actually run some code (the main function) on that thread.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried -sPROXY_TO_PTHREAD
initially but the test hangs instead of explicitly failing even though the same failure case occurs because the exception is caught by callMain
:
Line 115 in 34c1aa3
return handleException(e); |
-sPTHREADS_DEBUG
, I can see it tries to load the worker from file://
, but the exception doesn't make it up to where the test harness can see it failed.
Is there a way I could fix that in the test case to see the exception?
As an semi-related aside, in my own apps I've been building w/ -sINVOKE_RUN=0
because callMain
would gobble exceptions/crashes from Wasm making it hard to debug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by "would gobble exceptions/crashes from Wasm"? callMain should only deal with unwind/exit exceptions and should rethrow all other exceptions (i.e. exceptions that are not specifically supposed to be caught at the entry point).. Are you not seeing that behaviour? Or is it the rethrowning itself that is an issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah exactly, when I step through I see that in handleException
it checks for those unwind/exit exceptions and then goes to quit_
where it will rethrow the exception, but after that throw I just end up at this line: https://github.com/emscripten-core/emscripten/blob/main/test/webpack_es6/src/index.mjs#L16 where it prints loaded, and the exception seems to have disappeared? It's pretty strange, since the throw just seems to vanish after that point, when stepping through I don't end up at any other catch block/promise catch handling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stepping a bit more, I see that it does end up catching the error when calling instantiateAsync
and rejecting the promise, but that promise rejection doesn't seem to make it back up the app instantiating the module, so index.mjs
doesn't see the rejection
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well that certainly does seem like something worth investigating more. Do you have a global unhandled rejection handler on your page? Is your expectation that the module promise would be rejected? I support that would be the most logical expectation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a global handler on my page, but the stepping through I'm describing here was actually just on running the test case: https://github.com/emscripten-core/emscripten/blob/main/test/webpack_es6/src/index.mjs . I think that the module promise being rejected if an error was thrown during callMain would be expected behavior, for example if the test code was like below, I'd expect the error to end up in the catch
handler and print the error
import Module from './hello.mjs';
Module(params).then((instance) => {
console.log('loaded');
}).catch((error) => {
console.error(`Error: ${error}`);
})
The instantiateAsync
call:
Line 1082 in 34c1aa3
instantiateAsync(wasmBinary, wasmBinaryFile, info, receiveInstantiationResult).catch(readyPromiseReject); |
Module(params)
promise is seen as successful instead of rejected (since .then
is called instead)
Can you update the title so that its clear this is webpack/bundler fix. To be clear there no issue with existing syntax other than bundlers seem to be confused by it. Also, could you add a comment in the code saying that this is why need to use the longer form? |
Sounds good, I've updated the title and added a note explaining why we use this worse form of creating the file URL |
I'm also impacted by this - looking forward to this PR getting merged. |
I don't think so, that's a specific pattern recognised by all bundlers. I'm also surprised, I was pretty sure we already emitted a coorect form back when I added it and even had tests to ensure it's this specific pattern that gets emitted in the output? Did that change at some point? |
Yes, I think it changed with the removal of worker.js file in #21701 |
What happened in this change is that we went from creating a worker using a filename that was different to the current file (i.e. worker.js) to creating a worker using the exact same filename... so it made sense to me that we could transition to using the shorter/simpler form of |
Ah I see. Yeah I get the distaste for an explicit name, but to be fair that's already the case for other assets like Wasm itself, so doesn't seem too problematic on its own. One way or another you need to do find-replace when renaming those files. |
This change breaks my code which try to put everything into a single file. Please consider this example: // main.js. Will build into bundle `my-output.js`
// Emscripten generates `a.wasm` and `a.js`.
import wasmFactory from './a.js';
export const myFunc = async (param) => {
const instance = await wasmFactory();
instance.func(param);
}; Without this change, it is able to build the code above into a single file I understand the problem that may affect webpack users, but this is a feature regression to me if I upgrade from 3.1.62 to 3.1.63. It would be great if a setting is added to allow reverting back to 3.1.62 behavior. |
I agree with the "its a bit of a shame that we have to make the code strictly worse" part. For whatever reason, the current behavior on
It was a very elegant solution to use |
Hm I'd say you shouldn't be relying on |
This change broke
see: vitejs/vite#16103 |
I expect |
There are other places in the generated JS code which uses var _scriptName = import.meta.url; This will be rewritten into a static The real problem is, Webpack's default behavior does not allow runtime evaluation of |
While the build is broken here, I wonder if it's actually correct for it to be broken and that maybe Vite needs to support the main JS file referencing itself to make a Worker? Because Emscripten's new Worker call is a circular reference even before this change. The original URL: worker = new Worker(new URL(import.meta.url)) and worker = new Worker(new URL('{{{ TARGET_JS_NAME }}}', import.meta.url)); Technically do refer to the same file, so in both cases it's a circular reference. It's just that the bundler doesn't recognize/check the first case and lets it through. Vite's docs do mention the latter case that this PR added: https://vitejs.dev/guide/assets#new-url-url-import-meta-url . That PR mentions Vite supporting self referencing worker files, maybe it needs to extend support to allow a non-worker file to reference itself to create a worker? |
I'm not sure that use case you describe will work since wouldn't that result in e.g. if you added |
@brianpmaher and I have been trying to find a work around to support both vite and webpack for an internal library. We were able to bypass the circular deps error by replacing:
with
Maybe not the best solution but thought I'd share, hopefully that helps someone. |
Just to be clear, you are not actually removing the circularity here? (I think that is not actually possible). This somehow suppresses the warning but making the circularity obscure the algorithms checking for it? |
@sbc100 Correct. It's not removing it, more exploiting a hole in the algorithm vite uses to check. A couple more details:
|
@dwayneparton Which version of vite are you using? From versions The following config may need to be added to your
I think you would also need to update emscripten to use the flag However, I have noticed in recent versions of emscripten the switch to storing |
@jamsinclair We're are using vite
We also have the The main issue we had was getting it to work in both Vite and Webpack w/o any treatment to the worker url. |
This should now be fixed via PR vitejs/vite#17846, which is included in Vite v5.4.1. |
This PR closes #22140 by correcting the URL used to import the worker JS in
library_pthread.js::allocateUnusedWorker
. Previously the URL would be:which webpack translates to the
file:///
path, causing the import to fail b/c thefile://
path is not valid to access (webpack docs import meta). For example, from my own app that I hit this issue on:The correct URL for bundlers is:
Which webpack will understand and properly translate to the bundled path. Webpack will also automatically generate a new entry point for the worker file when bundling (docs).
I've updated the webpack ES6 test added in #22142 to exercise this code path by building w/ pthreads and adding
-sPTHREAD_POOL_SIZE=1
to cause an unallocated worker to be created. Let me know if it's preferred to add a new separate webpack + es6 + threads test instead of just adding the threading flags on to the existing webpack es6 test.Without the fix to the worker import URL added in this PR the test would fail with the error message below after enabling threads and starting one. After the change to the worker import URL, this test passes again.
After updating
library_pthread.js::allocateUnusedWorker()
this test passes again:I tried adding
-pthread -sPTHREAD_POOL_SIZE=1
to the non-es6 webpack test path as well but this fails with an error about the document not being defined. So I've left the non-es6 test alone, and just the es6 path will test threads support. I tried just adding someif (document)
here but still got the error about the document not being defined, so I wasn't sure quite what to change here to make this run with threads (or if that was even needed).