reactjswebpackreact-scriptsscript-taghamlet

Load different JS library files for different components


I have a website made in ReactJS. In public/index.html, I have

<head>
  <script src="/lib/analyzejs-v1.js"></script>
  <script src="/lib/analyzejs-v2.js"></script>
</head>
<body>
  <div id="root"></div>
</body>

where analyzejs-v1.js has 6Mo, and analyzejs-v2.js has 3Mo; they are all fixed files that I could not much modify.

These two files are not modules; their functions are declared (e.g., declare function f1(address: string): string; in src/defines/analyzejs-v1.d.ts). So some components call functions of analyzejs-v1.js by using a function name like f1(...) directly without any namespace, import, or export. And the rest of the components call functions of analyzejs-v2.js by using a function name like f2(...) directly without any namespace, import, or export.

It takes time to load these two js library files. So I'm looking for a way to load either analyzejs-v1.js or analyzejs-v2.js according to the component (or URL).

So does anyone know a conventional way to load different JS library files for different components?


Solution

  • If you don't need two script at the same time, you can add the script tag in the runtime when necessary. I can provide you a hook which I have used to load the script on the fly.

    export function useScript(url: string, clean: boolean = false, cleanJob: () => void = () => undefined): boolean {
      const [loaded, setLoaded] = useState(false);
      useEffect(() => {
        let create = false;
        let script = document.querySelector(`script[src="${url}"]`) as HTMLScriptElement | null;
        if (!script) {
          script = document.createElement('script');
          script.src = url;
          script.async = true;
          if (type (document as any).attachEvent === 'object') {
            (script as any).onreadystatechange = () => {
              if ((script as any).readyState === 'loaded') {
                setLoaded(true);
              }
            }
          } else {
            script.onload = () => {
              setLoaded(true);
            }
          }
          document.body.appendChild(script);
          create = true;
        } else {
          setLoaded(true);
        }
        // For a special library, you can do the clean work by deleting the variable it exports.
        return () => {
          if (create && script && clean) {
            setLoaded(false);
            document.body.removeChild(script);
            cleanJob && cleanJob();
          }
        }
      }, [url]);
      return loaded;
    }
    

    To use it:

    export const Comp = (props: ICompProps) => {
     const loaded = useScript('https://path/to/the/script.js');
     // if you want to do some clean work, Suppose the external script introduces the variable A, And A can be reasigned.
     // const loaded = useScript('https://path/to/the/script.js', true, () -> { A = undefined; });
     useEffect(() -> {
       if (loaded) {
         // Suppose the external script introduces the variable A. Now it is available.
         const a = new A();
         // do something with a.
       }
     }, [loaded]);
     if (loaded) {
       return XXX;  
     } else {
       return null;
     }
    }
    

    If the script is not a module, just add a typescript declare file without import statements, and declare the global variable the script export. Such as:

    declare interface XXX {
      YYY
    }
    declare const ScriptValue: XXX;