-
Notifications
You must be signed in to change notification settings - Fork 128
Contexts: supporting new Node's ESM Loader (without hacking) #33
Comments
Nodes esm loader is still very much in flux and may change significantly
before stabilizing. Just want to give a heads up
…On Fri, Mar 30, 2018, 1:54 PM Saleh Abdel Motaal ***@***.***> wrote:
- *Version*: Electron 2.0.0-beta.x
- *Platform*: macOS 10.13
- *Subsystem*: electron/node
After over a month of testing all aspects of Node's new loader
infrastructure in electron, only one issue in my opinion remains to be
resolved and in my best estimation it needs to be in electron/node.
*Setup*: (steps to use Node's loader)
// Construct
const Loader = process.NativeModule.require('internal/loader/Loader');
const loader = new Loader(`file://${__dirname}`);
// Configure (abstract)
loader.hook({
resolve(specifier, referrer, resolve) {
// Custom resolution logic …
// return {url: ..., format: ... }
// Or defer to default resolver
return resolve(specifier, referrer);
}
});
// Import a module
loader.import('./module.js', `file://${__filename}`);
*Problem*
// Import a module (works the first time)
loader.import('./module.js', `file://${__filename}`);
// Re-Importing a module fails *** throws ***
loader.import('./module.js', `file://${__filename}`);
The loader's internals are not designed to work in multi-context
environments. Both Electron and NW.js present the same issue where the
loader associates the "evaluated" namespace of the module to the current
node context which seems to not behave the same here as it does in node.
The end result is that a module will load fine on first import where the
loader holds a direct reference to it, but as soon as it is imported again
(if even on the next line) then the loader uses the stored reference in the
module map expecting it to point to the context-bound instance of the
module.
Unlike Node, in both Electron & NW.js, this instance would be undefined.
*Workaround*
It takes two ugly but effective steps (all kosher and pure JS). 1.
Override the loader's import method so that it keeps a reference of the
first instance of the module on the instance of the module wrap. 2. At the
same time, make sure to always create a separate loader instance per
context (in Electron, I create one for main, and one in preload which is
reliable both in preload as well as the associated window).
*Recommendation*
How Electron or NW.js can best go about resolving this depends on their
multi-context architectures, and best left to those more knowledgeable.
However, from all my research and testing, I am almost certain that
correctly associating contexts with modules is somewhere in
src/module_wrap.cc.
I believe that Node's loader will a little more effort to make it ideal in
Electron. If you are looking into it, I am very interested and would be
happy to exchange recipes regarding other related obstacles. As it stands,
the loader is very stable if you know what to expect, and it takes a lot of
patching. I managed to do it all in JS and without any custom rebuilds. If
this will eventually become mainstream, I honestly think some effort will
be needed to properly support it by the platform.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#33>, or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAecV9Qco40iRt6xLLcVDkAIcTBf76ntks5tjnFGgaJpZM4TB9eI>
.
|
That is very true, many aspects are in flux, especially with the more recent additions like dynamic imports and import.meta (other issues like what specifiers or packages ought to be or not are more of a backdrop from my perspective until the community works those out). As it stands, Electron 2.0.0-beta.x deals only deals with the more stable aspects of the loader, ie static imports (Node 8.x and Chrome 61). Would it not be a safe bet to assume that the implementation is rather stable with that regard. |
It is surprising to me that it does not work, in Electron the Node environment is created on the context of web pages, so normally we should have Node code and user code run in the same context. I'll need to look into the internals of the new loader to know better. |
/cc @bmeck who can chime in on the internals |
I know that Chrome ties their loader Module Map to Context data using That said, I would heavily encourage not using it while it is still flagged and subject to change. |
fwiw i can't reproduce any context-related crashing/errors with ModuleWrap. as i know it the only thing that should fail is dynamic import which we currently lock to the main context. +1 to not using node's loader at all for the moment. @SMotaal if you could let me know what the specific error you got is that would be pretty helpful :) |
@devsnek I created a quick repo to help illustrate this (Electron-specific) issue: The same thing also happens in NW.Js (maybe for different reasons) but I am not able to repo that at this point. The demo simply creates a loader in each context (main, preload and window) then imports the same module twice (with fallback to append a hash if the attempt fails). Here is a quick output from stdout:
Here is the same from the console (preload part):
Here is the same from the console (window part):
|
@SMotaal i believe this issue was inadvertently already fixed. in ModuleJob#instantiate the |
@devsnek, well, with my understanding of the standards, I make the assumption that the module evaluates once so I keep the evaluated/instantiated module promise from the first time and use it in subsequent calls to Dynamic imports down the road will not work with this (js side) fix because the context can/will be destroyed on reload while the loader is still hooked to isolate's callback, which makes it very tricky to manage especially if some other reload surprise kicks in at the same time. So I was hoping this would be stable on a ModuleWrap level (excuse my ignorance of the upstream but trust me I know ModuleWrap is not the bad guy here, thanks ModuleWrap) so that graceful loader replacement can be manageable on reload or avoidable all together (that would be super). |
@devsnek I should also not one crazy thing I noticed concurrently when this error happens, if I used setTimeout to call the |
Now that electron 3.0.0-beta.1 is out, has anyone tried doing static imports from the client html modules. Is having import syntax across the whole app achievable at this stage? |
the renderer should use chromium's loader right? this seems like a really confusing situation. |
Yeah. I think frankly this should be a flag, something you pass into the |
@pauliusuza Sorry, I needed time to update my blobbing abstraction to catch up with all the changes that happened between electron and node internals. Now that my experimental app is up and running, I can start to experiment. I am open for running some experiments between chromium's and node's loaders so please feel free to suggest scenarios. Fenced code blocks of simplified exporting/importing modules can really help minimize ambiguity — preferred over the actual modules because all I am testing is resolution paths and ES module bindings. A sample scenario is:
|
I'm following this issue, and in terms of the design concept of Electron, I also had the same impression of @zcbenz. Here's my thought. Node module eco system (npm) has been attractive, and that has been one of the major reason to use Node, and now ESM has become defacto-standard and all of the modern browsers support it including mobile browsers. Node has struggled to catch up to this new standard mostly because the own foundation is NPM, another module eco, hence, the topic is fundamentally an issue between 2 different module ecosystems . This issue has been and will be prolonged for a while because now, for some reason, ECMAScript evolves so rapidly, ecma2015, 2017, 2018, and all modern browser are eagerly catching up, and evolves fast, but Node is not. As a developer, I would like to catch up the latest standard, and the browsers mostly satisfy my/our demand, and in this sense, Node becomes much more problematic recently. So, I probably need to make a decision, not to use Node as much as possible, but browsers engine as much as possible. In any scenario, mixing up 2 different module ecosystems is not a healthy situation. So, what I am trying to do is to convert NPM libraries that I use to ESM, for instance, React16.* has not released ESM version, but probably in ver17. I obtained ESM version by my self. Probably, most certainly in near future, some projects to aim to convert CJS(npm libraries)->ESM will emerge in JavaScript community. So, probably the workaround would be to launch a small server inside Electron with a pure Chromium client script. In this way, all of the node ESM glitches is resolved. Perhaps, a hook can be prepared to convert the desired NPM to ESM to a In this workaround, why do we use Electron, not just the browsers? To me, the browser still has many restrictions to access local files, watch the file change and reload the contents etc. |
In the meantime there's also loaders, like |
@jdalton In fact, your library makes me refactor my on-going project, and I hope your implementation to Electron should be the default pattern, at least, a major alternative one. It seems you are now in position to be able to commit Electron project in deep. So I think that's also fortunate to all of us. Thanks a lot. |
@kenokabe I've been through this, tried to refactor existing work as things progress. I came to the realization that everything is a moving target, so best to keep current work isolated and experiment meanwhile with new patterns in experimental playgrounds of my own design. Just like you, I need to understand new patterns and really find it very counter-productive to continue working on my on going projects knowing they are implemented in retro. In my case, circumstances forced my hand into too much free time to experiment and I welcomed it, so did my collaborators on the projects I cared about and wanted to keep working on. My latest focus has been on browsers, not all browsers, just the ones that play nice, the ones that are likely to be PWA or App targets in the next few years, and Mozilla (cause they are just awesome folks). Here is a quick proof-of-concept of what it takes to reimagine bundling that retains ESM (and all other) files without any of the old-school bundling which relied on non-ESM module constructs. It is too early to present this better at the moment and the demo is strictly a browser thing, but my motivation is to devise ways to cleanly create projects without too many devDependency boilerplate and forced configs as with create-that-app stuff that has gotten way too ugly for anyone with a brain to be willing to use in their projects. Edit: I am gearing my experimental prototype project for Electron/Chrome assuming that other platforms will evolve, but not too concerned if they don't, it's about potential not adoption. |
@SMotaal Right, after Firefox Quantum release, I switched my main browser to Firefox and thought it's good to have both Firefox/Chromium in the world. In terms of Service-Worker, I expect adaptations by Gmail or GoogleMap will be a tipping point, like the AJAX shock to JavaScript By the way, for this issue, I somewhat have an impression that the outstanding technology provided by @jdalton and his implementation pattern to Electron : https://github.com/standard-things/electron-quick-start pretty much have resolved this issue; to use esm instead of Node's ESM Loader. I'm not 100% sure of your purpose, but to me it does the job. What do you think. |
@jdalton Thank you very much for this library, however I’m not able to use it in the renderer because it violates CSP without |
Hi @hguiney! Let's take care of that over on the issue tracker. I have a hunch at the root cause. I'll fix that right up. Update: Fixed in v3.0.83 🎉 |
After over a month of testing all aspects of Node's new loader infrastructure in electron, only one issue in my opinion remains to be resolved and in my best estimation it needs to be in electron/node.
Setup: (steps to use Node's loader)
Problem
The loader's internals are not designed to work in multi-context environments. Both Electron and NW.js present the same issue where the loader associates the "evaluated" namespace of the module to the current node context which seems to not behave the same here as it does in node.
The end result is that a module will load fine on first import where the loader holds a direct reference to it, but as soon as it is imported again (if even on the next line) then the loader uses the stored reference in the module map expecting it to point to the context-bound instance of the module.
Unlike Node, in both Electron & NW.js, this instance would be undefined.
Workaround
It takes two ugly but effective steps (all kosher and pure JS). 1. Override the loader's import method so that it keeps a reference of the first instance of the module on the actual JS instance of the module wrap (ie
moduleWrap[Symbol.for('module')]
). 2. At the same time, make sure to always create a separate loader instance per context (in Electron, I create one for main, and one in preload which is reliable both in preload as well as the associated window).Recommendation
How Electron or NW.js can best go about resolving this depends on their multi-context architectures, and best left to those more knowledgeable. However, from all my research and testing, I am almost certain that correctly associating contexts with modules is somewhere in
src/module_wrap.cc
.I believe that Node's loader will take little effort to make it ideal for mainstream use in Electron. If you are looking into it, I am very interested and would be happy to exchange recipes regarding other related obstacles. As it stands, the loader is very stable if you know what to expect, and it takes a lot of patching. I managed to do it all in JS and without any custom rebuilds. If this will eventually become mainstream, I honestly think some effort will be needed to properly support it by the platform.
The text was updated successfully, but these errors were encountered: