diff --git a/.github/workflows/on-pull-request.yml b/.github/workflows/on-pull-request.yml index 9169f17f715..20fd30315f4 100644 --- a/.github/workflows/on-pull-request.yml +++ b/.github/workflows/on-pull-request.yml @@ -264,22 +264,23 @@ jobs: npx cypress verify || true fi - - name: Install Playwright for projects that need it + - name: Install Playwright browsers for projects that use it run: | - if [ "${{ matrix.container }}" = "bi-directional" ]; then - echo "Setting up Playwright for bi-directional example..." - cd ${{ matrix.container }} - - # Check if we need to install Playwright browsers - if [ ! -d "$HOME/.cache/ms-playwright" ] || [ -z "$(ls -A $HOME/.cache/ms-playwright 2>/dev/null)" ]; then - echo "Installing Playwright browsers and dependencies..." - npx playwright install --with-deps chromium - else - echo "Playwright browsers already cached, installing system dependencies only..." - npx playwright install-deps chromium - fi - - cd .. + echo "Installing Playwright browsers for projects that use it..." + + # Install Playwright package first if not already available + if ! command -v playwright &> /dev/null; then + echo "Installing Playwright package..." + npm install -g playwright@^1.54.2 + fi + + # Check if we need to install Playwright browsers + if [ ! -d "$HOME/.cache/ms-playwright" ] || [ -z "$(ls -A $HOME/.cache/ms-playwright 2>/dev/null)" ]; then + echo "Installing Playwright browsers and dependencies..." + npx playwright install --with-deps chromium + else + echo "Playwright browsers already cached, installing system dependencies only..." + npx playwright install-deps chromium fi - name: Run sample webpack e2e tests @@ -298,11 +299,14 @@ jobs: pnpm --filter "${{ matrix.container }}" e2e:ci (lsof -i tcp:3000-3999 -i tcp:4000-4999 -i tcp:8080-8100 | awk 'NR!=1 {print $2}' | xargs -r kill) 2> /dev/null - - name: Create artifacts for Allure report - id: create-artifacts-allure-report + - name: Create artifacts for test reports + id: create-artifacts-test-reports uses: actions/upload-artifact@v4 if: always() with: - name: allure-results - path: 'cypress/results/allure-results' + name: test-results-${{ matrix.container }} + path: | + ${{ matrix.container }}/cypress/results/allure-results + ${{ matrix.container }}/playwright-report + ${{ matrix.container }}/test-results retention-days: 7 diff --git a/advanced-api/dynamic-remotes/README.md b/advanced-api/dynamic-remotes/README.md index a1c03aea2d7..236cb8a4d7f 100644 --- a/advanced-api/dynamic-remotes/README.md +++ b/advanced-api/dynamic-remotes/README.md @@ -1,26 +1,427 @@ # Dynamic Remote Vendor Sharing Example -This example demos a basic host application loading remote component and sharing vendor code dynamically between unknown remotes +This example demonstrates advanced Module Federation capabilities for **dynamic remote loading** and **vendor code sharing** between unknown remotes at runtime. It showcases how a host application can load remote components on-demand without compile-time knowledge of their locations, while efficiently sharing dependencies. -- `app1` is the host application. -- `app2` standalone application which exposes `Widget` component. -- `app3` standalone application which exposes `Widget` component that requires - `momentjs`. +> **⚠️ Important Note**: True dynamic remotes (where you don't know what you're importing at build time) are **very rare** in practice. For most use cases where you need dynamic remote URLs but know the component interfaces, consider using **runtime plugins** instead: +> +> - **[Remote Control Example](../../runtime-plugins/remote-control)** - Dynamic remote URL configuration with runtime plugins +> - **[Remote Router Example](../../runtime-plugins/remote-router)** - Dynamic routing with runtime remote management +> +> These approaches provide better type safety, performance, and maintainability while still allowing runtime URL configuration. -# Running Demo +## Project Overview -Run `pnpm start`. This will build and serve both `app1`, `app2`, and `app3` on -ports `3001`, `3002`, and `3003` respectively. +This example illustrates the power of Module Federation's runtime API for creating truly dynamic micro-frontend architectures. The host application (`app1`) can dynamically load and render components from remote applications (`app2` and `app3`) at runtime, demonstrating vendor sharing optimization where dependencies like React and Moment.js are shared efficiently between applications. -- [localhost:3001](http://localhost:3001/) (HOST) -- [localhost:3002](http://localhost:3002/) (STANDALONE REMOTE) -- [localhost:3003](http://localhost:3003/) (STANDALONE REMOTE) - +**Key Learning Objectives:** +- Runtime remote registration and loading +- Dynamic component rendering with error handling +- Vendor dependency sharing optimization +- Cross-application state and dependency management -# Running Cypress E2E Tests +## Architecture -To run tests in interactive mode, run `npm run cypress:debug` from the root directory of the project. It will open Cypress Test Runner and allow to run tests in interactive mode. [More info about "How to run tests"](../../cypress-e2e/README.md#how-to-run-tests) +### Applications Structure -To build app and run test in headless mode, run `pnpm e2e:ci`. It will build app and run tests for this workspace in headless mode. If tets failed cypress will create `cypress` directory in sample root folder with screenshots and videos. +- **`app1`** - Host Application (Port 3001) + - Serves as the container application + - Dynamically loads remotes using `@module-federation/runtime` + - Manages shared dependencies (React, ReactDOM) + - Provides UI for remote component selection -["Best Practices, Rules amd more interesting information here](../../cypress-e2e/README.md) +- **`app2`** - Remote Application (Port 3002) + - Exposes a `Widget` component + - Uses Moment.js for date formatting + - Demonstrates vendor sharing with external dependencies + +- **`app3`** - Remote Application (Port 3003) + - Exposes a `Widget` component with React hooks + - Uses Moment.js and Redux for state management + - Shows advanced dependency sharing scenarios + +### Dynamic Loading Flow + +``` +1. Host app initializes Module Federation runtime +2. Runtime registers remote entry points +3. User triggers component load via UI +4. Host dynamically imports remote component +5. Shared dependencies are resolved efficiently +6. Component renders with fallback handling +``` + +## Current Implementation + +### Host Application Dynamic Loading + +The host uses the Module Federation runtime API to initialize and load remotes: + +```javascript +// Runtime initialization with remote registration +init({ + name: 'app1', + remotes: [ + { name: 'app2', entry: 'http://localhost:3002/remoteEntry.js' }, + { name: 'app3', entry: 'http://localhost:3003/remoteEntry.js' } + ] +}); + +// Dynamic component loading hook +function useDynamicImport({ module, scope }) { + const [component, setComponent] = useState(null); + + useEffect(() => { + const loadComponent = async () => { + try { + const { default: Component } = await loadRemote(`${scope}/${module}`); + setComponent(() => Component); + } catch (error) { + console.error(`Error loading remote module ${scope}/${module}:`, error); + } + }; + + loadComponent(); + }, [module, scope]); + + return component; +} +``` + +### Vendor Sharing Configuration + +Each application configures shared dependencies to optimize bundle sizes: + +```javascript +// Host configuration (app1) +shared: { + react: { + singleton: true, + shareScope: 'default' + }, + 'react-dom': { + singleton: true + } +} + +// Remote configuration (app2/app3) +shared: { + react: { + requiredVersion: deps.react, + singleton: true + }, + 'react-dom': { + requiredVersion: deps['react-dom'], + singleton: true + }, + moment: deps.moment // Shared between remotes +} +``` + +## Setup Instructions + +### Prerequisites + +- Node.js 16+ and pnpm +- Modern browser with ES2020 support +- Network access for cross-origin requests + +### Installation & Running + +1. **Install dependencies for all applications:** + ```bash + pnpm install + ``` + +2. **Start all applications simultaneously:** + ```bash + pnpm start + ``` + This starts: + - Host app on [http://localhost:3001](http://localhost:3001) + - Remote app2 on [http://localhost:3002](http://localhost:3002) + - Remote app3 on [http://localhost:3003](http://localhost:3003) + +3. **Alternative: Legacy Webpack mode:** + ```bash + pnpm legacy:start + ``` + +4. **Production build:** + ```bash + pnpm build + pnpm serve + ``` + +### Usage Instructions + +1. Open the host application at [http://localhost:3001](http://localhost:3001) +2. Click "Load App 2 Widget" to dynamically load the red widget from app2 +3. Click "Load App 3 Widget" to dynamically load the purple widget from app3 +4. Observe shared dependency optimization in browser DevTools Network tab +5. Check browser console for loading logs and shared module information + +## Key Concepts Demonstrated + +### 1. Runtime Remote Registration +- Remotes are registered at runtime, not build time +- Entry points can be discovered dynamically +- No compile-time coupling between host and remotes + +### 2. Dynamic Import with Error Handling +- Graceful fallback when remotes are unavailable +- Loading states and error boundaries +- Component lazy loading with Suspense + +### 3. Vendor Code Sharing +- React/ReactDOM shared as singletons across applications +- Moment.js shared between app2 and app3 +- Automatic version resolution and deduplication + +### 4. Cross-Application Dependency Management +- Shared scope management for dependency isolation +- Version compatibility handling +- Singleton enforcement for framework libraries + +## Configuration Explained + +### Module Federation Plugin Configuration + +**Host Configuration:** +```javascript +new ModuleFederationPlugin({ + name: 'app1', + shared: { + react: { singleton: true }, + 'react-dom': { singleton: true } + } +}) +``` + +**Remote Configuration:** +```javascript +new ModuleFederationPlugin({ + name: 'app2', + filename: 'remoteEntry.js', + exposes: { + './Widget': './src/Widget' + }, + shared: { + react: { requiredVersion: deps.react, singleton: true }, + 'react-dom': { requiredVersion: deps['react-dom'], singleton: true }, + moment: deps.moment + } +}) +``` + +### Rspack vs Webpack Support + +The example supports both Rspack (default) and Webpack bundlers: +- **Rspack**: Faster builds with `rspack serve` +- **Webpack**: Legacy support with `webpack-cli serve` +- Both configurations maintain feature parity + +## Known Issues & Limitations + +### Critical Issues Requiring Attention + +1. **Severely Outdated React Version (16.13.0)** + - Missing modern features (Concurrent Mode, Suspense improvements) + - Security vulnerabilities in older versions + - Limited hooks and performance optimizations + +2. **Hardcoded Remote URLs** + - `http://localhost:3002/3003` URLs limit portability + - No environment-based configuration + - Deployment challenges across environments + +3. **Missing Error Boundaries** + - Remote loading failures can crash the host + - No graceful degradation strategies + - Limited user feedback for loading states + +4. **No Type Safety** + - Missing TypeScript definitions for remote contracts + - Runtime errors for interface mismatches + - No compile-time validation of remote APIs + +5. **Suboptimal Shared Configuration** + - Inconsistent version requirements across remotes + - Missing eager loading for critical dependencies + - No shared scope isolation strategies + +## Modernization Roadmap + +### Immediate Improvements (High Priority) + +1. **Upgrade to React 18+** + ```bash + pnpm update react react-dom --workspace-root + ``` + +2. **Environment-based Configuration** + ```javascript + const REMOTE_BASE_URL = process.env.REMOTE_BASE_URL || 'http://localhost'; + ``` + +3. **Enhanced Error Handling** + ```javascript + const ErrorBoundary = ({ children, fallback }) => { + // Implement comprehensive error boundary + }; + ``` + +### Medium-term Enhancements + +1. **TypeScript Migration** + - Add type definitions for remote contracts + - Implement shared type packages + - Enable compile-time validation + +2. **Advanced Shared Dependencies** + ```javascript + shared: { + react: { + singleton: true, + eager: true, + requiredVersion: '^18.0.0' + } + } + ``` + +3. **Runtime Remote Discovery** + - Service registry integration + - Dynamic remote manifest loading + - Health checking for remote availability + +### Advanced Features + +1. **Micro-frontend Orchestration** + - Centralized routing and navigation + - Inter-application communication patterns + - Shared state management strategies + +2. **Performance Optimization** + - Bundle analysis and optimization + - Lazy loading strategies + - Caching and prefetching policies + +## Best Practices Demonstrated + +### 1. Separation of Concerns +- Clear boundaries between host and remote responsibilities +- Independent deployment capabilities +- Isolated development workflows + +### 2. Dependency Management +- Singleton enforcement for framework libraries +- Version compatibility strategies +- Shared scope optimization + +### 3. Runtime Flexibility +- Dynamic component discovery and loading +- Graceful degradation patterns +- Environment-agnostic configuration + +### 4. Development Experience +- Hot module replacement support +- Independent development servers +- Comprehensive testing strategies + +## Troubleshooting + +### Common Issues and Solutions + +**1. CORS Errors During Development** +```javascript +// Add to webpack.config.js devServer +headers: { + 'Access-Control-Allow-Origin': '*' +} +``` + +**2. Shared Dependency Version Conflicts** +```javascript +// Use strict version matching +shared: { + react: { + requiredVersion: '^18.0.0', + singleton: true, + strictVersion: true + } +} +``` + +**3. Remote Loading Failures** +```javascript +// Implement retry logic +const loadWithRetry = async (remoteName, retries = 3) => { + for (let i = 0; i < retries; i++) { + try { + return await loadRemote(remoteName); + } catch (error) { + if (i === retries - 1) throw error; + await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); + } + } +}; +``` + +**4. Development Server Port Conflicts** +```bash +# Check and gracefully terminate processes using ports +lsof -ti:3001,3002,3003 | xargs kill -15 +# If any processes remain, force kill as a last resort: +lsof -ti:3001,3002,3003 | xargs kill -9 +``` + +**5. Build Failures with Rspack** +```javascript +// Add to rspack.config.js for compatibility +resolve: { + alias: { + '@module-federation/runtime$': require.resolve('@module-federation/runtime') + } +} +``` + +## Testing + +### Running E2E Tests + +**Interactive Mode:** +```bash +npm run cypress:debug +``` + +**Headless CI Mode:** +```bash +pnpm e2e:ci +``` + +**Legacy Webpack Testing:** +```bash +pnpm legacy:e2e:ci +``` + +The E2E tests verify: +- Dynamic component loading functionality +- Shared dependency optimization +- Cross-application UI interactions +- Error handling and fallback scenarios + +### Test Coverage + +- Component rendering verification +- Dynamic loading state management +- Vendor sharing optimization validation +- Cross-browser compatibility checks + +For comprehensive testing guidelines, see [Cypress E2E Documentation](../../cypress-e2e/README.md). + +## Next Steps + +1. **Immediate**: Address critical issues (React upgrade, environment config) +2. **Short-term**: Implement TypeScript and enhanced error handling +3. **Long-term**: Explore advanced micro-frontend patterns and tooling + +This example serves as a foundation for understanding Module Federation's dynamic capabilities while highlighting areas for production-ready improvements. diff --git a/advanced-api/dynamic-remotes/app1/package.json b/advanced-api/dynamic-remotes/app1/package.json index 272b987efb9..bb660262f4c 100644 --- a/advanced-api/dynamic-remotes/app1/package.json +++ b/advanced-api/dynamic-remotes/app1/package.json @@ -25,7 +25,7 @@ "clean": "rm -rf dist" }, "dependencies": { - "react": "^16.13.0", - "react-dom": "^16.13.0" + "react": "^18.3.1", + "react-dom": "^18.3.1" } } \ No newline at end of file diff --git a/advanced-api/dynamic-remotes/app1/rspack.config.js b/advanced-api/dynamic-remotes/app1/rspack.config.js index 5bb7877c70a..cb91134113b 100644 --- a/advanced-api/dynamic-remotes/app1/rspack.config.js +++ b/advanced-api/dynamic-remotes/app1/rspack.config.js @@ -56,14 +56,23 @@ module.exports = { // so it will always use the higher version found shared: { react: { - import: 'react', // the "react" package will be used a provided and fallback module - shareKey: 'react', // under this name the shared module will be placed in the share scope - shareScope: 'default', // share scope with this name will be used - singleton: true, // only a single version of the shared module is allowed + import: 'react', + shareKey: 'react', + shareScope: 'default', + singleton: true, + requiredVersion: '^18.3.1', + strictVersion: true, + }, + 'react/jsx-runtime': { + singleton: true, + }, + 'react/jsx-dev-runtime': { + singleton: true, }, - 'react/jsx-dev-runtime': {}, 'react-dom': { - singleton: true, // only a single version of the shared module is allowed + singleton: true, + requiredVersion: '^18.3.1', + strictVersion: true, }, }, }), diff --git a/advanced-api/dynamic-remotes/app1/src/App.js b/advanced-api/dynamic-remotes/app1/src/App.js index 2c10c1298c4..fdb7fc71ce2 100644 --- a/advanced-api/dynamic-remotes/app1/src/App.js +++ b/advanced-api/dynamic-remotes/app1/src/App.js @@ -1,39 +1,115 @@ import React, { useState, useEffect, Suspense } from 'react'; import { init, loadRemote } from '@module-federation/runtime'; +class ErrorBoundary extends React.Component { + constructor(props) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error) { + return { hasError: true, error }; + } + + componentDidCatch(error, errorInfo) { + console.error('Remote component error:', error, errorInfo); + } + + render() { + if (this.state.hasError) { + return ( +
+

⚠️ Component Failed to Load

+

Unable to load the remote component. Please try again or check the remote application.

+
+ Error Details +
+              {this.state.error?.toString()}
+            
+
+ +
+ ); + } + + return this.props.children; + } +} + +const getRemoteEntry = (port) => { + const baseUrl = process.env.NODE_ENV === 'production' + ? (process.env.REACT_APP_REMOTE_BASE_URL || window.location.origin) + : 'http://localhost'; + return `${baseUrl}:${port}/remoteEntry.js`; +}; + init({ name: 'app1', remotes: [ { name: 'app2', - entry: 'http://localhost:3002/remoteEntry.js', + entry: getRemoteEntry(3002), }, { name: 'app3', - entry: 'http://localhost:3003/remoteEntry.js', + entry: getRemoteEntry(3003), }, ], }); function useDynamicImport({ module, scope }) { const [component, setComponent] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); useEffect(() => { - if (!module || !scope) return; + if (!module || !scope) { + setComponent(null); + setError(null); + return; + } const loadComponent = async () => { + setLoading(true); + setError(null); + setComponent(null); + try { + console.log(`Loading remote module: ${scope}/${module}`); const { default: Component } = await loadRemote(`${scope}/${module}`); setComponent(() => Component); + console.log(`Successfully loaded: ${scope}/${module}`); } catch (error) { console.error(`Error loading remote module ${scope}/${module}:`, error); + setError(error); + } finally { + setLoading(false); } }; loadComponent(); }, [module, scope]); - return component; + return { component, loading, error }; } function App() { @@ -53,7 +129,54 @@ function App() { }); }; - const Component = useDynamicImport({ module, scope }); + const { component: Component, loading, error } = useDynamicImport({ module, scope }); + + const renderRemoteComponent = () => { + if (loading) { + return ( +
+
🔄 Loading {scope}/{module}...
+
+ ); + } + + if (error) { + return ( +
+

⚠️ Failed to Load Remote Component

+

Could not load {scope}/{module}

+
+ Error Details +
+              {error.toString()}
+            
+
+
+ ); + } + + if (Component) { + return ( + + + + ); + } + + return null; + }; return (
remotes and{' '} exposes. It will not load components that have already been loaded.

- - +
+ + +
- {Component ? : null} + + 🔄 Initializing component... +
+ }> + {renderRemoteComponent()} +
); diff --git a/advanced-api/dynamic-remotes/app1/src/bootstrap.js b/advanced-api/dynamic-remotes/app1/src/bootstrap.js index a8680f71cdf..129ffb0c0f9 100644 --- a/advanced-api/dynamic-remotes/app1/src/bootstrap.js +++ b/advanced-api/dynamic-remotes/app1/src/bootstrap.js @@ -1,5 +1,7 @@ import App from './App'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; -ReactDOM.render(, document.getElementById('root')); +const container = document.getElementById('root'); +const root = createRoot(container); +root.render(); diff --git a/advanced-api/dynamic-remotes/app1/webpack.config.js b/advanced-api/dynamic-remotes/app1/webpack.config.js index 1fa58ff6e58..1e9c4b2810f 100644 --- a/advanced-api/dynamic-remotes/app1/webpack.config.js +++ b/advanced-api/dynamic-remotes/app1/webpack.config.js @@ -41,13 +41,20 @@ module.exports = { // so it will always use the higher version found shared: { react: { - import: 'react', // the "react" package will be used a provided and fallback module - shareKey: 'react', // under this name the shared module will be placed in the share scope - shareScope: 'default', // share scope with this name will be used - singleton: true, // only a single version of the shared module is allowed + import: 'react', + shareKey: 'react', + shareScope: 'default', + singleton: true, + requiredVersion: '^18.3.1', + strictVersion: true, }, 'react-dom': { - singleton: true, // only a single version of the shared module is allowed + singleton: true, + requiredVersion: '^18.3.1', + strictVersion: true, + }, + 'react/jsx-runtime': { + singleton: true, }, }, }), diff --git a/advanced-api/dynamic-remotes/app2/package.json b/advanced-api/dynamic-remotes/app2/package.json index 59b1abb1e5a..5a8fe394821 100644 --- a/advanced-api/dynamic-remotes/app2/package.json +++ b/advanced-api/dynamic-remotes/app2/package.json @@ -25,7 +25,7 @@ }, "dependencies": { "moment": "^2.29.4", - "react": "^16.13.0", - "react-dom": "^16.13.0" + "react": "^18.3.1", + "react-dom": "^18.3.1" } } \ No newline at end of file diff --git a/advanced-api/dynamic-remotes/app2/rspack.config.js b/advanced-api/dynamic-remotes/app2/rspack.config.js index b9cbd3af6d5..8b08b700066 100644 --- a/advanced-api/dynamic-remotes/app2/rspack.config.js +++ b/advanced-api/dynamic-remotes/app2/rspack.config.js @@ -58,18 +58,28 @@ module.exports = { './Widget': './src/Widget', }, shared: { - moment: deps.moment, - 'react/jsx-dev-runtime': {}, + moment: { + requiredVersion: deps.moment, + singleton: false, + }, + 'react/jsx-runtime': { + singleton: true, + }, + 'react/jsx-dev-runtime': { + singleton: true, + }, react: { - requiredVersion: deps.react, - import: 'react', // the "react" package will be used a provided and fallback module - shareKey: 'react', // under this name the shared module will be placed in the share scope - shareScope: 'default', // share scope with this name will be used - singleton: true, // only a single version of the shared module is allowed + requiredVersion: '^18.3.1', + import: 'react', + shareKey: 'react', + shareScope: 'default', + singleton: true, + strictVersion: true, }, 'react-dom': { - requiredVersion: deps['react-dom'], - singleton: true, // only a single version of the shared module is allowed + requiredVersion: '^18.3.1', + singleton: true, + strictVersion: true, }, }, }), diff --git a/advanced-api/dynamic-remotes/app2/src/bootstrap.js b/advanced-api/dynamic-remotes/app2/src/bootstrap.js index a8680f71cdf..129ffb0c0f9 100644 --- a/advanced-api/dynamic-remotes/app2/src/bootstrap.js +++ b/advanced-api/dynamic-remotes/app2/src/bootstrap.js @@ -1,5 +1,7 @@ import App from './App'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; -ReactDOM.render(, document.getElementById('root')); +const container = document.getElementById('root'); +const root = createRoot(container); +root.render(); diff --git a/advanced-api/dynamic-remotes/app2/webpack.config.js b/advanced-api/dynamic-remotes/app2/webpack.config.js index be5b9e7d496..7351c6ebbbd 100644 --- a/advanced-api/dynamic-remotes/app2/webpack.config.js +++ b/advanced-api/dynamic-remotes/app2/webpack.config.js @@ -41,17 +41,25 @@ module.exports = { './Widget': './src/Widget', }, shared: { - moment: deps.moment, + moment: { + requiredVersion: deps.moment, + singleton: false, + }, react: { - requiredVersion: deps.react, - import: 'react', // the "react" package will be used a provided and fallback module - shareKey: 'react', // under this name the shared module will be placed in the share scope - shareScope: 'default', // share scope with this name will be used - singleton: true, // only a single version of the shared module is allowed + requiredVersion: '^18.3.1', + import: 'react', + shareKey: 'react', + shareScope: 'default', + singleton: true, + strictVersion: true, }, 'react-dom': { - requiredVersion: deps['react-dom'], - singleton: true, // only a single version of the shared module is allowed + requiredVersion: '^18.3.1', + singleton: true, + strictVersion: true, + }, + 'react/jsx-runtime': { + singleton: true, }, }, }), diff --git a/advanced-api/dynamic-remotes/app3/package.json b/advanced-api/dynamic-remotes/app3/package.json index 7cb78abf367..c71dc2776e1 100644 --- a/advanced-api/dynamic-remotes/app3/package.json +++ b/advanced-api/dynamic-remotes/app3/package.json @@ -25,9 +25,9 @@ }, "dependencies": { "moment": "^2.29.4", - "react": "^16.13.0", - "react-dom": "^16.13.0", - "react-redux": "^7.2.0", - "redux": "^4.2.1" + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-redux": "^9.1.2", + "redux": "^5.0.1" } } \ No newline at end of file diff --git a/advanced-api/dynamic-remotes/app3/rspack.config.js b/advanced-api/dynamic-remotes/app3/rspack.config.js index fe7222d6bd1..bb5f0970a09 100644 --- a/advanced-api/dynamic-remotes/app3/rspack.config.js +++ b/advanced-api/dynamic-remotes/app3/rspack.config.js @@ -50,31 +50,42 @@ module.exports = { plugins: [ new ModuleFederationPlugin({ name: 'app3', - library: { type: 'var', name: 'app3' }, filename: 'remoteEntry.js', exposes: { './Widget': './src/Widget', }, - // adds react as shared module - // version is inferred from package.json - // there is no version check for the required version - // so it will always use the higher version found shared: { react: { - requiredVersion: deps.react, - import: 'react', // the "react" package will be used a provided and fallback module - shareKey: 'react', // under this name the shared module will be placed in the share scope - shareScope: 'default', // share scope with this name will be used - singleton: true, // only a single version of the shared module is allowed + requiredVersion: '^18.3.1', + import: 'react', + shareKey: 'react', + shareScope: 'default', + singleton: true, + strictVersion: true, }, 'react-dom': { - requiredVersion: deps['react-dom'], - singleton: true, // only a single version of the shared module is allowed + requiredVersion: '^18.3.1', + singleton: true, + strictVersion: true, + }, + 'react/jsx-runtime': { + singleton: true, + }, + 'react/jsx-dev-runtime': { + singleton: true, + }, + moment: { + requiredVersion: deps.moment, + singleton: false, + }, + 'react-redux': { + requiredVersion: deps['react-redux'], + singleton: true, + }, + redux: { + requiredVersion: deps.redux, + singleton: true, }, - // adds moment as shared module - // version is inferred from package.json - // it will use the highest moment version that is >= 2.24 and < 3 - moment: deps.moment, }, }), new HtmlRspackPlugin({ diff --git a/advanced-api/dynamic-remotes/app3/src/bootstrap.js b/advanced-api/dynamic-remotes/app3/src/bootstrap.js index a8680f71cdf..129ffb0c0f9 100644 --- a/advanced-api/dynamic-remotes/app3/src/bootstrap.js +++ b/advanced-api/dynamic-remotes/app3/src/bootstrap.js @@ -1,5 +1,7 @@ import App from './App'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; -ReactDOM.render(, document.getElementById('root')); +const container = document.getElementById('root'); +const root = createRoot(container); +root.render(); diff --git a/advanced-api/dynamic-remotes/app3/webpack.config.js b/advanced-api/dynamic-remotes/app3/webpack.config.js index 6c4e51352c3..46c78cb785c 100644 --- a/advanced-api/dynamic-remotes/app3/webpack.config.js +++ b/advanced-api/dynamic-remotes/app3/webpack.config.js @@ -35,31 +35,39 @@ module.exports = { plugins: [ new ModuleFederationPlugin({ name: 'app3', - library: { type: 'var', name: 'app3' }, filename: 'remoteEntry.js', exposes: { './Widget': './src/Widget', }, - // adds react as shared module - // version is inferred from package.json - // there is no version check for the required version - // so it will always use the higher version found shared: { react: { requiredVersion: deps.react, - import: 'react', // the "react" package will be used a provided and fallback module - shareKey: 'react', // under this name the shared module will be placed in the share scope - shareScope: 'default', // share scope with this name will be used - singleton: true, // only a single version of the shared module is allowed + import: 'react', + shareKey: 'react', + shareScope: 'default', + singleton: true, + strictVersion: true, }, 'react-dom': { - requiredVersion: deps['react-dom'], - singleton: true, // only a single version of the shared module is allowed + requiredVersion: '^18.3.1', + singleton: true, + strictVersion: true, + }, + 'react/jsx-runtime': { + singleton: true, + }, + moment: { + requiredVersion: deps.moment, + singleton: false, + }, + 'react-redux': { + requiredVersion: deps['react-redux'], + singleton: true, + }, + redux: { + requiredVersion: deps.redux, + singleton: true, }, - // adds moment as shared module - // version is inferred from package.json - // it will use the highest moment version that is >= 2.24 and < 3 - moment: deps.moment, }, }), new HtmlWebpackPlugin({ diff --git a/advanced-api/dynamic-remotes/cypress.env.json b/advanced-api/dynamic-remotes/cypress.env.json deleted file mode 100644 index e63233bb67d..00000000000 --- a/advanced-api/dynamic-remotes/cypress.env.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "allure": true, - "allureResultsPath": "../../cypress-e2e/results/allure-results" -} diff --git a/advanced-api/dynamic-remotes/e2e/checkDynamicRemotesApps.cy.ts b/advanced-api/dynamic-remotes/e2e/checkDynamicRemotesApps.cy.ts deleted file mode 100644 index 3e2403e38c6..00000000000 --- a/advanced-api/dynamic-remotes/e2e/checkDynamicRemotesApps.cy.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { baseSelectors, commonSelectors } from '../../../cypress-e2e/common/selectors'; -import { BaseMethods } from '../../../cypress-e2e/common/base'; -import { Constants } from '../../../cypress-e2e/fixtures/constants'; -import { getDateWithFormat } from '../../../cypress-e2e/helpers/base-helper'; -import { CssAttr } from '../../../cypress-e2e/types/cssAttr'; -import { returnCommonDynamicAppsData } from '../../../cypress-e2e/fixtures/commonTestData'; - -const basePage: BaseMethods = new BaseMethods(); - -const appsData = returnCommonDynamicAppsData( - Constants.commonPhrases.dynamicRemotesApp.widgetParagraphText, -); - -appsData.forEach( - (property: { - headerSelector: string; - subHeaderSelector: string; - isButtonExist: boolean; - buttonSelector: string; - headerText: string; - appNameText: string; - widgetQuantity?: number; - widgetName: string[]; - widgetParagraph: string[]; - widgetColor: string[]; - paragraph: boolean; - host: number; - }) => { - const appName = - property.host === 3001 - ? appsData[0].appNameText - : property.host === 3002 - ? appsData[1].appNameText - : appsData[2].appNameText; - const host = - property.host === 3001 - ? appsData[0].host - : property.host === 3002 - ? appsData[1].host - : appsData[2].host; - const widget: number = - property.host === 3002 - ? Number(appsData[1].widgetQuantity) - : Number(appsData[2].widgetQuantity); - - describe('Dynamic Remotes', () => { - context(`Check ${appName}`, () => { - beforeEach(() => { - basePage.openLocalhost({ - number: host, - }); - }); - - it(`Check ${appName} elements exist on the page`, () => { - basePage.checkElementWithTextPresence({ - selector: property.headerSelector, - text: property.headerText, - }); - basePage.checkElementWithTextPresence({ - selector: property.subHeaderSelector, - text: appName, - }); - if (property.paragraph) { - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: Constants.commonPhrases.dynamicRemotesApp.paragraphText, - }); - - return; - } - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: Constants.commonPhrases.dynamicRemotesApp.paragraphText, - isVisible: false, - }); - }); - - it(`Check buttons in ${appName} exist`, () => { - basePage.openLocalhost({ - number: host, - }); - if (property.isButtonExist) { - Constants.elementsText.dynamicRemotesApp.buttonsText.forEach(button => { - basePage.checkElementWithTextPresence({ - selector: property.buttonSelector, - text: button, - }); - }); - - return; - } - - basePage.checkElementVisibility({ - selector: property.buttonSelector, - isVisible: property.isButtonExist, - }); - }); - - it(`Check elements functionality in ${appName}`, () => { - if (property.isButtonExist) { - Constants.elementsText.dynamicRemotesApp.buttonsText.forEach(button => { - basePage.clickElementWithText({ - selector: property.buttonSelector, - text: button, - }); - basePage.checkElementVisibility({ - selector: commonSelectors.commonWidget.replace( - '{appQuantity}', - ( - Constants.elementsText.dynamicRemotesApp.buttonsText.indexOf(button) + 2 - ).toString(), - ), - }); - basePage.checkElementHaveProperty({ - selector: commonSelectors.commonWidget.replace( - '{appQuantity}', - ( - Constants.elementsText.dynamicRemotesApp.buttonsText.indexOf(button) + 2 - ).toString(), - ), - prop: CssAttr.backgroundColor, - value: - property.widgetColor[ - Constants.elementsText.dynamicRemotesApp.buttonsText.indexOf(button) - ], - }); - basePage.checkElementWithTextPresence({ - selector: property.subHeaderSelector, - text: property.widgetName[ - Constants.elementsText.dynamicRemotesApp.buttonsText.indexOf(button) - ], - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: property.widgetParagraph[ - Constants.elementsText.dynamicRemotesApp.buttonsText.indexOf(button) - ], - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: getDateWithFormat('current', 'MMMM Do YYYY, h:mm'), - }); - }); - - return; - } - basePage.checkElementVisibility({ - selector: commonSelectors.commonWidget.replace( - '{appQuantity}', - (widget + 2).toString(), - ), - }); - basePage.checkElementHaveProperty({ - selector: commonSelectors.commonWidget.replace( - '{appQuantity}', - (widget + 2).toString(), - ), - prop: CssAttr.backgroundColor, - value: property.widgetColor[widget], - }); - basePage.checkElementWithTextPresence({ - selector: property.subHeaderSelector, - text: property.widgetName[widget], - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: property.widgetParagraph[widget], - }); - basePage.checkElementWithTextPresence({ - selector: baseSelectors.tags.paragraph, - text: getDateWithFormat('current', 'MMMM Do YYYY, h:mm'), - }); - }); - }); - }); - }, -); diff --git a/advanced-api/dynamic-remotes/e2e/checkDynamicRemotesApps.spec.ts b/advanced-api/dynamic-remotes/e2e/checkDynamicRemotesApps.spec.ts new file mode 100644 index 00000000000..1289765ab6f --- /dev/null +++ b/advanced-api/dynamic-remotes/e2e/checkDynamicRemotesApps.spec.ts @@ -0,0 +1,318 @@ +import { test, expect } from './utils/base-test'; +import { selectors } from './utils/selectors'; +import { Constants } from './utils/constants'; + +test.describe('Dynamic Remotes E2E Tests', () => { + + test.describe('Host Application (App 1)', () => { + test('should display host application elements correctly', async ({ basePage }) => { + const consoleErrors: string[] = []; + basePage.page.on('console', (msg) => { + if (msg.type() === 'error') { + consoleErrors.push(msg.text()); + } + }); + + await basePage.openLocalhost(3001); + + // Check main elements exist + await basePage.checkElementWithTextPresence('h1', 'Dynamic System Host'); + await basePage.checkElementWithTextPresence('h2', 'App 1'); + await basePage.checkElementWithTextPresence('p', 'The Dynamic System will take advantage of Module Federation'); + + // Check both buttons exist + await basePage.checkElementWithTextPresence('button', 'Load App 2 Widget'); + await basePage.checkElementWithTextPresence('button', 'Load App 3 Widget'); + + // Verify no critical console errors + const criticalErrors = consoleErrors.filter(error => + error.includes('Failed to fetch') || + error.includes('ChunkLoadError') || + error.includes('Module not found') + ); + expect(criticalErrors).toHaveLength(0); + }); + + test('should dynamically load App 2 widget successfully', async ({ basePage }) => { + const consoleErrors: string[] = []; + basePage.page.on('console', (msg) => { + if (msg.type() === 'error') { + consoleErrors.push(msg.text()); + } + }); + + await basePage.openLocalhost(3001); + + // Click to load App 2 widget + await basePage.clickElementWithText('button', 'Load App 2 Widget'); + await basePage.waitForDynamicImport(); + + // Verify App 2 widget loaded + await basePage.checkElementVisibility(selectors.dataTestIds.app2Widget); + await basePage.checkElementWithTextPresence('h2', 'App 2 Widget'); + await basePage.checkElementBackgroundColor(selectors.dataTestIds.app2Widget, 'rgb(255, 0, 0)'); + + // Check for moment.js date formatting + await basePage.checkDateFormat(); + + // Verify no module federation errors + const moduleErrors = consoleErrors.filter(error => + error.includes('Loading remote module') || + error.includes('Module Federation') + ); + expect(moduleErrors).toHaveLength(0); + }); + + test('should dynamically load App 3 widget successfully', async ({ basePage }) => { + const consoleErrors: string[] = []; + basePage.page.on('console', (msg) => { + if (msg.type() === 'error') { + consoleErrors.push(msg.text()); + } + }); + + await basePage.openLocalhost(3001); + + // Click to load App 3 widget + await basePage.clickElementWithText('button', 'Load App 3 Widget'); + await basePage.waitForDynamicImport(); + + // Verify App 3 widget loaded + await basePage.checkElementVisibility(selectors.dataTestIds.app3Widget); + await basePage.checkElementWithTextPresence('h2', 'App 3 Widget'); + await basePage.checkElementBackgroundColor(selectors.dataTestIds.app3Widget, 'rgb(128, 0, 128)'); + + // Check for moment.js date formatting + await basePage.checkDateFormat(); + + // Verify no module federation errors + const moduleErrors = consoleErrors.filter(error => + error.includes('Loading remote module') || + error.includes('Module Federation') + ); + expect(moduleErrors).toHaveLength(0); + }); + + test('should handle sequential loading of both widgets', async ({ basePage }) => { + await basePage.openLocalhost(3001); + + // Load App 2 widget first + await basePage.clickElementWithText('button', 'Load App 2 Widget'); + await basePage.waitForDynamicImport(); + + // Verify App 2 widget is loaded and get its content + await basePage.checkElementVisibility(selectors.dataTestIds.app2Widget); + await basePage.checkElementWithTextPresence('h2', 'App 2 Widget'); + + // Then load App 3 widget (this replaces the previous widget in this implementation) + await basePage.clickElementWithText('button', 'Load App 3 Widget'); + await basePage.waitForDynamicImport(); + + // Verify App 3 widget is loaded + await basePage.checkElementVisibility(selectors.dataTestIds.app3Widget); + await basePage.checkElementWithTextPresence('h2', 'App 3 Widget'); + + // Note: In this dynamic remotes implementation, widgets replace each other + // rather than accumulating, so we verify the latest widget is visible + }); + + test('should show loading states and handle errors gracefully', async ({ basePage }) => { + await basePage.openLocalhost(3001); + + // Check that buttons are initially enabled + const app2Button = basePage.page.locator('button').filter({ hasText: 'Load App 2 Widget' }); + await expect(app2Button).toBeEnabled(); + + // Monitor for any error boundaries or error states + const errorMessages = basePage.page.locator('text="⚠️"'); + await expect(errorMessages).toHaveCount(0); + }); + }); + + test.describe('Remote Application - App 2', () => { + test('should display App 2 standalone correctly', async ({ basePage }) => { + const consoleErrors: string[] = []; + basePage.page.on('console', (msg) => { + if (msg.type() === 'error') { + consoleErrors.push(msg.text()); + } + }); + + await basePage.openLocalhost(3002); + + // Check App 2 widget displays correctly when accessed directly + await basePage.checkElementVisibility(selectors.dataTestIds.app2Widget); + await basePage.checkElementWithTextPresence('h2', 'App 2 Widget'); + await basePage.checkElementBackgroundColor(selectors.dataTestIds.app2Widget, 'rgb(255, 0, 0)'); + + // Check moment.js functionality + await basePage.checkElementWithTextPresence('p', "Moment shouldn't download twice"); + await basePage.checkDateFormat(); + + // Verify no critical console errors (filter out expected warnings) + const criticalErrors = consoleErrors.filter(e => + !e.includes('webpack-dev-server') && + !e.includes('ReactDOM.render is no longer supported') && + !e.includes('DevTools') && + !e.includes('Warning:') + ); + expect(criticalErrors).toHaveLength(0); + }); + }); + + test.describe('Remote Application - App 3', () => { + test('should display App 3 standalone correctly', async ({ basePage }) => { + const consoleErrors: string[] = []; + basePage.page.on('console', (msg) => { + if (msg.type() === 'error') { + consoleErrors.push(msg.text()); + } + }); + + await basePage.openLocalhost(3003); + + // Check App 3 widget displays correctly when accessed directly + await basePage.checkElementVisibility(selectors.dataTestIds.app3Widget); + await basePage.checkElementWithTextPresence('h2', 'App 3 Widget'); + await basePage.checkElementBackgroundColor(selectors.dataTestIds.app3Widget, 'rgb(128, 0, 128)'); + + // Check for moment.js date formatting + await basePage.checkDateFormat(); + + // Verify no critical console errors (filter out expected warnings) + const criticalErrors = consoleErrors.filter(e => + !e.includes('webpack-dev-server') && + !e.includes('ReactDOM.render is no longer supported') && + !e.includes('DevTools') && + !e.includes('Warning:') + ); + expect(criticalErrors).toHaveLength(0); + }); + }); + + test.describe('Module Federation Features', () => { + test('should efficiently share dependencies between applications', async ({ page }) => { + const networkRequests: string[] = []; + + page.on('request', (request) => { + networkRequests.push(request.url()); + }); + + // Navigate to host + await page.goto('http://localhost:3001'); + await page.waitForLoadState('networkidle'); + + // Load both remotes + await page.click('button:has-text("Load App 2 Widget")'); + await page.waitForTimeout(3000); + + await page.click('button:has-text("Load App 3 Widget")'); + await page.waitForTimeout(3000); + + // Verify React is shared efficiently (should not be loaded multiple times) + const reactRequests = networkRequests.filter(url => + url.includes('react') && !url.includes('react-dom') && !url.includes('react-redux') + ); + expect(reactRequests.length).toBeLessThan(5); + + // Verify moment.js is shared between remotes + const momentRequests = networkRequests.filter(url => url.includes('moment')); + expect(momentRequests.length).toBeLessThan(4); + }); + + test('should handle cross-origin requests correctly', async ({ page }) => { + // Monitor for CORS errors + const corsErrors: string[] = []; + page.on('response', (response) => { + if (response.status() >= 400 && response.url().includes('localhost:300')) { + corsErrors.push(`${response.status()} - ${response.url()}`); + } + }); + + await page.goto('http://localhost:3001'); + await page.waitForLoadState('networkidle'); + + // Load remotes + await page.click('button:has-text("Load App 2 Widget")'); + await page.waitForTimeout(2000); + + // Should have no CORS errors + expect(corsErrors).toHaveLength(0); + }); + + test('should maintain proper error boundaries during failures', async ({ page }) => { + const consoleErrors: string[] = []; + page.on('console', (msg) => { + if (msg.type() === 'error') { + consoleErrors.push(msg.text()); + } + }); + + await page.goto('http://localhost:3001'); + await page.waitForLoadState('networkidle'); + + // Try to load widgets normally + await page.click('button:has-text("Load App 2 Widget")'); + await page.waitForTimeout(2000); + + // Check for React error boundaries working + + // Should handle any errors gracefully (either no errors or proper error boundaries) + const criticalErrors = consoleErrors.filter(error => + error.includes('Uncaught') && + !error.includes('webpack-dev-server') && + !error.includes('DevTools') + ); + expect(criticalErrors).toHaveLength(0); + }); + }); + + test.describe('Environment Configuration', () => { + test('should use environment-based remote URLs', async ({ page }) => { + const networkRequests: string[] = []; + + page.on('request', (request) => { + networkRequests.push(request.url()); + }); + + await page.goto('http://localhost:3001'); + await page.waitForLoadState('networkidle'); + + // Trigger dynamic loading to generate remote requests + await page.click('button:has-text("Load App 2 Widget")'); + await page.waitForTimeout(2000); + + // Verify requests are going to the correct localhost ports + const remoteRequests = networkRequests.filter(url => + url.includes('localhost:3002') || url.includes('localhost:3003') + ); + + expect(remoteRequests.length).toBeGreaterThan(0); + }); + }); + + test.describe('Performance and Loading', () => { + test('should load all applications within reasonable time', async ({ page }) => { + const startTime = Date.now(); + + await page.goto('http://localhost:3001'); + await page.waitForLoadState('networkidle'); + + const loadTime = Date.now() - startTime; + expect(loadTime).toBeLessThan(10000); // Should load within 10 seconds + }); + + test('should handle dynamic imports efficiently', async ({ page }) => { + await page.goto('http://localhost:3001'); + await page.waitForLoadState('networkidle'); + + const startTime = Date.now(); + + await page.click('button:has-text("Load App 2 Widget")'); + await page.waitForSelector('[data-e2e="APP_2__WIDGET"]', { timeout: 10000 }); + + const dynamicLoadTime = Date.now() - startTime; + expect(dynamicLoadTime).toBeLessThan(8000); // Dynamic loading should be fast + }); + }); +}); \ No newline at end of file diff --git a/advanced-api/dynamic-remotes/e2e/utils/base-test.ts b/advanced-api/dynamic-remotes/e2e/utils/base-test.ts new file mode 100644 index 00000000000..c1282ae9330 --- /dev/null +++ b/advanced-api/dynamic-remotes/e2e/utils/base-test.ts @@ -0,0 +1,97 @@ +import { test as base, expect, Page } from '@playwright/test'; + +export class BasePage { + constructor(public page: Page) {} + + async openLocalhost(port: number) { + await this.page.goto(`http://localhost:${port}`); + await this.page.waitForLoadState('networkidle'); + + // Wait for module federation to load (give it extra time for federated components) + await this.page.waitForTimeout(2000); + + // Wait for React to render + await this.page.waitForFunction(() => { + const elements = document.querySelectorAll('h1, h2, button, p'); + return elements.length > 0; + }, { timeout: 30000 }); + } + + async checkElementWithTextPresence(selector: string, text: string, shouldBeVisible = true) { + const element = this.page.locator(selector).filter({ hasText: text }); + if (shouldBeVisible) { + await expect(element).toBeVisible(); + } else { + await expect(element).not.toBeVisible(); + } + } + + async checkElementVisibility(selector: string, shouldBeVisible = true) { + const element = this.page.locator(selector); + if (shouldBeVisible) { + await expect(element).toBeVisible(); + } else { + await expect(element).not.toBeVisible(); + } + } + + async clickElementWithText(selector: string, text: string) { + const element = this.page.locator(selector).filter({ hasText: text }); + + // Wait for element to be ready + await element.waitFor({ state: 'visible', timeout: 10000 }); + + // Remove any overlays that might interfere + await this.page.evaluate(() => { + const overlays = document.querySelectorAll('#webpack-dev-server-client-overlay, iframe[src*="webpack-dev-server"]'); + overlays.forEach(overlay => overlay.remove()); + }); + + // Try clicking with retries + let attempts = 0; + while (attempts < 3) { + try { + await element.click({ timeout: 5000, force: true }); + break; + } catch (error) { + attempts++; + if (attempts >= 3) throw error; + await this.page.waitForTimeout(1000); + } + } + + // Wait for any dynamic loading to complete + await this.page.waitForTimeout(3000); + } + + async checkElementBackgroundColor(selector: string, expectedColor: string) { + const element = this.page.locator(selector); + await expect(element).toHaveCSS('background-color', expectedColor); + } + + async waitForDynamicImport() { + // Wait for dynamic import to complete - looking for loading states to disappear + await this.page.waitForTimeout(3000); // Give time for dynamic loading + + // Wait for any network activity to settle + await this.page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => { + // Ignore timeout - loading might already be complete + }); + } + + async checkDateFormat() { + // Check for moment.js formatted date (format: "Month Day Year, time") + const dateRegex = /\w+ \d+\w+ \d{4}, \d+:\d+/; + const textContent = await this.page.textContent('body'); + expect(textContent).toMatch(dateRegex); + } +} + +export const test = base.extend<{ basePage: BasePage }>({ + basePage: async ({ page }, use) => { + const basePage = new BasePage(page); + await use(basePage); + }, +}); + +export { expect } from '@playwright/test'; \ No newline at end of file diff --git a/advanced-api/dynamic-remotes/e2e/utils/constants.ts b/advanced-api/dynamic-remotes/e2e/utils/constants.ts new file mode 100644 index 00000000000..e68bdab80b4 --- /dev/null +++ b/advanced-api/dynamic-remotes/e2e/utils/constants.ts @@ -0,0 +1,24 @@ +export const Constants = { + commonPhrases: { + dynamicRemotesApp: { + hostTitle: 'Dynamic System Host', + app1Title: 'App 1', + app2WidgetTitle: 'App 2 Widget', + app3WidgetTitle: 'App 3 Widget', + paragraphText: 'The Dynamic System will take advantage of Module Federation remotes and exposes. It will not load components that have already been loaded.', + loadingText: 'Loading', + app2Button: 'Load App 2 Widget', + app3Button: 'Load App 3 Widget', + momentText: "Moment shouldn't download twice, the host has no moment.js", + }, + }, + elementsText: { + dynamicRemotesApp: { + buttonsText: ['Load App 2 Widget', 'Load App 3 Widget'], + }, + }, + colors: { + red: 'rgb(255, 0, 0)', + purple: 'rgb(128, 0, 128)', + }, +}; \ No newline at end of file diff --git a/advanced-api/dynamic-remotes/e2e/utils/selectors.ts b/advanced-api/dynamic-remotes/e2e/utils/selectors.ts new file mode 100644 index 00000000000..2c065f8676b --- /dev/null +++ b/advanced-api/dynamic-remotes/e2e/utils/selectors.ts @@ -0,0 +1,20 @@ +export const selectors = { + tags: { + coreElements: { + button: 'button', + h1: 'h1', + h2: 'h2', + paragraph: 'p', + div: 'div', + }, + }, + classes: { + title: '.title', + name: '.name', + description: '.description', + }, + dataTestIds: { + app2Widget: '[data-e2e="APP_2__WIDGET"]', + app3Widget: '[data-e2e="APP_3__WIDGET"]', + }, +}; \ No newline at end of file diff --git a/advanced-api/dynamic-remotes/package.json b/advanced-api/dynamic-remotes/package.json index caa57f06501..59b9ca20adf 100644 --- a/advanced-api/dynamic-remotes/package.json +++ b/advanced-api/dynamic-remotes/package.json @@ -14,10 +14,15 @@ "build": "pnpm --filter dynamic-remotes_app* --parallel build", "serve": "pnpm --filter dynamic-remotes_app* --parallel serve", "clean": "pnpm --filter dynamic-remotes_app* --parallel clean", - "e2e:ci": "pnpm start & sleep 1 && wait-on tcp:3001 && wait-on tcp:3002 && wait-on tcp:3003 && npx cypress run --config-file ../../cypress-e2e/config/cypress.config.ts --config '{\"supportFile\": \"../../cypress-e2e/support/e2e.ts\"}' --spec \"./e2e/*.cy.ts\" --browser=chrome", - "legacy:e2e:ci": "pnpm legacy:start & sleep 1 && wait-on tcp:3001 && wait-on tcp:3002 && wait-on tcp:3003 && npx cypress run --config-file ../../cypress-e2e/config/cypress.config.ts --config '{\"supportFile\": \"../../cypress-e2e/support/e2e.ts\"}' --spec \"./e2e/*.cy.ts\" --browser=chrome" + "test:e2e": "npx playwright test", + "test:e2e:ui": "npx playwright test --ui", + "test:e2e:debug": "npx playwright test --debug", + "e2e:ci": "pnpm start & sleep 5 && wait-on tcp:3001 tcp:3002 tcp:3003 && npx playwright test --reporter=list; kill $(jobs -p) 2>/dev/null || true", + "legacy:e2e:ci": "pnpm legacy:start & sleep 5 && wait-on tcp:3001 tcp:3002 tcp:3003 && LEGACY_MODE=true npx playwright test --reporter=list; kill $(jobs -p) 2>/dev/null || true" }, "devDependencies": { + "@playwright/test": "^1.54.2", + "playwright": "^1.54.2", "wait-on": "7.2.0" } } diff --git a/advanced-api/dynamic-remotes/playwright.config.ts b/advanced-api/dynamic-remotes/playwright.config.ts new file mode 100644 index 00000000000..26445e39243 --- /dev/null +++ b/advanced-api/dynamic-remotes/playwright.config.ts @@ -0,0 +1,34 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './e2e', + timeout: 60000, + expect: { + timeout: 15000, + }, + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 1 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: [ + ['html', { outputFolder: 'playwright-report', open: 'never' }], + ['list'], + ], + use: { + baseURL: 'http://localhost:3001', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + video: 'retain-on-failure', + viewport: { width: 1920, height: 1080 }, + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + // webServer configuration removed - servers are started manually in package.json scripts + // This ensures better compatibility with CI environments and matches the original Cypress approach +}); \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ebd22cc80f..5368a6fe916 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -185,6 +185,12 @@ importers: advanced-api/dynamic-remotes: devDependencies: + '@playwright/test': + specifier: ^1.54.2 + version: 1.54.2 + playwright: + specifier: ^1.54.2 + version: 1.54.2 wait-on: specifier: 7.2.0 version: 7.2.0 @@ -397,11 +403,11 @@ importers: advanced-api/dynamic-remotes/app1: dependencies: react: - specifier: ^16.13.0 - version: 16.14.0 + specifier: ^18.3.1 + version: 18.3.1 react-dom: - specifier: ^16.13.0 - version: 16.14.0(react@16.14.0) + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) devDependencies: '@babel/core': specifier: 7.24.7 @@ -411,7 +417,7 @@ importers: version: 7.24.7(@babel/core@7.24.7) '@module-federation/enhanced': specifier: 0.17.1 - version: 0.17.1(@rspack/core@1.4.11(@swc/helpers@0.5.17))(react-dom@16.14.0(react@16.14.0))(react@16.14.0)(typescript@5.9.2)(vue-tsc@1.8.27(typescript@5.9.2))(webpack@5.101.0) + version: 0.17.1(@rspack/core@1.4.11(@swc/helpers@0.5.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)(vue-tsc@1.8.27(typescript@5.9.2))(webpack@5.101.0) '@module-federation/runtime': specifier: 0.17.1 version: 0.17.1 @@ -449,11 +455,11 @@ importers: specifier: ^2.29.4 version: 2.30.1 react: - specifier: ^16.13.0 - version: 16.14.0 + specifier: ^18.3.1 + version: 18.3.1 react-dom: - specifier: ^16.13.0 - version: 16.14.0(react@16.14.0) + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) devDependencies: '@babel/core': specifier: 7.24.7 @@ -463,7 +469,7 @@ importers: version: 7.24.7(@babel/core@7.24.7) '@module-federation/enhanced': specifier: 0.17.1 - version: 0.17.1(@rspack/core@1.4.11(@swc/helpers@0.5.17))(react-dom@16.14.0(react@16.14.0))(react@16.14.0)(typescript@5.9.2)(vue-tsc@1.8.27(typescript@5.9.2))(webpack@5.101.0) + version: 0.17.1(@rspack/core@1.4.11(@swc/helpers@0.5.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)(vue-tsc@1.8.27(typescript@5.9.2))(webpack@5.101.0) '@rspack/cli': specifier: 1.4.11 version: 1.4.11(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@types/express@4.17.21)(webpack-cli@5.1.4)(webpack@5.101.0) @@ -498,17 +504,17 @@ importers: specifier: ^2.29.4 version: 2.30.1 react: - specifier: ^16.13.0 - version: 16.14.0 + specifier: ^18.3.1 + version: 18.3.1 react-dom: - specifier: ^16.13.0 - version: 16.14.0(react@16.14.0) + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) react-redux: - specifier: ^7.2.0 - version: 7.2.9(react-dom@16.14.0(react@16.14.0))(react@16.14.0) + specifier: ^9.1.2 + version: 9.2.0(@types/react@18.3.10)(react@18.3.1)(redux@5.0.1) redux: - specifier: ^4.2.1 - version: 4.2.1 + specifier: ^5.0.1 + version: 5.0.1 devDependencies: '@babel/core': specifier: 7.24.7 @@ -518,7 +524,7 @@ importers: version: 7.24.7(@babel/core@7.24.7) '@module-federation/enhanced': specifier: 0.17.1 - version: 0.17.1(@rspack/core@1.4.11(@swc/helpers@0.5.17))(react-dom@16.14.0(react@16.14.0))(react@16.14.0)(typescript@5.9.2)(vue-tsc@1.8.27(typescript@5.9.2))(webpack@5.101.0) + version: 0.17.1(@rspack/core@1.4.11(@swc/helpers@0.5.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)(vue-tsc@1.8.27(typescript@5.9.2))(webpack@5.101.0) '@rspack/cli': specifier: 1.4.11 version: 1.4.11(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@types/express@4.17.21)(webpack-cli@5.1.4)(webpack@5.101.0) @@ -600,7 +606,7 @@ importers: version: 15.2.10(@angular/compiler-cli@15.2.10(@angular/compiler@15.2.10(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10)))(typescript@5.5.3))(@angular/platform-server@15.2.10(6676711f88f9c40c18b0e2629d70e5ad))(@swc/core@1.13.3(@swc/helpers@0.5.17))(html-webpack-plugin@5.6.0(@rspack/core@1.4.11(@swc/helpers@0.5.17))(webpack@5.101.0))(sass-embedded@1.89.2)(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@18.19.39)(typescript@5.5.3)))(typescript@5.5.3)(webpack-cli@5.1.4) '@angular/cli': specifier: 15.2.10 - version: 15.2.10(chokidar@3.6.0) + version: 15.2.10(chokidar@3.5.3) '@angular/compiler-cli': specifier: 15.2.10 version: 15.2.10(@angular/compiler@15.2.10(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10)))(typescript@5.5.3) @@ -621,7 +627,7 @@ importers: version: 4.0.2(webpack@5.101.0) sass-loader: specifier: 14.2.1 - version: 14.2.1(@rspack/core@1.4.11(@swc/helpers@0.5.17))(sass-embedded@1.89.2)(sass@1.77.6)(webpack@5.101.0) + version: 14.2.1(@rspack/core@1.4.11(@swc/helpers@0.5.17))(sass-embedded@1.89.2)(sass@1.58.1)(webpack@5.101.0) ts-node: specifier: 10.9.2 version: 10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@18.19.39)(typescript@5.5.3) @@ -636,7 +642,7 @@ importers: version: 5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(webpack-cli@5.1.4) webpack-cli: specifier: 5.1.4 - version: 5.1.4(webpack@5.101.0) + version: 5.1.4(webpack-dev-server@4.11.1)(webpack@5.101.0) angular-universal-ssr/host-app: dependencies: @@ -688,7 +694,7 @@ importers: version: 15.2.10(@angular/compiler-cli@15.2.10(@angular/compiler@15.2.10(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10)))(typescript@5.5.3))(@angular/platform-server@15.2.10(6676711f88f9c40c18b0e2629d70e5ad))(@swc/core@1.13.3(@swc/helpers@0.5.17))(html-webpack-plugin@5.6.0(@rspack/core@1.4.11(@swc/helpers@0.5.17))(webpack@5.101.0))(sass-embedded@1.89.2)(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.5.3)))(typescript@5.5.3)(webpack-cli@5.1.4) '@angular/cli': specifier: 15.2.10 - version: 15.2.10(chokidar@3.5.3) + version: 15.2.10(chokidar@3.6.0) '@angular/compiler-cli': specifier: 15.2.10 version: 15.2.10(@angular/compiler@15.2.10(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10)))(typescript@5.5.3) @@ -697,7 +703,7 @@ importers: version: 15.2.11(@angular/compiler-cli@15.2.10(@angular/compiler@15.2.10(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10)))(typescript@5.5.3))(typescript@5.5.3)(webpack@5.101.0) '@nguniversal/builders': specifier: 16.2.0 - version: 16.2.0(@angular-devkit/build-angular@15.2.10(@angular/compiler-cli@15.2.10(@angular/compiler@15.2.10(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10)))(typescript@5.5.3))(@angular/platform-server@15.2.10(6676711f88f9c40c18b0e2629d70e5ad))(@swc/core@1.13.3(@swc/helpers@0.5.17))(html-webpack-plugin@5.6.0(@rspack/core@1.4.11(@swc/helpers@0.5.17))(webpack@5.101.0))(sass-embedded@1.89.2)(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.5.3)))(typescript@5.5.3)(webpack-cli@5.1.4))(@angular/common@15.2.10(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10))(rxjs@7.8.2))(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10))(@types/express@4.17.21)(chokidar@3.5.3)(typescript@5.5.3) + version: 16.2.0(@angular-devkit/build-angular@15.2.10(@angular/compiler-cli@15.2.10(@angular/compiler@15.2.10(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10)))(typescript@5.5.3))(@angular/platform-server@15.2.10(6676711f88f9c40c18b0e2629d70e5ad))(@swc/core@1.13.3(@swc/helpers@0.5.17))(html-webpack-plugin@5.6.0(@rspack/core@1.4.11(@swc/helpers@0.5.17))(webpack@5.101.0))(sass-embedded@1.89.2)(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.5.3)))(typescript@5.5.3)(webpack-cli@5.1.4))(@angular/common@15.2.10(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10))(rxjs@7.8.2))(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10))(@types/express@4.17.21)(chokidar@3.6.0)(typescript@5.5.3) clean-webpack-plugin: specifier: 4.0.0 version: 4.0.0(webpack@5.101.0) @@ -709,7 +715,7 @@ importers: version: 4.0.2(webpack@5.101.0) sass-loader: specifier: 14.2.1 - version: 14.2.1(@rspack/core@1.4.11(@swc/helpers@0.5.17))(sass-embedded@1.89.2)(sass@1.58.1)(webpack@5.101.0) + version: 14.2.1(@rspack/core@1.4.11(@swc/helpers@0.5.17))(sass-embedded@1.89.2)(sass@1.77.6)(webpack@5.101.0) ts-node: specifier: 10.9.2 version: 10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.5.3) @@ -724,7 +730,7 @@ importers: version: 5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(webpack-cli@5.1.4) webpack-cli: specifier: 5.1.4 - version: 5.1.4(webpack-dev-server@4.11.1)(webpack@5.101.0) + version: 5.1.4(webpack@5.101.0) apollo-client: devDependencies: @@ -9473,7 +9479,7 @@ importers: version: 17.0.2(react@17.0.2) react-scripts: specifier: 5.0.1 - version: 5.0.1(@babel/plugin-syntax-flow@7.27.1(@babel/core@7.24.7))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.24.7))(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.13.3(@swc/helpers@0.5.17))(@types/babel__core@7.20.5)(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(esbuild@0.23.0)(eslint@9.6.0)(react@17.0.2)(sass-embedded@1.89.2)(sass@1.77.6)(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2))(type-fest@2.19.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1) + version: 5.0.1(@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.0))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.0))(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.13.3(@swc/helpers@0.5.17))(@types/babel__core@7.20.5)(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(esbuild@0.23.0)(eslint@9.6.0)(react@17.0.2)(sass-embedded@1.89.2)(sass@1.77.6)(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2))(type-fest@2.19.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1) devDependencies: '@chromatic-com/storybook': specifier: ^1.6.1 @@ -9501,7 +9507,7 @@ importers: version: 8.6.14(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(storybook@8.6.14(prettier@3.3.3)) '@storybook/preset-create-react-app': specifier: ^8.2.6 - version: 8.6.14(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(react-refresh@0.11.0)(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.27.1(@babel/core@7.24.7))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.24.7))(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.13.3(@swc/helpers@0.5.17))(@types/babel__core@7.20.5)(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(esbuild@0.23.0)(eslint@9.6.0)(react@17.0.2)(sass-embedded@1.89.2)(sass@1.77.6)(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2))(type-fest@2.19.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1))(storybook@8.6.14(prettier@3.3.3))(type-fest@2.19.0)(typescript@5.9.2)(webpack-dev-server@4.15.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)))(webpack-hot-middleware@2.26.1)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + version: 8.6.14(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(react-refresh@0.17.0)(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.0))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.0))(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.13.3(@swc/helpers@0.5.17))(@types/babel__core@7.20.5)(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(esbuild@0.23.0)(eslint@9.6.0)(react@17.0.2)(sass-embedded@1.89.2)(sass@1.77.6)(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2))(type-fest@2.19.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1))(storybook@8.6.14(prettier@3.3.3))(type-fest@2.19.0)(typescript@5.9.2)(webpack-dev-server@5.2.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)))(webpack-hot-middleware@2.26.1)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) '@storybook/react': specifier: ^8.2.6 version: 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.3.3)))(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(storybook@8.6.14(prettier@3.3.3))(typescript@5.9.2) @@ -9543,7 +9549,7 @@ importers: version: 17.0.2(react@17.0.2) react-scripts: specifier: 5.0.1 - version: 5.0.1(@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.0))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.0))(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.13.3(@swc/helpers@0.5.17))(@types/babel__core@7.20.5)(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17)))(eslint@9.6.0)(react@17.0.2)(sass-embedded@1.89.2)(sass@1.77.6)(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2))(type-fest@2.19.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1) + version: 5.0.1(@babel/plugin-syntax-flow@7.27.1(@babel/core@7.24.7))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.24.7))(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.13.3(@swc/helpers@0.5.17))(@types/babel__core@7.20.5)(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17)))(eslint@9.6.0)(react@17.0.2)(sass-embedded@1.89.2)(sass@1.77.6)(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2))(type-fest@2.19.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1) react-webpack-host-vite-remote: devDependencies: @@ -22643,6 +22649,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@types/uuid@9.0.8': resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} @@ -33483,6 +33492,18 @@ packages: react-native: optional: true + react-redux@9.2.0: + resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} + peerDependencies: + '@types/react': 18.3.10 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + react-refresh@0.11.0: resolution: {integrity: sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==} engines: {node: '>=0.10.0'} @@ -33746,6 +33767,9 @@ packages: redux@4.2.1: resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + reflect-metadata@0.1.14: resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==} @@ -37522,9 +37546,9 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/architect@0.1602.16(chokidar@3.5.3)': + '@angular-devkit/architect@0.1602.16(chokidar@3.6.0)': dependencies: - '@angular-devkit/core': 16.2.16(chokidar@3.5.3) + '@angular-devkit/core': 16.2.16(chokidar@3.6.0) rxjs: 7.8.1 transitivePeerDependencies: - chokidar @@ -37822,7 +37846,7 @@ snapshots: optionalDependencies: chokidar: 3.6.0 - '@angular-devkit/core@16.2.16(chokidar@3.5.3)': + '@angular-devkit/core@16.2.16(chokidar@3.6.0)': dependencies: ajv: 8.12.0 ajv-formats: 2.1.1(ajv@8.12.0) @@ -37831,7 +37855,7 @@ snapshots: rxjs: 7.8.1 source-map: 0.7.4 optionalDependencies: - chokidar: 3.5.3 + chokidar: 3.6.0 '@angular-devkit/core@18.2.20(chokidar@3.6.0)': dependencies: @@ -38371,7 +38395,7 @@ snapshots: eslint-visitor-keys: 2.1.0 semver: 6.3.1 - '@babel/eslint-plugin@7.27.1(@babel/eslint-parser@7.24.7(@babel/core@7.28.0)(eslint@8.57.1))(eslint@8.57.1)': + '@babel/eslint-plugin@7.27.1(@babel/eslint-parser@7.24.7(@babel/core@7.24.7)(eslint@8.57.1))(eslint@8.57.1)': dependencies: '@babel/eslint-parser': 7.24.7(@babel/core@7.24.7)(eslint@8.57.1) eslint: 8.57.1 @@ -44624,7 +44648,7 @@ snapshots: dependencies: '@babel/core': 7.24.7 '@babel/eslint-parser': 7.24.7(@babel/core@7.24.7)(eslint@8.57.1) - '@babel/eslint-plugin': 7.27.1(@babel/eslint-parser@7.24.7(@babel/core@7.28.0)(eslint@8.57.1))(eslint@8.57.1) + '@babel/eslint-plugin': 7.27.1(@babel/eslint-parser@7.24.7(@babel/core@7.24.7)(eslint@8.57.1))(eslint@8.57.1) '@rsbuild/babel-preset': 0.7.10(@rsbuild/core@0.7.10)(@swc/helpers@0.5.17) '@rsbuild/core': 0.7.10 '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3) @@ -44651,7 +44675,7 @@ snapshots: dependencies: '@babel/core': 7.24.7 '@babel/eslint-parser': 7.24.7(@babel/core@7.24.7)(eslint@8.57.1) - '@babel/eslint-plugin': 7.27.1(@babel/eslint-parser@7.24.7(@babel/core@7.28.0)(eslint@8.57.1))(eslint@8.57.1) + '@babel/eslint-plugin': 7.27.1(@babel/eslint-parser@7.24.7(@babel/core@7.24.7)(eslint@8.57.1))(eslint@8.57.1) '@rsbuild/babel-preset': 0.7.10(@rsbuild/core@0.7.10)(@swc/helpers@0.5.17) '@rsbuild/core': 0.7.10 '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) @@ -44678,7 +44702,7 @@ snapshots: dependencies: '@babel/core': 7.24.7 '@babel/eslint-parser': 7.24.7(@babel/core@7.24.7)(eslint@8.57.1) - '@babel/eslint-plugin': 7.27.1(@babel/eslint-parser@7.24.7(@babel/core@7.28.0)(eslint@8.57.1))(eslint@8.57.1) + '@babel/eslint-plugin': 7.27.1(@babel/eslint-parser@7.24.7(@babel/core@7.24.7)(eslint@8.57.1))(eslint@8.57.1) '@rsbuild/babel-preset': 0.7.10(@rsbuild/core@0.7.10)(@swc/helpers@0.5.3) '@rsbuild/core': 0.7.10 '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) @@ -44705,7 +44729,7 @@ snapshots: dependencies: '@babel/core': 7.24.7 '@babel/eslint-parser': 7.24.7(@babel/core@7.24.7)(eslint@8.57.1) - '@babel/eslint-plugin': 7.27.1(@babel/eslint-parser@7.24.7(@babel/core@7.28.0)(eslint@8.57.1))(eslint@8.57.1) + '@babel/eslint-plugin': 7.27.1(@babel/eslint-parser@7.24.7(@babel/core@7.24.7)(eslint@8.57.1))(eslint@8.57.1) '@modern-js/babel-preset': 2.57.0(@rsbuild/core@1.0.1-beta.3) '@rsbuild/core': 1.0.1-beta.3 '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5) @@ -44731,7 +44755,7 @@ snapshots: dependencies: '@babel/core': 7.24.7 '@babel/eslint-parser': 7.24.7(@babel/core@7.24.7)(eslint@8.57.1) - '@babel/eslint-plugin': 7.27.1(@babel/eslint-parser@7.24.7(@babel/core@7.28.0)(eslint@8.57.1))(eslint@8.57.1) + '@babel/eslint-plugin': 7.27.1(@babel/eslint-parser@7.24.7(@babel/core@7.24.7)(eslint@8.57.1))(eslint@8.57.1) '@modern-js/babel-preset': 2.59.0(@rsbuild/core@1.0.1-rc.4) '@rsbuild/core': 1.0.1-rc.4 '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) @@ -50025,11 +50049,11 @@ snapshots: typescript: 5.5.3 webpack: 5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(webpack-cli@5.1.4) - '@nguniversal/builders@16.2.0(@angular-devkit/build-angular@15.2.10(@angular/compiler-cli@15.2.10(@angular/compiler@15.2.10(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10)))(typescript@5.5.3))(@angular/platform-server@15.2.10(6676711f88f9c40c18b0e2629d70e5ad))(@swc/core@1.13.3(@swc/helpers@0.5.17))(html-webpack-plugin@5.6.0(@rspack/core@1.4.11(@swc/helpers@0.5.17))(webpack@5.101.0))(sass-embedded@1.89.2)(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.5.3)))(typescript@5.5.3)(webpack-cli@5.1.4))(@angular/common@15.2.10(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10))(rxjs@7.8.2))(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10))(@types/express@4.17.21)(chokidar@3.5.3)(typescript@5.5.3)': + '@nguniversal/builders@16.2.0(@angular-devkit/build-angular@15.2.10(@angular/compiler-cli@15.2.10(@angular/compiler@15.2.10(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10)))(typescript@5.5.3))(@angular/platform-server@15.2.10(6676711f88f9c40c18b0e2629d70e5ad))(@swc/core@1.13.3(@swc/helpers@0.5.17))(html-webpack-plugin@5.6.0(@rspack/core@1.4.11(@swc/helpers@0.5.17))(webpack@5.101.0))(sass-embedded@1.89.2)(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.5.3)))(typescript@5.5.3)(webpack-cli@5.1.4))(@angular/common@15.2.10(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10))(rxjs@7.8.2))(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10))(@types/express@4.17.21)(chokidar@3.6.0)(typescript@5.5.3)': dependencies: - '@angular-devkit/architect': 0.1602.16(chokidar@3.5.3) + '@angular-devkit/architect': 0.1602.16(chokidar@3.6.0) '@angular-devkit/build-angular': 15.2.10(@angular/compiler-cli@15.2.10(@angular/compiler@15.2.10(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10)))(typescript@5.5.3))(@angular/platform-server@15.2.10(6676711f88f9c40c18b0e2629d70e5ad))(@swc/core@1.13.3(@swc/helpers@0.5.17))(html-webpack-plugin@5.6.0(@rspack/core@1.4.11(@swc/helpers@0.5.17))(webpack@5.101.0))(sass-embedded@1.89.2)(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.5.3)))(typescript@5.5.3)(webpack-cli@5.1.4) - '@angular-devkit/core': 16.2.16(chokidar@3.5.3) + '@angular-devkit/core': 16.2.16(chokidar@3.6.0) '@nguniversal/common': 16.2.0(@angular/common@15.2.10(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10))(rxjs@7.8.2))(@angular/core@15.2.10(rxjs@7.8.2)(zone.js@0.14.10)) browser-sync: 2.29.3 express: 4.19.2 @@ -52116,6 +52140,23 @@ snapshots: webpack-dev-server: 4.15.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) webpack-hot-middleware: 2.26.1 + '@pmmmwh/react-refresh-webpack-plugin@0.5.15(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(react-refresh@0.17.0)(type-fest@2.19.0)(webpack-dev-server@5.2.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)))(webpack-hot-middleware@2.26.1)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))': + dependencies: + ansi-html: 0.0.9 + core-js-pure: 3.44.0 + error-stack-parser: 2.1.4 + html-entities: 2.6.0 + loader-utils: 2.0.4 + react-refresh: 0.17.0 + schema-utils: 4.3.2 + source-map: 0.7.6 + webpack: 5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0) + optionalDependencies: + '@types/webpack': 5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0) + type-fest: 2.19.0 + webpack-dev-server: 5.2.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + webpack-hot-middleware: 2.26.1 + '@pmmmwh/react-refresh-webpack-plugin@0.5.15(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(webpack-cli@5.1.4))(react-refresh@0.14.2)(type-fest@2.19.0)(webpack-dev-server@5.0.4)(webpack-hot-middleware@2.26.1)(webpack@5.101.0)': dependencies: ansi-html: 0.0.9 @@ -55081,13 +55122,13 @@ snapshots: '@storybook/node-logger@7.6.20': {} - ? '@storybook/preset-create-react-app@8.6.14(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(react-refresh@0.11.0)(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.27.1(@babel/core@7.24.7))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.24.7))(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.13.3(@swc/helpers@0.5.17))(@types/babel__core@7.20.5)(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(esbuild@0.23.0)(eslint@9.6.0)(react@17.0.2)(sass-embedded@1.89.2)(sass@1.77.6)(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2))(type-fest@2.19.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1))(storybook@8.6.14(prettier@3.3.3))(type-fest@2.19.0)(typescript@5.9.2)(webpack-dev-server@4.15.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)))(webpack-hot-middleware@2.26.1)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))' + ? '@storybook/preset-create-react-app@8.6.14(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(react-refresh@0.17.0)(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.0))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.0))(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.13.3(@swc/helpers@0.5.17))(@types/babel__core@7.20.5)(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(esbuild@0.23.0)(eslint@9.6.0)(react@17.0.2)(sass-embedded@1.89.2)(sass@1.77.6)(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2))(type-fest@2.19.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1))(storybook@8.6.14(prettier@3.3.3))(type-fest@2.19.0)(typescript@5.9.2)(webpack-dev-server@5.2.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)))(webpack-hot-middleware@2.26.1)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))' : dependencies: - '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(react-refresh@0.11.0)(type-fest@2.19.0)(webpack-dev-server@4.15.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)))(webpack-hot-middleware@2.26.1)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(react-refresh@0.17.0)(type-fest@2.19.0)(webpack-dev-server@5.2.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)))(webpack-hot-middleware@2.26.1)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.9.2)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) '@types/semver': 7.7.0 pnp-webpack-plugin: 1.7.0(typescript@5.9.2) - react-scripts: 5.0.1(@babel/plugin-syntax-flow@7.27.1(@babel/core@7.24.7))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.24.7))(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.13.3(@swc/helpers@0.5.17))(@types/babel__core@7.20.5)(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(esbuild@0.23.0)(eslint@9.6.0)(react@17.0.2)(sass-embedded@1.89.2)(sass@1.77.6)(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2))(type-fest@2.19.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1) + react-scripts: 5.0.1(@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.0))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.0))(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.13.3(@swc/helpers@0.5.17))(@types/babel__core@7.20.5)(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(esbuild@0.23.0)(eslint@9.6.0)(react@17.0.2)(sass-embedded@1.89.2)(sass@1.77.6)(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2))(type-fest@2.19.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1) semver: 7.6.3 storybook: 8.6.14(prettier@3.3.3) transitivePeerDependencies: @@ -56386,6 +56427,8 @@ snapshots: '@types/unist@3.0.3': {} + '@types/use-sync-external-store@0.0.6': {} + '@types/uuid@9.0.8': {} '@types/webpack-bundle-analyzer@4.7.0(@swc/core@1.13.3(@swc/helpers@0.5.17))': @@ -61689,7 +61732,7 @@ snapshots: semver: 7.6.3 optionalDependencies: '@rspack/core': 1.4.11(@swc/helpers@0.5.17) - webpack: 5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(webpack-cli@5.1.4) + webpack: 5.101.0(@swc/core@1.6.13(@swc/helpers@0.5.17))(webpack-cli@5.1.4) css-minimizer-webpack-plugin@3.4.1(esbuild@0.23.0)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)): dependencies: @@ -69714,7 +69757,7 @@ snapshots: dependencies: schema-utils: 4.3.2 tapable: 2.2.2 - webpack: 5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(webpack-cli@5.1.4) + webpack: 5.101.0(@swc/core@1.6.13(@swc/helpers@0.5.17))(webpack-cli@5.1.4) minimalistic-assert@1.0.1: {} @@ -74257,6 +74300,15 @@ snapshots: optionalDependencies: react-dom: 16.14.0(react@16.14.0) + react-redux@9.2.0(@types/react@18.3.10)(react@18.3.1)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 18.3.1 + use-sync-external-store: 1.5.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.10 + redux: 5.0.1 + react-refresh@0.11.0: {} react-refresh@0.14.0: {} @@ -74417,56 +74469,56 @@ snapshots: '@remix-run/router': 1.23.0 react: 18.3.1 - react-scripts@5.0.1(@babel/plugin-syntax-flow@7.27.1(@babel/core@7.24.7))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.24.7))(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.13.3(@swc/helpers@0.5.17))(@types/babel__core@7.20.5)(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(esbuild@0.23.0)(eslint@9.6.0)(react@17.0.2)(sass-embedded@1.89.2)(sass@1.77.6)(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2))(type-fest@2.19.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1): + react-scripts@5.0.1(@babel/plugin-syntax-flow@7.27.1(@babel/core@7.24.7))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.24.7))(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.13.3(@swc/helpers@0.5.17))(@types/babel__core@7.20.5)(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17)))(eslint@9.6.0)(react@17.0.2)(sass-embedded@1.89.2)(sass@1.77.6)(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2))(type-fest@2.19.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1): dependencies: '@babel/core': 7.24.7 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(react-refresh@0.11.0)(type-fest@2.19.0)(webpack-dev-server@4.15.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)))(webpack-hot-middleware@2.26.1)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17)))(react-refresh@0.11.0)(type-fest@2.19.0)(webpack-dev-server@4.15.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))))(webpack-hot-middleware@2.26.1)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) '@svgr/webpack': 5.5.0 babel-jest: 27.5.1(@babel/core@7.24.7) - babel-loader: 8.4.1(@babel/core@7.24.7)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + babel-loader: 8.4.1(@babel/core@7.24.7)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) babel-plugin-named-asset-import: 0.3.8(@babel/core@7.24.7) babel-preset-react-app: 10.1.0 bfj: 7.1.0 browserslist: 4.25.1 camelcase: 6.3.0 case-sensitive-paths-webpack-plugin: 2.4.0 - css-loader: 6.11.0(@rspack/core@1.4.11(@swc/helpers@0.5.17))(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) - css-minimizer-webpack-plugin: 3.4.1(esbuild@0.23.0)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + css-loader: 6.11.0(@rspack/core@1.4.11(@swc/helpers@0.5.17))(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + css-minimizer-webpack-plugin: 3.4.1(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) dotenv: 10.0.0 dotenv-expand: 5.1.0 eslint: 9.6.0 eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.27.1(@babel/core@7.24.7))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.24.7))(eslint@9.6.0)(jest@27.5.1(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2)))(typescript@5.9.2) - eslint-webpack-plugin: 3.2.0(eslint@9.6.0)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) - file-loader: 6.2.0(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + eslint-webpack-plugin: 3.2.0(eslint@9.6.0)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + file-loader: 6.2.0(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) fs-extra: 10.1.0 - html-webpack-plugin: 5.6.0(@rspack/core@1.4.11(@swc/helpers@0.5.17))(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + html-webpack-plugin: 5.6.0(@rspack/core@1.4.11(@swc/helpers@0.5.17))(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) identity-obj-proxy: 3.0.0 jest: 27.5.1(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2)) jest-resolve: 27.5.1 jest-watch-typeahead: 1.1.0(jest@27.5.1(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2))) - mini-css-extract-plugin: 2.9.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + mini-css-extract-plugin: 2.9.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) postcss: 8.4.47 postcss-flexbugs-fixes: 5.0.2(postcss@8.4.47) - postcss-loader: 6.2.1(postcss@8.4.47)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + postcss-loader: 6.2.1(postcss@8.4.47)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) postcss-normalize: 10.0.1(browserslist@4.25.1)(postcss@8.4.47) postcss-preset-env: 7.8.3(postcss@8.4.47) prompts: 2.4.2 react: 17.0.2 react-app-polyfill: 3.0.0 - react-dev-utils: 12.0.1(eslint@9.6.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + react-dev-utils: 12.0.1(eslint@9.6.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) react-refresh: 0.11.0 resolve: 1.22.10 resolve-url-loader: 4.0.0 - sass-loader: 12.6.0(sass-embedded@1.89.2)(sass@1.77.6)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + sass-loader: 12.6.0(sass-embedded@1.89.2)(sass@1.77.6)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) semver: 7.6.3 - source-map-loader: 3.0.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) - style-loader: 3.3.4(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + source-map-loader: 3.0.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + style-loader: 3.3.4(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2)) - terser-webpack-plugin: 5.3.10(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) - webpack: 5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0) - webpack-dev-server: 4.15.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) - webpack-manifest-plugin: 4.1.1(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) - workbox-webpack-plugin: 6.6.0(@types/babel__core@7.20.5)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + terser-webpack-plugin: 5.3.10(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.17.19)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + webpack: 5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.17.19) + webpack-dev-server: 4.15.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + webpack-manifest-plugin: 4.1.1(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + workbox-webpack-plugin: 6.6.0(@types/babel__core@7.20.5)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) optionalDependencies: fsevents: 2.3.3 typescript: 5.9.2 @@ -74504,56 +74556,56 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - react-scripts@5.0.1(@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.0))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.0))(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.13.3(@swc/helpers@0.5.17))(@types/babel__core@7.20.5)(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17)))(eslint@9.6.0)(react@17.0.2)(sass-embedded@1.89.2)(sass@1.77.6)(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2))(type-fest@2.19.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1): + react-scripts@5.0.1(@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.0))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.0))(@rspack/core@1.4.11(@swc/helpers@0.5.17))(@swc/core@1.13.3(@swc/helpers@0.5.17))(@types/babel__core@7.20.5)(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(esbuild@0.23.0)(eslint@9.6.0)(react@17.0.2)(sass-embedded@1.89.2)(sass@1.77.6)(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2))(type-fest@2.19.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack-hot-middleware@2.26.1): dependencies: '@babel/core': 7.24.7 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17)))(react-refresh@0.11.0)(type-fest@2.19.0)(webpack-dev-server@4.15.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))))(webpack-hot-middleware@2.26.1)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(@types/webpack@5.28.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0))(react-refresh@0.11.0)(type-fest@2.19.0)(webpack-dev-server@4.15.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)))(webpack-hot-middleware@2.26.1)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) '@svgr/webpack': 5.5.0 babel-jest: 27.5.1(@babel/core@7.24.7) - babel-loader: 8.4.1(@babel/core@7.24.7)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + babel-loader: 8.4.1(@babel/core@7.24.7)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) babel-plugin-named-asset-import: 0.3.8(@babel/core@7.24.7) babel-preset-react-app: 10.1.0 bfj: 7.1.0 browserslist: 4.25.1 camelcase: 6.3.0 case-sensitive-paths-webpack-plugin: 2.4.0 - css-loader: 6.11.0(@rspack/core@1.4.11(@swc/helpers@0.5.17))(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) - css-minimizer-webpack-plugin: 3.4.1(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + css-loader: 6.11.0(@rspack/core@1.4.11(@swc/helpers@0.5.17))(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + css-minimizer-webpack-plugin: 3.4.1(esbuild@0.23.0)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) dotenv: 10.0.0 dotenv-expand: 5.1.0 eslint: 9.6.0 eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.0))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.0))(eslint@9.6.0)(jest@27.5.1(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2)))(typescript@5.9.2) - eslint-webpack-plugin: 3.2.0(eslint@9.6.0)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) - file-loader: 6.2.0(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + eslint-webpack-plugin: 3.2.0(eslint@9.6.0)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + file-loader: 6.2.0(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) fs-extra: 10.1.0 - html-webpack-plugin: 5.6.0(@rspack/core@1.4.11(@swc/helpers@0.5.17))(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + html-webpack-plugin: 5.6.0(@rspack/core@1.4.11(@swc/helpers@0.5.17))(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) identity-obj-proxy: 3.0.0 jest: 27.5.1(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2)) jest-resolve: 27.5.1 jest-watch-typeahead: 1.1.0(jest@27.5.1(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2))) - mini-css-extract-plugin: 2.9.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + mini-css-extract-plugin: 2.9.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) postcss: 8.4.47 postcss-flexbugs-fixes: 5.0.2(postcss@8.4.47) - postcss-loader: 6.2.1(postcss@8.4.47)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + postcss-loader: 6.2.1(postcss@8.4.47)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) postcss-normalize: 10.0.1(browserslist@4.25.1)(postcss@8.4.47) postcss-preset-env: 7.8.3(postcss@8.4.47) prompts: 2.4.2 react: 17.0.2 react-app-polyfill: 3.0.0 - react-dev-utils: 12.0.1(eslint@9.6.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + react-dev-utils: 12.0.1(eslint@9.6.0)(typescript@5.9.2)(vue-template-compiler@2.7.16)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) react-refresh: 0.11.0 resolve: 1.22.10 resolve-url-loader: 4.0.0 - sass-loader: 12.6.0(sass-embedded@1.89.2)(sass@1.77.6)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + sass-loader: 12.6.0(sass-embedded@1.89.2)(sass@1.77.6)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) semver: 7.6.3 - source-map-loader: 3.0.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) - style-loader: 3.3.4(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + source-map-loader: 3.0.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + style-loader: 3.3.4(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.13.3(@swc/helpers@0.5.17))(@swc/wasm@1.13.3)(@types/node@22.17.0)(typescript@5.9.2)) - terser-webpack-plugin: 5.3.10(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.17.19)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) - webpack: 5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.17.19) - webpack-dev-server: 4.15.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) - webpack-manifest-plugin: 4.1.1(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) - workbox-webpack-plugin: 6.6.0(@types/babel__core@7.20.5)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))) + terser-webpack-plugin: 5.3.10(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + webpack: 5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0) + webpack-dev-server: 4.15.2(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + webpack-manifest-plugin: 4.1.1(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) + workbox-webpack-plugin: 6.6.0(@types/babel__core@7.20.5)(webpack@5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(esbuild@0.23.0)) optionalDependencies: fsevents: 2.3.3 typescript: 5.9.2 @@ -74822,6 +74874,8 @@ snapshots: dependencies: '@babel/runtime': 7.28.2 + redux@5.0.1: {} + reflect-metadata@0.1.14: {} reflect-metadata@0.2.2: {} @@ -76500,7 +76554,7 @@ snapshots: style-loader@4.0.0(webpack@5.101.0): dependencies: - webpack: 5.101.0(@swc/core@1.13.3(@swc/helpers@0.5.17))(webpack-cli@5.1.4) + webpack: 5.101.0(@swc/core@1.6.13(@swc/helpers@0.5.17))(webpack-cli@5.1.4) style-resources-loader@1.5.0(webpack@5.88.2(@swc/core@1.13.3(@swc/helpers@0.5.17))): dependencies: @@ -81078,7 +81132,7 @@ snapshots: watchpack: 2.4.4 webpack-sources: 3.3.3 optionalDependencies: - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.0.4)(webpack@5.101.0) + webpack-cli: 5.1.4(webpack@5.101.0) transitivePeerDependencies: - '@swc/core' - esbuild