【问题标题】:Load different JS library files for different components为不同的组件加载不同的JS库文件
【发布时间】:2021-12-07 16:20:24
【问题描述】:

我有一个用 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;)。所以有些组件直接使用f1(...)这样的函数名调用analyzejs-v1.js的函数,没有任何命名空间、导入或导出。其余组件直接调用analyzejs-v2.js 的函数,直接使用f2(...) 之类的函数名,无需任何命名空间、导入或导出。

加载这两个js库文件需要时间。所以我正在寻找一种根据组件(或 URL)加载 analyzejs-v1.jsanalyzejs-v2.js 的方法。

那么有谁知道为不同组件加载不同 JS 库文件的常规方法吗?

【问题讨论】:

  • 什么是编译/捆绑你的index.tsx
  • 我猜是webpack
  • 您希望webpack 捆绑分析 js 文件还是手动将其与您的脚本标签一起包含在 head 中?
  • 目前,我只是手动将analyze js文件复制到需要的地方。我没有任何偏好,我正在寻找一种常规的方式。
  • 分析文件是源代码的一部分吗?我猜它是超级大的时候生成的。

标签: reactjs webpack react-scripts script-tag hamlet


【解决方案1】:

当您使用&lt;script&gt; 标记导入脚本时,该库只能用于客户端,因此不能用于节点。但是,如果将其标记为模块,则另一个脚本可以像这样使用它:

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>

【讨论】:

    【解决方案2】:

    如果你不需要同时使用两个脚本,可以在需要的时候在运行时添加脚本标签。 我可以为您提供一个我用来动态加载脚本的钩子。

    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;
     }
    }
    

    如果脚本不是模块,只需添加一个没有导入语句的打字稿声明文件,并声明脚本导出的全局变量。如:

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

    【讨论】:

    • 谢谢。你能告诉我更多关于如何在组件中调用useScript吗?
    • type (document as any) 应该是typeof (document as any)
    • 使用script as any是因为它是ie8的情况。 attachEvent 不是 ts 选项中文档的属性。我将编辑答案以添加用例
    • type (document as any) should be typeof (document as any)?
    【解决方案3】:

    如果您正在寻找更好的页面性能并且不让脚本阻止 DOM 内容加载,那么您可能需要考虑将延迟添加到脚本 https://javascript.info/script-async-defer#defer

    如果您正在寻找脚本的动态加载,并且仅在第一次使用时加载脚本,请查看此文档https://javascript.info/script-async-defer#dynamic-scripts

    【讨论】:

      【解决方案4】:

      您可以在加载组件时创建&lt;script&gt; 标签。

      首先,我们创建一个函数来创建脚本标签

      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。它还使脚本非阻塞。但它在行为上有重要的区别。

      async & defer Docs

      完成这些步骤后,我们可以在head定义我们的脚本。你可以使用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();
              }
          });
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-01-08
        • 1970-01-01
        • 1970-01-01
        • 2020-06-18
        • 2014-05-29
        • 1970-01-01
        • 2014-07-04
        相关资源
        最近更新 更多