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
PoC integration between browser and AMD modules #1533
Conversation
@@ -2,7 +2,6 @@ | |||
"name": "frontend-taglib-clay-sample-web", | |||
"private": true, | |||
"scripts": { | |||
"build": "liferay-npm-scripts build", |
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'm turning off any build here because I don't want babel or the bundler to rewrite anything.
); | ||
}); | ||
|
||
export default await promise; |
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 is a special module that would be created by the build system automagically. It provides a bridge from browser modules to AMD ones, i.e, lets index.js
import react
as if it was local to the project, but then it is diverted to the AMD loader transparently.
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.
The export await
syntax is pure gold because it lets interchange of sync and async imports/exports without any need to change the consumer 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.
browser modules to AMD ones
Any idea what the cost would be to migrate our AMD modules into browser modules? Or is this just a silly thought?
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 theory we would be able without touching anything in the source code. We would need a tool and some configuration files to specify where to find things.
However, we would need to do a PoC for that too, because the devil is in the details, and it's not the first time (in this context) we think something is easy, then we discover it is much more complex or even impossible (see what happened with webpack federation 😞 ).
@@ -0,0 +1,5 @@ | |||
import holi from './js/holi.js'; |
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.
Import local module holi.js synchronously
@@ -0,0 +1,5 @@ | |||
import holi from './js/holi.js'; | |||
import react from './_amd/react.js'; |
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.
Import third party module react
synchronously.
Note that this is a trick for the PoC, but in the original source code we would have something like import react from 'react';
and then a configuration to tell the bundler to remap that react
to something (in this case an AMD module, in other case it could be a local browser module inside node_modules
or in any other OSGi bundle).
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 looks interesting :)
Mixing amd and esm modules will not cause a race condition?
Does the browser do these imports sequentially?
If not, I think the amd loader can be slower(I'm guessing, yes) and the browser can load assets in esm faster and a dependency of an esm can be the currently-loading amd loader
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.
There's no race condition because we would use export await
in the AMD-browser bridge module.
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.
Does the browser do these imports sequentially?
I haven't checked it, but I guess it doesn't. It can simply create the dep graph, fetch the files, and visit the graph in parallel (respecting deps, of course) instantiating and resolving modules. Much in the same way we do in AMD loader.
However, I haven't done any performance test yet. And I'm not 100% sure if deploying npm packages without bundling will be performant, because I don't know if the browser is going to download the modules one by one or in parallel (using HTTP 2 or something like that). This is something we need to explore before committing to any design.
@@ -0,0 +1,3 @@ | |||
export default function holi() { |
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 would be a regular browser module.
Note that this is put inside the JAR verbatim: we don't need to build anything.
<script src="/o/frontend-taglib-clay-sample-web/index.js" type="module" /> |
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 is how you bootstrap a browser module.
It is the equivalent of <aui:script require="<%= npmResolvedPackageName + ...%>">
In the future, we could make a new tag to avoid using <aui:script>
to be able to make our own magic before printing the <script>
tag to the HTML, but it would be straightforward, most probably.
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.
to make our own magic before printing
By magic you mean pointing to a valid resolved path(with version of the module and a path to /o/) like the generated NPMResolver ones?
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.
By magic I meant "anything we want to do with it, now or in the future". So I'm proposing to have our own taglib just for the sake of having an extra layer between the JSP and the actual <script>
tag.
I don't know if in this browser modules solution we would need to have version numbers or not 🤔 In this case, the canonical names of modules contain the Web-ContextPath
of the OSGi bundle containing them, and that has to be unique per OSGi bundle version, so maybe we can switch from npm versions to OSGi locations to designate which npm modules we want to use. It's something that we should investigate.
Most probably, we would want to do things like:
<new:script-tag import="/index.js"/>
to import local (contained in the OSGi bundle contanining the JSP's web context) modules.<new:script-tag import="react" context="frontend-js-react-web" />
to import a module contained in a different web context (if we wanted to allow for it).
Or, altenatively, we could have some way to configure mappings from logical package names to web context + name which are then applied when building. So, doing something like:
<new:script-tag import="react" />
in your code, then having a file where it says that react
has to be loaded from frontend-js-react-web
Nothing is yet decided, but those are the possibilities I have currently in mind...
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 is very interesting @izaera, I also think that having this tag would probably make things easier. Can't wait to see the final result
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't wait to see the final result
Well, you will have too, because this is only a brainstorming, nothing that we have decided or planned yet 😅
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 looks simple enough @izaera |
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.
Overall this is pretty neat! I expected much more of a headache when interacting between the two but this doesn't seem so bad.
Hey @izaera, I think we should proceed a bit further to see if this actually plays out. Import mapsI think this should be pretty straightforward. Import maps are shipped by default on Chrome and can be polyfilled using es-module-shims, so that should give us enough support to give them a try. From there, we'd want to check:
Additionally, there's quite a big movement in the Rails community about this, so I'd suggest you check some of these for context and inspiration:
ReactReact is a key part of our ecosystem now, so we would need to figure out:
I'm sure some other questions will arise, but these are the first that come to mind, so I'd love to see some experimentation in this area. I can work on that if you're busy with something else as well, so just let me know how can I help! |
Interesting links to read related to this PR(I will maintain a list of interesting articles I find, related to this feature, in this comment)
|
I'm going to close this, since all information and feedback gathered here has already been used in LPS-143013. Special thanks to @jbalsas for the very useful links regarding import maps 😘 |
This is a quick and dirty test to show how it is possible to interop browser and AMD modules.
I initially started it to decide if we should get rid of bundler 3, or keep it for further refinement, but given that this simple PoC can give rise to new ideas, I wanted to share it as a draft.
I explain in the comments what each thing does.
Note that I'm not proposing this as the final solution, but showing that we may be nearer to using browser modules than we think.
We would still need interoperation the other way round (use browser modules from AMD ones) but in case we decided to support that, it would be a matter of injecting the browser modules into the AMD loader (i.e: it would need a new AMD loader). In any case, another legitimate option would be not supporting it, I think (the same way we don't provide any too to loadAUI from AMD).
One last thing to note is that, in theory, we wouldn't need to rewrite any line of JS in portal to switch from AMD to browser modules, given that we are already using ES module syntax.