Frontend Development 21 min read

Micro‑Component State Management, Hook Mechanism, and Cross‑Sandbox Communication in a Front‑End Activity Platform

The article explains how the Wukong Activity Platform enables micro‑components to interact with the editor and property panel by registering lifecycle hooks through a pre‑render step, storing them in a unified Vuex module, and synchronizing state across sandboxed iframes via postMessage and a parent‑store mixin that restores Vue reactivity.

vivo Internet Technology
vivo Internet Technology
vivo Internet Technology
Micro‑Component State Management, Hook Mechanism, and Cross‑Sandbox Communication in a Front‑End Activity Platform

The article continues the series on the Wukong Activity Platform, focusing on how micro‑components interact with the platform and across sandboxed iframes. It revisits the evolution from EventBus to a pre‑script solution and finally to a unified Vuex‑based state management approach.

Background : In the platform, each activity page consists of an editor (left pane) and a property panel (right pane). Micro‑components need to sense platform actions such as save, delete, and drag‑reorder, and execute custom logic (validation, error prompts, etc.).

Result : The team designed a hook mechanism that lets micro‑components register lifecycle callbacks which the platform collects and invokes at the appropriate moments. Two main scenarios were solved:

Connecting micro‑components with the platform (e.g., detecting save or delete actions).

Connecting micro‑components with a cross‑sandbox configuration panel.

Micro‑Component ↔ Platform State Management

File structure of a plugin:

hello-goku/          # current plugin directory
├── code.vue         # component code displayed in the editor
├── prop.vue         # property‑panel module, senses platform actions
└── setting.json     # configuration file
├── package.json    # npm module info

The hook concept is introduced: a micro‑component can register a series of platform lifecycle methods that the platform automatically collects and calls.

Using the hook

// prop.vue
export default {
mixins: [
platformActionHook({
/* Register hook before activity save */
beforeSaveTopicHook() {/* TODO: param check */},
/* Register hook after activity save */
afterSaveTopicHook() {},
/* Register hook before plugin delete */
beforeDeletePluginHook() {},
/* Register hook after plugin delete */
afterDeletePluginHook() {}
})
]
};

The platform must collect all hooks even though prop.vue is loaded lazily. The solution is a pre‑render step that loads every UMD plugin, extracts its hooks, and registers them once (hidden rendering).

Pre‑render implementation (prerender‑prop.vue)

<template>
<Component :is="prop" :item="item"></Component>
</template>
<script>
// prerender‑prop.vue
export default {
name: "DynamicProp",
data() { return { prop: null } },
props: ['distProp', 'item', 'renderIndex'],
watch: {
distProp: {
immediate: true, deep: true,
handler(val) {
const propComponent = this.preval(val);
const mixins = propComponent.prop.mixins || [];
const hasHook = mixins.filter(item => item.hook).length;
if (hasHook) {
this.prop = {
...propComponent,
mixins: [{ beforeCreate () { this.$options.registerHook = true; this.$options.renderIndex = this.renderIndex } }, ...mixins],
/* If UI error display is not needed, override render */
render() { return null }
};
}
}
}
},
methods: {
preval(js) { const mode = {}; new Function("self", `return ${js}`)(mode); return mode.prop; }
}
};
</script>

The platform stores collected hooks in a Vuex module ( hook-store.js ) that keeps queues for each lifecycle type and a map of render indexes to maintain order.

Hook store

// hook-store.js
import Vue from 'vue'
export default {
state () {
return {
beforeSaveTopicHook: [],
afterSaveTopicHook: [],
beforeDeletePluginHook: [],
afterDeletePluginHook: [],
mapIndex: {
beforeSaveTopicHook: {},
afterSaveTopicHook: {},
beforeDeletePluginHook: {},
afterDeletePluginHook: {}
}
}
},
mutations: {
register (state, { type, fn, registerHook, renderIndex }) {
Vue.nextTick(() => {
const list = state[type];
if (registerHook) {
list.push(fn);
const hookIndex = list.indexOf(fn);
state.mapIndex[type][renderIndex] = { hookIndex, err: false };
}
})
},
unregister (state, { type, fn }) {
const list = state[type];
const i = list.indexOf(fn);
if (i > -1) list.splice(i, 1);
const map = state.mapIndex[type];
for (let renderIndex in map) {
if (map[renderIndex].hookIndex === i) { delete map[renderIndex]; }
}
}
}
};

The platform registers hooks via platformActionHook which commits to the store.

platformActionHook implementation

// platform-action-hook.js
export default function platformActionHook(params = {}) {
let { beforeSaveTopicHook, afterSaveTopicHook, beforeDeletePluginHook, afterDeletePluginHook } = params;
return {
hook: true,
beforeCreate() {
const renderIndex = this.$options.renderIndex;
const registerHook = this.$options.registerHook;
if (isDef(beforeSaveTopicHook)) {
beforeSaveTopicHook = beforeSaveTopicHook.bind(this);
store.commit('hook/register', { type: 'beforeSaveTopicHook', fn: beforeSaveTopicHook, registerHook, renderIndex });
}
// similar for other hooks …
}
}
}

Cross‑iframe communication is achieved with postMessage . The property panel watches data changes and sends messages to the editor iframe; the iframe listens and updates its UI.

postMessage example

watch: {
'itemWatch': {
handler: (val) => {
const win = document.querySelector('.iframe').contentWindow;
win.postMessage({ action: 'syncItemInfo', params: val });
}, deep: true
}
}

In the iframe:

methods: {
messageListener(ev) {
if (ev.source != window.top) return;
const data = ev.data;
if (data.action == 'syncItemInfo') { this.num = data.params.numInfo.num; }
}
},
mounted() { window.addEventListener('message', this.messageListener, false); }

Limitations of postMessage are discussed (need for load order, bidirectional sync issues). The article then explores using Vuex across iframes, but discovers that Vue’s reactivity breaks because the Dep target is null in the child iframe.

To restore reactivity, Vue.observable is used to wrap the parent store’s state inside the child iframe, and a parent‑store‑mixin injects the parent store into the child’s Vue instance.

parent‑store‑mixin.js

// parent-store-mixin.js
module.exports = function ParentMixin(store) {
return function (Vue) {
Vue.mixin({
beforeCreate: function () {
Vue.observable(store.state);
this.$pstore = store;
}
})
}
}

A small example component demonstrates using the injected $pstore and Vuex‑style helpers.

Example component

<template>
<div class="hello">
<p>{{$pstore.state.top.test.hh}}</p>
<h1>{{ foo }}</h1>
<div>test:{{ testGetter }}</div>
<h3 @click="fooChange(Date.now())">update</h3>
<h3 @click="fooAction(Date.now())">action</h3>
</div>
</template>
<script>
import Vue from 'vue'
import { mapParentState, mapParentGetters, mapParentMutations, mapParentActions, parentStoreMixin } from 'vuex-parent-helper'
Vue.use(parentStoreMixin(window.top._store_))
export default {
name: 'HelloWorld',
computed: { ...mapParentState('top', ['foo']), ...mapParentGetters('top', ['testGetter']) },
methods: { ...mapParentMutations('top', { fooChange: 'foo' }), ...mapParentActions('top', { fooAction: 'fooTest' }) }
}
</script>

The parent Vuex store is defined with a top module containing state, getters, mutations, and actions.

Parent store

import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
top: {
namespaced: true,
state() { return { foo: 'foo', test: { hh: '' } } },
getters: { testGetter(state) { return state.test.hh || 'default' } },
mutations: { foo(state, txt) { state.foo = txt }, test(state) { Vue.set(state.test, 'hh', Date.now()) } },
actions: { fooTest({ commit }) { commit('test') } }
}
}
})

The article concludes with reflections on the engineering challenges, the importance of systematic state management, and an invitation to future technical articles.

frontendState ManagementVueHooksMicro-ComponentsCross-iframe
vivo Internet Technology
Written by

vivo Internet Technology

Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.