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...

相关推荐
步行cgn26 分钟前
Vue 中的数据代理机制
前端·javascript·vue.js
风吹头皮凉4 小时前
vue实现气泡词云图
前端·javascript·vue.js
萌萌哒草头将军4 小时前
🚀🚀🚀尤雨溪推荐的这个库你一定要知道!轻量⚡️,优雅!
前端·vue.js·react.js
BillKu5 小时前
Vue3 + Vite 中使用 Lodash-es 的防抖 debounce 详解
前端·javascript·vue.js
chengchong_cc6 小时前
海康对接摄像头
java·vue.js
逝缘~6 小时前
小白学Pinia状态管理
前端·javascript·vue.js·vscode·es6·pinia
光影少年6 小时前
vite原理
前端·javascript·vue.js
源猿人7 小时前
文化与代码的交汇:OpenCC 驱动的中文语系兼容性解决方案
前端·vue.js
難釋懷7 小时前
Vue非单文件组件
前端·vue.js
克里斯前端7 小时前
vue在打包的时候能不能固定assets里的js和css文件名称
javascript·css·vue.js