icestark

repository·master·Indexed 24 days ago

https://github.com/ice-lab/icestark

A micro-frontend solution for large-scale applications that allows multiple sub-applications built with different frameworks (such as React, Vue, Angular, or jQuery) to run within a single main-application shell. It features a two-tier architecture, a JavaScript sandbox based on the Proxy API, and a communication system via @ice/stark-data. The ecosystem includes packages for app lifecycle management (@ice/stark-app), micro-module capabilities (@ice/stark-module), and isolated script execution (@ice/sandbox).

Tokens
43.3K
Snippets
95
Records
212
Agent score
84%

What's inside icestark

  1. Implement Micro-application Lifecycles

    master

    In icestark, micro-applications are frontend resources with a lifecycle consisting of two stages: mounting to the main application and unmounting from the main application.

    There are two ways to declare these lifecycles:

    To ensure better compatibility with the single-spa ecosystem, it is recommended to export mount and unmount functions in UMD format (available in icestark 1.6.0+).

    2. Using Global Registration

    You can use registerAppEnter and registerAppLeave from @ice/stark-app to handle lifecycle events.

    // Option 1: Recommended (UMD export)
    import ReactDOM from 'react-dom';
    import App from './App';
    
    export function mount(props) {
      ReactDOM.render(<App />, props.container);
    }
    
    export function unmount(props) {
      ReactDOM.unmountComponentAtNode(props.container);
    }
    
    // Option 2: Global registration
    import ReactDOM from 'react-dom';
    import { registerAppEnter, registerAppLeave } from '@ice/stark-app';
    import App from './App';
    
    registerAppEnter((props) => {
      ReactDOM.render(<App />, props.container);
    });
    
    registerAppLeave((props) => {
      ReactDOM.unmountComponentAtNode(props.container);
    });
  2. How icestark architecture works

    master

    icestark is a micro-frontend solution designed for large applications. It uses a two-tier architecture:

    1. Main-application (Framework Application): Responsible for registering, loading, and rendering sub-applications. It also manages the global layout (e.g., Header, Sidebar, Footer).
    2. Sub-application: Responsible for the specific business logic and content display related to its own domain.

    Key features include:

    • Framework agnostic: Main and sub-applications can use different frameworks (React, Vue, Angular, etc.).
    • Multiple entry types: Supports JS/CSS, HTML entry, or HTML content.
    • Compatibility: Compatible with single-spa sub-applications and lifecycles.
    • Isolation: Uses a JavaScript sandbox based on the Proxy API.
  3. Unmount vs Unload a micro-app

    master

    When manually managing micro-apps, you can choose between unmountMicroApp and unloadMicroApp:

    • unmountMicroApp(appName): Executes the micro-app's unmount lifecycle method and removes it from the DOM. The micro-app's resources remain loaded, so the next mount can be performed immediately without re-fetching resources.
    • unloadMicroApp(appName): Executes the unmount method and also removes the micro-app's execution results (mount/unmount state). The next time the micro-app is mounted, its resources must be re-loaded to re-execute its lifecycle.
  4. Understand the role of the Main Application (Base Application)

    master

    In an icestark system, the Main Application (also known as the Framework Application or Base Application) is a singleton component. A well-designed Main Application should have a limited scope to ensure stability and prevent coupling. Its responsibilities are strictly limited to two tasks:

    1. System-wide Layout Design: Defining the overall visual structure and shell of the application.
    2. Micro-app Configuration and Registration: Managing the lifecycle and settings of all micro-apps within the system.

    Best Practices

    To maintain system stability and micro-app independence, avoid including specific page UI code in the Main Application. Overloading the Main Application leads to:

    • Style Conflicts: Excessive CSS in the Main Application increases the risk of collisions with micro-apps.
    • Tight Coupling: Providing global APIs from the Main Application can break the independence of micro-apps.
    • Stability Risks: Since the Main Application is a centralized component, changes to it theoretically require regression testing across all micro-apps. Keeping it simple ensures it remains stable.
  5. Define route activation rules with activePath

    master

    The activePath property determines when a micro-app should be activated. It supports several formats:

    1. Single route: Matches a specific string.
    2. Multiple routes: An array of strings.
    3. PathData objects: Allows adding constraints like exact or strict.
    4. Custom logic: A function that returns a boolean based on the URL.

    Examples:

  6. Understand Micro-applications (Sub-apps) in icestark

    master

    A micro-application (or sub-app) in icestark is typically a Single Page Application (SPA) that contains one or more routes.

    Key characteristics:

    • It is a standard frontend application responsible for specific business logic.
    • It can be developed, deployed, and run independently, but is generally integrated into a main application.
    • It can potentially be integrated into different main applications if necessary.
  7. Handle module name collisions during registration

    master

    If multiple modules are registered with the same name, the last one registered will overwrite previous ones. Only the most recent registration for a specific name is kept in the registry.

    import { registerModule, registerModules, getModules } from '@ice/stark-module';
    
    registerModules([
      {
        url: 'https://localhost/module-a.js',
        name: 'module-a',
      },
      {
        url: 'https://localhost/module-b.js',
        name: 'module-a',
      },
    ]);
    
    const modules = getModules();
    /** the modules will be:
    [{
      url: 'https://localhost/module-b.js',
      name: 'module-a',
    }]
    */
    
    registerModule({
      url: 'https://localhost/module-c.js',
      name: 'module-a',
    });
    
    const modules = getModules();
    /** the modules will be:
    [{
      url: 'https://localhost/module-c.js',
      name: 'module-a',
    }]
    */
  8. How micro-module lifecycles work

    master

    When packing code as a micro module (using UMD), you can specify a lifecycle by exporting mount and unmount functions. These functions are triggered when the micro module is mounted or unmounted from the host application.

    • mount(ModuleComponent, targetNode, props): Triggered when the module is mounted. Use this to render the component into the target DOM node.
    • unmount(targetNode): Triggered when the module is unmounted. Use this to clean up the component or DOM node.
    const SampleComponent = () => {
      return <div>Sample</div>;
    }
    
    // mount function will be trigger when mount micro module
    export function mount(ModuleComponent, targetNode, props) {
      ReactDOM.render(<ModuleComponent {...props} />, targetNode);
    }
    
    // unmount function will be trigger when unmount micro module
    export function unmount(targetNode) {
      ReactDOM.unmountComponentAtNode(targetNode);
    }
    
    export default SampleComponent;
  9. Advanced Micro-module usage: Registration and Customization

    master

    Centralized Registration

    Use registerModules to pre-register modules so they can be loaded by name using <MicroModule moduleName="..." />.

    import { MicroModule, registerModules } from '@ice/stark-module';
    
    registerModules([
      { url: 'https://localhost/module-a.js', name: 'module-a' },
      { url: 'https://localhost/module-b.js', name: 'module-b' },
    ]);
    
    const App = () => (
      <div>
        <MicroModule moduleName="module-a" />
        <MicroModule moduleName="module-b" />
      </div>
    );

    Custom Lifecycle

    If a module does not export mount or unmount, you can provide them directly in the moduleInfo object. Note that exported module lifecycles take precedence over these.

    const moduleInfo = {
      name: 'moduleName',
      url: 'https://localhost/module.js',
      mount: (ModuleComponent, mountNode, props) => {
        ReactDOM.render(<ModuleComponent />, mountNode, props);
      },
    };

    Registering Local Modules

    You can render built-in local components by using the render property in registerModules.

    import LocalComponent from './localComponent';
    
    registerModules([
      {
        name: 'moduleName',
        render: () => LocalComponent,
      }
    ]);
  10. Cache micro-apps with the `cached` option

    master

    The cached option allows icestark to cache micro-apps during transitions. When enabled, icestark will not clean up the previous micro-app's static resources and will not re-execute script resources, speeding up subsequent loads of the same micro-app.

    Warning: Side Effects

    1. Style Pollution: Since styles are not unloaded, they may leak into other micro-apps. Using CSS Modules helps mitigate this, but global modifications to third-party component libraries (like AntD or Fusion) can still cause issues.
    2. Memory Leaks: Enabling cached while using the sandbox capability may lead to memory leaks.
    import { AppRouter, AppRoute } from '@ice/stark';
    
    const App = () => (
      <AppRouter>
        <AppRoute
          name="waiter"
          activePath="/waiter"
          title="商家平台"
          cached
          url={['...js', '...css']}
        />
      </AppRouter>
    );
  11. Install @ice/stark-app

    master

    Install the @ice/stark-app package via npm to access the APIs required by sub-applications in an icestark micro-frontend architecture. This package is decoupled from the main icestark package to ensure API stability and compatibility with non-relay systems.

    npm install @ice/stark-app --save