react: Uncaught TypeError: factory is not a function
Everythign works in dev mode, but if you build without adding:
exposes: {
'./App': './src/App', // Needed for the build to work
},
the app won't work and you get Uncaught TypeError: factory is not a function
hi @PaRoxUs which framework are you using? can you create a basic reproduction please?
We are using react, I'll try my best to create an example, but it has to wait ATM.
Got this error once, the cause was that I was using vite-plugin-top-level-await plugin and building for target >chrome87, which doesn't need this plugin.
@PaRoxUs did you solve this issue?
Well, it works if we add the App to the build and I did't have time to create an example with the error.
I'll try to recreate it, so from the remote you are only exposing the components, right?
Its our main app that only host remote components, so it dosen't need to expose any components.
Its our main app that only host remote components, so it dosen't need to expose any components.
Can you share the configurations here please?
import { defineConfig, UserConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { federation } from '@module-federation/vite'
import { dependencies } from './package.json'
import dotenv from 'dotenv'
dotenv.config({
path: '.env.' + process.env.ENV,
})
export default defineConfig((): UserConfig => {
return {
base: process.env.VITE_APP_BASE,
plugins: [
react(),
federation({
name: 'main',
filename: 'main.js',
shared: {
react: {
requiredVersion: dependencies.react,
singleton: true,
},
'react-dom': {
requiredVersion: dependencies['react-dom'],
singleton: true,
},
jotai: {
requiredVersion: dependencies.jotai,
singleton: true,
},
'react-router-dom': {
requiredVersion: dependencies['react-router-dom'],
singleton: true,
},
'react-hook-form': {
requiredVersion: dependencies['react-hook-form'],
singleton: true,
},
'@tanstack/react-query': {
requiredVersion: dependencies['@tanstack/react-query'],
singleton: true,
},
'@tanstack/react-table': {
requiredVersion: dependencies['@tanstack/react-table'],
singleton: true,
},
},
exposes: {
'./App': './src/App',
},
remotes: {
components: {
type: 'module',
name: 'components',
entry: process.env.VITE_REMOTE_URL_COMPONENTS,
entryGlobalName: 'components',
shareScope: 'default',
},
},
}),
],
server: {
host: 'localhost',
origin: process.env.VITE_APP_BASE,
port: 5170,
proxy: {
'/api': {
target: 'http://localhost:8054',
changeOrigin: true,
},
},
},
build: {
modulePreload: false,
target: 'esnext',
minify: false,
},
}
})
you should solve the issue with modulepreload: true it's the default btw
Hey @gioboa Here is our case (Vue 3.5.13, Vite 6.0.7) with the same behavior: if we don't expose App we get "TypeError: factory is not a function". So, it's not react-specific. My guess: there is something with wrong order of imports if we don't expose App.
import { fileURLToPath, URL } from 'node:url'
import { existsSync } from 'node:fs'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import components from 'unplugin-vue-components/vite'
import autoImport from 'unplugin-auto-import/vite'
import { federation } from '@module-federation/vite'
import unheadVite from '@unhead/addons/vite'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
//@ts-expect-error
import { StudioUiResolver } from '@appmaster/studio-ui/resolver/index'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
return {
base: env.VITE_BASE_URL,
server: {
port: 8080,
proxy: {
'/api': { target: 'https://[MASKED]' },
},
},
preview: {
proxy: {
'/api': { target: 'https://[MASKED]' },
},
},
optimizeDeps: {
include: ['qs', 'tailwindcss/plugin', 'naive-ui'],
},
build: {
target: 'esnext',
sourcemap: true,
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
plugins: [
vue(),
unheadVite(),
components({
extensions: ['vue'],
include: [/\.vue$/, /\.vue\?vue/],
directoryAsNamespace: true,
resolvers: [
NaiveUiResolver(),
StudioUiResolver({ existsSync, sideEffects: env.NODE_ENV === 'development' }),
],
}),
autoImport({
dirs: ['src/use', 'src/store', 'src/utils', 'src/types'],
imports: [
'vue',
'vue-i18n',
'vue-router',
{
'naive-ui': ['useMessage'],
'@vueuse/rxjs': ['useSubscription'],
},
],
}),
federation({
name: 'base-app',
filename: 'remoteEntry-[hash].js',
manifest: true,
// Uncomment to fix factory is not a function error
// exposes: {
// './App': './src/App.vue',
// },
remotes: {
'mod-auth': {
type: 'module',
name: 'mod-auth',
entry: '/auth/mf-manifest.json',
},
'mod-workspace': {
type: 'module',
name: 'mod-workspace',
entry: '/workspace/mf-manifest.json',
},
},
shared: {
axios: { singleton: true },
vue: { singleton: true },
'vue-i18n': { singleton: true },
'vue-router': { singleton: true },
dayjs: { singleton: true },
},
}),
],
}
})
It looks like in our particular case we have an issue with unplugin-auto-import If we remove it and use manual imports across the project it starts working.
you should solve the issue with modulepreload: true it's the default btw
Did not help. Actually I came across this issue #78 (which has been closed, but I guess need to re-open it) and it seems to be very similar w.r.t this issue. I've shared my findings there and I think it might be helpful for debugging this.
but @PaRoxUs @OlegSotnikov , why do we need to expose the entire App in the first place ? 🤔
@PaRoxUs @tejas-1206 @OlegSotnikov
I hit this recently but it was due to mismatched app names causing multiple FederatedHosts to exists and using the one without shared items.
If you are able to give a reprod/demo to your case it could be helpful. What I can say is that factory comes from the sharing code which means at that moment the shared map is empty.
but @PaRoxUs @OlegSotnikov , why do we need to expose the entire App in the first place ? 🤔
Did you read through the whole issue? The build fails without including it.
can confirm this same issue - was able to fix it by doing the mentioned expose in OP
but @PaRoxUs @OlegSotnikov , why do we need to expose the entire App in the first place ? 🤔
Did you read through the whole issue? The build fails without including it.
Sorry for the late response, but I did. I did. But if I only want to expose certain parts of my app and not the entire thing, how would I do that then ? That was what I meant. Why expose the entire App ? Why not only the exact parts of the App, if both remote and host run on the same react, ts, UI lib versions ?
but @PaRoxUs @OlegSotnikov , why do we need to expose the entire App in the first place ? 🤔
Did you read through the whole issue? The build fails without including it.
Sorry for the late response, but I did. I did. But if I only want to expose certain parts of my app and not the entire thing, how would I do that then ? That was what I meant. Why expose the entire App ? Why not only the exact parts of the App, if both remote and host run on the same react, ts, UI lib versions ?
I do this just fine. See https://github.com/LumeWeb/web/blob/31f29d40d6a725654b452c643ec1c314080e2799/libs/portal-plugin-dashboard/plugin.config.ts and im the one whos been working to improve stuff like https://github.com/module-federation/vite/pull/330
Ive had these issues before and it can be advanced to solve them, but it was usually a design/setup error, though it took me a lot of debugging to trace.
If you are able to create a somewhat minimal repro, then we might be able to help.