Advertisement
crutch12

loadComponent.ts

Jan 25th, 2022 (edited)
1,210
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import React from 'react';
  2.  
  3. type Scope = unknown;
  4. type Factory = () => any;
  5.  
  6. type Container = {
  7.   init(shareScope: Scope): void;
  8.   get(module: string): Factory;
  9. };
  10.  
  11. declare const __webpack_init_sharing__: (shareScope: string) => Promise<void>;
  12. declare const __webpack_share_scopes__: { default: Scope };
  13.  
  14. // @NOTE: Cache for <script> promises
  15. const dynamicScriptPromises = new Map<string, Promise<any>>();
  16.  
  17. /**
  18.  * Creates <script src="url"> and returns promise (!!! uses cache, won't create duplicated elements)
  19.  * @param url - https://example.org/remoteEntry.js
  20.  * @param removeElement - should it remove <script> element after onLoad/onError event. Default - false
  21.  */
  22. export function loadDynamicScript(url: string, removeElement = false) {
  23.   const promiseKey = url.trim();
  24.  
  25.   // @NOTE: Take Promise from cache if script is already loading/loaded (otherwise it will reload and reload all contexts)
  26.   if (dynamicScriptPromises.has(promiseKey)) return dynamicScriptPromises.get(promiseKey)!;
  27.  
  28.   const scriptPromise = new Promise((resolve, reject) => {
  29.     const element = document.createElement('script');
  30.     element.src = url;
  31.     element.type = 'text/javascript';
  32.     element.async = true;
  33.  
  34.     element.onload = () => {
  35.       resolve(element);
  36.       if (removeElement) document.head.removeChild(element);
  37.     };
  38.  
  39.     element.onerror = (event, source, lineno, colno, error) => {
  40.       reject(event);
  41.       if (removeElement) document.head.removeChild(element);
  42.       dynamicScriptPromises.delete(promiseKey);
  43.     };
  44.  
  45.     document.head.appendChild(element);
  46.   });
  47.  
  48.   dynamicScriptPromises.set(promiseKey, scriptPromise);
  49.  
  50.   return scriptPromise;
  51. }
  52.  
  53. /**
  54.  * Dynamically load remote module
  55.  * @param scope - appName
  56.  * @param module - './AppComponent'
  57.  * @see https://github.com/webpack/webpack/issues/11033#issuecomment-644349386
  58.  */
  59. export function loadRemoteModule(scope: string, module: string) {
  60.   return async () => {
  61.     // Initializes the share scope. This fills it with known provided modules from this build and all remotes
  62.     await __webpack_init_sharing__('default');
  63.     // @ts-expect-error // @ERROR: TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'.
  64.     const container = window[scope] as Container; // or get the container somewhere else
  65.     // Initialize the container, it may provide shared modules
  66.     await container.init(__webpack_share_scopes__.default);
  67.     const factory = await container.get(module);
  68.     const Module = factory();
  69.     return Module;
  70.   };
  71. }
  72.  
  73. /**
  74.  * Creates React.lazy component for provided `scope@url` and `module`
  75.  * @description equivalent to
  76.  *
  77.  *    `const App = React.lazy(() => import('appName/AppComponent'))`
  78.  * @param url - https://example.org/remoteEntry.js
  79.  * @param scope - appName
  80.  * @param module - './AppComponent'
  81.  */
  82. export function loadDynamicComponent<T extends React.ComponentType<any>>(
  83.   url: string,
  84.   scope: string,
  85.   module: string,
  86. ): React.LazyExoticComponent<T> {
  87.   const Component: React.LazyExoticComponent<T> = React.lazy(() => {
  88.     return loadDynamicScript(url).then(loadRemoteModule(scope, module));
  89.   });
  90.  
  91.   return Component;
  92. }
  93.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement