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.vue
file 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.
defineAsyncComponent
asynchronous component
I'm sure that's the first thing that came to mind.defineAsyncComponent
Methods. Let's start with the official explanation of thedefineAsyncComponent
Explanation 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.
defineAsyncComponent
The 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 usingdefineAsyncComponent
method 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.
defineAsyncComponent
method 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 thedefineAsyncComponent
method is handled internally and finally used in the template just like a normal componentAsyncComp
Component.
Getting remote components from the server
I've got a solution!defineAsyncComponent
method, we just need to write a method to get the code string of the vue file from the server, and then add it in thedefineAsyncComponent
method using theresolve
Get 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 aretemplate
、ref
Responsive Variables,style
Style.
The next step is to execute thehttp-server ./public --cors
command to start a local server with a default port of8080
. But since the default port for our locally started vite project is5173
So to avoid cross-domain you need to add--cors
。 ./public
means to specify the current directory'spublic
Folder.
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:
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.
defineAsyncComponent
Loading 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 thedefineAsyncComponent
method 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:
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.defineAsyncComponent
The method is in the callbackresolve(/* Fetched component */)
. And we got it here.code
Is it a component?
We got it here.code
is just the source code of the component, which is the common single-file component SFC. and thedefineAsyncComponent
What we need is a vue component object compiled from the source code, and we throw the component source code to thedefineAsyncComponent
Of 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 default
Export 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:
From the image above you can see that the compiled vue component is an object that has therender
、setup
and 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-sfc
It's exposed in the bag.compileTemplate
、compileScript
、compileStyleAsync
and other methods.
If you've read what I've writtenvue3 Compilation Principles Revealed Open Source eBook, you should feel familiar with these methods.
-
compileTemplate
method: used to handle template modules in the single file component SFC. -
compileScript
Method: Used to handle the script module in the single file component SFC. -
compileStyleAsync
method: used to handle the style module in the single file component SFC.
(indicates contrast)vue3-sfc-loader
The core code of the package is a call to@vue/compiler-sfc
package of these methods to compile our vue component source code into the desired vue component object.
The following was changed to usevue3-sfc-loader
The 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;
});
loadModule
The first parameter received by the function is the URL of the remote component, the second parameter is theoptions
The Inoptions
There's agetFile
method, 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.loadModule
The vue component object that the function gets after processing looks like the following:
As you can see from the graph above afterloadModule
function to get to the vue component object, and this component object also has the familiarrender
functions andsetup
function. One of therender
function is compiled from the template module of the remote component.setup
Functions 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 theoptions
upperaddStyle
method is passed back to us.addStyle
The parameters received by the methodtextContent
value is the css string compiled by the style module in theaddStyle
method 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 renderedLocalChild
Then 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:
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-loader
There is also support for de-referencing sub-components in a remote component, you just need to add a new sub-component to theoptions
Configure an additionalpathResolve
It's fine.pathResolve
The 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-loader
The core code of the package is about 300 lines, mainly calling some of the underlying APIs exposed by vue, as shown below:
summarize
This article talks about how to load a remote component from the server in vue3, first we need to use thedefineAsyncComponent
method defines an asynchronous component that can be used directly in the template as a normal component.
However, due todefineAsyncComponent
The 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-loader
Package.vue3-sfc-loader
The 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.