为不同的组件加载不同的JS库文件
Load different JS library files for different components
我有一个用 ReactJS 制作的网站。在public/index.html
,我有
<head>
<script src="/lib/analyzejs-v1.js"></script>
<script src="/lib/analyzejs-v2.js"></script>
</head>
<body>
<div id="root"></div>
</body>
其中analyzejs-v1.js
有6Mo,analyzejs-v2.js
有3Mo;它们都是固定文件,我不能修改太多。
这两个文件不是模块;它们的功能已声明(例如,src/defines/analyzejs-v1.d.ts
中的 declare function f1(address: string): string;
)。所以一些组件调用analyzejs-v1.js
的函数是直接使用f1(...)
这样的函数名,没有任何命名空间,没有import,没有export。其余组件直接使用f2(...)
这样的函数名调用analyzejs-v2.js
的函数,无需任何命名空间、导入或导出。
加载这两个js库文件需要时间。所以我正在寻找一种方法来根据组件(或 URL)加载 analyzejs-v1.js
或 analyzejs-v2.js
。
那么有没有人知道一个常规的方法,为不同的组件加载不同的JS库文件?
当您使用 <script>
标签导入脚本时,该库只能在客户端使用,因此不能与节点一起使用。但是,如果您将其标记为模块,则另一个脚本可以像这样使用它:
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")
}
唯一的问题是你的反应脚本和内联脚本之间的通信。我想通了,唯一的方法是使用 window
.
免责声明
我真的不确定,是否有人应该这样使用它。我只测试过一次,它很可能会坏掉……也许其他人可以告诉我他们对我的方法的看法。
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>
如果您不需要同时使用两个脚本,您可以在必要时在运行时添加脚本标签。
我可以为您提供一个钩子,我用它来动态加载脚本。
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;
}
使用方法:
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;
}
}
如果脚本不是模块,只需添加一个不带导入语句的typescript声明文件,并声明脚本导出的全局变量。如:
declare interface XXX {
YYY
}
declare const ScriptValue: XXX;
如果您正在寻找更好的页面性能并且不让脚本阻止 DOM 内容加载,那么您可能需要考虑向脚本添加延迟 https://javascript.info/script-async-defer#defer。
如果您正在寻找脚本的动态加载,并且仅在首次使用时加载脚本,请查看此文档 https://javascript.info/script-async-defer#dynamic-scripts
您可以在加载组件时创建 <script>
标记。
首先,我们创建一个函数来创建脚本标签
const scriptGenerator = (options = {}) => {
const s = document.createElement("script");
for (const option in options) {
s[option] = options[option]
}
document.querySelector("head").appendChild(s);
}
我们可以使用两个属性来加载脚本
- 推迟
- 异步
defer
: defer 属性告诉浏览器不要等待脚本。相反,浏览器将继续处理 HTML,构建 DOM。该脚本“在后台”加载,然后在 DOM 完全构建时运行。
async
: async属性有点像defer。它还使脚本成为非阻塞的。但它在行为上有重要差异。
完成这些步骤后,我们就可以定义我们的脚本在头。您可以使用 useEffect
useEffect(() => {
// V1
scriptGenerator({
src: "...",
async: 1,
});
// V2
scriptGenerator({
src: "...",
async: 1,
});
}, []);
退出组件后不要忘记delete
它们
useEffect(() => {
// Generate script
return () => {
document
.querySelector("head")
.querySelectorAll('script[src="..."]')
.remove();
}
});
如果要得出一个结论,就是如下:
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();
}
});
}
我有一个用 ReactJS 制作的网站。在public/index.html
,我有
<head>
<script src="/lib/analyzejs-v1.js"></script>
<script src="/lib/analyzejs-v2.js"></script>
</head>
<body>
<div id="root"></div>
</body>
其中analyzejs-v1.js
有6Mo,analyzejs-v2.js
有3Mo;它们都是固定文件,我不能修改太多。
这两个文件不是模块;它们的功能已声明(例如,src/defines/analyzejs-v1.d.ts
中的 declare function f1(address: string): string;
)。所以一些组件调用analyzejs-v1.js
的函数是直接使用f1(...)
这样的函数名,没有任何命名空间,没有import,没有export。其余组件直接使用f2(...)
这样的函数名调用analyzejs-v2.js
的函数,无需任何命名空间、导入或导出。
加载这两个js库文件需要时间。所以我正在寻找一种方法来根据组件(或 URL)加载 analyzejs-v1.js
或 analyzejs-v2.js
。
那么有没有人知道一个常规的方法,为不同的组件加载不同的JS库文件?
当您使用 <script>
标签导入脚本时,该库只能在客户端使用,因此不能与节点一起使用。但是,如果您将其标记为模块,则另一个脚本可以像这样使用它:
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")
}
唯一的问题是你的反应脚本和内联脚本之间的通信。我想通了,唯一的方法是使用 window
.
免责声明
我真的不确定,是否有人应该这样使用它。我只测试过一次,它很可能会坏掉……也许其他人可以告诉我他们对我的方法的看法。
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>
如果您不需要同时使用两个脚本,您可以在必要时在运行时添加脚本标签。 我可以为您提供一个钩子,我用它来动态加载脚本。
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;
}
使用方法:
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;
}
}
如果脚本不是模块,只需添加一个不带导入语句的typescript声明文件,并声明脚本导出的全局变量。如:
declare interface XXX {
YYY
}
declare const ScriptValue: XXX;
如果您正在寻找更好的页面性能并且不让脚本阻止 DOM 内容加载,那么您可能需要考虑向脚本添加延迟 https://javascript.info/script-async-defer#defer。
如果您正在寻找脚本的动态加载,并且仅在首次使用时加载脚本,请查看此文档 https://javascript.info/script-async-defer#dynamic-scripts
您可以在加载组件时创建 <script>
标记。
首先,我们创建一个函数来创建脚本标签
const scriptGenerator = (options = {}) => {
const s = document.createElement("script");
for (const option in options) {
s[option] = options[option]
}
document.querySelector("head").appendChild(s);
}
我们可以使用两个属性来加载脚本
- 推迟
- 异步
defer
: defer 属性告诉浏览器不要等待脚本。相反,浏览器将继续处理 HTML,构建 DOM。该脚本“在后台”加载,然后在 DOM 完全构建时运行。
async
: async属性有点像defer。它还使脚本成为非阻塞的。但它在行为上有重要差异。
完成这些步骤后,我们就可以定义我们的脚本在头。您可以使用 useEffect
useEffect(() => {
// V1
scriptGenerator({
src: "...",
async: 1,
});
// V2
scriptGenerator({
src: "...",
async: 1,
});
}, []);
退出组件后不要忘记delete
它们
useEffect(() => {
// Generate script
return () => {
document
.querySelector("head")
.querySelectorAll('script[src="..."]')
.remove();
}
});
如果要得出一个结论,就是如下:
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();
}
});
}