So bereiten Sie Mikrofronts in Webpack 5 vor

Hallo allerseits, mein Name ist Ivan und ich bin ein Front-End-Entwickler.





In meinem Kommentar zu Mikrofronts gab es bis zu drei Likes. Deshalb habe ich beschlossen, einen Artikel zu schreiben, in dem alle Bigwigs beschrieben werden, die unser Stream aufgrund der Einführung von Mikrofronts gefüllt und gefüllt hat.





Beginnen wir mit der Tatsache, dass die Jungs von Habr (@ artemu78, @dfuse, @Katsuba) bereits über die Module Federation geschrieben haben, also ist mein Artikel nichts Einzigartiges und Durchbruchreiches. Dies sind vielmehr Unebenheiten, Krücken und Fahrräder, die für diejenigen nützlich sind, die diese Technologie verwenden werden.





Ursache

, , - , , - . , Webpack 5 Module Federation. , -. , , . , , Webpack, -, ... .





, , Webpack 5?





, , Webpack , Module Federation .





shell-

, , , . Webpack 4.4 5 . , .





Webpack Webpack- :





const webpack = require('webpack');

// ...

const { ModuleFederationPlugin } = webpack.container;

const deps = require('./package.json').dependencies;

module.exports = {
  // ...
  output: {
    // ...
    publicPath: 'auto', // !    publicPath,  auto
  },
  module: {
    // ...
  },
  plugins: [
    // ...
    new ModuleFederationPlugin({
      name: 'shell',
      filename: 'shell.js',
      shared: {
        react: { requiredVersion: deps.react },
        'react-dom': { requiredVersion: deps['react-dom'] },
        'react-query': {
          requiredVersion: deps['react-query'],
        },
      },
      remotes: {
        widgets: `widgets@http://localhost:3002/widgets.js`,
      },
    }),
  ],
  devServer: {
    // ...
  },
};

      
      



, , bootstrap.tsx index.tsx





// bootstrap.tsx

import React from 'react';
import { render } from 'react-dom';

import { App } from './App';
import { config } from './config';

import './index.scss';

config.init().then(() => {
  render(<App />, document.getElementById('root'));
});
      
      



index.tsx bootstrap





import('./bootstrap');
      
      



, - remotes <name>@< >/<filename>. , , .





import React from 'react';

// ...

import Todo from 'widgets/Todo';

// ...

const queryClient = new QueryClient();

export const App = () => {
  // ...

  return (
    <QueryClientProvider client={queryClient} contextSharing>
      <Router>
        <Layout sidebarContent={<Navigation />}>
          <Switch>
            {/* ... */}

            <Route exact path='/'>
              <Todo />
            </Route>

            {/* ... */}
          </Switch>
        </Layout>
      </Router>
    </QueryClientProvider>
  );
};
      
      



, , , , , React, React- LazyService:





// LazyService.tsx

import React, { lazy, ReactNode, Suspense } from 'react';

import { useDynamicScript } from './useDynamicScript';
import { loadComponent } from './loadComponent';
import { Microservice } from './types';
import { ErrorBoundary } from '../ErrorBoundary/ErrorBoundary';

interface ILazyServiceProps<T = Record<string, unknown>> {
  microservice: Microservice<T>;
  loadingMessage?: ReactNode;
  errorMessage?: ReactNode;
}

export function LazyService<T = Record<string, unknown>>({
  microservice,
  loadingMessage,
  errorMessage,
}: ILazyServiceProps<T>): JSX.Element {
  const { ready, failed } = useDynamicScript(microservice.url);

  const errorNode = errorMessage || <span>Failed to load dynamic script: {microservice.url}</span>;

  if (failed) {
    return <>{errorNode}</>;
  }

  const loadingNode = loadingMessage || <span>Loading dynamic script: {microservice.url}</span>;

  if (!ready) {
    return <>{loadingNode}</>;
  }

  const Component = lazy(loadComponent(microservice.scope, microservice.module));

  return (
    <ErrorBoundary>
      <Suspense fallback={loadingNode}>
        <Component {...(microservice.props || {})} />
      </Suspense>
    </ErrorBoundary>
  );
}
      
      



useDynamicScript , html-.





// useDynamicScript.ts
  
import { useEffect, useState } from 'react';

export const useDynamicScript = (url?: string): { ready: boolean; failed: boolean } => {
  const [ready, setReady] = useState(false);
  const [failed, setFailed] = useState(false);

  useEffect(() => {
    if (!url) {
      return;
    }

    const script = document.createElement('script');

    script.src = url;
    script.type = 'text/javascript';
    script.async = true;

    setReady(false);
    setFailed(false);

    script.onload = (): void => {
      console.log(`Dynamic Script Loaded: ${url}`);
      setReady(true);
    };

    script.onerror = (): void => {
      console.error(`Dynamic Script Error: ${url}`);
      setReady(false);
      setFailed(true);
    };

    document.head.appendChild(script);

    return (): void => {
      console.log(`Dynamic Script Removed: ${url}`);
      document.head.removeChild(script);
    };
  }, [url]);

  return {
    ready,
    failed,
  };
};
      
      



loadComponent Webpack-, - .





// loadComponent.ts

export function loadComponent(scope, module) {
  return async () => {
    // Initializes the share scope. This fills it with known provided modules from this build and all remotes
    await __webpack_init_sharing__('default');

    const container = window[scope]; // or get the container somewhere else
    // Initialize the container, it may provide shared modules
    await container.init(__webpack_share_scopes__.default);
    const factory = await window[scope].get(module);
    const Module = factory();
    return Module;
  };
}
      
      



, , .





// types.ts

export type Microservice<T = Record<string, unknown>> = {
  url: string;
  scope: string;
  module: string;
  props?: T;
};
      
      



  • url - + (, http://localhost:3002/widgets.js),





  • scope - name, ModuleFederationPlugin





  • module - ,





  • props - , ,





LazyService :





import React, { FC, useState } from 'react';

import { LazyService } from '../../components/LazyService';
import { Microservice } from '../../components/LazyService/types';
import { Loader } from '../../components/Loader';
import { Toggle } from '../../components/Toggle';
import { config } from '../../config';

import styles from './styles.module.scss';

export const Video: FC = () => {
  const [microservice, setMicroservice] = useState<Microservice>({
    url: config.microservices.widgets.url,
    scope: 'widgets',
    module: './Zack',
  });

  const toggleMicroservice = () => {
    if (microservice.module === './Zack') {
      setMicroservice({ ...microservice, module: './Jack' });
    }

    if (microservice.module === './Jack') {
      setMicroservice({ ...microservice, module: './Zack' });
    }
  };

  return (
    <>
      <div className={styles.ToggleContainer}>
        <Toggle onClick={toggleMicroservice} />
      </div>
      <LazyService microservice={microservice} loadingMessage={<Loader />} />
    </>
  );
};
      
      



-, , , url , , .





, shell- , - .





shell- , Webpack => 5





ModuleFederationPlugin, , .





// ...

new ModuleFederationPlugin({
      name: 'widgets',
      filename: 'widgets.js',
      shared: {
        react: { requiredVersion: deps.react },
        'react-dom': { requiredVersion: deps['react-dom'] },
        'react-query': {
          requiredVersion: deps['react-query'],
        },
      },
      exposes: {
        './Todo': './src/App',
        './Gallery': './src/pages/Gallery/Gallery',
        './Zack': './src/pages/Zack/Zack',
        './Jack': './src/pages/Jack/Jack',
      },
    }),

// ...
      
      



exposes , , . , LazyService .





, .





, . , , , , . , , React JavaScript, , Webpack, , , . CDN, . .





, , . , , . , , .





, , , . , shell- , Module Federation . , , , .





, , , , , , .





React-

react-router, , useLocation, , .





Fehler beim Versuch, über die Mikrofront auf den Kontext einer Shell-Anwendung zuzugreifen
shell-

Apollo, , ApolloClient shell-. useQuery, useLocation.





, , npm- , shell-, .





UI- shell-

, , shell- . , :





  1. UI- npm- shared-





  2. "" ModuleFederationPlugin





, , , . Module Federation , npm.





TypeScript, , Module Federation , . - , . , .d.ts , - .





emp-tune-dts-plugin, , .





, Webpack 5 Module Federation , , - . , , , .





, , . - , .





, , , , , Module Federation.









Modulverbunddokumentation in Webpack 5-Docks





Beispiele für die Verwendung von Module Federation





YouTube-Wiedergabeliste von Module Federation








All Articles