Skip to content

Vue3 远程加载组件

1. 基本远程组件加载

html
<template>
  <div>
    <h1>远程组件示例</h1>
    <button @click="loadRemoteComponent">加载远程组件</button>
    <Suspense v-if="showRemoteComponent">
      <template #default>
        <RemoteComponent />
      </template>
      <template #fallback>
        <div>加载中...</div>
      </template>
    </Suspense>
  </div>
</template>

<script setup>
import { ref, defineAsyncComponent } from 'vue';

const showRemoteComponent = ref(false);
const RemoteComponent = ref(null);

const loadRemoteComponent = async () => {
  showRemoteComponent.value = true;
  
  RemoteComponent.value = defineAsyncComponent(() => 
    import('https://your-cdn.com/path/to/remote-component.js').then(module => {
      // 可能需要根据实际导出进行调整
      return module.default || module;
    })
  );
};
</script>

2. 带参数的远程组件加载

html
<template>
  <div>
    <button @click="loadComponent('UserProfile')">加载用户资料组件</button>
    <button @click="loadComponent('ProductCard')">加载产品卡片组件</button>
    
    <Suspense>
      <template #default>
        <component :is="currentComponent" v-bind="componentProps" />
      </template>
      <template #fallback>
        <div>加载组件中...</div>
      </template>
    </Suspense>
  </div>
</template>

<script setup>
import { ref, shallowRef, defineAsyncComponent } from 'vue';

const currentComponent = shallowRef(null);
const componentProps = ref({});

const componentMap = {
  UserProfile: {
    url: 'https://your-cdn.com/components/user-profile.js',
    props: { userId: '123' }
  },
  ProductCard: {
    url: 'https://your-cdn.com/components/product-card.js',
    props: { productId: '456' }
  }
};

const loadComponent = async (componentName) => {
  const config = componentMap[componentName];
  if (!config) return;
  
  currentComponent.value = defineAsyncComponent(() =>
    import(/* @vite-ignore */ config.url).then(module => module.default || module)
  );
  componentProps.value = config.props;
};
</script>

3. 带错误处理的远程组件

html
  <div>
    <button @click="loadComponent">加载远程组件</button>
    
    <div v-if="error" class="error">
      加载失败: {{ error.message }}
      <button @click="retry">重试</button>
    </div>
    
    <Suspense v-else-if="isLoading">
      <template #default>
        <RemoteComponent />
      </template>
      <template #fallback>
        <div>加载中...</div>
      </template>
    </Suspense>
  </div>
</template>

<script setup>
import { ref, defineAsyncComponent } from 'vue';

const isLoading = ref(false);
const error = ref(null);
const RemoteComponent = ref(null);

const loadComponent = async () => {
  try {
    isLoading.value = true;
    error.value = null;
    
    RemoteComponent.value = defineAsyncComponent({
      loader: () => import(/* @vite-ignore */ 'https://your-cdn.com/components/special-component.js'),
      onError: (err, retry, fail) => {
        error.value = err;
        console.error('加载组件失败:', err);
      }
    });
  } catch (err) {
    error.value = err;
  } finally {
    isLoading.value = false;
  }
};

const retry = () => {
  error.value = null;
  loadComponent();
};
</script>

4. 使用 Webpack 模块联邦的远程组件

html
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'hostApp',
      remotes: {
        remoteApp: 'remoteApp@http://remote-domain.com/remoteEntry.js'
      }
    })
  ]
};

// Vue组件中使用
<template>
  <Suspense>
    <template #default>
      <RemoteComponent />
    </template>
    <template #fallback>
      <div>加载中...</div>
    </template>
  </Suspense>
</template>

<script setup>
import { defineAsyncComponent } from 'vue';

const RemoteComponent = defineAsyncComponent(() =>
  import('remoteApp/SomeComponent').then(module => module.default)
);
</script>

服务器端配置

ts
const app = express();

// 设置CORS
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  next();
});

// 提供组件文件
app.get('/components/:componentName.js', (req, res) => {
  const componentName = req.params.componentName;
  
  // 这里可以根据请求动态生成组件,或返回预编译的文件
  const componentCode = `
    export default {
      name: '${componentName}',
      template: '<div>这是远程加载的${componentName}组件</div>'
    }
  `;
  
  res.type('application/javascript').send(componentCode);
});

app.listen(3000, () => console.log('Server running on port 3000'));

Vue3 直接远程加载 .vue 文件

html
  <div>
    <h1>远程加载.vue文件示例</h1>
    <button @click="loadRemoteVueFile">加载远程组件</button>
    
    <div v-if="loading">加载中...</div>
    <div v-if="error" class="error">{{ error }}</div>
    
    <component :is="remoteComponent" v-if="remoteComponent" />
  </div>
</template>

<script setup>
import { ref, shallowRef } from 'vue';
import { compile } from 'vue/compiler-sfc';

const remoteComponent = shallowRef(null);
const loading = ref(false);
const error = ref(null);

const loadRemoteVueFile = async () => {
  try {
    loading.value = true;
    error.value = null;
    remoteComponent.value = null;

    // 1. 获取远程.vue文件内容
    const response = await fetch('http://localhost:3000/remote-component.vue');
    const vueFileContent = await response.text();

    // 2. 编译.vue文件
    const { descriptor } = compile(vueFileContent, {
      filename: 'remote-component.vue'
    });

    // 3. 提取script和template
    const scriptContent = descriptor.script?.content || 'export default {}';
    const templateContent = descriptor.template?.content || '<div>No template</div>';

    // 4. 创建组件选项对象
    const componentOptions = new Function(`
      ${scriptContent.replace('export default', 'return ')}
    `)();

    componentOptions.template = templateContent;

    // 5. 注册组件
    remoteComponent.value = componentOptions;
  } catch (err) {
    error.value = `加载失败: ${err.message}`;
    console.error('加载远程组件失败:', err);
  } finally {
    loading.value = false;
  }
};
</script>

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

服务端实现 (Node.js + Express)

ts
const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();

// 允许跨域
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  next();
});

// 提供.vue文件
app.get('/remote-component.vue', (req, res) => {
  const filePath = path.join(__dirname, 'components', 'RemoteComponent.vue');
  
  fs.readFile(filePath, 'utf8', (err, data) => {
    if (err) {
      res.status(404).send('Component not found');
      return;
    }
    
    res.type('text/plain').send(data);
  });
});

// 静态文件服务(如果需要加载组件中的资源)
app.use(express.static('public'));

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

远程组件示例 (RemoteComponent.vue)

html
<template>
  <div class="remote-component">
    <h2>这是远程加载的组件</h2>
    <p>当前时间: {{ new Date().toLocaleString() }}</p>
    <button @click="count++">点击计数: {{ count }}</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  mounted() {
    console.log('远程组件已挂载');
  }
}
</script>

<style scoped>
.remote-component {
  border: 2px dashed #42b983;
  padding: 20px;
  margin: 20px 0;
  border-radius: 8px;
}

.remote-component h2 {
  color: #42b983;
}
</style>

优化版本(使用预编译)

对于生产环境,建议服务端预编译组件:

ts
app.get('/precompiled-component.js', async (req, res) => {
  const filePath = path.join(__dirname, 'components', 'RemoteComponent.vue');
  const vueFileContent = await fs.promises.readFile(filePath, 'utf8');
  
  // 使用@vue/compiler-sfc预编译
  const { compile } = require('vue/compiler-sfc');
  const { descriptor } = compile(vueFileContent);
  
  const script = descriptor.script?.content || 'export default {}';
  const template = descriptor.template?.content || '<div></div>';
  
  const componentCode = `
    ${script}
    export default {
      ...${script.includes('export default') ? '__script__' : '{}'},
      template: ${JSON.stringify(template)}
    }
  `.replace('export default', 'const __script__ =');
  
  res.type('application/javascript').send(componentCode);
});

然后在客户端可以直接加载预编译的版本:

ts
  const module = await import('http://localhost:3000/precompiled-component.js');
  remoteComponent.value = module.default;
};

如有转载或 CV 的请标注本站原文地址