vue-create-api
Version:
A Vue plugin which make Vue component invocated by API.
401 lines (341 loc) • 11.2 kB
JavaScript
/**
* vue-create-api v0.2.3
* (c) 2025 ustbhuangyi
* @license MIT
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.VueCreateAPI = factory());
}(this, (function () { 'use strict';
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var camelizeRE = /-(\w)/g;
function camelize(str) {
return (str + '').replace(camelizeRE, function (m, c) {
return c ? c.toUpperCase() : '';
});
}
function escapeReg(str, delimiter) {
return (str + '').replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + (delimiter || '') + '-]', 'g'), '\\$&');
}
function isBoolean(value) {
return typeof value === 'boolean';
}
function isUndef(value) {
return value === undefined;
}
function isStr(value) {
return typeof value === 'string';
}
function isFunction(fn) {
return typeof fn === 'function';
}
function isArray(arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
function assert(condition, msg) {
if (!condition) {
throw new Error("[vue-create-api error]: " + msg);
}
}
function instantiateComponent(Vue, Component, data, renderFn, options) {
var renderData = void 0;
var childrenRenderFn = void 0;
var instance = new Vue(_extends({}, options, {
render: function render(createElement) {
var children = childrenRenderFn && childrenRenderFn(createElement);
if (children && !Array.isArray(children)) {
children = [children];
}
return createElement(Component, _extends({}, renderData), children || []);
},
methods: {
init: function init() {
document.body.appendChild(this.$el);
},
destroy: function destroy() {
this.$destroy();
if (this.$el && this.$el.parentNode === document.body) {
document.body.removeChild(this.$el);
}
}
}
}));
instance.updateRenderData = function (data, render) {
renderData = data;
childrenRenderFn = render;
};
instance.updateRenderData(data, renderFn);
instance.$mount();
instance.init();
var component = instance.$children[0];
component.$updateProps = function (props) {
_extends(renderData.props, props);
instance.$forceUpdate();
};
return component;
}
function parseRenderData() {
var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var events = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
events = parseEvents(events);
var props = _extends({}, data);
var on = {};
for (var name in events) {
if (events.hasOwnProperty(name)) {
var handlerName = events[name];
if (props[handlerName]) {
on[name] = props[handlerName];
delete props[handlerName];
}
}
}
return {
props: props,
on: on
};
}
function parseEvents(events) {
var parsedEvents = {};
events.forEach(function (name) {
parsedEvents[name] = camelize('on-' + name);
});
return parsedEvents;
}
var instances = [];
function add(component) {
var ins = void 0;
var len = instances.length;
for (var i = 0; i < len; i += 1) {
ins = instances[i];
if (ins === component) {
return;
}
}
instances.push(component);
}
function remove(component) {
var ins = void 0;
var len = instances.length;
for (var i = 0; i < len; i += 1) {
ins = instances[i];
if (ins === component) {
instances.splice(i, 1);
}
}
}
function batchDestroy(filter) {
var hasFilter = isFunction(filter);
var instancesCopy = instances.slice();
var _instances = hasFilter ? filter(instancesCopy) : instancesCopy;
if (!isArray(_instances)) {
return;
}
_instances.forEach(function (ins) {
if (ins && isFunction(ins.remove)) {
ins.remove();
}
});
}
var eventBeforeDestroy = 'hook:beforeDestroy';
function apiCreator(Component) {
var events = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
var single = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
var Vue = this;
var singleMap = {};
var beforeHooks = [];
function createComponent(renderData, renderFn, options, single, ownerInstance) {
beforeHooks.forEach(function (before) {
before(renderData, renderFn, single);
});
var ownerInsUid = options.parent ? options.parent._uid : -1;
var _ref = singleMap[ownerInsUid] ? singleMap[ownerInsUid] : {},
comp = _ref.comp,
ins = _ref.ins;
if (single && comp && ins) {
ins.updateRenderData(renderData, renderFn);
ins.$forceUpdate();
return comp;
}
var component = instantiateComponent(Vue, Component, renderData, renderFn, options);
var instance = component.$parent;
var originRemove = component.remove;
var isInVueInstance = !!ownerInstance.$on;
component.remove = function () {
if (isInVueInstance) {
cancelWatchProps(ownerInstance);
}
if (single) {
if (!singleMap[ownerInsUid]) {
return;
}
singleMap[ownerInsUid] = null;
}
originRemove && originRemove.apply(this, arguments);
instance.destroy();
remove(component);
};
var originShow = component.show;
component.show = function () {
originShow && originShow.apply(this, arguments);
return this;
};
var originHide = component.hide;
component.hide = function () {
originHide && originHide.apply(this, arguments);
return this;
};
if (single) {
singleMap[ownerInsUid] = {
comp: component,
ins: instance
};
}
return component;
}
function processProps(ownerInstance, renderData, isInVueInstance, onChange) {
var $props = renderData.props.$props;
if ($props) {
delete renderData.props.$props;
var watchKeys = [];
var watchPropKeys = [];
Object.keys($props).forEach(function (key) {
var propKey = $props[key];
if (isStr(propKey) && propKey in ownerInstance) {
// get instance value
renderData.props[key] = ownerInstance[propKey];
watchKeys.push(key);
watchPropKeys.push(propKey);
} else {
renderData.props[key] = propKey;
}
});
if (isInVueInstance) {
var unwatchFn = ownerInstance.$watch(function () {
var props = {};
watchKeys.forEach(function (key, i) {
props[key] = ownerInstance[watchPropKeys[i]];
});
return props;
}, onChange);
ownerInstance.__unwatchFns__.push(unwatchFn);
}
}
}
function processEvents(renderData, ownerInstance) {
var $events = renderData.props.$events;
if ($events) {
delete renderData.props.$events;
Object.keys($events).forEach(function (event) {
var eventHandler = $events[event];
if (typeof eventHandler === 'string') {
eventHandler = ownerInstance[eventHandler];
}
renderData.on[event] = eventHandler;
});
}
}
function process$(renderData) {
var props = renderData.props;
Object.keys(props).forEach(function (prop) {
if (prop.charAt(0) === '$') {
renderData[prop.slice(1)] = props[prop];
delete props[prop];
}
});
}
function cancelWatchProps(ownerInstance) {
if (ownerInstance.__unwatchFns__) {
ownerInstance.__unwatchFns__.forEach(function (unwatchFn) {
unwatchFn();
});
ownerInstance.__unwatchFns__ = null;
}
}
var api = {
before: function before(hook) {
beforeHooks.push(hook);
},
create: function create(config, renderFn, _single) {
if (!isFunction(renderFn) && isUndef(_single)) {
_single = renderFn;
renderFn = null;
}
if (isUndef(_single)) {
_single = single;
}
var ownerInstance = this;
var isInVueInstance = !!ownerInstance.$on;
var options = {};
if (isInVueInstance) {
// Set parent to store router i18n ...
options.parent = ownerInstance;
if (!ownerInstance.__unwatchFns__) {
ownerInstance.__unwatchFns__ = [];
}
}
var renderData = parseRenderData(config, events);
var component = null;
processProps(ownerInstance, renderData, isInVueInstance, function (newProps) {
component && component.$updateProps(newProps);
});
processEvents(renderData, ownerInstance);
process$(renderData);
component = createComponent(renderData, renderFn, options, _single, ownerInstance);
if (isInVueInstance) {
ownerInstance.$on(eventBeforeDestroy, component.remove.bind(component));
}
add(component);
return component;
}
};
return api;
}
function install(Vue) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var _options$componentPre = options.componentPrefix,
componentPrefix = _options$componentPre === undefined ? '' : _options$componentPre,
_options$apiPrefix = options.apiPrefix,
apiPrefix = _options$apiPrefix === undefined ? '$create-' : _options$apiPrefix;
Vue.createAPI = function (Component, events, single) {
if (isBoolean(events)) {
single = events;
events = [];
}
var api = apiCreator.call(this, Component, events, single);
var createName = processComponentName(Component, {
componentPrefix: componentPrefix,
apiPrefix: apiPrefix
});
Vue.prototype[createName] = Component.$create = api.create;
return api;
};
}
function processComponentName(Component, options) {
var componentPrefix = options.componentPrefix,
apiPrefix = options.apiPrefix;
var name = Component.name;
assert(name, 'Component must have name while using create-api!');
var prefixReg = new RegExp('^' + escapeReg(componentPrefix), 'i');
var pureName = name.replace(prefixReg, '');
var camelizeName = '' + camelize('' + apiPrefix + pureName);
return camelizeName;
}
var index = {
install: install,
batchDestroy: batchDestroy,
instantiateComponent: instantiateComponent,
version: '0.2.3'
};
return index;
})));