Skip to content
字数
1564 字
阅读时间
8 分钟

一、前提:Vue组件库设计(NPM包)

首先需开发一个Vue组件库(发布为NPM包),包含基础组件和渲染工具。

1. 组件库结构

my-vue-components/
├─ src/
│  ├─ components/       # 业务组件
│  │  ├─ Coupon.vue     # 优惠券组件
│  │  └─ Form.vue       # 表单组件
│  ├─ engine/           # 渲染引擎
│  │  ├─ ComponentMap.js # 组件映射表
│  │  └─ renderer.js    # 渲染工具函数
│  └─ index.js          # 入口文件
└─ package.json         # 配置UMD打包

2. 核心文件实现

  • Coupon.vue(示例组件)

    vue
    <template>
      <div class="coupon" @click="handleClick">
        <h3>{{ title }}</h3>
        <p>满{{ condition }}减{{ discount }}</p>
      </div>
    </template>
    <script setup>
    import { defineProps, emit } from 'vue';
    const props = defineProps({
      title: { type: String, default: '优惠券' },
      condition: { type: Number, default: 100 },
      discount: { type: Number, default: 20 }
    });
    const emit = defineEmits(['click']);
    const handleClick = () => emit('click', { id: props.id });
    </script>
    <style scoped>
    .coupon { padding: 16px; border: 1px solid #f00; border-radius: 8px; }
    </style>
  • ComponentMap.js(组件映射表)

    javascript
    import Coupon from '../components/Coupon.vue';
    import Form from '../components/Form.vue';
    // 映射组件类型到实际组件
    export default {
      Coupon,
      Form
    };
  • renderer.js(渲染工具)

    javascript
    import { createApp } from 'vue';
    import ComponentMap from './ComponentMap';
    
    export default {
      // 渲染组件到指定容器
      render(config, container) {
        const { componentType, props = {}, events = {} } = config;
        const Component = ComponentMap[componentType];
        if (!Component) throw new Error(`组件${componentType}不存在`);
    
        // 销毁已有实例
        if (container.__vueApp__) {
          container.__vueApp__.unmount();
        }
    
        // 创建Vue实例,合并props和事件
        const app = createApp(Component, {
          ...props,
          ...events // 事件回调(如onClick)
        });
    
        // 挂载到容器
        const instance = app.mount(container);
        container.__vueApp__ = app; // 保存实例用于后续销毁
        return instance;
      }
    };
  • index.js(入口文件)

    javascript
    import renderer from './engine/renderer';
    import ComponentMap from './engine/ComponentMap';
    // 暴露给外部的API
    export default {
      renderer,
      ComponentMap
    };

3. 打包配置(Vite)

确保输出UMD格式,支持浏览器直接引入:

javascript
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  build: {
    lib: {
      entry: 'src/index.js',
      name: 'MyVueComponents', // 全局变量名
      formats: ['umd'],
      fileName: () => 'my-vue-components.umd.js'
    },
    rollupOptions: {
      external: ['vue'], // 外部化Vue,避免打包
      output: { globals: { vue: 'Vue' } } // 浏览器中Vue的全局变量
    }
  }
});

发布NPM包后,可通过CDN访问(如https://unpkg.com/my-vue-components@1.0.0/dist/my-vue-components.umd.js)。

二、iframe实现(加载组件库并渲染)

iframe作为独立容器,负责加载Vue和组件库,接收父H5的配置并渲染组件。

1. iframe入口HTML

html
<!-- iframe.html -->
<!DOCTYPE html>
<html style="margin:0; padding:0;">
<head>
  <meta name="viewport" content="width=device-width">
  <!-- 引入Vue 3 -->
  <script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
  <!-- 引入自定义Vue组件库(NPM包的CDN地址) -->
  <script src="https://unpkg.com/my-vue-components@1.0.0/dist/my-vue-components.umd.js"></script>
</head>
<body>
  <!-- 组件渲染容器 -->
  <div id="component-container"></div>

  <script>
    // 全局变量:组件库API(挂载在window上)
    const { renderer } = window.MyVueComponents;
    const container = document.getElementById('component-container');

    // 监听父H5的消息
    window.addEventListener('message', (e) => {
      // 验证消息来源(安全校验)
      if (e.origin !== 'https://parent-h5.com') return;

      const { type, data } = e.data;
      if (type === 'RENDER_COMPONENT') {
        try {
          // 渲染组件:传递配置(包含componentType、props、events)
          renderer.render(data, container);
          // 通知父H5渲染成功
          window.parent.postMessage({ type: 'RENDER_SUCCESS' }, e.origin);
        } catch (error) {
          // 渲染失败处理
          container.innerHTML = `<div style="color:red">组件渲染失败: ${error.message}</div>`;
          window.parent.postMessage({ type: 'RENDER_ERROR', error: error.message }, e.origin);
        }
      }
    });
  </script>
</body>
</html>

三、父H5实现(嵌入iframe并交互)

父H5通过iframe嵌入组件,传递配置并接收组件事件。

1. 父H5页面

html
<!-- parent.html -->
<!DOCTYPE html>
<html>
<body>
  <h1>父H5页面</h1>
  <!-- 嵌入iframe -->
  <iframe
    id="vue-component-iframe"
    src="https://your-cdn.com/iframe.html" <!-- iframe的部署地址 -->
    style="width: 100%; border: none; min-height: 200px;"
    scrolling="no"
  ></iframe>

  <script>
    const iframe = document.getElementById('vue-component-iframe');
    const iframeOrigin = 'https://your-cdn.com'; // iframe的域名

    // 等待iframe加载完成
    iframe.onload = () => {
      // 向iframe发送组件配置(渲染优惠券组件)
      iframe.contentWindow.postMessage({
        type: 'RENDER_COMPONENT',
        data: {
          componentType: 'Coupon', // 组件类型(对应ComponentMap)
          props: { // 组件属性
            id: 'c123',
            title: '双11专属券',
            condition: 200,
            discount: 50
          },
          events: { // 组件事件(映射到组件的emit)
            onClick: (payload) => { // 优惠券点击事件
              console.log('父H5接收点击事件:', payload);
              alert(`点击了优惠券${payload.id}`);
            }
          }
        }
      }, iframeOrigin);
    };

    // 监听iframe的消息(如渲染结果、组件事件)
    window.addEventListener('message', (e) => {
      if (e.origin !== iframeOrigin) return;

      switch (e.data.type) {
        case 'RENDER_SUCCESS':
          console.log('组件渲染成功');
          break;
        case 'RENDER_ERROR':
          console.error('组件渲染失败:', e.data.error);
          break;
      }
    });
  </script>
</body>
</html>

四、核心逻辑说明

  1. 组件库与iframe的关系
    组件库通过UMD格式暴露renderer工具,iframe加载后可直接调用renderer.render()渲染组件,无需关心Vue内部逻辑。

  2. 配置解析与渲染流程

    • 父H5传递的componentType(如Coupon)通过ComponentMap映射到Coupon.vue组件;
    • props作为组件属性传入,events中的函数会被绑定为组件的事件回调(通过Vue的emit触发);
    • 渲染引擎负责创建Vue实例、挂载组件,并在重新渲染时销毁旧实例,避免内存泄漏。
  3. 事件通信
    组件的交互事件(如点击)通过emit触发iframe中的回调,再由iframe通过postMessage传递给父H5,形成“组件→iframe→父H5”的完整通信链。

五、优化与扩展

  1. 样式隔离增强
    在iframe中使用Shadow DOM挂载组件,彻底隔离父H5样式:

    javascript
    // iframe中修改renderer.render的挂载逻辑
    const container = document.getElementById('component-container');
    const shadowRoot = container.attachShadow({ mode: 'open' });
    renderer.render(data, shadowRoot); // 挂载到shadowRoot
  2. 动态加载组件
    组件库支持按需加载(如通过import()动态引入未使用的组件),减少初始加载体积。

  3. 参数加密
    父H5传递敏感参数时,可通过AES加密,iframe接收后解密,提升安全性。

总结

基于Vue的实现核心是利用“Vue组件库UMD化 + iframe沙箱 + postMessage通信”,实现组件的独立渲染与跨环境交互。组件库负责提供可复用的Vue组件和渲染工具,iframe负责隔离环境并执行渲染,父H5负责传递配置和处理交互结果,三者协同完成低代码组件的嵌入集成。

需要运行示例代码可直接保存为HTML文件,替换CDN地址后在浏览器中打开(注意跨域问题,可通过本地服务器运行)。

贡献者

The avatar of contributor named as jiechen jiechen

页面历史

撰写