I’m investigating webpack’s module federation and the potential for allowing separate teams to choose their own frameworks and still share UI elements on one website. I have set up a number of different projects to test module federation with different build tools.
What I have tested is:
- webpack remote with webpack host (works)
- webpack remote with rspack host (works)
- webpack remote with vite host (works when webpack is configured to output esm modules)
- rspack remote with webpack host (works)
- vite host with vite remote (works)
- vite host with webpack remote (does not work)
I’m able to get module federation to work when I have a webpack remote that is configured to use esm modules that are being hosted by a vite host. However, with the same configuration, I cannot get a webpack host to host modules from a vite remote. When I have a webpack remote and a webpack host both configured to output esm modules, I get the same behavior where the webpack host will not load the modules.
There error being output in the console on the webpack host is:
Uncaught Syntax Error: Cannot use import statement outside a module
My webpack.config.js file for the webpack host is:
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);import path from 'path';
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
import { VueLoaderPlugin } from 'vue-loader';
import * as fs from 'fs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
import { fileURLToPath } from "url";
let packageJson = JSON.parse(fs.readFileSync('./package.json').toString());
let deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
export default {
entry: './src/index.ts',
mode: 'development',
devtool: 'inline-source-map',
devServer: {
port: 8000,
historyApiFallback: true
},
module: {
rules: [
{
test: /.m?js/,
type: "javascript/auto",
resolve: {
fullySpecified: false,
},
},
{
test: /.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /.vue$/,
loader: 'vue-loader'
},
// this will apply to both plain `.css` files
// AND `<style>` blocks in `.vue` files
{
test: /.css$/,
exclude: /node_modules/,
use: [
'vue-style-loader',
'css-loader'
//{
// loader: 'css-loader',
// options: { }
//}
]
}
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
//module: true,
//library: { type: 'module' },
//chunkFormat: 'module',
//chunkLoading: 'import',
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
uniqueName: 'webpack-client'
},
target: 'es2020',
experiments: {
outputModule: true
},
//externalsType: "module",
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: './index.html'
//template: './index.ejs',
//inject: false
}),
new ModuleFederationPlugin({
name: "host",
library: { type: 'module' },
remotes: {
web_common: "web_common@http://localhost:8082/remoteEntry.js",
vite_remote: "vite_remote@http://localhost:8083/assets/remoteEntry.js"
},
shared: {
...deps,
vue: {
eager: true,
singleton: true,
requiredVersion: deps.vue
},
}
})
],
//optimization: { splitChunks: { chunks: 'initial' } }
};
My tsconfig.json for my webpack host looks like:
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
//"module": "ES2022",
//"target": "es5",
"jsx": "react",
"allowJs": true,
"moduleResolution": "Node",
//"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
//"moduleResolution": "bundler",
//"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
//"noEmit": true,
//"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
}
}
My vite.config.ts for my vite remote looks like:
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import federation from '@originjs/vite-plugin-federation';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
federation({
name: 'vite_remote',
filename: 'remoteEntry.js',
exposes: {
'./moduleC': './src/components/moduleC.vue'
},
shared: [
'vue'
]
})
],
build: {
target: 'ES2022'
}
})
My tsconfig.json for my vite remote looks like:
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
I’d like to be able to have bidirectional module federation working between a webpack project and a vite project. If this is not possible, I’d like to understand why it is not and why I am getting the error that I am seeing in the console for the webpack host.