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?
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;
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>
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
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
: 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();
}
});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With