Skip to content

Commit

Permalink
docs: add dynamic remote to example
Browse files Browse the repository at this point in the history
  • Loading branch information
judithhartmann committed Oct 6, 2024
1 parent dd525d8 commit 44a86e3
Show file tree
Hide file tree
Showing 28 changed files with 2,989 additions and 7,519 deletions.
31 changes: 31 additions & 0 deletions e2e/vite-webpack-rspack/tests/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,34 @@ test.describe('Webpack remote', () => {
await expect(furtherRecommendations).toBeVisible();
});
});

test.describe('Dynamic remote', () => {
test('shows dynamic banner on toggle', async ({ page, baseURL }) => {
await page.goto(baseURL!);
const showAdToggle = page.getByRole('checkbox', { name: 'Show Dynamic Ad', exact: true });

const signUpBanner = page.getByRole('heading', { level: 2, name: 'Sign up now!', exact: true });
const specialPromoBanner = page.getByRole('heading', {
level: 2,
name: 'Up to 50% off!',
});

await showAdToggle.check({ force: true });

// Special Promo banner should be visible after toggling
await expect(specialPromoBanner).toBeVisible();
await expect(signUpBanner).not.toBeVisible();

// Toggle again, no banner should be visible
await showAdToggle.uncheck({ force: true });

await expect(signUpBanner).not.toBeVisible();
await expect(specialPromoBanner).not.toBeVisible();

// Toggle again, SignUpBanner should be visible
await showAdToggle.check({ force: true });

await expect(signUpBanner).toBeVisible();
await expect(specialPromoBanner).not.toBeVisible();
});
});
24 changes: 24 additions & 0 deletions examples/vite-webpack-rspack/dynamic-remote/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
8 changes: 8 additions & 0 deletions examples/vite-webpack-rspack/dynamic-remote/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# React + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
35 changes: 35 additions & 0 deletions examples/vite-webpack-rspack/dynamic-remote/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import js from '@eslint/js';
import globals from 'globals';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';

export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
settings: { react: { version: '18.3' } },
plugins: {
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
},
},
];
13 changes: 13 additions & 0 deletions examples/vite-webpack-rspack/dynamic-remote/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
26 changes: 26 additions & 0 deletions examples/vite-webpack-rspack/dynamic-remote/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "multi-example-remote",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"start": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"@module-federation/vite": "workspace:*"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"globals": "^15.9.0",
"tailwindcss": "^3.4.13",
"vite": "^5.4.1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
13 changes: 13 additions & 0 deletions examples/vite-webpack-rspack/dynamic-remote/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import SignUpBanner from './SignUpBanner';
import SpecialPromo from './SpecialPromo';

function App() {
return (
<div>
<SignUpBanner />
<SpecialPromo />
</div>
);
}

export default App;
14 changes: 14 additions & 0 deletions examples/vite-webpack-rspack/dynamic-remote/src/SignUpBanner.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const SignUpBanner = () => {
return (
<div className="bg-blue-500 text-white p-4 m-4 rounded ring-offset-ring-4 flex justify-between">
<div>
<h2 className="text-xl">Sign up now!</h2>
<p>Get started with our amazing service today.</p>
<button className="bg-white text-blue-500 p-2 rounded mt-2">Sign up</button>
</div>
<img src="https://picsum.photos/200/200" alt="Random" className="mt-2" />
</div>
);
};

export default SignUpBanner;
13 changes: 13 additions & 0 deletions examples/vite-webpack-rspack/dynamic-remote/src/SpecialPromo.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const SpecialPromo = () => {
return (
<div className="bg-red-500 text-white p-4 m-4 rounded ring-offset-ring-4 flex justify-between">
<div>
<h2 className="text-xl">Up to 50% off!</h2>
<p>Only for a limited time.</p>
</div>
<img src="https://picsum.photos/200/200" alt="Random" className="mt-2" />
</div>
);
};

export default SpecialPromo;
10 changes: 10 additions & 0 deletions examples/vite-webpack-rspack/dynamic-remote/src/bootstrap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.jsx';
import './index.css';

createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
);
3 changes: 3 additions & 0 deletions examples/vite-webpack-rspack/dynamic-remote/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
1 change: 1 addition & 0 deletions examples/vite-webpack-rspack/dynamic-remote/src/main.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import('./bootstrap');
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {},
},
plugins: [],
};
29 changes: 29 additions & 0 deletions examples/vite-webpack-rspack/dynamic-remote/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { federation } from '@module-federation/vite';
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
federation({
name: 'dynamicRemote',
filename: 'remoteEntry.js',
// Modules to expose
exposes: {
'./SignUpBanner': './src/SignUpBanner.jsx',
'./SpecialPromo': './src/SpecialPromo.jsx',
},
shared: ['react', 'react-dom'],
}),
],
server: {
port: 4002,
},
build: {
modulePreload: false,
target: 'esnext',
minify: false,
cssCodeSplit: false,
},
});
36 changes: 35 additions & 1 deletion examples/vite-webpack-rspack/host/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React, { lazy, StrictMode, Suspense } from 'react';
import { registerRemotes } from '@module-federation/runtime';
import React, { lazy, StrictMode, Suspense, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
import { Footer } from './components/Footer';
import { Header } from './components/Header';
import { Toggle } from './components/Toggle';
import { useDynamicImport } from './hooks/useDynamicImport';
import './index.css';

const RemoteProduct = lazy(
Expand All @@ -20,11 +23,42 @@ const WebpackRelated = lazy(
import('webpack/Related')
);

const DynamicSignUpBanner = lazy(() => import('./components/DynamicSignUpBanner'));

// if module federation is already setup through plugin in vite.config.js,
// there is no need to call init() from '@module-federation/runtime' here
registerRemotes([
{
name: 'dynamicRemote',
entry: 'http://localhost:4002/remoteEntry.js',
type: 'module',
},
]);

const App = () => {
const [showAd, setShowAd] = React.useState(false);
const [randomBanner, setRandomBanner] = React.useState<'SignUpBanner' | 'SpecialPromo'>(
'SignUpBanner'
);

const Banner = useDynamicImport({
module: randomBanner,
scope: 'dynamicRemote',
});

useEffect(() => {
// alternate between SignUpBanner and SpecialPromo on toggle
if (showAd) {
setRandomBanner((prev) => (prev === 'SignUpBanner' ? 'SpecialPromo' : 'SignUpBanner'));
}
}, [showAd]);

return (
<div className="bg-white">
<Header />
<main className="mx-auto mt-8 max-w-2xl px-4 pb-16 sm:px-6 sm:pb-24 lg:max-w-7xl lg:px-8">
<Toggle label="Show Dynamic Ad" checked={showAd} onValueChange={setShowAd} />
{showAd && <Suspense fallback="Loading...">{Banner ? <Banner /> : null}</Suspense>}
<Suspense fallback="Loading...">
<RemoteProduct />
</Suspense>
Expand Down
23 changes: 23 additions & 0 deletions examples/vite-webpack-rspack/host/src/components/Toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';

interface ToggleProps {
label: string;
checked: boolean;
onValueChange: (checked: boolean) => void;
}

export const Toggle = ({ checked, label, onValueChange }: ToggleProps) => {
return (
<label className="inline-flex items-center cursor-pointer">
<input
checked={checked}
type="checkbox"
value=""
className="sr-only peer"
onChange={(event) => onValueChange(event.target.checked)}
/>
<div className="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
<span className="ms-3 text-sm font-medium">{label}</span>
</label>
);
};
30 changes: 30 additions & 0 deletions examples/vite-webpack-rspack/host/src/hooks/useDynamicImport.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { loadRemote } from '@module-federation/runtime';
import { ElementType, useEffect, useState } from 'react';

interface DynamicImportProps {
module: string;
scope: string;
}

export function useDynamicImport({ module, scope }: DynamicImportProps) {
const [component, setComponent] = useState<ElementType | null>(null);

useEffect(() => {
if (!module || !scope) return;

const loadComponent = async () => {
try {
const { default: Component } = (await loadRemote(`${scope}/${module}`)) as {
default: ElementType;
};
setComponent(() => Component);
} catch (error) {
console.error(`Error loading remote module ${scope}/${module}:`, error);
}
};

loadComponent();
}, [module, scope]);

return component;
}
1 change: 1 addition & 0 deletions examples/vite-webpack-rspack/host/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export default {
content: [
'./src/**/*.{js,jsx,ts,tsx}',
'../remote/src/**/*.{js,jsx,ts,tsx}',
'../dynamic-remote/src/**/*.{js,jsx,ts,tsx}',
'../rspack/src/**/*.{js,jsx,ts,tsx}',
'../webpack/src/**/*.{js,jsx,ts,tsx}',
],
Expand Down
4 changes: 3 additions & 1 deletion examples/vite-webpack-rspack/remote/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Product from './Product';

function App() {
return <></>;
return <Product />;
}

export default App;
3 changes: 2 additions & 1 deletion examples/vite-webpack-rspack/rspack/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import './index.css';
import Reviews from './Reviews';

export const App = () => <></>;
export const App = () => <Reviews />;
6 changes: 6 additions & 0 deletions examples/vite-webpack-rspack/rspack/src/bootstrap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';

const root = ReactDOM.createRoot(document.getElementById('app')!);
root.render(<App />);
Loading

0 comments on commit 44a86e3

Please sign in to comment.