Location>code7788 >text

Give me 5 minutes and I promise to teach you to dynamically load remote components in vue3!

Popularity:149 ℃/2024-08-07 08:39:09

preamble

In some special scenarios (e.g., low code, reducing applet package size, app-like hot updates), we need to dynamically load from the server side the.vuefile and then render the dynamically loaded remote vue component into our project. Today in this post I will take you through how to go about dynamically loading remote components in vue3.

Ouyang wrote an open source ebookvue3 Compilation Principles Revealed, this book is accessible to middle school level front end. Totally free, just asking for a STAR.

defineAsyncComponentasynchronous component

I'm sure that's the first thing that came to mind.defineAsyncComponentMethods. Let's start with the official explanation of thedefineAsyncComponentExplanation of the methodology:

Defines an asynchronous component that is lazy loaded at runtime. The argument can be either an asynchronous loading function or an option object for more specific customization of the loading behavior.

defineAsyncComponentThe return value of the method is an asynchronous component that we can use directly in the template just like a normal component. The difference from a normal component is that the function to load the actual internal component is called only when rendering to the asynchronous component.

Let's take a brief look at usingdefineAsyncComponentmethod example with the following code:

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    // ... Getting the component from the server
    resolve(/* Fetched component */)
  })
})
// ... Use `AsyncComp` as you would any other general component.

defineAsyncComponentmethod receives a callback function that returns a Promise, in which we can get the code string of the vue component from the server. We then use theresolve(/* Fetched component */)Pass the obtained component to thedefineAsyncComponentmethod is handled internally and finally used in the template just like a normal componentAsyncCompComponent.

Getting remote components from the server

I've got a solution!defineAsyncComponentmethod, we just need to write a method to get the code string of the vue file from the server, and then add it in thedefineAsyncComponentmethod using theresolveGet the vue component.

The first step is to start a server locally and use it to return our vue component. Here I am using thehttp-server, and installation was easy:

npm install http-server -g

Using the above command you can globally install an http server.

Then I created a new directory in the public directory of the project namedfile, this vue file is the remote component we want to load from the server.The code in the file is as follows:

<template>
  <p>I'm a remote component.</p>
  <p>
    Current Remote Componentcountbe valued at:<span class="count">{{ count }}</span>
  </p>
  <button @click="count++">Click Add Remote Componentcount</button>
</template>

<script setup>
import { ref } from "vue";
const count = ref(0);
</script>

<style>
.count {
  color: red;
}
</style>

As you can see from the code above the remote vue component is not much different from the vue code we normally write, there aretemplaterefResponsive Variables,styleStyle.

The next step is to execute thehttp-server ./public --corscommand to start a local server with a default port of8080. But since the default port for our locally started vite project is5173So to avoid cross-domain you need to add--cors ./publicmeans to specify the current directory'spublicFolder.

After starting a local server, we can use thehttp://localhost:8080/The link to access the remote component from the server is as shown below:
remote-component

As you can see from the image above the action to download the remote vue component is triggered when this link is accessed in the browser.

defineAsyncComponentLoading Remote Components

const RemoteChild = defineAsyncComponent(async () => {
  return new Promise(async (resolve) => {
    const res = await fetch("http://localhost:8080/");
    const code = await ();
    ("code", code);
    resolve(code);
  });
});

The next thing we did was to get a little more information on thedefineAsyncComponentmethod receives the Promise callback function using fetch from the server to get the remote component code code string should be on the line, the code is as follows:

simultaneous use("code", code)Hit the log to see the vue code coming from the server.

The code above looks like it has been perfectly implementedDynamically loading remote componentsThe result was, not surprisingly, an error when running it in the browser. The following is a picture:
error

In the image above you can see the code of the remote component that we got from the server and ourThe source code is the same, but why does it report an error?

The error message here showsError loading asynchronous componentsRemember what we said earlier.defineAsyncComponentThe method is in the callbackresolve(/* Fetched component */). And we got it here.codeIs it a component?

We got it here.codeis just the source code of the component, which is the common single-file component SFC. and thedefineAsyncComponentWhat we need is a vue component object compiled from the source code, and we throw the component source code to thedefineAsyncComponentOf course it will report an error.

See here some of our partners have questions, we usually import child components in the parent component is not the same in the template on the direct use?

subassemblyCode:

<template>
  <p>I'm a local component.</p>
  <p>
    current local componentcountbe valued at:<span class="count">{{ count }}</span>
  </p>
  <button @click="count++">Click Add Local Componentcount</button>
</template>

<script setup>
import { ref } from "vue";
const count = ref(0);
</script>

<style>
.count {
  color: red;
}
</style>

Parent component code:

<template>
  <LocalChild />
</template>

<script setup lang="ts">
import LocalChild from "./";
("LocalChild", LocalChild);
</script>

Doesn't it seem weird to you that the above code for importing subcomponents has been written for so many years?

According to common sense to import sub-components, then in the sub-component must be written export can be, but in the sub-componentWe didn't write any code about export in the

The answer is that it is triggered when the parent component import imports the child component'svue-loaderor@vitejs/plugin-vueplugin's hook function, in which the source code of ourSingle File Component SFCCompile it into a normal js file, and in the js fileexport defaultExport the compiled vue component object.

Here's how to use the("LocalChild", LocalChild)Let's see what the compiled vue component object looks like, as shown below:
import-comp

From the image above you can see that the compiled vue component is an object that has therendersetupand other methods.The component received by the defineAsyncComponent method is this vue component objectBut we're throwing the vue component source code at him, so of course we're going to get an error.

Final Solutionvue3-sfc-loader

After getting the remote vue component source code from the server, we need a tool to compile the obtained vue component source code into vue component objects. Luckily good vue exposes not only some common APIs, but also some underlying APIs. For example, in the@vue/compiler-sfcIt's exposed in the bag.compileTemplatecompileScriptcompileStyleAsyncand other methods.

If you've read what I've writtenvue3 Compilation Principles Revealed Open Source eBook, you should feel familiar with these methods.

  • compileTemplatemethod: used to handle template modules in the single file component SFC.

  • compileScriptMethod: Used to handle the script module in the single file component SFC.

  • compileStyleAsyncmethod: used to handle the style module in the single file component SFC.

(indicates contrast)vue3-sfc-loaderThe core code of the package is a call to@vue/compiler-sfcpackage of these methods to compile our vue component source code into the desired vue component object.
The following was changed to usevue3-sfc-loaderThe code after the package is as follows:

import * as Vue from "vue";
import { loadModule } from "vue3-sfc-loader";

const options = {
  moduleCache: {
    vue: Vue,
  },
  async getFile(url) {
    const res = await fetch(url);
    const code = await ();
    return code;
  },
  addStyle(textContent) {
    const style = (("style"), {
      textContent,
    });
    const ref = ("style")[0] || null;
    (style, ref);
  },
};

const RemoteChild = defineAsyncComponent(async () => {
  const res = await loadModule(
    "http://localhost:8080/",
    options
  );
  ("res", res);
  return res;
});

loadModuleThe first parameter received by the function is the URL of the remote component, the second parameter is theoptionsThe InoptionsThere's agetFilemethod, where getting the code string of the remote component is done.

Let's take a look at the terminal and see what's going on.loadModuleThe vue component object that the function gets after processing looks like the following:
compiler-remote-comp

As you can see from the graph above afterloadModulefunction to get to the vue component object, and this component object also has the familiarrenderfunctions andsetupfunction. One of therenderfunction is compiled from the template module of the remote component.setupFunctions are compiled from the remote component's script module.

Seeing this you may have a question, how come the style module of the remote component doesn't have a mention on top of the generated vue component object?

The answer is that the css compiled by the style module doesn't get stuffed on top of the vue component object, but instead is passed separately through theoptionsupperaddStylemethod is passed back to us.addStyleThe parameters received by the methodtextContentvalue is the css string compiled by the style module in theaddStylemethod we are creating a style tag and then inserting the resulting css string into the page.

The complete parent component code is as follows:

<template>
  <LocalChild />
  <div class="divider" />
  <button @click="showRemoteChild = true">Loading Remote Components</button>
  <RemoteChild v-if="showRemoteChild" />
</template>

<script setup lang="ts">
import { defineAsyncComponent, ref, onMounted } from "vue";
import * as Vue from "vue";
import { loadModule } from "vue3-sfc-loader";
import LocalChild from "./";

const showRemoteChild = ref(false);

const options = {
  moduleCache: {
    vue: Vue,
  },
  async getFile(url) {
    const res = await fetch(url);
    const code = await ();
    return code;
  },
  addStyle(textContent) {
    const style = (("style"), {
      textContent,
    });
    const ref = ("style")[0] || null;
    (style, ref);
  },
};

const RemoteChild = defineAsyncComponent(async () => {
  const res = await loadModule(
    "http://localhost:8080/",
    options
  );
  ("res", res);
  return res;
});
</script>

<style scoped>
.divider {
  background-color: red;
  width: 100vw;
  height: 1px;
  margin: 20px 0;
}
</style>

In the full example above, the local component is first renderedLocalChildThen render the remote component after clicking the "Load Remote Component" button. Then when you click on the "Load Remote Component" button, you can render the remote component.RemoteChild. Let's take a look at the results of the execution, as shown below:
full

As you can see from the gif above, the remote component is loaded in network only after we click the "Load Remote Component" button.. And after rendering the remote component to the page, you can see that the responsiveness of the remote component is still valid by the button click event.

vue3-sfc-loaderThere is also support for de-referencing sub-components in a remote component, you just need to add a new sub-component to theoptionsConfigure an additionalpathResolveIt's fine.pathResolveThe methods are configured as follows:

const options = {
  pathResolve({ refPath, relPath }, options) {
    if (relPath === ".")
      // self
      return refPath;

    // relPath is a module name ?
    if (relPath[0] !== "." && relPath[0] !== "/") return relPath;

    return String(
      new URL(relPath, refPath === undefined ? : refPath)
    );
  },
  // getFilemethodologies
  // addStylemethodologies
}

indeedvue3-sfc-loaderThe core code of the package is about 300 lines, mainly calling some of the underlying APIs exposed by vue, as shown below:
vue3-sfc-loader

summarize

This article talks about how to load a remote component from the server in vue3, first we need to use thedefineAsyncComponentmethod defines an asynchronous component that can be used directly in the template as a normal component.

However, due todefineAsyncComponentThe component received must be a compiled vue component object, and the remote component we get from the server is just a regular vue file, so that's when we introduce thevue3-sfc-loaderPackage.vue3-sfc-loaderThe purpose of the package is to compile a vue file into a vue component object at runtime so that we can implement loading remote components from the server.

Follow the public number: [Front-end Ouyang], give yourself a chance to advance vue

Also Ouyang wrote an open source ebookvue3 Compilation Principles Revealed, this book is accessible to middle school level front end. Totally free, just asking for a STAR.