-
Notifications
You must be signed in to change notification settings - Fork 22
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
fix(cloudflare): make sure not fallback to globalThis
for env
#183
Conversation
Can you please update the polyfill to explictly override nextTick impl? (env implementation is more generic in unenv. we can explicitly add any more override in the future that needed) Also would it make sense to have a generic polyfills/cloudflare file that adds more of future polyfills via one entry? |
Great suggestions, updated! Thanks for the speedy response time π. |
src/runtime/polyfill/cloudflare.ts
Outdated
import { nextTick } from "node:process"; | ||
import { process as _process } from "../node/process/_process"; | ||
|
||
Object.assign(_process, { nextTick }); |
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.
Another suggestion: Considering this effectively uses a wrapper around global queueMicrotask
in worked, would it make sense that we avoid node:
imports or do you see that in the future workerd might do it differently?
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.
And one more! We could also prefer (if available) queueMicrotask
in main process polyfill with an early return. This makes the fix generic for all compatible runtimes.
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.
And one more! We could also prefer (if available) queueMicrotask in main process polyfill with an early return. This makes the fix generic for all compatible runtimes.
That polyfill relies on setTimeout to schedule the work which is not the same as queueMicrotask
β microtasks take precedence over tasks, so the current polyfill will run the callbacks in incorrect order.
Is there any reason for the nextTick polyfill not to use queueMicrotask? It seems to be widely supported API: https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask
would it make sense that we avoid node: imports or do you see that in the future workerd might do it differently?
We do expect the workerd runtime to get a few more (process) APIs polyfilled over time, but more importantly this PR is an attempt to create a hybrid polyfill at a symbol level, similarly how #181 proposes it for the module level. We want to prove that this is possible, as that would then inform our overall node compat strategy.
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.
No reason main polyfill not using queueMicrotask
it was simply remaining from the legacy polyfills I used.
We can use a condition in there to use queueMicrotask
when available and eventually drop fallback if most of the runtimes support it.
We want to prove that this is possible, as that would then inform our overall node compat strategy.
If it is helpful for your testing strategy, looks good to me this way ππΌ
From #181 (comment) I was under the impression that you would want to go with process.getBuiltinModule
API and that this PR particularly can be natively improved in main unenv's nextTick
polyfill.
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 ended up importing node:process
this way because I couldn't find another way to grab the native workerd global process
in a reliable way. Is there a better way of doing it?
These all didn't seem to work:
import _global from "./global-this";
console.log(_global.process); // undefined
console.log(process); //undefined
console.log(globalThis.process); // undefined
console.log(global.process); // undefined
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.
@jculvey the issue here is that workerd doesn't expose the process object on global at all (unless you use the underutilized nodeJsCompatModule
, which we want to avoid because we are thinking of removing it).
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.
globalThis.process and node:process
seem to have the same shape however, with the exception of node:process#default
which exists to support default import.
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.
Right, my mistake. Thanks!
src/runtime/polyfill/cloudflare.ts
Outdated
import { nextTick } from "node:process"; | ||
import { process as _process } from "../node/process/_process"; | ||
|
||
Object.assign(_process, { nextTick }); |
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.
And one more! We could also prefer (if available) queueMicrotask in main process polyfill with an early return. This makes the fix generic for all compatible runtimes.
That polyfill relies on setTimeout to schedule the work which is not the same as queueMicrotask
β microtasks take precedence over tasks, so the current polyfill will run the callbacks in incorrect order.
Is there any reason for the nextTick polyfill not to use queueMicrotask? It seems to be widely supported API: https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask
would it make sense that we avoid node: imports or do you see that in the future workerd might do it differently?
We do expect the workerd runtime to get a few more (process) APIs polyfilled over time, but more importantly this PR is an attempt to create a hybrid polyfill at a symbol level, similarly how #181 proposes it for the module level. We want to prove that this is possible, as that would then inform our overall node compat strategy.
src/runtime/polyfill/cloudflare.ts
Outdated
@@ -0,0 +1,4 @@ | |||
import { nextTick } from "node:process"; | |||
import { process as _process } from "../node/process/_process"; |
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.
can you add a comment here about the issue with importing ../node/process/import
directly? it might be tempting for someone to refactor this in the future and run into the issues with messed up bundling we saw and can't explain.
src/runtime/polyfill/cloudflare.ts
Outdated
@@ -0,0 +1,4 @@ | |||
import { nextTick } from "node:process"; |
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.
didn't you want to import the whole module and spread it? or would that introduce some additional properties on process that should not be on the global? (e.g. default`).
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.
ah.. I see that @pi0 requested this change. I think we could keep as is then, and we can reconsider once there are more keys we'd like to use from the native layer.
However, I'm not quite sure about the process.env
implementation in unenv:
unenv/src/runtime/node/process/_process.ts
Lines 160 to 187 in bf79a17
const _envShim = Object.create(null); | |
const _processEnv = globalThis.process?.env; | |
const _getEnv = (useShim: boolean) => | |
_processEnv || globalThis.__env__ || (useShim ? _envShim : globalThis); | |
process.env = new Proxy(_envShim, { | |
get(_, prop) { | |
const env = _getEnv(); | |
return env[prop] ?? _envShim[prop]; | |
}, | |
has(_, prop) { | |
const env = _getEnv(); | |
return prop in env || prop in _envShim; | |
}, | |
set(_, prop, value) { | |
const env = _getEnv(true); | |
env[prop] = value; | |
return true; | |
}, | |
deleteProperty(_, prop) { | |
const env = _getEnv(true); | |
delete env[prop]; | |
}, | |
ownKeys() { | |
const env = _getEnv(); | |
return Object.keys(env); | |
}, | |
}); |
It comes across to me as very convoluted and potentially incorrect as reads and write target different underlying storage. @pi0 can you shed some light on why the env polyfill works this way? it might be the best to add a comment into that code to explain the reasoning.
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.
This allows Nitro users to access variables using process.env[key]
(this is not normally possible via cloudflare edge). __env__
is injected in nitro's cf entrypoint.
I would be happy to explain more later if you wanted and see how to improve it :)
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 see. I think I understand how nitro usesglobalThis.__env__
and I don't mind that part, but what I find problematic is the fallback on globalThis
which is used as the fallback here:
If I'm reading the code right, then n an environment like workerd, we'd end up reading globalThis.foo
when someone tries to access globalThis.process.env.foo
- this is not desirable for us.
I'm also puzzled about these two lines in get
and has
:
unenv/src/runtime/node/process/_process.ts
Line 168 in bf79a17
return env[prop] ?? _envShim[prop]; |
unenv/src/runtime/node/process/_process.ts
Line 172 in bf79a17
return prop in env || prop in _envShim; |
Since these two lines prefer env
over _envShim
, this means that any assignments or deletions performed on process.env will not be observable/readable if the key is present on the original env
. This seems like a bug to me.
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.
If I'm reading the code right, then in an environment like workerd, we'd end up reading globalThis.foo when someone tries to access globalThis.process.env.foo - this is not desirable for us.
Indeed and it was because of cloudlare service-worker format support actually. As far as I remember it was a requirement for the cloudflare (legacy) sw-worker format that bindings (env vars) were accessible via globalThis.*
. (docs)
I am up to avoid this and probably workaround in nitro legacy worker preset but it needs a major version of unenv
or at least waiting for a minor version of Nitro to allow this.
A quick solution would that we can set globalthis.__env__
to an empty object in polyfill/cloudflare.ts
since it targets new formats, basically disables the legacy behavior without breaking changes.
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.
Indeed and it was because of cloudlare service-worker format support actually. As far as I remember it was a requirement for the cloudflare (legacy) sw-worker format that bindings (env vars) were accessible via globalThis.*. (docs)
fascinating! looks like we've made a full circle here. I actually thought of the idea to initialize globalThis.__env__
but wanted to understand the motivation behind the the fallback on globalThis. Give that it exists to support the legacy service worker format of cloudflare workers, I am fairly sure that it's safe to stop supporting that in unenv and nobody will notice. If we work around this just in the cloudflare preset, the same set of people (if they do exist afterall) would be affected, so I'd rather take the risk and clean this up for everyone.
btw do you have any thoughts on the bug I pointed out in the last paragraph of my previous comment?
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.
nitro has not yet adopted cloudflare
preset of unenv so it won't affect legacy users π€πΌ
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.
regarding issue seems valid (and honestly this polyfill is hacky anyway), i guess as long as we have __env__
it will use consistent source right?
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.
BTW there is almost same impl in std-env (standalone util). We can probably test different scenarios better there using vitest and backport to unenv. (I plan to merge at some point in the future)
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.
how about this approach: #186
Created #184 for On this PR, I would suggest to introduce Using |
I addressed this in #183 (comment)
if we resolve the process.env issue as suggested above, we might not need to import so I think the last remaining thing here is to resolve the two issues I broght up with the current process.env polyfill (1/ removal of globalThis fallback 2/ swapping the order in read operations). |
Update: I've changed this PR so that the polyfill simply sets |
globalThis
for env
π Linked issue
β Type of change
π Description
π Checklist
Adds a polyfill for
process
to the Cloudflare preset which combines the node polyfill with the workerd polyfill implementation. We'd specifically like to retain the polyfillednextTick
from workerd which usesqueueMicrotask
, but benefit from all the other globalprocess
polyfills provided by unenv.