Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 852
SoftTimur Avatar asked Oct 20 '21 20:10

SoftTimur


4 Answers

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;
like image 170
vipcxj Avatar answered Sep 18 '22 01:09

vipcxj


When you import a script using the <script> tag, the library can only be used client side and therefore not with node. However, if you mark it as a module, another script can use it like this:

index.html:

<script src="test.mjs" type="module"></script>
<script type="module">
      import {hello} from "./test.mjs"
      hello()
</script>

test.mjs:

export function hello(text) {
    console.log("hello from test")
}

The only thing is the communication between your react scripts and that inline script. The only way, I figured out, how to achieve this is using window.

DISCLAIMER

I'm really not sure, if anyone should use it this way. I have only tested this once and it might very well break... Maybe someone else can tell me their opinion on my approach.

index.tsx

... // imports

(window as any).importStuff = (a: any) => {
    a.hello()
}

...

index.html

<script src="test.mjs" type="module"></script>
<script type="module">
      import {hello} from "./test.mjs"
      window.importStuff({
            hello: hello
      })
</script>
like image 33
Vector-Hector Avatar answered Sep 21 '22 01:09

Vector-Hector


If you are looking for better performance of the page and not let the scripts block the DOM content loading, then you may want to look at adding defer to the script https://javascript.info/script-async-defer#defer.

If you are looking for dynamic loading of script, and load the script only when its first used, check this doc https://javascript.info/script-async-defer#dynamic-scripts

like image 32
MaReAL Avatar answered Sep 18 '22 01:09

MaReAL


You can create <script> tag when your component is loaded.

At the first, We create a function to create the script tag

const scriptGenerator = (options = {}) => {
    const s = document.createElement("script");
    for (const option in options) {
        s[option] = options[option]
    }

    document.querySelector("head").appendChild(s);
}

We can use two attributes to load scripts

  • defer
  • async

defer: The defer attribute tells the browser not to wait for the script. Instead, the browser will continue to process the HTML, build DOM. The script loads “in the background”, and then runs when the DOM is fully built.

async: The async attribute is somewhat like defer. It also makes the script non-blocking. But it has important differences in the behavior.

async & defer Docs

After all these steps, we can define our script in the head. You can use useEffect

useEffect(() => {
    // V1
    scriptGenerator({
        src: "...",
        async: 1,
    });

    // V2
    scriptGenerator({
        src: "...",
        async: 1,
    });
}, []);

Do not forget to delete them after exiting the component

useEffect(() => {
    // Generate script

    return () => {
        document
            .querySelector("head")
            .querySelectorAll('script[src="..."]')
            .remove();
    }
});

If we want to reach a conclusion, it is as follows:

import { useEffect } from 'react';

const Component = () => {
    const scriptGenerator = (options = {}) => {
        const s = document.createElement("script");
        for (const option in options) {
            s[option] = options[option]
        }

        document.querySelector("head").appendChild(s);
    }

    useEffect(() => {
        // V1
        scriptGenerator({
            src: "...",
            async: 1,
        });

        // V2
        scriptGenerator({
            src: "...",
            async: 1,
        });

        return () => {
            document
                .querySelector("head")
                .querySelectorAll('script[src="..."]')
                .remove();
        }
    });
}
like image 26
Ali Yaghoubi Avatar answered Sep 22 '22 01:09

Ali Yaghoubi