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

[feature request] Cross domain blob building fallback #36

Open
mizchi opened this issue Aug 3, 2019 · 13 comments
Open

[feature request] Cross domain blob building fallback #36

mizchi opened this issue Aug 3, 2019 · 13 comments
Labels
enhancement New feature or request

Comments

@mizchi
Copy link

mizchi commented Aug 3, 2019

I tried to publish 3rd party script with webpack and worker-plugin.

https://cdn.example.com/main.js <- entry
https://cdn.example.com/sub.js <- chunk
https://cdn.example.com/0.worker.js <- worker

I set output.publicPath to "https://cdn.example.com/" in this case;

But I can not exec worker because of cross domain restriction.

> new Worker("http://localhost:8080/0.worker.js")
VM84:1 Uncaught DOMException: Failed to construct 'Worker': Script at 'http://localhost:8080/0.worker.js' cannot be accessed from origin 'https://www.google.com'.

I know this fallback works to avoid it.

// ASSET_HOST="https://cdn.example.com/" webpack --mode production
if (process.env.ASSET_HOST === location.protocol + "//" + location.host) {
    return new Worker(process.env.ASSET_HOST + "0.worker.js")
} else {
    const code = await fetch(process.env.ASSET_HOST + "0.worker.js").then(res =>
      res.text()
    );
    // console.log(t);
    const blob = new Blob([code], { type: "text/javascript" });
    const url = URL.createObjectURL(blob);
    return worker = new Worker(url);
}

but publisher need to add CORS header to fetch. (Most CDN have CORS header)

I will fork and try it at first in my hand.

@developit
Copy link
Collaborator

developit commented Aug 13, 2019

Note for folks finding this: this is a proposal, inline doesn't yet exist

This isn't because of CORS, but rather because sites like google.com tend to disallow all subresources using something like a Content Security Policy. The issue with CSP is that most configurations you find in the wild also block Blob and Data URL sources, so none of these solutions would work.

CSP issues aside, I wonder if your use-case an inline Blob would work? It would be easy to add an option for this in worker-plugin. It would look something like this:

input: (your source code)

const w = new Worker('./my-worker.js', {
  type: 'module',
  inline: true  // <-- special property observed by the plugin
})

compiled output:

var w = new Worker(URL.createObjectURL(
    new Blob(["onmessage=e=>postMessage('pong')"])
));
            // ^ bundled worker code compiled into main JS as a string

@TotallWAR
Copy link

TotallWAR commented Oct 22, 2019

@developit I tried setup you suggested but it doesn't work.

const worker = new Worker('./worker.js', {
      inline: true,
      type: 'module'
});

ia have next error:
Uncaught DOMException: Failed to construct 'Worker': ..... cannot be accessed from origin......

@jackyef
Copy link

jackyef commented Jan 3, 2020

@developit Adding an inline option would be great! I came from using workerize-loader, then comlink-loader, and now trying to move to worker-plugin + comlink because the formers are no longer actively maintained. This is the only thing keeping me from moving.

@piotrblasiak
Copy link

Same here - inline mode is the one thing stopping me from moving from the seemingly unmaintained worker-loader.

@alangdm
Copy link

alangdm commented Feb 20, 2020

Does anyone have a solution for this? The inline flag doesn't seem to do anything, compiled code just ends up like this:

var s=new Worker(e,{inline:!0})

@developit
Copy link
Collaborator

developit commented Feb 21, 2020

For the folks commenting about inline not working - I was proposing that feature, it is not something WorkerPlugin currently implements.

Building transparent Blob/inline support is likely possible, but in the meantime I would suggest using something like this to patch Worker for your use-case:

function Worker(url, opts) {
  return self.Worker('data:,importScripts('+JSON.stringify(url)+')', opts);
}

new Worker("./my-worker.js", { type: "module" });

The result will be a Worker instantiated via a data URL (which will mean it has an opaque origin), where the bundled worker script is fetched via importScripts. This may help with CSP, since the request will be validated as "script-src", not "worker-src".

Honestly though, most websites that ship restrictive CSP's also disable Blob, Data URL and eval() scripts. I don't think any solution that tries to "work around" this is going to help much.

@developit developit added enhancement New feature or request question Further information is requested labels Feb 21, 2020
@alangdm
Copy link

alangdm commented Mar 2, 2020

@developit
Thanks for your answer, I think the inline feature you propose would be a great enhancement for my use case at least

I tried using the workaround you mentioned but that still couldn't bypass the error

Blobs apparently bypass it successfully though, I've been using worker-loader's blobs so far and they work just fine

But as some of the other comments here mention, if possible, I would rather drop the unmaintained worker-loader for this plugin so it would be amazing if the inline feature became a reality

@developit
Copy link
Collaborator

developit commented Mar 9, 2020

@alangdm try this version:

function Worker(url, opts) {
  var blob = new Blob(['importScripts('+JSON.stringify(url)+')'], { type: 'text/javascript' });
  return self.Worker(URL.createObjectURL(blob), opts);
}

new Worker("./my-worker.js", { type: "module" });

The issue I have with the inline option I proposed above is that "inline" isn't spec'd anywhere and only works if bundled. It breaks the premise of this plugin, which is that it transparently bundles Module Workers.

@alangdm
Copy link

alangdm commented Mar 10, 2020

@developit
I tried doing it as you said but got an error regarding the usage of new, I changed it slightly and the closest I got was by doing this:

export function Worker(url, opts) {
  var blob = new Blob(["importScripts(" + JSON.stringify(url) + ")"], {
    type: "text/javascript"
  });
  return new self.Worker(URL.createObjectURL(blob), opts);
}

new Worker("../workers/my.worker.js", {
  type: "module"
});

Which threw this error in Chrome:

Uncaught TypeError: Failed to execute 'importScripts' on 'WorkerGlobalScope': 
Module scripts don't support importScripts().

Doing it like this also didn't seem to be bundling the worker at all though 😢

The issue I have with the inline option I proposed above is that "inline" isn't spec'd anywhere and only works if bundled. It breaks the premise of this plugin, which is that it transparently bundles Module Workers.

To be honest I agree with you on this, I don't really like that kind of non-standard syntax, but Blobs seem to be the only option for use cases like mine and some of the other people who commented before me

@alangdm
Copy link

alangdm commented Mar 10, 2020

@developit
You can pretty much ignore my last comment, I managed to get this working, thanks a lot!! 💯

The important steps are:
Add the following to a script that's directly on the html:

(function() {
  var _Worker = window.Worker;
  window.Worker = function (url, opts) {
    var blob = new Blob(["importScripts(" + JSON.stringify(url) + ")"], {
      type: "text/javascript"
    });
    return new _Worker(URL.createObjectURL(blob), opts);
  }
})();

And on the code actually getting bundled just use it as normally recommended:

new Worker("./my-worker.js", {
  type: "module"
});

(My problem on the last comment was that I was adding the fix as part of the bundled code, that didn't go well)

@maksnester
Copy link

I didn't make it work from the first time, so just wanted to clarify that it's not necessary to add that script directly to HTML. It's a hack where you just replace native Worker with your own implementation (with importScript that won't suffer from CORS). So it's ok to just have that in your code:

const _Worker = window.Worker;
window.Worker = function (url, opts) {
  const blob = new Blob(["importScripts(" + JSON.stringify(url) + ")"], {
    type: "text/javascript"
  });
  return new _Worker(URL.createObjectURL(blob), opts);
}
// worker-plugin magic still works, 
// but now you can use CORS, you can specify webpack's publicPath pointing to CDN
new Worker("./my-worker.js", {
  type: "module"
});
window.Worker = _Worker // put it back to not break any other worker usage

@developit
Copy link
Collaborator

developit commented Aug 11, 2020

FWIW I'd be open to adding an option to worker-plugin that outputs the shimmed code.

Better yet, an option to specify "here's how to get the Worker constructor", like:

// to use Node worker_threads:
new WorkerPlugin({
    workerConstructor: `require('web-worker')`
})

// to use the importScripts workaround for CORS/preload:
new WorkerPlugin({
    workerConstructor: `(function(u,o){return new Worker(URL.createObjectURL(new Blob(['importScripts('+JSON.stringify(u)+')'])),o)})`
})

@finalljx
Copy link

@developit You can pretty much ignore my last comment, I managed to get this working, thanks a lot!! 💯

The important steps are: Add the following to a script that's directly on the html:

(function() {
  var _Worker = window.Worker;
  window.Worker = function (url, opts) {
    var blob = new Blob(["importScripts(" + JSON.stringify(url) + ")"], {
      type: "text/javascript"
    });
    return new _Worker(URL.createObjectURL(blob), opts);
  }
})();

And on the code actually getting bundled just use it as normally recommended:

new Worker("./my-worker.js", {
  type: "module"
});

(My problem on the last comment was that I was adding the fix as part of the bundled code, that didn't go well)

this works for me, with protocal. url =location.protocol + url;

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

No branches or pull requests

8 participants