-
-
Notifications
You must be signed in to change notification settings - Fork 408
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
Introduce no-globals
optional feature
#784
Conversation
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.
Broadly, this seems great! I've left a few inline comments, but overall I'd like to see us move away from requiring the IIFE solution (in favor of providing an API more broadly that folks can leverage to do the app's own require
/define
as needed directly).
* naming global variables in a unique way, e.g. scoped to the app's name (in `package.json`), so that two different Ember | ||
apps cannot cause conflicts |
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 describe how this would work RE: ember-engines (where applicable)? I don't think ember-engines itself will have an issue here, but I could absolutely imagine specific engines needing/wanting some global coordination; should they use ${appName}-${engineName}
?
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.
Tbh I am not very knowledgable about engines. What do you mean with ${appName}-${engineName}
, does that refer to the renamed version of require
? Why would they need any special coordination?
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.
In general, folks should think of an engine as another app (in that it's self contained/isolated). I was mostly wondering if we should advise folks to scope engine related globals to the engine name or to a combination of the app name and the engine name.
Totally fine to not have an answer, I don't think we have to have concrete guidance here for that (we can make an issue over on ember-engines when this RFC merges)
* ideally also adding a non-deterministic part like a random string that changes *per build*, that is only known to | ||
privileged code (as listed above), so user-land code cannot rely on the existence of these globals. |
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 like this alot, we should provide a mechanism to actually do this conveniently (it's probably already possible to leverage @embroider/macros
's setConfig
in some combination?)
|
||
The global functions/namespaces `define` and `require` (and its aliases `requireModule`, `require`, `requirejs`) are set | ||
as globals and provided by the [`loader.js`](https://github.com/ember-cli/loader.js) addon, which is part of the default | ||
blueprint of any Ember app. Not having this addon or any equivalent replacement will break the app, as a.o. any `define()` |
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.
as a.o.
I'm sorry, I'm not familiar with this abbreviation; can you expand?
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.
Oh, it was meant to mean "amongst others", but I don't know if that is a valid abbreviation at all. 😅. Will expand that phrase...
Despite that, Ember comes with its own (stripped down) [loader library](https://github.com/emberjs/ember.js/blob/master/packages/loader/lib/index.js), | ||
that has been used (pre Ember 3.27) internally only. As of Ember 3.27 it is effectively unused when `loader.js` is present | ||
(which it has to be, see above), making it dead code. |
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.
FWIW, I think we can remove https://github.com/emberjs/ember.js/blob/master/packages/loader/lib/index.js completely now (as of Ember 4.0.0).
<summary>vendor.js</summary> | ||
|
||
```diff | ||
+ (function(){ |
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 think we actually want to wrap all of vendor.js
in an IIFE here, I'd much prefer to change Babel compilation to use the alternate function names (and still have them dynamic by app name like you suggest).
Can you explore that and see if that is plausible?
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.
But wouldn't that break any addon code that still uses e.g. require()
?
The idea here was to prevent leaking those into the global namespace, while still allowing all existing addon code that uses these things in the wild (most notably ember-resolver
) to still work. That is as long as that addon code is processed through the normal Ember CLI build process, i.e. it lands within the IIFE. When you call require
from somewhere else like an external <script>
, that would cease to work, hence the optional feature to allow this "intimate API" to break in such rare cases.
Note that I am referring to addons directly calling require()
etc., like here, and not regular ESM imports that happen to be transformed to define
/require
during the build.
Or do you suggest to transform any addon code that uses require()
directly to refer to the renamed version of 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.
Also note that I mentioned under "Alternatives" that we could also drop all support of user-land code calling the AMD functions. Which would be more invasive, breaking some addon's code. Still no breaking change, as that is opt-in only.
Then indeed we don't need that IIFE nor any addon code transformations...
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.
But wouldn't that break any addon code that still uses e.g. require()?
Yes 😈 , but that's the point of creating an optional feature anyways (it's opt-in, we are allowed to limit semantics).
The idea here was to prevent leaking those into the global namespace, while still allowing all existing addon code that uses these things in the wild (most notably ember-resolver) to still work. That is as long as that addon code is processed through the normal Ember CLI build process, i.e. it lands within the IIFE. When you call require from somewhere else like an external <script>, that would cease to work, hence the optional feature to allow this "intimate API" to break in such rare cases.
Ya, I think we should provide a global that is app scoped (e.g. globalThis._appNameGlobals.require
and globalThis._appNameGlobals.define
) instead of continuing to use define
/require
unscoped. This would require some tweaks to the babel setup (e.g. we'd need a custom version of https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-modules-amd/src/index.ts or send a PR to allow the define
/require
hard coded constants to change).
Note that I am referring to addons directly calling require() etc., like here, and not regular ESM imports that happen to be transformed to define/require during the build.
I actually think this case is not a good example. Specifically because that is not using require
global, it is using import require from 'require';
:
^ pattern should still continue to work just fine (because whatever loader happens to be running, will respond to that import with the correctly scoped require
).
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.
Yes 😈 , but that's the point of creating an optional feature anyways (it's opt-in, we are allowed to limit semantics).
Yes, which is basically what I say here, right? 👇
Also note that I mentioned under "Alternatives" that we could also drop all support of user-land code calling the AMD functions. Which would be more invasive, breaking some addon's code. Still no breaking change, as that is opt-in only.
At least when they are called as globals. Which the current form of the RFC still tries to support...
So to get this 100% clear: you are suggesting that we do this alternative approach, i.e. stop supporting the global use of require()
& Co as globals all together, right?
This does mean that we need to "fix" a lot more of the core addons, like ember-resolver
, which AFAICT does use require()
as a global here
I actually think this case is not a good example. Specifically because that is not using require global
Oh, you are right, I missed that! It's not so easy to search for examples of addons doing require()
using emberobserver's code search, given that you get a bazillion hits for legit use of require()
in node-land... 🙈
pattern should still continue to work just fine (because whatever loader happens to be running, will respond to that import with the correctly scoped require)
While that is possible, I wonder if we actually want that. Probably out of scope for this RFC, but shouldn't we eventually try to get rid of any usage of loader.js stuff at all?
Like in a future where we have Polaris and Embroider by default, shouldn't we tell users that the only way to import stuff is via ESM imports (and @embroider/macros
)? So that we can eventually get to a point where we could get rid of any specific runtime module system like loader.js, when we have just ESM imports at stage 3, then throw that at webpack, which then emits whatever it emits.
So maybe turning on no-globals
is actually a good moment to make all of require()
stop working for unprivileged code?
into account. Implementation wise this can happen in the same way as described above, by defining `EmberENV` within the | ||
IIFE (and not using the explicit `window.` namespace). |
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 would be better to have Ember leverage @embroider/macros
getConfig
or getOwnConfig
inside Ember's own code where we currently read window.EmberENV
instead of leveraging the IIFE style.
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.
It seems fine to still support reading from window.EmberENV
even in no-globals mode? Otherwise we need to propose something for the Ember Inspector to enable the debug render tree.
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.
Actually, come to think about it, I think the inspector in its current form/architecture will most likely just completely break in no-globals mode as it relies on being able to get access to the things you are trying to no leak with this RFC. Probably not a good enough reason to hold this back, but it's definitely a drawback that should be noted. We can overhaul the inspector in theory but it will require more design than is in this RFC currently, and it's probably not fair to have this RFC solve that problem, plus I am not sure if anyone has time to work on a big inspector project. In the short/medium term, I think the inspector will probably just not work with no-globals
mode. Is that an acceptable drawback?
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.
(cc @rwwagner90)
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.
Oh, that's a good point! I didn't think about that. Certainly needs to be mentioned. I don't know enough about the Inspector to assess how much needs to change to support no-globals
. I guess all those possible ways to coordinate between those privileged tools what the scoped version of require()
& Co are (e.g. getConfig
) will not work for the Inspector, as it is a standalone app that does not take part in the build system of the app...
Maybe there still could be some escape hatch like defining window.__ember_scoped_globals_only_for_inspector_dont_ever_use_this_in_user_code = 'myEmberApp_somerandomhash'
, when the app has require
(and define
) scoped as window.myEmberApp_somerandomhash.require
?
Is that an acceptable drawback?
I would still say yes. You can still define the flag conditionally, like enable it for test and prod, while disabling it for dev!
See my other inline comment about this. The idea was to let any direct use of
Not sure I get what you mean here. Who is "folks"? Like I would see user-land code to only use ESM, and eventually Or do you refer to what I called the privileged addons, so they use some private API to get references to define/require? But again, this would require either breaking user-land code, or transforming it also. The IIFE was meant to let user code continue to work, without transformation... Which brings me to the overarching question of how deep we should start to dig into the implementation details of this RFC. I did all this IIFE exploration only to prove that it's feasible to make this work somehow (I literally changed the Or do you think the RFC must specify the actual implementation path? |
Ya makes sense! I think I agree with you that 99.9% of users won't have to ever care what we name the app specific globals (from my comment above that might be
I think that we need to explain enough of how it will work for folks to be able to have a mental model. In my mind that "simple" mental model is that when you opt-in to the optional feature, we don't use |
Can you give an example of a legit use case where you would absolutely need this, and cannot express this in terms of regular imports or the If we make this possible for unprivileged code, aren't we then basically introducing a new public API, that we need to support? Or at least kind of replacing one intimate API with another one? See also my other inline comment about whether to support
Speaking of the implementation detail of how to get hold of the scoped version of |
It looks like this still has a good bit of interest so I'll see what I can do to keep it moving forwards. |
I think the framing of this RFC is too focused on what not to do ("don't use globals") when the problem would also go away if we clarify what to do instead. I would rather solve the same problems by clarifying exactly how does one boot an Ember app, starting from only web standards like ES modules. It's a problem that our boot process is currently implementation-defined. This RFC is about changes in that implementation. But the implementation details should be invisible and shouldn't require an RFC at all! We need to get the implementation details behind public API and web standards. |
I'm interested to hear what standards you might have in mind. I haven't seen any for "how to boot an application within the DOM." The closest things I can think of are
In any case, there would have to be some non-module entrypoint JS ( |
Why non-module? I suspect it can be all modules all the way down.
Something like:
|
You're absolutely right.
This is very similar to the single-spa API. Each application is defined as a single top-level module that exports They are registered with the application loader with this API: registerApplication({
name: '@organization/app2',
app: () => import('/src/app2/main.js'),
activeWhen(location) => location.pathname.startsWith('/app2'),
customProps(appName, location) => ({}),
); or the HTML version of the same: <script type="importmap">
{
"imports": {
"@organization/app2": "/src/app2/main.js"
}
}
</script>
<route path="/app2">
<application name="@organization/app2"></application>
</route> |
Sure, I fully agree. But the goal of this RFC was not a full-scale major change, but rather a first stepping stone towards that future, like making the AMD implementation not leak into the outside world, while still preserving compatibility inside the Ember world. The use of AMD leaks so much into the ecosystem, that it is not really an implementation details as it should, but rises to the point of being a public API, that's why I introduced a first opt-in feature to scale that down by making it invisible from the outside world. I did also have in mind to maybe deprecate it as part of this RFC, but chose to leave that for a later RFC. Having said that, I don't think it makes much sense to keep working on this RFC at this point, so I will close this in favor of #938, which I think goes far beyond what is proposed here, so that stepping stone is not really needed anymore. Even what that RFC might not cover the use cases brought up here (idk, does it?), and when to cover those we still need another RFC, it probably makes sense to define that one on top of #938. |
Rendered
For the record: there has been an early draft and some discussion around it here: #781. Special thanks to @bendemboski for contributing a lot of details about the ember-electron use case!