rfcs
rfcs copied to clipboard
Introduce `no-globals` optional feature
For the record: there has been an early draft and some discussion around it here: https://github.com/emberjs/rfcs/issues/781. Special thanks to @bendemboski for contributing a lot of details about the ember-electron use case!
overall I'd like to see us move away from requiring the IIFE solution
See my other inline comment about this. The idea was to let any direct use of require() continue to work...
(in favor of providing an API more broadly that folks can leverage to do the app's own require/define as needed directly).
Not sure I get what you mean here. Who is "folks"? Like I would see user-land code to only use ESM, and eventually @embroider/macros (like importSync()) for the special cases. But not expose anything new here as a public API for users.
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 /dist code by hand to see that the approach works), while intentionally stating in the RFC that those details are not normative. The TLDR of the publicly visible changes for users is basically: there is a new optional feature, that when enabled prevents leaking globals. For users everything should continue work, except for some exotic edge cases (code that is not processed through the normal build pipeline and which is relying on those globals). Everything else are implementation details.
Or do you think the RFC must specify the actual implementation path?
Not sure I get what you mean here. Who is "folks"? Like I would see user-land code to only use ESM, and eventually @embroider/macros (like importSync()) for the special cases. But not expose anything new here as a public API for users.
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 globalThis._appName.require/globalThis._appName.define), but I do think it should be possible for that 0.01% case to be written. However, I think my other comments address this as well: users in that edge case can leverage getConfig to find out the scoped property name.
Which brings me to the overarching question of how deep we should start to dig into the implementation details of this RFC.
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 define/require globals at all and instead we put those in a scoped globals bucket for the app itself (like I mentioned above). We don't have to get into all the places that will need to be updated/fixed for that to work though.
but I do think it should be possible for that 0.01% case to be written.
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 importSync macro?
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 import require from 'require'...
users in that edge case can leverage getConfig to find out the scoped property name
Speaking of the implementation detail of how to get hold of the scoped version of define and require, are you sure we actually need a "runtime" (in the sense that it goes into app code) thing like getConfig? I was rather thinking about something that is available for the build system, i.e. in node.js. So something that you could use for example in ember-auto-import here.
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.
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
- Web Components, which defines
constructor,connectedCallback,disconnectedCallback,adoptedCallback, andattributeChangedCallbackas the lifecycle API. I think it's architecturally elegant to think of a whole Ember application as a single web component, though there are likely technical limitations to the approach. We could adopt the lifecycle API without actually making the Ember application a true Web Component, setting the stage for a standards-trackWebComponentLifecycleinterface. - iframe -- I'm certainly not proposing that Ember applications be forced into iframes, but the iframe is a standard unit of web composability and exposes known lifecycle APIs like
DOMContentLoadedandBeforeUnloadEvent. - The single-spa application lifecycle, which defines
bootstrap,mount, andunmountas the lifecycle API. single-spa isn't a web standard, but it is reasonably popular.
In any case, there would have to be some non-module entrypoint JS (<script type="text/javascript">) to kick things off. Everything after that could be a <script type="module">.
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.
I'm interested to hear what standards you might have in mind.
Something like:
- in an ES module
- import a particular function from ember and call it with arguments that include
- an HTMLElement to render into
- some description of the ES modules that define your application, so that it can load them when it want to
- and maybe something like a Location that mediates all access to the current URL.
Why non-module? I suspect it can be all modules all the way down.
You're absolutely right. <script type="module" src="/ember-entrypoint.js"> works great. I should have said there has to be a <script> tag to kick things off; everything else can be imported by URL or importmap.
Something like…
This is very similar to the single-spa API. Each application is defined as a single top-level module that exports { boostrap, mount, unmount }.
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>
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.
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 https://github.com/emberjs/rfcs/pull/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.