Loading remote Angular MFE into React shell using Webpack's Module federation - webpack-module-federation

Can some one help me with an example to load remote Angular micro frontend application into React Shell using webpack's Module federation concept?
I have checked https://www.angulararchitects.io/en/aktuelles/multi-framework-and-version-micro-frontends-with-module-federation-the-good-the-bad-the-ugly/ where React is loaded in angular. But I am looking for other way.

With webpack, you can put the bootstrapping of angular app in a mount method and export this method. This must be done in another file to avoid conflicts for angular to run independently.
(Make sure to have the bootstrap.js file imported in main.ts, as used by module federation)
remote/src/load.ts
const mount = ()=>{
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
export{mount}
expose this new file with webpack from angular
remote/webpack.config.ts
output: {
scriptType: 'text/javascript',
},
plugins: [
new ModuleFederationPlugin({
name: "remoteMfe",
filename: "remoteEntry.js",
exposes: {
'./webcomponent':'./src/loadApp.ts',
},
})
load the exposed module in react app using webpack
host/webpack.config.js
plugins: [
new ModuleFederationPlugin(
{
name: 'host',
filename:
'remoteEntry.js',
remotes: {
RemoteMFE:
'remoteMfe#http://localhost:3000/remoteEntry.js',
},
}
),
import the mount method that was exposed from angular remote and get the root element(i.e. ) from angular mfe. This can be used as a regular DOM element
host/src/Example.js
const ExampleComponent = () => {
useEffect(() => {
mount();
}, []);
return <div className="left-sidebar-module"><app-root></app-root></div>;
};

Related

Jest: Cannot read property of undefined when importing from own package nextjs

Got this weird bug when running the jest test, one of the UI component from a self defined UI package keeps throwing error, saying that an object in that package is undefined...
The component itself works perfectly fine, and the same component's testing logic works in another repo without nextjs, and that repo utilize #swc/jest for js transform in jest.config file.
I've also added that package itself to transformIgnorePatterns in jest-config file, but somehow the bug still presents...
The project itself is in nextjs, and below is a snapshot of the jest.config file
/** #type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
testPathIgnorePatterns: [
'<rootDir>/.next/',
'<rootDir>/node_modules/',
'<rootDir>/e2e/'
],
preset: 'ts-jest',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['#testing-library/jest-dom/extend-expect'],
setupFiles: [require.resolve('whatwg-fetch')],
transform: {
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }]
},
transformIgnorePatterns: ['/node_modules/myPackage', 'jest-runner'],
testMatch: ['**/*.spec.{js,jsx,ts,tsx}'],
};
and the error itself goes Error: Uncaught [TypeError: Cannot read property 'object' of undefined], which tracks down to /node_modules/myPackage
how the package is used
import { InputBox } from 'myPackage';
const MyComponent = () => {
return (
<div>
<InputBox />
</div>
);
}
export default MyComponent;
and here's the test:
import { act, render } from '#testing-library/react';
import React from 'react';
describe('show component', () => {
it('should render', async () => {
await act(async () => {
render(
<MyComponent/>
);
});
});
});
I've found a similar question on stackoverflow Jest: Cannot read property of undefined when importing from own package
but that one is using regular js, and this one being next.js, there's really nowhere I can update .babelrc to update those configs...
Any input would be appreciated.
Update: it turns out the component that causes me bug is built on top of react-popper library, which is built on top of popper.js library. popper.js library doesn't support jsdom by default, and it requires to do jest mock. But my library is 2 layers abstractions on top of popper.js library, I'm not sure how to do that, or even if that is doable...

How to share assets [css,images,...] in module federation with react

Basically I want to find a way to share example some css file or image using module federation in my case I want to use react.
I did some research by I don't find a solid information abou that.
Some recomendations ?
One solution that I found was using css-module, you can use webpack or browserify in any case your css will be transform in js and you can expose it and consume it like other object/component,...
Eg:
[ Home Content Component]
import styles from './exampleCss.module.scss'; // using css module
export const globalHomeStyle = styles; //exporting the new transformed js.
webpack.config.js
...
plugins: [
new ModuleFederationPlugin({
name: 'home',
filename: 'remoteEntry.js',
remotes: {
...
},
exposes: {
'./HomeContent': './src/HomeContent.jsx',
}
Consuming the style from Home microfrontend in PDP Microfrontend:
webpack.config.js //calling the microfrontend
...
plugins: [
new ModuleFederationPlugin({
name: 'home',
filename: 'remoteEntry.js',
remotes: {
home: 'home#http://localhost:3000/remoteEntry.js',
},
exposes: {
...
}
Using the Style in PDP Microfrontend:
import { globalHomeStyle } from 'home/HomeContent';
...rest implementation
<div className='flex'>
<div className={`font-bold text-3x1 flex-grow ${globalHomeStyle.exampleCss}`}>
I hope this can be useful, if you find other solutions don't be shy add them here 👍

How can i load changes to my code in a Vue app?

I deployed a Django+VueJS app that uses django webpack loader in order to render Vue apps in my Django templates. I used Nginx and Gunicorn to deploy the app to a DigitalOcean VPS, everything works without any problem but i have some doubts on how to edit my components in production, since i'm fairly new to Vue
Here is my vue.config:
const BundleTracker = require("webpack-bundle-tracker");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const pages = {
'main': {
entry: './src/main.js',
chunks: ['chunk-vendors']
},
}
module.exports = {
pages: pages,
runtimeCompiler: true,
filenameHashing: false,
productionSourceMap: false,
publicPath: process.env.NODE_ENV === 'production'
? 'static/vue'
: 'http://localhost:8080/',
outputDir: '../django_vue_mpa/static/vue/',
chainWebpack: config => {
config.optimization
.splitChunks({
cacheGroups: {
moment: {
test: /[\\/]node_modules[\\/]moment/,
name: "chunk-moment",
chunks: "all",
priority: 5
},
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "chunk-vendors",
chunks: "all",
priority: 1
},
},
});
Object.keys(pages).forEach(page => {
config.plugins.delete(`html-${page}`);
config.plugins.delete(`preload-${page}`);
config.plugins.delete(`prefetch-${page}`);
})
config
.plugin('BundleTracker')
.use(BundleTracker, [{filename: '../vue_frontend/webpack-stats.json'}]);
// Uncomment below to analyze bundle sizes
// config.plugin("BundleAnalyzerPlugin").use(BundleAnalyzerPlugin);
config.resolve.alias
.set('__STATIC__', 'static')
config.devServer
.public('http://localhost:8080')
.host('localhost')
.port(8080)
.hotOnly(true)
.watchOptions({poll: 1000})
.https(false)
.headers({"Access-Control-Allow-Origin": ["*"]})
}
};
So in order to deploy the Vue part i did npm run build and npm created a bunch of files in my static directory. Now, every time i edit a component, in order to see the changes on the web, i do npm run build every time, which takes some time. Is this how am i supposed to do it? Or is there a shorter way?
I don't know about django, But I know about vue..
is this how am I supposed to do it?
For me, I don't suggest it, you can use your django as a backend for your frontend
that should mean you would have 2 servers running. 1 for your django and 1 for your vue app. use XHR request to access your django App, remember to handle CORS. IMHO I don't want vue to be used as a component based framework.
is there a shorter way.
YES, and this is how you do it.
add to package.json
{
...,
scripts: {
...,
'watch' : 'vue-cli-service build --watch --inline-vue',
...,
}
}
while using the following settings in vue.config.js
module.exports = {
'publicPath': '/django/path/to/public/folder',
'outputDir': '../dist',
'filenameHashing': false,
runtimeCompiler: true,
'css': {
extract: true,
},
}
i forgot about how publicPath and outputDir works..
but you can check it out here https://cli.vuejs.org/config/#publicpath
regarding the code on the package.json file..
you can check it here
https://github.com/vuejs/vue-cli/issues/1120#issuecomment-380902334

How to make Flask Heroku app a Progressive Web Application (pwa)

I've deployed a simple web application named "ogagnage" with Flask and Heroku (gunicorn). It works perfectly in production and I try now to run it as a Progressive Web Application. Following Heroku instructions, I've created manifest.json and service workerfile (sw.js). Manifest file is recognized by my browsers but not service worker and i unfortunately don't understand why...
Manifest.json OK
sw not recognized
sw error message
Here is the structure of my project:
Directory tree
And here are the code added:
In views.py :
#app.route('/sw.js')
def sw():
return app.send_static_file('sw.js')
#app.route('/manifest.json')
def manifest():
return app.send_static_file('manifest.json')
#app.route('/app/static/app.js')
def app_js():
return app.send_static_file('app.js')
In sw.js :
console.log('Hello from sw.js');
importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.2.0/workbox-sw.js');
if (workbox) {
console.log("Yay! Workbox is loaded 🎉");
workbox.precaching.precacheAndRoute([
{
"url": "/",
"revision": "1"
}
]);
workbox.routing.registerRoute(
/\.(?:js|css)$/,
workbox.strategies.staleWhileRevalidate({
cacheName: 'static-resources',
}),
);
workbox.routing.registerRoute(
/\.(?:png|gif|jpg|jpeg|svg)$/,
workbox.strategies.cacheFirst({
cacheName: 'images',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
}),
],
}),
);
workbox.routing.registerRoute(
new RegExp('https://fonts.(?:googleapis|gstatic).com/(.*)'),
workbox.strategies.cacheFirst({
cacheName: 'googleapis',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 30,
}),
],
}),
);
} else {
console.log("Boo! Workbox didn't load 😬");
}
In app.js:
(function() {
if('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('sw.js')
.then(function(registration) {
console.log('Service Worker Registered');
return registration;
})
.catch(function(err) {
console.error('Unable to register service worker.', err);
});
navigator.serviceWorker.ready.then(function(registration) {
console.log('Service Worker Ready');
});
});
}
})();
In my base template:
<script type="text/javascript" src="../static/js/app.js"></script>
And my git:
Github project
Hope someone could help me, it would be a beautiful Christmas present.
Tx
You also need to send the manifest.json file
#app.route('/manifest.json')
def manifest():
return app.send_from_directory('static', 'manifest.json')
If you'd like a complete example, I have created a Flask PWA before. Here is the repository: https://github.com/MurphyAdam/Flask-chatbot
I've solved the problem after changing #app routes in views.py and relative path to go to sw.js in app.js (cf code updated)
Now , my service worker is working:
Service Worker Ok

Django with Angular - relative paths when font and back have different url

After two days I failed to setup (any form of) webpack working with django3 in the back and angular10 in the front, so I decided to just use gulp to start ng serve for frontend and python manage.py runserver for backend. I am new to this, so this is probably very stupid but really two days is a lot of time to give on setup and get nothing back ..
Currently I am trying to call an API on the django server that is on http://127.0.0.1:8000 while ng serve is running on http://127.0.0.0:4200
#Injectable()
export class EchoService {
constructor(private httpClient: HttpClient) {}
public makeCall(): Observable<any> {
return this.httpClient.get<any>(
'http://127.0.0.1:8000/my-api/'
);
}
}
'''
Is there a better way how to do this in Angular without using "http://127.0.0.1:8000" in every component call I do? How can I make it as close as possible to relative paths, that will be used in the prod version of this (for prod I will just put the bundles manually in the html, but I can not do that manually for dev)
Angular allows defining an environment. Here is what I did:
in your src folder, find the environments folder.
create the following files and adjust the content as needed (there should already be a file named environment.ts there, I'll get back to that later. For now:
environment.dev.ts
export const environment = {
production: true,
apiURL: "http://127.0.0.1:8000/my-api"
};
environment.prod.ts
export const environment = {
production: true,
apiURL: "https://api.yoururl.com/"
};
modify your angular.json:
...
"configurations": {
...
"production": {
"outputPath": "dist-prod/",
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
}
"dev": {
"outputPath": "dist-dev/",
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev.ts"
}
],
}
...
For ng serve, the standard environment.ts file is used, so the contents should probably match those of environment.dev.ts.
You can create builds with this setup by just calling:
ng build --configuration dev
ng build --configuration prod
Use in your service/component:
import { environment } from 'your/path/environments/environment';
#Injectable()
export class EchoService {
constructor(private httpClient: HttpClient) {}
public makeCall(): Observable<any> {
return this.httpClient.get<any>(
environment.apiURL + 'your/endpoint/
);
}
}
Now, depending on the configuration, environment.apiURL might be 127.0.0.1:8000 or https://.....
I have one for development, one for staging, and one for production.
In case I`ve missed something, you can read about it here.