最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

vue.js - Dynamically loading components based on tag-name - Stack Overflow

programmeradmin4浏览0评论

I'm trying to automatically load components from a URL while parsing HTML templates, based on tag names. i.e. If an tag name ends with Component (like xxxComponent) this is a component which should be loaded from server, if is not already loaded.

I've wrote a code, but I can't figure out how I can force Vue to load components based on tag name.

Here is a simple VueJS app code which:

  • uses CDN version of VueJS.
  • has 2 components: myComponent and anotherComponent.
  • the loadComponent function tries to load a component by name and returns template, script, and style of the component.
  • tries to use config.isUnknownElements which doesn't work at all and is never called (this link suggests this approach)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue App</title>
  <script src="@3/dist/vue.global.js"></script>
  <style>
    /* Dynamically loaded styles will be added here */
  </style>
</head>
<body>

    <div id="app">
        <h1>START</h1>
        <hr>

        <myComponent />
        <anotherComponent />
        
        <hr>
        <h1>END</h1>
    </div>

  <script>
    const componentCache = {};

    async function loadComponent(name) {
      if (componentCache[name]) {
        return componentCache[name]; // Return cached component if already loaded
      }

      const url = `http://127.0.0.1:8080/src/${name}.vue`;

      try {
        const response = await fetch(url);
        if (!response.ok) throw new Error(`Component ${name} not found`);
        
        const componentCode = await response.text();

        const parsedComponent = {
            template: componentCode.match(/<template>([\s\S]*?)<\/template>/).trim(),
            script: componentCode.match(/<script>([\s\S]*?)<\/script>/).trim(),
            style: componentCode.match(/<style>([\s\S]*?)<\/style>/).trim(),
        };

        componentCache[name] = parsedComponent; // Cache the loaded component

        return parsedComponent;
      } catch (error) {
        console.error(error);

        return {
          template: `<div>The component <strong>${name}</strong> is not available.</div>`,
          script: ``,
          style: `div {color: red; border: solid 1px red;}`,
        };
      }
    }

    const app = Vue.createApp({
      template: document.getElementById('app').innerHTML,
    });

    app.config.isUnknownElements = function () {
        let tagName = arguments[0].toLowerCase();

        if (/\S+Component$/i.test(tagName)) {
          console.log('loading component: ' + tagName);

          if (typeof Vue.optionsponents[tagName] === 'undefined') {
            // register a lazy-loader for the component
            Vueponent(tagName, function (resolve) {
              // ... Load the component 'componentName' in a "lazy" way, see link (1)
            });
          }
        }
        return false;  // Same behavior of the default Vue configuration, see link (2)
      };

console.log(app.config);

    app.mount("#app");
  </script>
</body>
</html>

I'm trying to automatically load components from a URL while parsing HTML templates, based on tag names. i.e. If an tag name ends with Component (like xxxComponent) this is a component which should be loaded from server, if is not already loaded.

I've wrote a code, but I can't figure out how I can force Vue to load components based on tag name.

Here is a simple VueJS app code which:

  • uses CDN version of VueJS.
  • has 2 components: myComponent and anotherComponent.
  • the loadComponent function tries to load a component by name and returns template, script, and style of the component.
  • tries to use config.isUnknownElements which doesn't work at all and is never called (this link suggests this approach)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue App</title>
  <script src="https://unpkg/vue@3/dist/vue.global.js"></script>
  <style>
    /* Dynamically loaded styles will be added here */
  </style>
</head>
<body>

    <div id="app">
        <h1>START</h1>
        <hr>

        <myComponent />
        <anotherComponent />
        
        <hr>
        <h1>END</h1>
    </div>

  <script>
    const componentCache = {};

    async function loadComponent(name) {
      if (componentCache[name]) {
        return componentCache[name]; // Return cached component if already loaded
      }

      const url = `http://127.0.0.1:8080/src/${name}.vue`;

      try {
        const response = await fetch(url);
        if (!response.ok) throw new Error(`Component ${name} not found`);
        
        const componentCode = await response.text();

        const parsedComponent = {
            template: componentCode.match(/<template>([\s\S]*?)<\/template>/).trim(),
            script: componentCode.match(/<script>([\s\S]*?)<\/script>/).trim(),
            style: componentCode.match(/<style>([\s\S]*?)<\/style>/).trim(),
        };

        componentCache[name] = parsedComponent; // Cache the loaded component

        return parsedComponent;
      } catch (error) {
        console.error(error);

        return {
          template: `<div>The component <strong>${name}</strong> is not available.</div>`,
          script: ``,
          style: `div {color: red; border: solid 1px red;}`,
        };
      }
    }

    const app = Vue.createApp({
      template: document.getElementById('app').innerHTML,
    });

    app.config.isUnknownElements = function () {
        let tagName = arguments[0].toLowerCase();

        if (/\S+Component$/i.test(tagName)) {
          console.log('loading component: ' + tagName);

          if (typeof Vue.optionsponents[tagName] === 'undefined') {
            // register a lazy-loader for the component
            Vueponent(tagName, function (resolve) {
              // ... Load the component 'componentName' in a "lazy" way, see link (1)
            });
          }
        }
        return false;  // Same behavior of the default Vue configuration, see link (2)
      };

console.log(app.config);

    app.mount("#app");
  </script>
</body>
</html>

Share Improve this question asked Mar 25 at 18:15 user1243815user1243815 113 bronze badges 8
  • Parsing that is based just on regexp is faulty. As for loading, this is what async components are for. You're probably trying to reinvent the wheel, github/FranckFreiburger/vue3-sfc-loader . – Estus Flask Commented Mar 25 at 21:54
  • @EstusFlask I had a quick look at the link, it looks like that I need to load components inside Vue.createApp(). That is not what I want. I don't want to maintain a list of available components, nor load them on page load. I just want to use a HTML tag to load corresponding component from a url and execute it. something like <myComponent>, <loadableComponent load="myComponent">, or <myComponent load=true>, or something similar, without prior knowledge that if that component already exists or is loaded or not. I did something like this in angular cli few years ago, but I'm newbie in VueJS. – user1243815 Commented Mar 25 at 22:06
  • @EstusFlask Also thanks for the warning, I can send the components code as JSON if it is needed. I have all control on server/client code, but as I told before, I don't want to send a list of components to the client, or load them on page load time. – user1243815 Commented Mar 25 at 22:08
  • You may need to keep the list of loaded components to avoid recurring requests. componentCache would contain defineAsyncComponent() for this purpose. It's not related to createApp, the docs show that defineAsyncComponent( () => loadModule('./myComponent.vue', options) ) can be used in any place where dynamically loaded component is required. loadableComponent would be a suitable place for this . I'm not sure it's possible to do in Vue 3 what you tried to do for <myComponent /> with isUnknownElements (it's isUnknownElement in the link) because it was a solution for v2 only, I'm unaware of v3 – Estus Flask Commented Mar 25 at 22:17
  • I.e. loadableComponent would be as simple as <template><component :is="cache[props.load]"/></template><script setup>let cache=shallowReactive({}); let props=defineProps(['load']); watchEffect(() => { if (props.load && !cache[props.load]) { cache[props.load] = defineAsyncComponent(() => loadModule(`.../src/${name}.vue`)) }). Cache could be global in case there are multiple occurrences of loadableComponent. I'd expect something like this to work. Let me know if this is what you asked about – Estus Flask Commented Mar 25 at 22:28
 |  Show 3 more comments

1 Answer 1

Reset to default 0

Maybe you're looking for this https://vuejs./guide/essentials/component-basics#dynamic-components

<component :is="tagName" />

发布评论

评论列表(0)

  1. 暂无评论