Skip to content

fix production usage with bundlers (fix #14, fix #15)#22

Merged
ije merged 2 commits into
esm-dev:mainfrom
undefined-moe:patch-5
Sep 14, 2025
Merged

fix production usage with bundlers (fix #14, fix #15)#22
ije merged 2 commits into
esm-dev:mainfrom
undefined-moe:patch-5

Conversation

@undefined-moe

@undefined-moe undefined-moe commented Sep 11, 2025

Copy link
Copy Markdown
Contributor

Using the original way, the resource was imported into whatever resource bundle, meaning that the url contains not only the requested worker, but lots of other modules (vite dev mode don't perform a bundle action so it works like magic in dev but broke in production).
Need to use a way to tell bundlers that it needs to be a seperate chunk and it runs in a worker environment (otherwise some bundlers might inject some dependency management stuff using non-exist document variable)
Luckily most bundlers understand new Worker(new URL(src, import.meta.url)) syntax:

Integration on esm.sh might need to be tested as I don't know how it processes those code

@undefined-moe

Copy link
Copy Markdown
Contributor Author

also might be helpful for #10

@ije

ije commented Sep 11, 2025

Copy link
Copy Markdown
Member

for esm.sh, change worker.js to worker.mjs

// for esm.sh
new Worker(new URL("./worker.mjs", import.meta.url), { type: "module"})

@ije

ije commented Sep 11, 2025

Copy link
Copy Markdown
Member

does this work in nextjs with turbopack?

@undefined-moe

undefined-moe commented Sep 11, 2025

Copy link
Copy Markdown
Contributor Author

Seems turbopack will, or already supports this.

And I can't see any message indicating web workers not supported in turbopack docs so i guess it's already fixed

@undefined-moe

Copy link
Copy Markdown
Contributor Author

for esm.sh, change worker.js to worker.mjs

// for esm.sh
new Worker(new URL("./worker.mjs", import.meta.url), { type: "module"})

This actully breaks bundler (mjs file doesn't exist)
The fix is to use new Worker(new URL(eval("./worker.mjs"), import.meta.url), { type: "module" }) which throws a compile warning but works; wonder if there's better way to do it (maybe ?format=mjs in querystring?)

@ije

ije commented Sep 11, 2025

Copy link
Copy Markdown
Member

just check if the import.meta.url is from esm.sh:

if (new URL(import.meta.url).hostname === "esm.sh") {
  return new Worker(new URL("./worker.mjs", import.meta.url), { type: "module"})
}
return new Worker(new URL("./worker.js", import.meta.url), { type: "module"})

@undefined-moe

Copy link
Copy Markdown
Contributor Author

No it's a compile time error, not runtime
Bundler will try to locate this file, add it to dist, find it not there, and throw an error

@undefined-moe

Copy link
Copy Markdown
Contributor Author

that eval is to make the path dynamic, and then bundlers will skip to process this file and throw 2 warnings (eval usage, unhandled dynamic import)

@ije

ije commented Sep 11, 2025

Copy link
Copy Markdown
Member
if (new URL(import.meta.url).hostname === "esm.sh") {
  const workerUrl = new URL("./worker.mjs", import.meta.url).href
  return new Worker(workerUrl, { type: "module"})
}

does dynamic worker url work?

@undefined-moe

Copy link
Copy Markdown
Contributor Author

bundlers also handle new URL(string, import.meta.url) as it's a common way to load raw assets so bundlers will copy the original resource as-is to dist folder, the file still needs to be present

src = new URL("./icon.txt", import.meta.url)
req = await fetch(src)
content = await req.text()

@undefined-moe

Copy link
Copy Markdown
Contributor Author

Guess it will work now

@ije ije left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

thanks!

@ije ije merged commit 2bf0905 into esm-dev:main Sep 14, 2025
@ije

ije commented Sep 15, 2025

Copy link
Copy Markdown
Member

@undefined-moe i just released the changes, now neither dev nor prod build works in vite😭

@undefined-moe

undefined-moe commented Sep 15, 2025

Copy link
Copy Markdown
Contributor Author

const workerUrl: URL = new URL("./worker.mjs", import.meta.url);
// create a blob url for cross-origin workers if the url is not same-origin
if (workerUrl.origin !== location.origin) {
return new Worker(
URL.createObjectURL(new Blob([`import "${workerUrl.href}"`], { type: "application/javascript" })),
{ type: "module" },
);
}

No you can't do this, it must be new Worker(new URL(path, import.meta.url), options), in the same line, otherwise bundlers will not process this reference, and it trys to load non-exist worker scripts

@ije

ije commented Sep 15, 2025

Copy link
Copy Markdown
Member

it never reach the cross-origin condition in vite env, the new Worker(new URL(path, import.meta.url), options) return a non-existent js:
Screenshot 2025-09-15 at 23 10 05

Screenshot 2025-09-15 at 23 10 51

@ije

ije commented Sep 15, 2025

Copy link
Copy Markdown
Member

anyway, i will fix it in #27

@undefined-moe

Copy link
Copy Markdown
Contributor Author

That resource was inlined, thus workerUrl.origin is null

image image

bundlers also handle new URL(string, import.meta.url) as it's a common way to load raw assets so bundlers will copy the original resource as-is to dist folder, the file still needs to be present

src = new URL("./icon.txt", import.meta.url)
req = await fetch(src)
content = await req.text()

@ije

ije commented Sep 15, 2025

Copy link
Copy Markdown
Member

Shhhh! Any idea for fixing the cross-origin worker from CDN?

@undefined-moe

Copy link
Copy Markdown
Contributor Author

Not very sure. What's blocking it from loading from another origin?

@ije

ije commented Sep 16, 2025

Copy link
Copy Markdown
Member

i removed the cross-origin condition, new Worker(new URL(path, import.meta.url), options) still doesn't work in vite development mode, however the prod build works fine

npm i https://pkg.pr.new/esm-dev/modern-monaco@d5a7c7c

@undefined-moe

Copy link
Copy Markdown
Contributor Author

just need to add it to optimizeDeps.exclude in vite.config.ts and everything should work

@ije

ije commented Sep 16, 2025

Copy link
Copy Markdown
Member

ideally we don't need the extra optimizeDeps.exclude option. besides that, why do development and production builds behave differently?

@undefined-moe

Copy link
Copy Markdown
Contributor Author

Vite doesn't bundle inputs when starting dev server. Instead, it transforms code once requested (pretty similar to what esm.sh does), using module support built into the browser (and that's why it's dev server is super fast compared to other ones), which works pretty well under most scenarios.

And that inconsistency is the issue so they are planning to switch to full bundle mode in the future

https://vite.dev/guide/rolldown.html#why-introducing-a-full-bundle-mode

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.

2 participants