App Structure
I'm using Next JS in Host app and React JS in remote app. Both apps are using below library as dependency
Note
I'm using ModuleFederationPlugin for both app and not using Next Js Specific module federation plugin
Remote app deps
"#mui/icons-material": "^5.11.0",
"#mui/material": "^5.11.4",
"#mui/styles": "^5.11.2",
"#emotion/react": "^11.10.5",
"#emotion/styled": "^11.10.5",
Host app deps
"#emotion/react": "^11.10.4",
"#emotion/styled": "^11.10.4",
"#mui/icons-material": "^5.10.9",
"#mui/lab": "^5.0.0-alpha.103",
"#mui/material": "^5.10.9",
"#mui/styles": "^5.10.9",
Problem Statement
I'm having a problem when trying to share material related module in Host app. When running host app, it shown below error.
I don't want to add eager: true in material related dependencies since its ended up creating large initial chunk which I don't want. Also removing material related dependencies from Host app seems not a good idea because then multiple instance warning is showing.
The remote app is also configured as shown here
Please share some thought on how to share these deps in an optimised way.
Error Screenshot
Host app shared config (Next JS App)
const dotenvFile = `.env.${process.env.DOTENV_RUNTIME || 'local'}`
require('dotenv').config({ path: dotenvFile })
const deps = require('../../package.json').dependencies
module.exports = {
distDir: '../../dist/client',
async rewrites() {
/*
This rewrites method is needed to persist route state while using MFE.
Keep adding base route here when onboarding new MFE App
*/
return [ ]
},
webpack: (config, { webpack, isServer }) => {
const { ModuleFederationPlugin } = webpack.container
if (!isServer) {
config.plugins.push(
new webpack.IgnorePlugin({
resourceRegExp: /urlencoded-body-parser/,
}),
new ModuleFederationPlugin({
name: 'Host ui',
remotes: {},
shared: {
react: {
eager: true,
singleton: true,
requiredVersion: deps.react,
},
'react-dom': {
eager: true,
singleton: true,
requiredVersion: deps['react-dom'],
},
'#mui/materia': {
requiredVersion: deps['#mui/materia'],
singleton: true,
},
'#mui/icons-material': {
requiredVersion: deps['#mui/icons-material'],
singleton: true,
},
'#mui/styles': {
requiredVersion: deps['#mui/styles'],
singleton: true,
},
'#emotion/react': {
requiredVersion: deps['#emotion/react'],
singleton: true,
},
},
}),
)
}
return config
},
}
Remote app shared config (React JS App)
shared: {
react: {
requiredVersion: deps.react,
singleton: true,
},
'react-dom': {
requiredVersion: deps['react-dom'],
singleton: true,
},
'react-router': {
requiredVersion: deps['react-router'],
singleton: true,
},
'react-router-dom': {
requiredVersion: deps['react-router-dom'],
singleton: true,
},
'#mui/materia': {
requiredVersion: deps['#mui/materia'],
singleton: true,
},
'#mui/icons-material': {
requiredVersion: deps['#mui/icons-material'],
singleton: true,
},
'#mui/styles': {
requiredVersion: deps['#mui/styles'],
singleton: true,
},
'#emotion/react': {
requiredVersion: deps['#emotion/react'],
singleton: true,
},
},
Github Issue link - https://github.com/module-federation/module-federation-examples/issues/2740
I have tried adding eager:true in Host app but its making initial chunk 5 MB which is not expected. My expectation is my configuration works without adding eager key in any app.
Related
I have a React component library that is built by webpack 5.75.0. The library exports 2 components:
// src/index.ts
export { default as ComponentA } from "components/ComponentA";
export { default as ComponentB } from "components/ComponentB";
A simple CRA app uses this library and imports only ComponentA. But when analyzing the bundle I see that ComponentB's dependencies are in it and have not been tree-shaken.
The parent app is just a CRA app with no config overrides. Tree shaking works there for other modules (MUI, lodash-es, etc), so the problem is with the my library.
I seem to have followed all requirements: in my package.json I do have
"sideEffects": false,
"main": "lib/index.js",
"module": "lib/index.js",
Here's my babel config:
module.exports = {
presets: [
["#babel/preset-env", { targets: { node: "current", modules: false } }],
"#babel/preset-typescript",
[
"#babel/preset-react",
{
runtime: "automatic",
},
],
],
plugins: [
"#babel/plugin-syntax-jsx",
[
"#babel/plugin-transform-runtime",
{
regenerator: true,
useESModules: true,
},
],
],
};
And here's my webpack config:
const config = {
mode: "production",
devtool: false,
entry: "./src/index.ts",
output: {
path: path.resolve(__dirname, "lib"),
filename: "index.js",
module: true,
library: {
type: "module",
},
},
plugins: [
//...
],
module: {
rules: [
//...
],
},
resolve: {
//...
},
externals: [
"react",
"react-dom",
],
experiments: {
outputModule: true,
},
optimization: {
minimize: false,
},
};
But still, the ComponentB's dependencies are showing up in the bundle of the CRA app that uses this library.
Am I missing something? Is there some official example of how to configure tree-shaking library with webpack 5?
Thanks.
P.S. My question relates to this question, but since 2021 it's become possible to build ES modules with Webpack 5.
I used a few web components in my Svelte project, but after running npm run dev (which is vite dev actually), error HTMLElement is not defined shows up.
The whole error message is
HTMLElement is not defined
ReferenceError: HTMLElement is not defined
at /src/components/shared/formkit/classes/composite.js:21:39
at async instantiateModule (file:///D:/my_project/node_modules/vite/dist/node/chunks/dep-0fc8e132.js:50548:9)
The problematic line of code is
// File name is composite.js
export class FormKitComposite extends HTMLElement {
But it works fine in Storybook. No error shows up there.
Could anyone teach me how to solve it please?
Thanks in advance!
tsconfig.json:
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": true,
"noUnusedLocals": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"types": [
"#testing-library/jest-dom",
"vitest/globals"
]
},
"include": [
"decs.d.ts"
]
}
vite.config.ts:
const config: UserConfig = {
plugins: [sveltekit()],
resolve: {
alias: {
$components: path.resolve('src/components'),
$functions: path.resolve('src/functions'),
$graphql: path.resolve('src/graphql'),
$stores: path.resolve('src/stores'),
$styles: path.resolve('src/styles'),
$types: path.resolve('src/types')
}
},
server: {
fs: {
strict: false
}
},
test: {
environment: 'jsdom',
globals: true,
include: ['src/components/**/*.test.ts', 'src/functions/**/*.test.ts'],
setupFiles: ['./setup-test.ts'],
coverage: {
branches: 100,
functions: 100,
lines: 100,
statements: 100
}
}
};
It is because web components cannot be rendered in server side.
To solve it, use dynamic import like
onMount(async () => {
await import('your file URL');
});
I have set up a container app that consumes several remote apps. The issue is that I need the remote urls to be dynamic based on what environment they are in (Test, Dev, QA). As you can see my vehicle remote url is hardcoded pointing to dev. I need this URL to be updated based on the environment. I want to use env variables if possible. I can't find a clear answer and was hoping someone would have some suggestions.
const devConfig = {
mode: "development",
devServer: {
port: 3000,
historyApiFallback: true,
},
plugins: [
new ModuleFederationPlugin({
name: "container",
filename: "remoteEntry.js",
remotes: {
vehicle:
"vehicle#https://vehicle-mf-dev.com/remoteEntry.js",
}
exposes: {},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},
}),
],
};
Getting this issue when trying to run tests on my Angular microfrontend to use inside Module Federation. Angular 12, Webpack 5.
Uncaught Error: Shared module is not available for eager consumption: 5487
How to configure eager consumption for these modules?
In most cases, solve this issue by setting eager: true on your shared common Angular modules:
shared: share({
"#angular/core": { singleton: true, strictVersion: true, requiredVersion: 'auto', eager: true },
"#angular/common": { singleton: true, strictVersion: true, requiredVersion: 'auto', eager: true },
"#angular/common/http": { singleton: true, strictVersion: true, requiredVersion: 'auto', eager: true },
"#angular/router": { singleton: true, strictVersion: true, requiredVersion: 'auto', eager: true },
Create a separate webpack.conf for testing
You can set all shared modules to eager: true in your primary webpack.config.js, but that would force you to use a larger bundle size, one of the things Module Federation aims to avoid.
A better option could be to set up a separate webpack.test.config.js, which will just be used for running tests, and in that file set your modules to eager: true:
webpack.test.config.js
shared: share({
"#angular/core": {
eager: true,
singleton: true,
strictVersion: true,
requiredVersion: 'auto'
},
"#angular/common": {
eager: true,
singleton: true,
strictVersion: true,
requiredVersion: 'auto'
},
"#angular/common/http": {
eager: true,
singleton: true,
strictVersion: true,
requiredVersion: 'auto'
}
})
I'm having a weird issue when using styled-components along with VSCode. Below is basically what I get for any components coming from styled-components, they all return any.
I got it working before, but can't tell when and I can't see what's wrong in the setup to return any for all the components. I tried to move back to tslint config, removing/commenting out all rules inside the eslintrc files, but couldn't make it work either.
Supprisingly enough, I tried the starter kit I'm using for my project and the types there are working with the original setup.
I tried to use the same version of styled-components packages, but still couldn't make it work. Any help, or direction to look at this issue would be very welcomed!
.eslintrc.js
module.exports = {
env: {
browser: true,
es6: true,
},
extends: [
'plugin:#typescript-eslint/recommended',
'plugin:#typescript-eslint/recommended-requiring-type-checking',
'plugin:react/recommended',
'prettier/#typescript-eslint',
],
parser: '#typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
sourceType: 'module',
ecmaFeatures: { jsx: true },
},
plugins: ['#typescript-eslint', 'react', 'react-native'],
rules: {
camelcase: 'off',
'react/display-name': 'off',
'react/prop-types': 'off',
'#typescript-eslint/ban-ts-ignore': 'off',
'#typescript-eslint/camelcase': 'off',
'#typescript-eslint/explicit-function-return-type': 'off',
'#typescript-eslint/interface-name-prefix': 'off',
'#typescript-eslint/no-explicit-any': 'off',
'#typescript-eslint/no-use-before-define': 'off',
'#typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'#typescript-eslint/no-non-null-assertion': 'off',
'#typescript-eslint/unbound-method': 'off',
'#typescript-eslint/no-unsafe-assignment': 'off',
'#typescript-eslint/no-unsafe-call': 'off',
'#typescript-eslint/no-unsafe-member-access': 'off',
'#typescript-eslint/no-unsafe-return': 'off',
'#typescript-eslint/no-misused-promises': [
'error',
{
checksVoidReturn: false,
},
],
'#typescript-eslint/explicit-module-boundary-types': ['error', { allowArgumentsExplicitlyTypedAsAny: true }],
},
settings: {
react: {
pragma: 'React',
version: 'detect',
},
},
ignorePatterns: ['node_modules/**/*', 'docs/**/*', 'examples/**/*', 'lib/**/*'],
};
tsconfig.json
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"baseUrl": "./src",
"experimentalDecorators": true,
"inlineSources": true,
"jsx": "react",
"lib": ["es2017", "dom"],
"module": "commonjs",
"moduleResolution": "node",
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"plugins": [
{
"name": "typescript-styled-plugin",
"lint": {
"validProperties": [
"shadow-color",
"shadow-opacity",
"shadow-offset",
"shadow-radius",
"padding-horizontal",
"padding-vertical",
"margin-vertical",
"margin-horizontal",
"tint-color",
"aspect-ratio",
"elevation"
]
}
}
],
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"sourceRoot": "./src",
"strict": true,
"strictPropertyInitialization": false,
"suppressImplicitAnyIndexErrors": true,
"target": "es2015"
},
"include": [".eslintrc.js", "src/**/*.ts", "src/**/*.tsx"]
}
"lint": "yarn format && yarn eslint && yarn stylelint",
"eslint": "tsc -p . --noEmit --skipLibCheck; eslint --fix 'src/**/*.{ts,tsx}'",
...
"#typescript-eslint/eslint-plugin": "3.8.0",
"#typescript-eslint/parser": "3.8.0",
"eslint": "7.6.0",
"eslint-config-prettier": "6.11.0",
"eslint-plugin-react": "7.20.5",
"eslint-plugin-react-native": "3.8.1",
styled.d.ts
import 'styled-components';
declare module 'styled-components' {
// tslint:disable-next-line
export interface DefaultTheme {
darkMode: boolean;
background: string;
lightBackground: string;
grayBackground: string;
darkBackground: string;
heading: string;
subheading: string;
copy: string;
stroke: string;
underlay: string;
map: string;
}
}
This is how I solved it -
after navigating to the types file of styled-components inside the project I saw this error: node_modules/hoist-non-react-statics/index"' has no exported member 'NonReactStatics'. ts
It turned out that I had packages using an older version of hoist-non-react-static, and it was overriding the types being used by styled-components.
upgrading those packages (in my case it was react-redux) solved the problem.
Types got quietly moved to #types/styled-components-react-native. Still good to update your other dependencies, of course :)
Reference: https://github.com/styled-components/styled-components/issues/2099