vue中结合vue3-sfc-loader加载远程组件

前言

在一些特殊的场景中(比如低代码、减少小程序包体积、类似于APP的热更新,我们需要从服务端动态加载.vue文件,然后将动态加载的远程vue组件渲染到我们的项目中。今天这篇文章我将带你学会,在vue3中如何去动态加载远程组件。

defineAsyncComponent异步组件

想必聪明的你第一时间就想到了defineAsyncComponent方法。我们先来看看官方对defineAsyncComponent方法的解释:

定义一个异步组件,它在运行时是懒加载的。参数可以是一个异步加载函数,或是对加载行为进行更具体定制的一个选项对象。

defineAsyncComponent方法的返回值是一个异步组件,我们可以像普通组件一样直接在template中使用。和普通组件的区别是,只有当渲染到异步组件时才会调用加载内部实际组件的函数。

我们先来简单看看使用defineAsyncComponent方法的例子,代码如下:

JavaScript 复制代码
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    // ...从服务器获取组件
    resolve(/* 获取到的组件 */)
  })
})
// ... 像使用其他一般组件一样使用 `AsyncComp`

defineAsyncComponent方法接收一个返回 Promise 的回调函数,在Promise中我们可以从服务端获取vue组件的code代码字符串。然后使用resolve(/* 获取到的组件 */)将拿到的组件传给defineAsyncComponent方法内部处理,最后和普通组件一样在template中使用AsyncComp组件。

从服务端获取远程组件

有了defineAsyncComponent方法后事情从表面上看着就很简单了,我们只需要写个方法从服务端拿到vue文件的code代码字符串,然后在defineAsyncComponent方法中使用resolve拿到的vue组件。

第一步就是本地起一个服务器,使用服务器返回我们的vue组件。这里我使用的是express结合cors处理跨域问题,安装也很简单:

JavaScript 复制代码
npm install express cors

接着在项目的src目录下新建一个名为server.js的文件,node执行这个文件就会在4333端口起一个服务

js 复制代码
import express from 'express';
import cors from 'cors';
const app = express()
const port = 4333
app.use(cors());
app.use(express.static('src'));
app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

接着我在项目的src/views目录下新建一个名为index.vue的文件,这个vue文件就是我们想从服务端加载的远程组件。index.vue文件中的代码如下:

vue 复制代码
<script setup>
import { ref } from 'vue'
</script>

<template>
  <div>
    <a href="https://vite.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="/vue.svg" class="logo vue" alt="Vue logo" />
    </a>
  </div>
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

defineAsyncComponent加载远程组件

js 复制代码
const RemoteChild = defineAsyncComponent(async () => {
  return new Promise(async (resolve) => {
    const res = await fetch("http://localhost:4333/views/index.vue");
    const code = await res.text();
    console.log("code", code);
    resolve(code);
  });
});

接下来我们就是在defineAsyncComponent方法接收的 Promise 的回调函数中使用fetch从服务端拿到远程组件的code代码字符串应该就行啦,代码如下: 同时使用console.log("code", code)打个日志看一下从服务端过来的vue代码。 上面的代码看着已经完美实现动态加载远程组件了,结果不出意外在浏览器中运行时报错了。如下图:

在上图中可以看到从服务端拿到的远程组件的代码和我们的index.vue的源代码是一样的,但是为什么会报错呢?

这里的报错信息显示加载异步组件报错,还记得我们前面说过的defineAsyncComponent方法是在回调中resolve(/* 获取到的组件 */)。而我们这里拿到的code是一个组件吗?

我们这里拿到的code只是组件的源代码,也就是常见的单文件组件SFC。而defineAsyncComponent中需要的是由源代码编译后拿的的vue组件对象,我们将组件源代码丢给defineAsyncComponent当然会报错了。

最终解决方案vue3-sfc-loader

从服务端拿到远程vue组件源码后,我们需要一个工具将拿到的vue组件源码编译成vue组件对象。幸运的是优秀的vue不光暴露出一些常见的API,而且还将一些底层API给暴露了出来。比如在@vue/compiler-sfc包中就暴露出来了compileTemplatecompileScriptcompileStyleAsync等方法。

vue3-sfc-loader包的核心代码就是调用@vue/compiler-sfc包的这些方法,将我们的vue组件源码编译为想要的vue组件对象。

下面这个是改为使用vue3-sfc-loader包后的代码,vue3-sfc-loader同时也支持在远程组件中去引用子组件,你只需在options额外配置一个pathResolve就行啦。如下:

vue 复制代码
<template>
  <button @click="showRemoteChild = true">加载远程组件</button>
  <RemoteChild v-if="showRemoteChild" />
</template>
 
<script setup>
import { defineAsyncComponent, ref, onMounted } from "vue";
import * as Vue from "vue";
import { loadModule } from "vue3-sfc-loader";
 
const showRemoteChild = ref(false);
const componentBaseUrl = 'http://localhost:4333/views/'
const options = {
  pathResolve({ refPath, relPath }) {
    if (relPath === ".")
      return refPath;
    if (relPath[0] !== "." && relPath[0] !== "/") return relPath;
    return String(
      new URL(relPath, refPath === undefined ? window.location : refPath)
    );
  },
  moduleCache: {
    vue: Vue,
  },
  async getFile(url) {
    const res = await fetch(url);
    const code = await res.text();
    return code;
  },
  addStyle(textContent) {
    const style = Object.assign(document.createElement("style"), {
      textContent,
    });
    const ref = document.head.getElementsByTagName("style")[0] || null;
    document.head.insertBefore(style, ref);
  },
};

const RemoteChild = defineAsyncComponent(async () => {
  const res = await loadModule(
    `${componentBaseUrl}index.vue`,
    options
  );
  return res;
});
</script>
 
<style scoped>
</style>

附上完整代码地址 gitee.com/TriF/vue3-r...

相关推荐
卤蛋fg6几秒前
vxe-table 列拖拽排序与行拖拽排序:让表格布局任意排序
vue.js
粉末的沉淀34 分钟前
vue:Vite项目中高效管理纯色SVG图标的方案
前端·javascript·vue.js
卤蛋fg635 分钟前
vxe-table 列宽与行高拖拽调整:让表格布局极其灵活,拖拽功能非常强大
vue.js
向日的葵0061 小时前
Vue 路由传参的三种方式(三)
vue.js·路由
如果超人不会飞1 小时前
TinyVue Checkbox复选框组件使用指南
前端·vue.js
如果超人不会飞2 小时前
TinyVue Radio单选框组件使用指南
vue.js
鲁班小子2 小时前
Vite resolve.dedupe 使用教程
vue.js·vite
如果超人不会飞2 小时前
TinyVue Input输入框组件使用指南
vue.js
如果超人不会飞2 小时前
TinyVue Pager分页组件使用指南
前端·vue.js
大刚测试开发实战2 小时前
TestHub重磅更新!AI用例生成增加流式输出、Markdown文档上传、模型配置检测、AI评审开关控制...
vue.js·后端·github