Wrong module loaded on webpack federation with angular - javascript

I have 3 apps on ports 4200, 4201 and 4202.
On my first app (on 4200) I have a route to other apps which loads their corresponding modules, a ProfileModule, and I'm creating components based on this module's exported components.
Note that my apps have the same module and exported component names.
This works perfectly but for some reason, when I change my route from an app to the other one, when components have already been loaded once, webpack retrieves the wrong module.
What I'm doing here is:
I'm routing to my second app (which is on 4201), it loads my ProfileModule from second-app
I'm routing to my third app (which is on 4202), it loads my ProfileModule from third-app
I'm going back to my second-app and it loads my ProfileModule from third-app instead of second-app
I guess it is due to the module names, but shouldn't it work if it's on a different remote?
Here is how I get my modules:
async function lookupExposedModule<T>(
remoteName: string,
exposedModule: string
): Promise<T> {
// Initializes the share scope. This fills it with known provided modules from this build and all remotes
await __webpack_init_sharing__("default");
const container = window[remoteName] as Container; // or get the container somewhere else
// Initialize the container, it may provide shared modules
await container.init(__webpack_share_scopes__.default);
const factory = await container.get(exposedModule);
const Module = factory();
return Module as T;
}

I found a solution here.
But in my case, I had the same names for different remotes in my webpack config.

Related

How to create vanilla javascript widgets as web components and import into a page dynamically?

I am working on a web application which can host mini-apps (or modules) developed in vanilla Js, HTML, CSS.
The host application dynamically loads (using fetch API) the mini-apps (or modules) into its pages and then I want these mini-apps to independently request for their data or do whatever they want to. I want these mini-apps isolated from the host scripts and styling but the host should be able to execute functions of these mini-apps (or modules).
Example: The dashboard of Microsoft Azure portal. It has widgets which can be selected, customised and placed by the user, and after loading of the host dashboard these widgets independently fetch for their data. Also, the period and auto-refresh time can be controlled by the host application.
Priorities:
• Modules should be able to execute their own JS scripts.
• If possible then everything should be in vanilla js (or Stenciljs / Vue.js)
Current File Structure:
main.html
js (dir)
style (dir)
modules (dir)
• module-one
| module.html
| module.css
| module.js
• module-n
...
I have tried creating custom HTML element and then appending HTML and CSS of module to shadowDom. But I still don't know how to get its JS working. If I insert module.js dynamically to main.html then somehow I need to change roots of all module.js appended in host application from
document to shadowRoot
Example:
//module.js
const sayHelloBtn = document.getElementById('sayHello');
sayHelloBtn.addEventListener('click', () => {console.log('Hello')});
//module.js after appending to host (main.html)
const mod = document.querySelector('module-one').shadowRoot;
const sayHelloBtn = mod.getElementById('sayHello');
sayHelloBtn.addEventListener('click', () => {console.log('Hello')});
Please let me know if further elaboration or clarification on question is required. Thank You :)
Problem Update:
I get a json of all the modules from an API and dynamically create custom elements with shadow root . I fetch the module files using the fetch API and then append them to the custom elements. I am able to successfully append the .html and .css to respective custom elements but cannot run JS inside shadow DOM. If I dynamically import the JS globally to the main.html then some how I need the JS to access the elements in its shadowRoot and also the functions should not conflict with other module's js functions with same name.
I have tried creating classes in each module.js which holds its respective methods, variable and a init() which does all the module's initialisation.
//module.js
class ModuleAbc {
constructor(host = document) { // shadowRoot is passed when instantiating from the host application
this.docRoot = host;
console.log('docRoot set: ', this.docRoot);
}
init() {
console.log('initialising module at host: ', this.docRoot);
const docRoot = this.docRoot;
const btn = docRoot.getElementById('cm'); //this element is inside shadowRoot
btn.addEventListener('click', () => {
console.log('Hello from mod 1!');
});
}
}
Now I do not know how to call init() of all the classes because their names are different for every module.
//HOST JS (main.js)
let mod_name = 'ModuleAbc';
const mod = new mod_name(module_root); //THIS DOESN'T WORK
mod.init();
Problem is resolved using Estus Flask's suggested solution

When using dynamic import in a function, how can I specify type info in global variable?

My simplified server code looks like below.
server.ts
import google from "googleapis";
const androidPublisher = google.androidpublisher("v3");
app.use('something', function(req, res, n){
...
})
...(only one of the dozens of other methods use androidPublisher)
I am importing googleapis library in order to setup androidpublisher variable. However, this googleapis library is big and it takes 400ms~700ms to fully import file, when it takes 10ms~30ms to import other library files.
Because my environment is serverless architecture (firebase functions), AND because approximately 1 out of 100 requests actually need androidPublisher, I want to take advantage of dynamic import to import googleapis when it is necessary. Otherwise, above setup actually adds 400ms/700ms latency to every request that spins up new serverless instance, even when androidPublisher is not needed.
So I have made changes like below.
server.ts
let androidPublisherInstance:any;
async function getAndroidPublisher() {
const googleapis = await import("googleapis");
if (androidPublisherInstance === undefined) {
const ap = googleapis.google.androidpublisher("v3");
androidPublisherInstance = ap;
}
return androidPublisherInstance;
}
...(one of methods use getAndroidPublisher() to get androidPublisher instance)
with above setup where I am using global variable & helper function to initialize androidPublisher only when needed. This works as intended and 400ms~700ms latency gets added when androidPublisher is needed for the first time. However, I ended up with type of androidPublisherInstance to be any. I couldn't correctly define the type because type definition is available inside of googleapis and that is residing inside of getAndroidPublisher function.
Thus, I lose all benefit of using typescript and have to play guess games while using methods/properties when I use androidPublisherInstance.
And I think I must use global variable, because I do not want to initialize androidPublisher multiple times (googleapis.google.androidpublisher("v3")) whenever a function calls getAndroidPublisher()
Am I missing something? Is there a way to use dynamic import & let client to be initialized only once without needing to use global variable?
You can just import the type. As long as you use it only in type definitions, not in value expressions, the compiled JavaScript will never load the module:
import { androidpublisher_v3 } from "googleapis";
let androidpublisher: androidpublisher_v3 | undefined;
Alternatively, to make sure you don't accidentally reference it in the wrong place, use only import types:
let androidpublisher: import("googleapis").androidpublisher_v3 | undefined;

A list of the namespaces created by import statements

I've a script tag (type="module"), which is dynamically populated with import statements by PHP based on some parameters. The populating process is controlled by the authorization system of the site, and it can drop some imports from the script, depending on the rights of the current user.
An example of the final rendered script tag on a page:
<script type="module">
import ui from 'path_to_ui_module';
import read from 'path_to_read_module';
import edit from 'path_to_edit_module'; // Could be omitted by the authorization
import create from 'path_to_create_module'; // Could be omitted by the authorization
import delete from 'path_to_delete_module'; // Could be omitted by the authorization
ui.init();
</script>
All the imports are guaranteed to be objects only.
Now, the problem lies in ui.init method, where I want to assign some references of some properties of the loaded modules (and their submodules) to a property in ui, but I don't know, which modules are actually loaded, because the authorization system may have dropped some of the modules from the script. For example, all the loaded modules have a cmd object containing various methods, and I'd like to assign a reference to those methods to ui.cmd object. How can I iterate through all the namespaces of the imported modules, including the possible submodules imported by the modules loaded in the script?
Currently I've modified the authorization system to create the ui.init call, which passes the references of the really loaded modules to ui.init method, but the submodules are naturally not included.
ui.init({modules: {read: read, edit: edit, create: create, delete: delete});
(The modules dropped by the authorization system are not included in modules object.)
With the passed modules object, a simplified ui.init method looks like this ("main module" in the comments is ui, "submodule" refers to the modules passed via the arguments):
init (options) {
for (const module in options.modules) {
this[module] = options.modules[module]; // Add submodule reference to the main object
this[module].main = this; // Add main module reference to submodule
Object.assign(this.cmd, this[module].cmd); // Assign the cmd methods of the submodule to the main module's cmd object
this[module].init(); // Initialize the submodule
}
}
Reformed question: Is there a native "list" of all imported modules, including the submodules the imported modules have imported, available, that I can use instead of options.modules in the for .. in loop? Or alternatively, is there a way for the submodules to somehow "expose" themselves after they've been loaded, so that they can create a list of the loaded modules?
You can try using dynamic imports:
(I am assuming that your PHP script can dynamically generate the .then callback)
<html>
<body>
<script type="module">
Promise
.all
( [ import('./node_modules/the-answer/dist/the-answer.es.js')
, import('./node_modules/the-answer/dist/the-answer.es.js')
, import('./node_modules/the-answer/dist/the-answer.es.js')
]
)
.then(([{default: answer1}, {default: answer2}, {default: answer3}]) => {
// You now know all the modules that have loaded 😊
console.log(answer1, answer2, answer3);
});
</script>
</body>
</html>
Because the dynamic form of import returns a promise, you can wait for all of them to resolve and have access to them.
The the-answer module is literally this:
export default 42;
Which is why I need to destructure each module in the .then() method, e.g.
.then(([{default: answer1}]) => alert(answer1))

inject a require into a js vendor module

About the framework (while I think the problem itself does not heavily rely on that): Angular 2 with Webpack
There is the library Leaflet.heat which relies on simpleheat. I got the missing type definitions under control.
I'm importing the libraries in my vendor.ts
[...]
import 'simpleheat';
import 'leaflet.heat/src/HeatLayer';
[...]
Inside of the HeatLayer class, the function simpleheat:
[simpleheat.js]
if (typeof module !== 'undefined') module.exports = simpleheat;
function simpleheat(canvas) {
...
is called. However, the HeatLayer module file does not require simpleheat inside it's file.
Thus, creating an instance of L.HeatLayer works, but the execution of the respective code in it's function fails with
ReferenceError: simpleheat is not defined
Now, adding (for testing purposes) simpleheat = require('simpleheat'); into the HeatLayer file (a vendor), it works.
Understandably, I don't want to modify a vendor file.
Question:
What options do I have, to make the function simpleheat accessible from inside the HeatLayer module?
One Solution I just found:
Change the vendor.ts to the following:
(<any>window).simpleheat = require('simpleheat');
import 'leaflet.heat/src/HeatLayer';
Are there others/better?

In Requirejs, can I register the same path by different names?

I'm using RequireJS to manage an app that is implemented as an undetermined number of classes split across files.
The app has some some utility classes, and then some consumer classes which use the utility classes. Each consumer class uses RequireJS to load only the utility classes it needs, and is responsible for its own Require config.
Consumer classes aren't aware of each other, and the app as a whole doesn't know the needs of each consume class. It merely knows the consumer class exists, and loads it. The problem is, if two different consumer classes define the same path but with different names, the first one wins, and subsequent one times out.
For example, this is some utility.js
define( function(){
return function(msg){
console.log(msg);
};
});
This is consumer1.js
require.config({
paths: {
ut1: '/utility'
}
});
require(['ut1'], function(utility){
utility('hello from client1');
})
And this is consumer2.js
require.config({
paths: {
ut2: '/utility'
}
});
require(['ut2'], function(utility){
utility('hello from client2');
})
If used independently each consumer class works, but when called together, the second one returns an error "Load timeout for modules: ut2".
As part of the modular nature of the app design, consumer classes cannot know each other's content, so I cannot enforce naming rules, nor can I have a central app-level require.config. How can I stop one consumer from breaking another's require config?
I supposed I could write a global helper that managed names and paths, grouping common paths and returning the first path name only, but I'd prefer a simpler solution within Require itself.
You should use the map option:
map: {
consumer1: {
ut1: '/utility'
},
consumer2: {
ut2: '/utility'
}
}
And then you don't need a paths configuration for /utility. What this does is tell RequireJS that when consumer1 requires ut1, it should load the /utility module instead. When consumer2 requires ut2, RequireJS should load the /utility module instead.

Categories