Module Federation Angular in React Shell - javascript

I want to use angular app inside react app using module federation.
I found a way to use angular's app.component in React. But I can't use other components.
I am sharing the codes below.
Angular webpack config
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"), [
]);
module.exports = {
output: {
uniqueName: "mfeRemote",
publicPath: "auto",
scriptType: "text/javascript",
},
optimization: {
runtimeChunk: false,
},
resolve: {
alias: {
...sharedMappings.getAliases(),
},
},
experiments: {
outputModule: true,
},
plugins: [
new ModuleFederationPlugin({
name: "mfeRemote",
filename: "remoteEntry.js",
exposes: {
"./AppModule": "./src/loadApp.ts",
"./DashComp": "./src/app/external/dashboard/dashboard.component.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(),
],
};
loadApp.ts
import 'zone.js';
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
const mount = () => {
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
};
export { mount };
React Craco config
const ModuleFederationPlugin = require("webpack").container.ModuleFederationPlugin;
const deps = require("./package.json").dependencies;
module.exports = {
plugins: [
{
plugin: {
overrideCracoConfig: ({ cracoConfig, pluginOptions, context: { env, paths } }) => {
return cracoConfig;
},
overrideWebpackConfig: ({ webpackConfig, cracoConfig, pluginOptions, context: { env, paths } }) => {
webpackConfig.plugins = [
...webpackConfig.plugins,
new ModuleFederationPlugin({
name: "app",
remotes: {
appModule: "mfeRemote#http://localhost:3002/remoteEntry.js",
},
shared: {
...deps,
"react-dom": {
singleton: true,
eager: true,
},
react: {
singleton: true,
eager: true,
},
},
}),
];
return webpackConfig;
},
overrideDevServerConfig: ({
devServerConfig,
cracoConfig,
pluginOptions,
context: { env, paths, proxy, allowedHost },
}) => {
return devServerConfig;
},
overrideJestConfig: ({ jestConfig, cracoConfig, pluginOptions, context: { env, paths, resolve, rootDir } }) => {
return jestConfig;
},
},
},
],
};
React using App-Component (is Work)
import React, { useEffect, useRef } from "react";
import { mount } from "appModule/AppModule";
const RemoteAppModule = () => {
const ref = useRef(null);
useEffect(() => {
mount();
}, []);
return (
<div className="remote-module">
<app-root></app-root>
</div>
);
};
export default RemoteAppModule;
React using Other Comp (not Work)
import React from "react";
let DashComp = React.lazy(() =>
import("appModule/DashComp").then((module) => {
// this row for -> class component without new
let ExtModule = new module.DashboardComponent();
return { default: ExtModule };
})
);
const RemoteExtModule = () => {
return (
<div>
<React.Suspense fallback="...loading">
<DashComp />
</React.Suspense>
</div>
);
};
export default RemoteExtModule;
Error
enter image description here
------------------UPDATE ------------------------------------------
i solved my problem different way. I used multiple bootstrap module .
check my repo :
https://github.com/scerci/mfe-angular-react

Related

Defining routes in a separate file and using them inside index.js

I have this routes inside my index.js
import {
createRouter,
createWebHistory
}
from '#ionic/vue-router';
import {
RouteRecordRaw
}
from 'vue-router';
const routes = [{
path: '',
redirect: '/tabs'
}, {
path: '/tabs',
component: () => import('../views/tabs/TabRoot.vue'),
children: [{
path: '',
redirect: '/tabs/home'
}, {
path: '/tabs/home',
component: () => import('../views/tabs/Home.vue')
},
]
},
//Sell
{
path: '/sell',
component: () => import('../views/pages/sell/Sell.vue')
},
//Categories
{
path: '/trending',
component: () => import('../views/pages/Trending.vue')
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
and i would like to define the follwoing routes isnide others.js file and have this inside
{
path: '/crud_ui_landing_vehicles',
component: () => import('../views/pages/sell/categories/Vehicles.vue')
},
{
path: '/crud_ui_landing_properties',
component: () => import('../views/pages/sell/categories/Properties.vue')
},
The imported routes should be used inside const routes array object.
How can i define and import the routes file?
Using the spread operator
// others.js
export const others = [...]
import { others } from './others.js'
const routes = [
...others,
👆
{
path: ...,
component: ...
},
...
]

How to customize theme in Vuetify using Storybook 6?

I want to customize themes in Vuetify using Storybook 6 and I am using #socheatsok78/storybook-addon-vuetify package https://storybook.js.org/addons/#socheatsok78/storybook-addon-vuetify
I did exactly what documentation says but theme is still not working at all. I want to configure vuetify with custom properties and with my own color palette.
preview.js
import '!style-loader!css-loader!sass-loader!./main.scss';
import {
withVuetify,
withThemeProvider,
} from '#socheatsok78/storybook-addon-vuetify/dist/decorators';
import minifyTheme from 'minify-css-string';
export const globalTypes = {
theme: {
dark: false,
options: {
customProperties: true,
minifyTheme,
},
themes: {
light: {
primary: '#007BBF',
secondary: '#008574',
},
dark: {
primary: '#f099aa',
},
},
},
};
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};
export const decorators = [withThemeProvider, withVuetify];
main.js
const path = require('path');
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.#(js|jsx|ts|tsx)'],
addons: [
'#storybook/addon-links',
'#storybook/addon-docs',
'#storybook/addon-essentials',
'#storybook/preset-scss',
'#socheatsok78/storybook-addon-vuetify',
],
webpackFinal: async (config) => {
config.module.rules.push({
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'sass-loader',
{
loader: 'sass-resources-loader',
options: {
resources: path.resolve(__dirname, 'main.scss'),
},
},
],
sideEffects: true,
include: path.resolve(__dirname, '../'),
});
return config;
},
};
Ok I fixed the theme, you can find an tutorial how to do this and with all working code down below.
I found a great explanation here:
https://morphatic.com/2020/09/30/configuring-storybook-6-for-vue-2-vuetify-2-3/
preview.html
import '!style-loader!css-loader!sass-loader!./main.scss';
import { withVuetify } from '#socheatsok78/storybook-addon-vuetify/dist/decorators';
import vuetify from './vuetify';
import Vue from 'vue';
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};
export const decorators = [
(story, context) => {
const wrapped = story(context);
return Vue.extend({
vuetify,
components: { wrapped },
template: `
<v-app>
<v-container fluid>
<wrapped/>
</v-container>
</v-app>
`,
});
},
withVuetify,
];
main.js
I removed one line from addons
'#socheatsok78/storybook-addon-vuetify',
vuetify.js
import Vue from 'vue';
import Vuetify from 'vuetify';
import minifyTheme from 'minify-css-string';
import theme from './theme';
import LRU from 'lru-cache';
const themeCache = new LRU({
max: 10,
maxAge: 1000 * 60 * 60, // 1 hour
});
Vue.use(Vuetify);
export default new Vuetify({
theme: {
options: {
customProperties: true,
minifyTheme,
themeCache,
},
themes: {
light: theme,
},
},
});
theme.js
export default {
// ... other colors
primary: '#007BBF',
};
Theme works perfect now, only variables are not loaded correctly and I don't know how to solve this, you can read about it in the article comments

VueJS is asking to login every i'm open in new tab

i wanna ask about my code, by the way, i'm really new in vue.js so, i wanna ask about this unusual behaviour i've ever meet.
When i've logged in, and then open the url in new tab, it's ask me to login again.
it's always ask me to do login every i open in new tab. am i missed something?
here is my store.js, auth.js and router.js
store.js
import Vue from "vue";
import Vuex from "vuex";
import auth from "./stores/auth.js";
import user from "./stores/user.js";
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
auth,
user
},
state: {
admin_id: sessionStorage.getItem("admin_id"),
token: sessionStorage.getItem("token"),
errors: [],
msg: [],
user_lists: [],
role_lists: [],
user_data: [],
show_spinner: false,
},
getters: {
isAuth: state => {
return state.token != "null" && state.token != null;
}
},
mutations: {
SET_TOKEN(state, payload) {
state.token = payload;
},
SET_ADMIN_ID(state, payload) {
state.admin_id = payload;
},
SET_ERRORS(state, payload) {
state.errors = payload;
},
SET_MSG(state, payload) {
state.msg = payload;
},
CLEAR_ERRORS(state) {
state.errors = [];
},
ASSIGN_USER_LIST(state, payload) {
state.user_lists = payload
},
ASSIGN_ROLE_LIST(state, payload) {
state.role_lists = payload
},
GET_USER_DATA(state, payload) {
state.user_data = payload;
},
SHOW_SPINNER(state, payload) {
state.show_spinner = payload;
},
}
});
export default store;
auth.js
import $axios from "../api.js";
const state = () => ({});
const mutations = {};
const actions = {
submitlogin({ commit }, payload) {
return new Promise((resolve, reject) => {
$axios
.post("/auth/login", payload)
.then(response => {
if (response.data.status == "success") {
sessionStorage.setItem("token", response.data.token);
sessionStorage.setItem(
"userdata",
JSON.stringify(response.data.userdata)
);
sessionStorage.setItem('admin_id', response.data.userdata[0].user_id)
commit("SET_TOKEN", sessionStorage.getItem("token"), {
root: true
});
commit("SET_ADMIN_ID", sessionStorage.getItem('admin_id'), {
root: true
});
} else {
commit(
"SET_ERRORS",
{ invalid: response.data.msg },
{ root: true }
);
}
resolve(response.data);
})
.catch(error => {
if (error.response.status == 422) {
commit(
"SET_ERRORS",
{ invalid: error.response.msg },
{
root: true
}
);
}
});
});
},
submitlogout({ commit }) {
return new Promise((resolve, reject) => {
$axios
.post("/auth/logout")
.then(response => {
if (response.data.status == "success") {
sessionStorage.removeItem("token");
sessionStorage.removeItem("userdata");
commit("SET_TOKEN", null, {
root: true
});
}
resolve(response.data);
})
.catch(error => {});
});
},
};
export default {
namespaced: true,
state,
actions,
mutations
};
router.js
import Vue from "vue";
import Router from "vue-router";
import store from "./store.js";
import Home from "./pages/DashboardPage";
import Login from "./pages/Login";
import Logout from "./pages/Logout";
import User from "./pages/User";
import UserList from "./pages/UserList";
import AddUser from "./pages/AddUser";
import EditUser from "./pages/EditUserData";
import Profile from "./pages/Profile";
Vue.use(Router);
//DEFINE ROUTE
const router = new Router({
mode: "history",
linkExactActiveClass: "active",
routes: [
{
path: "/",
name: "home",
component: Home,
meta: {
requiresAuth: true
}
},
{
path: "/user/",
name: "user",
component: User,
meta: {
requiresAuth: true
}
},
{
path: "/user/list",
name: "userlist",
component: UserList,
meta: {
requiresAuth: true
}
},
{
path: "/user/tambah",
name: "adduser",
component: AddUser,
meta: {
requiresAuth: true
}
},
{
path: "/user/edit/:id",
name: "edituser",
component: EditUser,
meta: {
requiresAuth: true
}
},
{
path: "/profile",
name: "edituser",
component: Profile,
meta: {
requiresAuth: true
}
},
{
path: "/login",
name: "login",
component: Login
},
{
path: "/logout",
name: "logout",
component: Logout
}
]
});
//Navigation Guards
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
let auth = store.getters.isAuth;
if (!auth) {
next({
name: "login"
});
} else {
next();
}
} else {
next();
}
});
export default router;
i've tried to use localStorage to store the token, but also it's seems not working.
every feedback or suggestion are appreciated. Thank You.
ps : sorry for bad english...
This is a known issue. Vuex state is not persistent. So you need to install and use
vuex-persistedstate https://www.npmjs.com/package/vuex-persistedstate
I hope this helps. Let me know if you have any questions.
Thanks.

window is not defined server side rendering(react + express)

If the browser URL is requested, routing works normally.
By the way, location = ${path};
The part that says Component An error occurs when calling react renderToString function.
I'm looking at web pack settings or something, but I can't find the cause. Help!
// App.js
import React, {useState, useEffect} from 'react';
import moment from "moment";
import Header from "./components/header/Header";
import Footer from "./components/footer/Footer";
export default function App() {
const [time, setTime] = useState(null);
useEffect(() => {
console.log('use effect..');
setTime(moment().format('hh:mm:ss a'));
}, []);
return (
<div style={{height:'100%'}}>
<Header/>
<div style={{height:'100%'}}>
<h1>Sample App</h1>
<p>Current time is {time}</p>
</div>
<Footer/>
</div>
)
}
// Header.js
import React from 'react';
export default function Header() {
function goPage(path) {
console.log(`goPage..`);
console.log(window.location); // error
// window.location = path;
}
return (
<header style={{width: '100%', border: '1px dotted black'}}>
<div>
<span style={{padding: '4px'}}>My App |</span>
<span style={{padding: '4px', cursor: 'pointer'}}>Home</span>
<span style={{padding: '4px', cursor: 'pointer'}} onClick={goPage('/about')}>About</span>
</div>
</header>
)
}
server.js
const express = require('express');
const path = require('path');
import React from 'react';
import ReactDOMServer from 'react-dom/server';
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('../webpack.config.js');
const compiler = webpack(config);
import Router from '../routes/index';
import App from '../src/App';
import Html from "../src/Html";
const expressApp = express();
const port = process.env.PORT || 3000;
if (process.env.NODE_ENV != 'production') {
expressApp.use(webpackDevMiddleware(compiler, {
publicPath: '/'
}));
}
expressApp.use(express.static(path.join(__dirname, '')));
expressApp.get('*', (request, response) => {
console.log('request.. in express');
console.log(Router.match(request));
const component = ReactDOMServer.renderToString(Router.match(request));
const html = ReactDOMServer.renderToStaticMarkup(<Html
title="Sample Title"
description="Isomorphic web application sample"
body={component}
/>);
response.send(`<!DOCTYPE html>` + html);
});
expressApp.listen(port, () => {
console.log(`App is listening at http://localhost:${port}/`);
});
// Router
import React from 'react';
import App from "../src/App";
import NotFound from "../src/components/error/NotFound";
import Error from "../src/components/error/Error";
import About from "../src/components/about/About";
const routes = [
{name : 'home', path : '/', action : () => <App/>},
{name : 'about', path : '/about', action : () => <About/>},
{name : '404', path : '/404', action : () => <NotFound/>},
{name : '500', path : '/500', action : () => <Error/>},
];
export default {
match(location) {
console.log(location.path);
const route = routes.find(x => x.path === location.path);
if (route) {
try {
return route.action();
} catch (err) {
console.log('err');
return routes.find(x => x.path === '/500').action();
}
} else {
console.log('404');
return routes.find(x => x.path === '/404').action();
}
}
}
// webpack config
const path = require('path');
// plugin
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MomentLocalesPlugin = require('moment-locales-webpack-plugin');
const nodeExternals = require('webpack-node-externals');
// SSR
const ssr = {
mode : 'development',
entry: './server/server.js',
output: {
path: path.join(__dirname, '/build'),
filename: "server.js",
},
target: 'node',
node: false,
externals: [nodeExternals()],
resolve: {
modules: ['node_modules']
},
module: {
rules: [
{
test: /(.js|.jsx)/,
exclude: /node_modules/,
include: [
path.join(__dirname, '/src'),
path.join(__dirname, '/routes/index.js'),
path.join(__dirname, '/server/server.js'),
],
use: {
loader: 'babel-loader'
}
},
]
},
plugins: [
new CleanWebpackPlugin({
cleanAfterEveryBuildPatterns: ['build']
})
],
};
// CSR
const csr = {
mode : 'development',
entry:'./client/client.js',
output: {
publicPath: '/',
path: path.join(__dirname, '/build'),
filename: 'client.js'
},
target: 'web',
module: {
rules: [
{
test: /(.js|.jsx)/,
exclude: /node_modules/,
include: [
path.join(__dirname, '/src'),
path.join(__dirname, '/routes/index.js'),
path.join(__dirname, '/client/client.js'),
],
use: {
loader: 'babel-loader'
}
},
]
},
// devtool: 'source-map',
plugins: [
new MomentLocalesPlugin({
localesToKeep: ['es-us', 'ru'],
}),
]
};
module.exports = [ssr, csr];
The server is composed of express, and is set to babel7 and webpack4.
If you look at the server.js part, you get the corresponding react component in the request URL.
If renderToString() is called on the obtained component, an error of window is not defined occurs.
I really want to solve it. Please help.
Why window is not defined in SSR
On the ssr there is no definition for window. Because window is a browser based property.
you need to do something to avoid that. e.g
function goPage(path) {
console.log(`goPage..`);
if (window) {
console.log(window.location);
}
}
and on the server side you can get the url by this
req.url
// or
app.get('/users/:userId/books/:bookId', function (req, res) {
console.log(req.params)
}

Use custom middleware for async actions. Actions must be plain objects

I am new to this, and this error occurs, looked at a lot of solutions on it, nothing helped to fix.
Another mistake in store.js. When point to thunk.
Argument type ThunkMiddleware & {withExtraArgument (extraArgument: E): ThunkMiddleware{}, AnyAction, E>} is not assignable to parameter type Function
Actions.js
export const SET_YEAR = 'SET_YEAR';
export const FETCH_USERS_EXAMPLE = "FETCH_USERS_EXAMPLE";
export function setYear(year) {
return {
type: 'SET_YEAR',
payload: year,
}
}
export async function getFreeData() {
try {
return async (dispatch) => {
let res = await fetch(`https://jsonplaceholder.typicode.com/users`);
let userList = await res.json();
dispatch({
type: "FETCH_USERS_EXAMPLE",
payload: userList
});
return userList;
}
} catch (e) {
console.log("Error", e);
}
}
Reducer.js
import {SET_YEAR, FETCH_USERS_EXAMPLE} from '../Actions/TestAction';
export function testReducer(state ={year: ''}, action) {
switch (action.type) {
case 'SET_YEAR':
return {...state, year: action.payload};
case 'FETCH_USERS_EXAMPLE':
return {...state, userList: action.payload};
default:
return state
}
}
Container.js
import TestComponent from "./TestComponent";
import {setYear, getFreeData} from "../../Redux/Actions/TestAction";
import {connect} from "react-redux";
import React from "react";
const mapStateToProps = (store) => ({
items: store.user,
userList: store.page.userList,
year: store.page.year
});
const mapDispatchToProps = {
setYear,
getFreeData
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(TestComponent);
Store.js
import {createStore, applyMiddleware} from 'redux'
import {rootReducer} from './Reducers/rootReducer';
import thunk from 'redux-thunk';
import logger from "redux-logger";
export const store = createStore(rootReducer, applyMiddleware(thunk, logger));
webpack
const path = require("path");
module.exports = {
entry: ['babel-polyfill', "./src/index.js"],
mode: "development",
output: {
filename: "./main.js"
},
devServer: {
contentBase: path.join(__dirname, "dist"),
compress: true,
port: 3000,
watchContentBase: true,
progress: true
},
devtool: "source-map",
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: "babel-loader"
}
},
{
test: /\.html$/,
use: ['raw-loader']
},
{
test: /\.scss$/,
use: [
{
loader: "style-loader"
},
{
loader: "css-loader"
},
{
loader: "sass-loader"
}
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: ["file-loader"]
}
]
}
};
Your getFreeData function should not be async or it will return a promise.
Instead, return the function for redux-thunk:
export function getFreeData() {
return async (dispatch) => {
try {
let res = await fetch(`https://jsonplaceholder.typicode.com/users`);
let userList = await res.json();
dispatch({
type: "FETCH_USERS_EXAMPLE",
payload: userList
});
return userList;
}
catch (e) {
console.log("Error", e);
}
}
}
Hope this helps.

Categories

Resources