This API removed since 0.32, now globalThis.Deno in worker returns undefined.
That's right, we're striving to have Web compatible Worker API. Deno global namespace is a Deno specific feature so it should be available in workers by default.
After structured clone is done and landed we might want to add something like new Deno.Worker() which will be a web worker but with Deno namespace, ops, resources, etc.
The deno it self is a environment Web compatible and has more one build-in module named Deno. We can run normal js code without using Deno in deno, a global name will not hinder anything. So I think remove it for Web compatibility is unnecessary.
What's more, two different environments will increase the maintenance cost. And the worst, this make it possible to make Deno.Worker not Web compatible, and has a different APIs from Worker.
I do personally prefer an option to expose Deno namespace on Worker creation, like new Worker(..., { type: "module", withDenoNamespace: true }) or similar (extra option is not a violation of Worker spec anyways). Also the ability to share resource table can be an important feature for Deno to have. (I wonder if recent decisions to shift things to use Rc<...> is making this harder).
This allows code to be run also in the browser. The browser would just ignore unrecognized options, and the worker script can still do the globalThis.Deno check.
Besides Deno global namespace, import .ts, .json, using .ts files for workers are Deno specific features too. If we need a strictly Web compatible Worker API. We should disable all of them. Why disable one but enable others?
Web API will change continually, We need a way to detect the version of deno.
There will always be problems like this #4342
@Fenzland how does client side code detect which browser it鈥檚 running on?
@Fenzland how does client side code detect which browser it鈥檚 running on?
@ry we can detect browser version via parsing navigator.appVersion. The globalThis.navigator in worker is an instanceof WorkerNavigator.
But in deno, as a part of BOM, it's undefined.
A possible use case of globalThis.Deno in workers is the possibility of using Deno.plugin ;)
Currently if a plugin requires a lot of resources it completely blocks the main thread.
Our plan is that new Worker() should correspond exactly to web workers, with the idea people might use this for sandboxing untrusted code.
We'd like to add a new interface (name TBD) that would allow spawning a worker that contains the Deno namespace. Maybe something like Deno.newWorker().
Edit: Oops I realize @bartlomieju already explained this above https://github.com/denoland/deno/issues/3998#issuecomment-586295859
I would be +1 for new Deno.Worker(), not sure what Deno.newWorker() would be... Deno.createWorker() would be a factory, but factories, good or bad, are limited in JavaScript (though they are common in the DOM APIs 馃し鈥嶁檪 ).
When we say 'Worker', we say an isolated thread that do some works, usually heavy works standalone. If we need a sandbox, why not just design as new SandBox()?
But as a user, I will not care about the API name, but care about there is an API to use. Waiting for the Worker with Deno that worth to me.
If we need a sandbox, why not just design as
new SandBox()?
Because isn't based on any common standard in runtime engines. But people aren't talking about a sandbox here anyways, they are talking about a worker with the Deno namespace. As explained, that is coming, but the default new Worker() will always be without the Deno namespace.
@bartlomieju @ry thinking more about this, instead of adding another API to the namespace, especially one that might make the code less portable if the worker itself actually effectively detects Deno ns and manages it, is extending the options. Per the specification, excess keys in the options are ignored, so it is totally safe to do something like:
const worker = new Worker("./foo.js", { type: "module", denoNs: true });
I was thinking if we add an option to the Worker constructor, we could also provide a way for users to define stricter permissions for the worker code?
new Worker('./foo.js', { type: 'module', denoPermissions: {
denoGlobal: true,
allowRead: false,
allowNet: true,
});
The default value could either be inherited from main thread, or false if we want a more sandboxy approach.
const worker = new Worker("./foo.js", { type: "module", denoNs: true });
I second this. Extra options are also what I originally went for.
I will prefer extra options more than two constructors too. That can prevent the worst future need to worry about: code can run in Worker cannot run in Deno.Worker, can run in Deno.Worker cannot run in Worker. Just a tiny little bit difference will cause this.
By the way, as a web compatible Worker, I think the globalThis.navigator instance of WebNavigator is necessary. How do you think
I was thinking if we add an option to the
Workerconstructor, we could also provide a way for users to define stricter permissions for the worker code?new Worker('./foo.js', { type: 'module', denoPermissions: { denoGlobal: true, allowRead: false, allowNet: true, });The default value could either be inherited from main thread, or
falseif we want a more sandboxy approach.
It looks good on the surface, but there are so many options we could potentially provide that they will dwarf standard workers options (there are 3 of them: type, credentials and name). Let me show it on example
new Worker("./foo.js", {
type: "module", // required argument
name: "My worker"
// Deno specific arguments
denoNs: true,
allowRead: true,
allowWrite: ["./some/dir", "./some/other/dir"],
allowNet: true,
allowEnv: true,
seed: 115,
importMap: "./path/to/import/map.json",
});
Running such worker outside of Deno doesn't make sense and won't work anyway (because of missing Deno namespace).
I will prefer extra options more than two constructors too. That can prevent the worst future need to worry about: code can run in Worker cannot run in Deno.Worker, can run in Deno.Worker cannot run in Worker. Just a tiny little bit difference will cause this.
I don't see a reason why would that happen. If you don't use Deno namespace in your worker code it's guaranteed to work in Worker API. If you do use Deno namespace, then there's no way to run that code in browser's Worker API so why should it work in Deno's Worker API?
I don't see a reason why would that happen. If you don't use
Denonamespace in your worker code it's guaranteed to work inWorkerAPI. If you do useDenonamespace, then there's no way to run that code in browser'sWorkerAPI so why should it work inDeno'sWorkerAPI?
One scenario I was thinking about is that some workers might be optionally writing logs to filesystem (e.g. as a backup storage) if Deno namespace is available, but can still work with a pure in-memory storage. In that sense, if we extend Worker options, the script works just fine on both Deno and the browser since the browser can simply ignore extra options, and the user only need to write a single line for worker creation instead of 2 (with Deno.Worker under condition). This feels in some way like the main worker we have in Deno: modules check if Deno namespace is present to do different behavior.
We can probably restrict Deno specific options as a field on worker options, such that it won't become too messy.
new Worker("./foo.js", {
type: "module", // required argument
name: "My worker",
deno: {
ns: true,
perm: { ... },
},
});
Running such worker outside of Deno doesn't make sense and won't work anyway (because of missing
Denonamespace).
No, not if your worker fails sensibly if the namespace isn't there. How else would anyone write isomorphic code for Deno and a browser?
const isDeno = "Deno" in globalThis;
if (isDeno) {
/** do Deno something **/
} else {
/** it isn't Deno **/
}
We can probably restrict Deno specific options as a field on worker options, such that it won't become too messy.
馃憤 and hey, we can type it as well so people have easier friction in using it. 馃槺
I don't see a reason why would that happen. If you don't use
Denonamespace in your worker code it's guaranteed to work inWorkerAPI. If you do useDenonamespace, then there's no way to run that code in browser'sWorkerAPI so why should it work inDeno'sWorkerAPI?
There are not only two type of codes that "use Deno" or "not use Deno" on the Earth. There is a third type of codes called "library", that may run in Worker and Deno.Worker both. If there is only one environment with two status: "with Deno" and "without Deno", library authors can just leave the Deno away and write general code (maybe need to check navigator to detect browser or deno sometimes). If there are two environments, there may be some unexpected differences.
In my opinion, more APIs then standards is not a breaking of standards, but missing some APIs is. But it seems few that agree with that. If we are avoiding any additional API than standards. We can just add the options in Deno.Worker.
new Deno.Worker('./foo.js', {
...
deno: {
ns: true, // true as default
perm: {...}, // same as outside as default for each
// perm: false, // for deny all
},
});
And the global Worker, for compatiblity, just use the same environment with specific options.
function Worker(path, options={}) {
return new Deno.worker(path, {...options, deno: {ns: false, perm: false}});
}
Done in #4784
Most helpful comment
I was thinking if we add an option to the
Workerconstructor, we could also provide a way for users to define stricter permissions for the worker code?The default value could either be inherited from main thread, or
falseif we want a more sandboxy approach.