'In Angular, using WebPack 5 Module Federations, how can we expose Web Workers used by a remote?
in my configuration I am currently using a shell application and a remote, both written in angular 12.2.0, and I'm facing a problem I am not sure how to solve.
SHELL webpack.config.js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const mf = require("@angular-architects/module-federation/webpack");
const path = require("path");
const share = mf.share;
const sharedMappings = new mf.SharedMappings();
sharedMappings.register(
path.join(__dirname, '../../tsconfig.json'),
['auth-lib']
);
module.exports = {
output: {
uniqueName: "portal",
publicPath: "auto"
},
optimization: {
runtimeChunk: false
},
resolve: {
alias: {
...sharedMappings.getAliases(),
}
},
plugins: [
new ModuleFederationPlugin({
// For hosts (please adjust)
remotes: {
"myremote": "myremote@http://localhost:4250/remoteEntry.js"
},
shared: share({
"@angular/core": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
"@angular/common": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
"@angular/common/http": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
"@angular/router": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
...sharedMappings.getDescriptors()
})
}),
sharedMappings.getPlugin()
],
};
REMOTE webpack.config.js
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const mf = require("@angular-architects/module-federation/webpack");
const path = require("path");
const share = mf.share;
const sharedMappings = new mf.SharedMappings();
sharedMappings.register(
path.join(__dirname, 'tsconfig.json'),
[/* mapped paths to share */]
);
module.exports = {
output: {
uniqueName: "interviews",
publicPath: "auto"
},
optimization: {
runtimeChunk: false
},
resolve: {
alias: {
...sharedMappings.getAliases(),
}
},
plugins: [
new ModuleFederationPlugin({
// For remotes (please adjust)
name: "myremote",
filename: "remoteEntry.js",
exposes: {
'./Module': './src/app/myremote/myremote.module.ts'
},
shared: share({
"@angular/core": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
"@angular/common": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
"@angular/common/http": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
"@angular/router": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
...sharedMappings.getDescriptors()
})
}),
sharedMappings.getPlugin()
],
};
Everything works smoothly when I use the reference to my remote module in the shell app like so:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './components/home/home.component';
const routes: Routes = [{
path: '',
component: HomeComponent,
pathMatch: 'full'
}, {
path: 'myremote',
loadChildren: () => import('myremote/Module').then(m => m.MyRemoteModule)
}];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
The problem appears when the remote module's component within its code instantiate a new Web Worker like so:
const myWorker = new Worker(
new URL('../path/to/workers/worker.ts',
import.meta.url
),
{ type: 'module', name: 'my-worker' }
);
// since I'm also using comlink, I also do that, but that's not really relevant here
this.worker = this.createWrapper<WorkerService>(myWorker);
The code above, in Webpack 5, already generates a separate bundle in my app called my-worker.js, and it's not part nor of the component's neither the module's bundle. Therefore exposing the module to Module Federation, will not expose into the remoteEntry.js also the worker's code. Then trying with the shell app to navigate to the route which loads my remote, will error and notify that:
ERROR Error: Uncaught (in promise): SecurityError: Failed to construct 'Worker': Script at 'http://localhost:4250/my-worker.js' cannot be accessed from origin 'http://localhost:5000'. Error: Failed to construct 'Worker': Script at 'http://localhost:4250/my-worker.js' cannot be accessed from origin 'http://localhost:5000'.
So the question is, if I want to have a remote and expose its modules to be used in a shell app, and one of its modules uses a Web Worker, how can I make sure that the separate worker bundle created by Webpack is also exposed in order for the shell app to be able to instantiate it?
Solution 1:[1]
I have found a workaround.
Step 1. Create a custom worker class in a separate file
export class CorsWorker {
private readonly worker: Worker;
constructor(url: string | URL) {
const objectURL = URL.createObjectURL(
new Blob([`importScripts(${JSON.stringify(url.toString())});`], {
type: 'application/javascript'
})
);
this.worker = new Worker(objectURL);
URL.revokeObjectURL(objectURL);
}
getWorker() {
return this.worker;
}
}
Step 2 - do not use Webpack Worker instead use CorsWorker to import your worker file.
import { CorsWorker as Worker } from './cors-worker';
// aliasing CorsWorker to Worker makes it statically analyzable
const corsWorker = new Worker(new URL('../worker', import.meta.url));
const worker = corsWorker.getWorker();
This example works for me. We can get more info by following this GitHub discussion thread https://github.com/webpack/webpack/discussions/14648
Solution 2:[2]
Here's a quick code review:
#include <iostream>
using namespace std; // Bad practice; especially in a multi-file project which
// this should be.
// The Node should be an "implementation detail." I care about the queue, not
// the plumbing that makes it work.
class Node {
private:
int age;
Node* next; // A sorted queue using only a singly linked list seems like a
// bad idea.
public:
// Use of this is not needed; it can hurt readability
int getAge() { return this->age; }
void setAge(int age) { this->age = age; }
Node* getNext() { return this->next; }
// Return should be void; you don't return anything
Node* setNext(Node* next) { this->next = next; }
};
class Priority_Queue {
private:
Node* front;
Node* rear;
public:
Priority_Queue() {
front =
NULL; // Prefer default member initialization, then default this ctor
rear = NULL; // Prefer nullptr over NULL, it's been around for a decade now
}
/*
* SUPER BAD: No destructor, copy constructor, or assignment operator
* overload. This class manages heap-allocated resources, and no care is taken
* to ensure that proper copies are made, or that the object is properly
* destroyed.
*
* This is known as the Rule of 3, but has recently been called the Rule of
* 0/3/5. Ideally, you would be adhering to the Rule of 5.
*/
void enqueue(int x) {
if (front == NULL && rear == NULL) { // Unnecessarily redundant
Node* newNode = new Node(); // Why not provide a constructor for this?
newNode->setAge(x);
newNode->setNext(NULL);
}
else if (front != NULL &&
front->getAge() < x) { // Pointers can be directly checked
Node* newNode = new Node();
newNode->setAge(x);
newNode->setNext(rear);
front = newNode;
}
else if (front != NULL && front->getAge() > x) {
Node* newNode = new Node();
newNode->setAge(x);
newNode->setNext(NULL);
rear = newNode;
}
}
void dequeue() {
if (front == NULL)
cout << "Can't remove from an empty queue!"
<< endl; // Do nothing, no need for a message
else {
int x = front->getAge();
Node* p = front;
front = front->getNext();
delete p;
cout << "Deleted Age is " << x
<< endl; // Printing in general is a bad idea from within a data
// structure
}
}
void display() {
Node* ptr;
ptr = rear; // Starting at end of list for printing
if (front == NULL)
cout << "Queue is empty\n";
else {
cout << "Queue is :\n";
while (ptr != NULL) {
cout << ptr->getAge() << endl;
ptr = ptr->getNext();
}
}
}
};
As far as student code goes, this is not bad. When it comes to the sorting, we won't bother actually moving nodes around; it's unnecessary given the layout of your Node class.
The first thing I'm going to do is split this into multiple files. It actually makes the writing a bit easier. The first file is our priority queue header.
PriorityQueue.hpp
#ifndef PQUEUE_HPP
#define PQUEUE_HPP
class Priority_Queue {
private:
struct Node; // Prevents Nodes from being declared outside of this class
Node* front = nullptr;
Node* rear = nullptr;
// Helper functions
void sort();
void push_front(int x); // Very simple with singly linked list
friend void swap(Priority_Queue& lhs, Priority_Queue& rhs);
public:
Priority_Queue() = default;
// Only going for Rule of 3 for brevity
Priority_Queue(const Priority_Queue& other);
~Priority_Queue();
Priority_Queue& operator=(Priority_Queue other);
void enqueue(int x);
void dequeue();
void display();
};
// Now that we're nested, a struct can be used instead
// Will make things a lot easier
struct Priority_Queue::Node {
int age = 0;
Node* next = nullptr;
Node() = default;
Node(int a);
Node(int a, Node* n);
};
#endif
This declares the classes and the functions they contain. The biggest single change is that Node is now a struct and is declared privately in Priority_Queue. I should not be able to see, let alone declare Nodes outside of the class. They are what's known as an implementation detail. They help us write the class, but have no business being visible.
Making Node a struct also allowed us to delete all of the member functions. We'll let the class manipulate the data directly.
PriorityQueue.cpp
#include "PriorityQueue.hpp"
#include <algorithm>
#include <iostream>
// This is sloppy, but time contraints won't allow me to make this nicer.
Priority_Queue::Priority_Queue(const Priority_Queue& other) {
Node* otherNode = other.front;
while (otherNode) {
push_front(otherNode->age);
}
sort();
}
Priority_Queue::~Priority_Queue() {
while (front) {
Node* d = front;
front = front->next;
delete d;
}
rear = nullptr;
}
// Untested
Priority_Queue& Priority_Queue::operator=(Priority_Queue other) {
swap(*this, other);
return *this;
}
void Priority_Queue::enqueue(int x) {
if (!front) { // Simpler pointer check
front = new Node(x); // Provided ctor makes this simpler
rear = front;
// Single node, no need to sort
return;
}
push_front(x);
sort();
// Separate your concerns. Enqueue gets a node into the list, we worry about
// sorting elsewhere.
}
// Should be called pop(); not changing so that your main() can be left alone
void Priority_Queue::dequeue() {
if (!front) return;
Node* p = front;
front = front->next;
delete p;
}
void Priority_Queue::display() {
Node* ptr = front;
if (!front)
std::cout << "Queue is empty\n";
else {
std::cout << "Queue is :\n";
while (ptr) {
std::cout << ptr->age << '\n';
ptr = ptr->next;
}
}
}
void Priority_Queue::sort() {
// We'll go with a bubble sort
bool madeSwap;
do {
madeSwap = false;
Node* node = front;
while (node->next) {
if (node->age > node->next->age) {
std::swap(node->age, node->next->age);
madeSwap = true;
}
node = node->next;
}
} while (madeSwap);
}
void Priority_Queue::push_front(int x) { front = new Node(x, front); }
// Untested
void swap(Priority_Queue& lhs, Priority_Queue& rhs) {
using std::swap;
swap(lhs.front, rhs.front);
swap(lhs.rear, rhs.rear);
}
// Nested class
Priority_Queue::Node::Node(int a) : age(a) {} // next is automatically nullptr
Priority_Queue::Node::Node(int a, Node* n) : age(a), next(n) {}
Your main.cpp is largely untouched. It now includes the PriorityQueue.hpp, but not much else is different.
You must compile both cpp files in order to get a working program. Something like g++ -Wall -Wextra -std=c++17 main.cpp PriorityQueue.cpp. You'll note that I also sort in ascending order, to avoid blatant copy/paste. It's not a huge deterrent, but it's better than nothing.
To see it running, you can visit this link.
Solution 3:[3]
There are way too many issues with your code, you are too far away from an working code for me to be able to help you with it.
Please read this: https://www.programiz.com/dsa/priority-queue
There is also an example in C++ that you can test and debug to understand exactly how it's working.
Some of the issues:
- setNext should return void.
- you never set the front in enque(see first if in enque).
- you should use std::vector or std::list as underlying type.
- you don't need to use "this" pointer(e.g "this->age = age")
- the algorithm doesn't do what you think is doing(not near close) ... I could go for a long time with this.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Pravanjan |
| Solution 2 | sweenish |
| Solution 3 |
