From cb14f1eb9c4dc663011cb91ed6d447f8acbdfc4e Mon Sep 17 00:00:00 2001 From: jili Date: Tue, 8 Jan 2019 11:19:55 +0100 Subject: [PATCH 01/17] spread props utils --- .gitignore | 2 + dist/vue-wc-wrapper.global.js | 502 ++++++++++++++++++---------------- dist/vue-wc-wrapper.js | 185 +++++++------ src/index.js | 4 +- src/utils.js | 20 ++ 5 files changed, 389 insertions(+), 324 deletions(-) diff --git a/.gitignore b/.gitignore index fd4f2b0..a45a18c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules .DS_Store +.idea/ +package-lock.json \ No newline at end of file diff --git a/dist/vue-wc-wrapper.global.js b/dist/vue-wc-wrapper.global.js index db8253a..0050cc4 100644 --- a/dist/vue-wc-wrapper.global.js +++ b/dist/vue-wc-wrapper.global.js @@ -1,272 +1,292 @@ var wrapVueWebComponent = (function () { -'use strict'; - -const camelizeRE = /-(\w)/g; -const camelize = str => { - return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') -}; - -const hyphenateRE = /\B([A-Z])/g; -const hyphenate = str => { - return str.replace(hyphenateRE, '-$1').toLowerCase() -}; - -function getInitialProps (propsList) { - const res = {}; - propsList.forEach(key => { - res[key] = undefined; - }); - return res -} - -function injectHook (options, key, hook) { - options[key] = [].concat(options[key] || []); - options[key].unshift(hook); -} - -function callHooks (vm, hook) { - if (vm) { - const hooks = vm.$options[hook] || []; - hooks.forEach(hook => { - hook.call(vm); - }); + 'use strict' + + const camelizeRE = /-(\w)/g + const camelize = str => { + return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') + } + + const hyphenateRE = /\B([A-Z])/g + const hyphenate = str => { + return str.replace(hyphenateRE, '-$1').toLowerCase() + } + + function getInitialProps (propsList) { + const res = {} + propsList.forEach(key => { + res[key] = undefined + }) + return res } -} - -function createCustomEvent (name, args) { - return new CustomEvent(name, { - bubbles: false, - cancelable: false, - detail: args - }) -} - -const isBoolean = val => /function Boolean/.test(String(val)); -const isNumber = val => /function Number/.test(String(val)); - -function convertAttributeValue (value, name, { type } = {}) { - if (isBoolean(type)) { - if (value === 'true' || value === 'false') { - return value === 'true' + + function injectHook (options, key, hook) { + options[key] = [].concat(options[key] || []) + options[key].unshift(hook) + } + + function callHooks (vm, hook) { + if (vm) { + const hooks = vm.$options[hook] || [] + hooks.forEach(hook => { + hook.call(vm) + }) } - if (value === '' || value === name) { - return true + } + + function createCustomEvent (name, args) { + return new CustomEvent(name, { + bubbles: false, + cancelable: false, + detail: args + }) + } + + const isBoolean = val => /function Boolean/.test(String(val)) + const isNumber = val => /function Number/.test(String(val)) + + function convertAttributeValue (value, name, { type } = {}) { + if (isBoolean(type)) { + if (value === 'true' || value === 'false') { + return value === 'true' + } + if (value === '' || value === name) { + return true + } + return value != null + } else if (isNumber(type)) { + const parsed = parseFloat(value, 10) + return isNaN(parsed) ? value : parsed + } else { + return value } - return value != null - } else if (isNumber(type)) { - const parsed = parseFloat(value, 10); - return isNaN(parsed) ? value : parsed - } else { - return value } -} -function toVNodes (h, children) { - const res = []; - for (let i = 0, l = children.length; i < l; i++) { - res.push(toVNode(h, children[i])); + function toVNodes (h, children) { + const res = [] + for (let i = 0, l = children.length; i < l; i++) { + res.push(toVNode(h, children[i])) + } + return res } - return res -} - -function toVNode (h, node) { - if (node.nodeType === 3) { - return node.data.trim() ? node.data : null - } else if (node.nodeType === 1) { - const data = { - attrs: getAttributes(node), - domProps: { - innerHTML: node.innerHTML + + function toVNode (h, node) { + if (node.nodeType === 3) { + return node.data.trim() ? node.data : null + } else if (node.nodeType === 1) { + const data = { + attrs: getAttributes(node), + domProps: { + innerHTML: node.innerHTML + } } - }; - if (data.attrs.slot) { - data.slot = data.attrs.slot; - delete data.attrs.slot; + if (data.attrs.slot) { + data.slot = data.attrs.slot + delete data.attrs.slot + } + return h(node.tagName, data) + } else { + return null } - return h(node.tagName, data) - } else { - return null } -} -function getAttributes (node) { - const res = {}; - for (let i = 0, l = node.attributes.length; i < l; i++) { - const attr = node.attributes[i]; - res[attr.nodeName] = attr.nodeValue; + function getAttributes (node) { + const res = {} + for (let i = 0, l = node.attributes.length; i < l; i++) { + const attr = node.attributes[i] + res[attr.nodeName] = attr.nodeValue + } + return res } - return res -} - -function wrap (Vue, Component) { - const isAsync = typeof Component === 'function' && !Component.cid; - let isInitialized = false; - let hyphenatedPropsList; - let camelizedPropsList; - let camelizedPropsMap; - - function initialize (Component) { - if (isInitialized) return - - const options = typeof Component === 'function' - ? Component.options - : Component; - - // extract props info - const propsList = Array.isArray(options.props) - ? options.props - : Object.keys(options.props || {}); - hyphenatedPropsList = propsList.map(hyphenate); - camelizedPropsList = propsList.map(camelize); - const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {}; - camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => { - map[key] = originalPropsAsObject[propsList[i]]; - return map - }, {}); - - // proxy $emit to native DOM events - injectHook(options, 'beforeCreate', function () { - const emit = this.$emit; - this.$emit = (name, ...args) => { - this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args)); - return emit.call(this, name, ...args) - }; - }); - - injectHook(options, 'created', function () { - // sync default props values to wrapper on created - camelizedPropsList.forEach(key => { - this.$root.props[key] = this[key]; - }); - }); - - // proxy props as Element properties - camelizedPropsList.forEach(key => { - Object.defineProperty(CustomElement.prototype, key, { - get () { - return this._wrapper.props[key] - }, - set (newVal) { - this._wrapper.props[key] = newVal; - }, - enumerable: false, - configurable: true - }); - }); - - isInitialized = true; + + function spreadProps (component) { + const result = {} + appendProps(result, component) + console.log(result) + return result } - function syncAttribute (el, key) { - const camelized = camelize(key); - const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined; - el._wrapper.props[camelized] = convertAttributeValue( - value, - key, - camelizedPropsMap[camelized] - ); + function appendProps (result, component) { + for (const key in component.props) { + const camelKey = camelize(key) + if (!(camelKey in result)) { + result[camelKey] = component.props[key] + } + } + + if (component.extends) { + appendProps(result, component.extends) + } } - class CustomElement extends HTMLElement { - constructor () { - super(); - this.attachShadow({ mode: 'open' }); - - const wrapper = this._wrapper = new Vue({ - name: 'shadow-root', - customElement: this, - shadowRoot: this.shadowRoot, - data () { - return { - props: {}, - slotChildren: [] - } - }, - render (h) { - return h(Component, { - ref: 'inner', - props: this.props - }, this.slotChildren) - } - }); - - // Use MutationObserver to react to future attribute & slot content change - const observer = new MutationObserver(mutations => { - let hasChildrenChange = false; - for (let i = 0; i < mutations.length; i++) { - const m = mutations[i]; - if (isInitialized && m.type === 'attributes' && m.target === this) { - syncAttribute(this, m.attributeName); - } else { - hasChildrenChange = true; - } - } - if (hasChildrenChange) { - wrapper.slotChildren = Object.freeze(toVNodes( - wrapper.$createElement, - this.childNodes - )); + function wrap (Vue, Component) { + const isAsync = typeof Component === 'function' && !Component.cid + let isInitialized = false + let hyphenatedPropsList + let camelizedPropsList + let camelizedPropsMap + + function initialize (Component) { + if (isInitialized) return + + const options = typeof Component === 'function' + ? Component.options + : Component + + options.props = spreadProps(options) + // extract props info + const propsList = Array.isArray(options.props) + ? options.props + : Object.keys(options.props || {}) + hyphenatedPropsList = propsList.map(hyphenate) + camelizedPropsList = propsList.map(camelize) + const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {} + camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => { + map[key] = originalPropsAsObject[propsList[i]] + return map + }, {}) + + // proxy $emit to native DOM events + injectHook(options, 'beforeCreate', function () { + const emit = this.$emit + this.$emit = (name, ...args) => { + this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args)) + return emit.call(this, name, ...args) } - }); - observer.observe(this, { - childList: true, - subtree: true, - characterData: true, - attributes: true - }); + }) + + injectHook(options, 'created', function () { + // sync default props values to wrapper on created + camelizedPropsList.forEach(key => { + this.$root.props[key] = this[key] + }) + }) + + // proxy props as Element properties + camelizedPropsList.forEach(key => { + Object.defineProperty(CustomElement.prototype, key, { + get () { + return this._wrapper.props[key] + }, + set (newVal) { + this._wrapper.props[key] = newVal + }, + enumerable: false, + configurable: true + }) + }) + + isInitialized = true } - get vueComponent () { - return this._wrapper.$refs.inner + function syncAttribute (el, key) { + const camelized = camelize(key) + const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined + el._wrapper.props[camelized] = convertAttributeValue( + value, + key, + camelizedPropsMap[camelized] + ) } - connectedCallback () { - const wrapper = this._wrapper; - if (!wrapper._isMounted) { + class CustomElement extends HTMLElement { + constructor () { + const self = super() + self.attachShadow({ mode: 'open' }) + + const wrapper = self._wrapper = new Vue({ + name: 'shadow-root', + customElement: self, + shadowRoot: self.shadowRoot, + data () { + return { + props: {}, + slotChildren: [] + } + }, + render (h) { + return h(Component, { + ref: 'inner', + props: this.props + }, this.slotChildren) + } + }) + + // Use MutationObserver to react to future attribute & slot content change + const observer = new MutationObserver(mutations => { + let hasChildrenChange = false + for (let i = 0; i < mutations.length; i++) { + const m = mutations[i] + if (isInitialized && m.type === 'attributes' && m.target === self) { + syncAttribute(self, m.attributeName) + } else { + hasChildrenChange = true + } + } + if (hasChildrenChange) { + wrapper.slotChildren = Object.freeze(toVNodes( + wrapper.$createElement, + self.childNodes + )) + } + }) + observer.observe(self, { + childList: true, + subtree: true, + characterData: true, + attributes: true + }) + } + + get vueComponent () { + return this._wrapper.$refs.inner + } + + connectedCallback () { + const wrapper = this._wrapper + if (!wrapper._isMounted) { // initialize attributes - const syncInitialAttributes = () => { - wrapper.props = getInitialProps(camelizedPropsList); - hyphenatedPropsList.forEach(key => { - syncAttribute(this, key); - }); - }; - - if (isInitialized) { - syncInitialAttributes(); - } else { + const syncInitialAttributes = () => { + wrapper.props = getInitialProps(camelizedPropsList) + hyphenatedPropsList.forEach(key => { + syncAttribute(this, key) + }) + } + + if (isInitialized) { + syncInitialAttributes() + } else { // async & unresolved - Component().then(resolved => { - if (resolved.__esModule || resolved[Symbol.toStringTag] === 'Module') { - resolved = resolved.default; - } - initialize(resolved); - syncInitialAttributes(); - }); + Component().then(resolved => { + if (resolved.__esModule || resolved[Symbol.toStringTag] === 'Module') { + resolved = resolved.default + } + initialize(resolved) + syncInitialAttributes() + }) + } + // initialize children + wrapper.slotChildren = Object.freeze(toVNodes( + wrapper.$createElement, + this.childNodes + )) + wrapper.$mount() + this.shadowRoot.appendChild(wrapper.$el) + } else { + callHooks(this.vueComponent, 'activated') } - // initialize children - wrapper.slotChildren = Object.freeze(toVNodes( - wrapper.$createElement, - this.childNodes - )); - wrapper.$mount(); - this.shadowRoot.appendChild(wrapper.$el); - } else { - callHooks(this.vueComponent, 'activated'); + } + + disconnectedCallback () { + callHooks(this.vueComponent, 'deactivated') } } - disconnectedCallback () { - callHooks(this.vueComponent, 'deactivated'); + if (!isAsync) { + initialize(Component) } - } - if (!isAsync) { - initialize(Component); + return CustomElement } - return CustomElement -} - -return wrap; - -}()); + return wrap +}()) diff --git a/dist/vue-wc-wrapper.js b/dist/vue-wc-wrapper.js index 36b4469..cf488eb 100644 --- a/dist/vue-wc-wrapper.js +++ b/dist/vue-wc-wrapper.js @@ -1,32 +1,32 @@ -const camelizeRE = /-(\w)/g; +const camelizeRE = /-(\w)/g const camelize = str => { return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') -}; +} -const hyphenateRE = /\B([A-Z])/g; +const hyphenateRE = /\B([A-Z])/g const hyphenate = str => { return str.replace(hyphenateRE, '-$1').toLowerCase() -}; +} function getInitialProps (propsList) { - const res = {}; + const res = {} propsList.forEach(key => { - res[key] = undefined; - }); + res[key] = undefined + }) return res } function injectHook (options, key, hook) { - options[key] = [].concat(options[key] || []); - options[key].unshift(hook); + options[key] = [].concat(options[key] || []) + options[key].unshift(hook) } function callHooks (vm, hook) { if (vm) { - const hooks = vm.$options[hook] || []; + const hooks = vm.$options[hook] || [] hooks.forEach(hook => { - hook.call(vm); - }); + hook.call(vm) + }) } } @@ -38,8 +38,8 @@ function createCustomEvent (name, args) { }) } -const isBoolean = val => /function Boolean/.test(String(val)); -const isNumber = val => /function Number/.test(String(val)); +const isBoolean = val => /function Boolean/.test(String(val)) +const isNumber = val => /function Number/.test(String(val)) function convertAttributeValue (value, name, { type } = {}) { if (isBoolean(type)) { @@ -51,7 +51,7 @@ function convertAttributeValue (value, name, { type } = {}) { } return value != null } else if (isNumber(type)) { - const parsed = parseFloat(value, 10); + const parsed = parseFloat(value, 10) return isNaN(parsed) ? value : parsed } else { return value @@ -59,9 +59,9 @@ function convertAttributeValue (value, name, { type } = {}) { } function toVNodes (h, children) { - const res = []; + const res = [] for (let i = 0, l = children.length; i < l; i++) { - res.push(toVNode(h, children[i])); + res.push(toVNode(h, children[i])) } return res } @@ -75,10 +75,10 @@ function toVNode (h, node) { domProps: { innerHTML: node.innerHTML } - }; + } if (data.attrs.slot) { - data.slot = data.attrs.slot; - delete data.attrs.slot; + data.slot = data.attrs.slot + delete data.attrs.slot } return h(node.tagName, data) } else { @@ -87,55 +87,76 @@ function toVNode (h, node) { } function getAttributes (node) { - const res = {}; + const res = {} for (let i = 0, l = node.attributes.length; i < l; i++) { - const attr = node.attributes[i]; - res[attr.nodeName] = attr.nodeValue; + const attr = node.attributes[i] + res[attr.nodeName] = attr.nodeValue } return res } +function spreadProps (component) { + const result = {} + appendProps(result, component) + console.log(result) + return result +} + +function appendProps (result, component) { + for (const key in component.props) { + const camelKey = camelize(key) + if (!(camelKey in result)) { + result[camelKey] = component.props[key] + } + } + + if (component.extends) { + appendProps(result, component.extends) + } +} + function wrap (Vue, Component) { - const isAsync = typeof Component === 'function' && !Component.cid; - let isInitialized = false; - let hyphenatedPropsList; - let camelizedPropsList; - let camelizedPropsMap; + const isAsync = typeof Component === 'function' && !Component.cid + let isInitialized = false + let hyphenatedPropsList + let camelizedPropsList + let camelizedPropsMap function initialize (Component) { if (isInitialized) return const options = typeof Component === 'function' ? Component.options - : Component; + : Component + options.props = spreadProps(options) // extract props info const propsList = Array.isArray(options.props) ? options.props - : Object.keys(options.props || {}); - hyphenatedPropsList = propsList.map(hyphenate); - camelizedPropsList = propsList.map(camelize); - const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {}; + : Object.keys(options.props || {}) + hyphenatedPropsList = propsList.map(hyphenate) + camelizedPropsList = propsList.map(camelize) + const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {} camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => { - map[key] = originalPropsAsObject[propsList[i]]; + map[key] = originalPropsAsObject[propsList[i]] return map - }, {}); + }, {}) // proxy $emit to native DOM events injectHook(options, 'beforeCreate', function () { - const emit = this.$emit; + const emit = this.$emit this.$emit = (name, ...args) => { - this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args)); + this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args)) return emit.call(this, name, ...args) - }; - }); + } + }) injectHook(options, 'created', function () { // sync default props values to wrapper on created camelizedPropsList.forEach(key => { - this.$root.props[key] = this[key]; - }); - }); + this.$root.props[key] = this[key] + }) + }) // proxy props as Element properties camelizedPropsList.forEach(key => { @@ -144,35 +165,35 @@ function wrap (Vue, Component) { return this._wrapper.props[key] }, set (newVal) { - this._wrapper.props[key] = newVal; + this._wrapper.props[key] = newVal }, enumerable: false, configurable: true - }); - }); + }) + }) - isInitialized = true; + isInitialized = true } function syncAttribute (el, key) { - const camelized = camelize(key); - const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined; + const camelized = camelize(key) + const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined el._wrapper.props[camelized] = convertAttributeValue( value, key, camelizedPropsMap[camelized] - ); + ) } class CustomElement extends HTMLElement { constructor () { - super(); - this.attachShadow({ mode: 'open' }); + const self = super() + self.attachShadow({ mode: 'open' }) - const wrapper = this._wrapper = new Vue({ + const wrapper = self._wrapper = new Vue({ name: 'shadow-root', - customElement: this, - shadowRoot: this.shadowRoot, + customElement: self, + shadowRoot: self.shadowRoot, data () { return { props: {}, @@ -185,32 +206,32 @@ function wrap (Vue, Component) { props: this.props }, this.slotChildren) } - }); + }) // Use MutationObserver to react to future attribute & slot content change const observer = new MutationObserver(mutations => { - let hasChildrenChange = false; + let hasChildrenChange = false for (let i = 0; i < mutations.length; i++) { - const m = mutations[i]; - if (isInitialized && m.type === 'attributes' && m.target === this) { - syncAttribute(this, m.attributeName); + const m = mutations[i] + if (isInitialized && m.type === 'attributes' && m.target === self) { + syncAttribute(self, m.attributeName) } else { - hasChildrenChange = true; + hasChildrenChange = true } } if (hasChildrenChange) { wrapper.slotChildren = Object.freeze(toVNodes( wrapper.$createElement, - this.childNodes - )); + self.childNodes + )) } - }); - observer.observe(this, { + }) + observer.observe(self, { childList: true, subtree: true, characterData: true, attributes: true - }); + }) } get vueComponent () { @@ -218,50 +239,50 @@ function wrap (Vue, Component) { } connectedCallback () { - const wrapper = this._wrapper; + const wrapper = this._wrapper if (!wrapper._isMounted) { // initialize attributes const syncInitialAttributes = () => { - wrapper.props = getInitialProps(camelizedPropsList); + wrapper.props = getInitialProps(camelizedPropsList) hyphenatedPropsList.forEach(key => { - syncAttribute(this, key); - }); - }; + syncAttribute(this, key) + }) + } if (isInitialized) { - syncInitialAttributes(); + syncInitialAttributes() } else { // async & unresolved Component().then(resolved => { if (resolved.__esModule || resolved[Symbol.toStringTag] === 'Module') { - resolved = resolved.default; + resolved = resolved.default } - initialize(resolved); - syncInitialAttributes(); - }); + initialize(resolved) + syncInitialAttributes() + }) } // initialize children wrapper.slotChildren = Object.freeze(toVNodes( wrapper.$createElement, this.childNodes - )); - wrapper.$mount(); - this.shadowRoot.appendChild(wrapper.$el); + )) + wrapper.$mount() + this.shadowRoot.appendChild(wrapper.$el) } else { - callHooks(this.vueComponent, 'activated'); + callHooks(this.vueComponent, 'activated') } } disconnectedCallback () { - callHooks(this.vueComponent, 'deactivated'); + callHooks(this.vueComponent, 'deactivated') } } if (!isAsync) { - initialize(Component); + initialize(Component) } return CustomElement } -export default wrap; +export default wrap diff --git a/src/index.js b/src/index.js index 4409bd7..e273ea2 100644 --- a/src/index.js +++ b/src/index.js @@ -6,7 +6,8 @@ import { injectHook, getInitialProps, createCustomEvent, - convertAttributeValue + convertAttributeValue, + spreadProps } from './utils.js' export default function wrap (Vue, Component) { @@ -23,6 +24,7 @@ export default function wrap (Vue, Component) { ? Component.options : Component + options.props = spreadProps(options) // extract props info const propsList = Array.isArray(options.props) ? options.props diff --git a/src/utils.js b/src/utils.js index 0834f03..d06640c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -94,3 +94,23 @@ function getAttributes (node) { } return res } + +export function spreadProps (component) { + const result = {} + appendProps(result, component) + console.log(result) + return result +} + +function appendProps (result, component) { + for (const key in component.props) { + const camelKey = camelize(key) + if (!(camelKey in result)) { + result[camelKey] = component.props[key] + } + } + + if (component.extends) { + appendProps(result, component.extends) + } +} From 3f0a5b52d516ba8641b75d0080c849414f308f06 Mon Sep 17 00:00:00 2001 From: jili Date: Tue, 8 Jan 2019 11:40:27 +0100 Subject: [PATCH 02/17] new test for spread props --- dist/vue-wc-wrapper.global.js | 518 +++++++++++++------------- dist/vue-wc-wrapper.js | 167 ++++----- src/index.js | 3 +- test/fixtures/spreadedProperties.html | 41 ++ 4 files changed, 387 insertions(+), 342 deletions(-) create mode 100644 test/fixtures/spreadedProperties.html diff --git a/dist/vue-wc-wrapper.global.js b/dist/vue-wc-wrapper.global.js index 0050cc4..6fcaa7f 100644 --- a/dist/vue-wc-wrapper.global.js +++ b/dist/vue-wc-wrapper.global.js @@ -1,292 +1,294 @@ var wrapVueWebComponent = (function () { - 'use strict' - - const camelizeRE = /-(\w)/g - const camelize = str => { - return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') - } - - const hyphenateRE = /\B([A-Z])/g - const hyphenate = str => { - return str.replace(hyphenateRE, '-$1').toLowerCase() - } - - function getInitialProps (propsList) { - const res = {} - propsList.forEach(key => { - res[key] = undefined - }) - return res - } - - function injectHook (options, key, hook) { - options[key] = [].concat(options[key] || []) - options[key].unshift(hook) +'use strict'; + +const camelizeRE = /-(\w)/g; +const camelize = str => { + return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') +}; + +const hyphenateRE = /\B([A-Z])/g; +const hyphenate = str => { + return str.replace(hyphenateRE, '-$1').toLowerCase() +}; + +function getInitialProps (propsList) { + const res = {}; + propsList.forEach(key => { + res[key] = undefined; + }); + return res +} + +function injectHook (options, key, hook) { + options[key] = [].concat(options[key] || []); + options[key].unshift(hook); +} + +function callHooks (vm, hook) { + if (vm) { + const hooks = vm.$options[hook] || []; + hooks.forEach(hook => { + hook.call(vm); + }); } - - function callHooks (vm, hook) { - if (vm) { - const hooks = vm.$options[hook] || [] - hooks.forEach(hook => { - hook.call(vm) - }) +} + +function createCustomEvent (name, args) { + return new CustomEvent(name, { + bubbles: false, + cancelable: false, + detail: args + }) +} + +const isBoolean = val => /function Boolean/.test(String(val)); +const isNumber = val => /function Number/.test(String(val)); + +function convertAttributeValue (value, name, { type } = {}) { + if (isBoolean(type)) { + if (value === 'true' || value === 'false') { + return value === 'true' } + if (value === '' || value === name) { + return true + } + return value != null + } else if (isNumber(type)) { + const parsed = parseFloat(value, 10); + return isNaN(parsed) ? value : parsed + } else { + return value } +} - function createCustomEvent (name, args) { - return new CustomEvent(name, { - bubbles: false, - cancelable: false, - detail: args - }) +function toVNodes (h, children) { + const res = []; + for (let i = 0, l = children.length; i < l; i++) { + res.push(toVNode(h, children[i])); } - - const isBoolean = val => /function Boolean/.test(String(val)) - const isNumber = val => /function Number/.test(String(val)) - - function convertAttributeValue (value, name, { type } = {}) { - if (isBoolean(type)) { - if (value === 'true' || value === 'false') { - return value === 'true' - } - if (value === '' || value === name) { - return true + return res +} + +function toVNode (h, node) { + if (node.nodeType === 3) { + return node.data.trim() ? node.data : null + } else if (node.nodeType === 1) { + const data = { + attrs: getAttributes(node), + domProps: { + innerHTML: node.innerHTML } - return value != null - } else if (isNumber(type)) { - const parsed = parseFloat(value, 10) - return isNaN(parsed) ? value : parsed - } else { - return value + }; + if (data.attrs.slot) { + data.slot = data.attrs.slot; + delete data.attrs.slot; } + return h(node.tagName, data) + } else { + return null } +} - function toVNodes (h, children) { - const res = [] - for (let i = 0, l = children.length; i < l; i++) { - res.push(toVNode(h, children[i])) - } - return res +function getAttributes (node) { + const res = {}; + for (let i = 0, l = node.attributes.length; i < l; i++) { + const attr = node.attributes[i]; + res[attr.nodeName] = attr.nodeValue; } - - function toVNode (h, node) { - if (node.nodeType === 3) { - return node.data.trim() ? node.data : null - } else if (node.nodeType === 1) { - const data = { - attrs: getAttributes(node), - domProps: { - innerHTML: node.innerHTML - } - } - if (data.attrs.slot) { - data.slot = data.attrs.slot - delete data.attrs.slot - } - return h(node.tagName, data) - } else { - return null + return res +} + +function spreadProps (component) { + const result = {}; + appendProps(result, component); + console.log(result); + return result +} + +function appendProps (result, component) { + for (const key in component.props) { + const camelKey = camelize(key); + if (!(camelKey in result)) { + result[camelKey] = component.props[key]; } } - function getAttributes (node) { - const res = {} - for (let i = 0, l = node.attributes.length; i < l; i++) { - const attr = node.attributes[i] - res[attr.nodeName] = attr.nodeValue - } - return res + if (component.extends) { + appendProps(result, component.extends); } - - function spreadProps (component) { - const result = {} - appendProps(result, component) - console.log(result) - return result +} + +function wrap (Vue, Component) { + const isAsync = typeof Component === 'function' && !Component.cid; + let isInitialized = false; + let hyphenatedPropsList; + let camelizedPropsList; + let camelizedPropsMap; + + function initialize (Component) { + if (isInitialized) return + + const options = typeof Component === 'function' + ? Component.options + : Component; + + //spread props + options.props = spreadProps(options); + // extract props info + const propsList = Array.isArray(options.props) + ? options.props + : Object.keys(options.props || {}); + hyphenatedPropsList = propsList.map(hyphenate); + camelizedPropsList = propsList.map(camelize); + const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {}; + camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => { + map[key] = originalPropsAsObject[propsList[i]]; + return map + }, {}); + + // proxy $emit to native DOM events + injectHook(options, 'beforeCreate', function () { + const emit = this.$emit; + this.$emit = (name, ...args) => { + this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args)); + return emit.call(this, name, ...args) + }; + }); + + injectHook(options, 'created', function () { + // sync default props values to wrapper on created + camelizedPropsList.forEach(key => { + this.$root.props[key] = this[key]; + }); + }); + + // proxy props as Element properties + camelizedPropsList.forEach(key => { + Object.defineProperty(CustomElement.prototype, key, { + get () { + return this._wrapper.props[key] + }, + set (newVal) { + this._wrapper.props[key] = newVal; + }, + enumerable: false, + configurable: true + }); + }); + + isInitialized = true; } - function appendProps (result, component) { - for (const key in component.props) { - const camelKey = camelize(key) - if (!(camelKey in result)) { - result[camelKey] = component.props[key] - } - } - - if (component.extends) { - appendProps(result, component.extends) - } + function syncAttribute (el, key) { + const camelized = camelize(key); + const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined; + el._wrapper.props[camelized] = convertAttributeValue( + value, + key, + camelizedPropsMap[camelized] + ); } - function wrap (Vue, Component) { - const isAsync = typeof Component === 'function' && !Component.cid - let isInitialized = false - let hyphenatedPropsList - let camelizedPropsList - let camelizedPropsMap - - function initialize (Component) { - if (isInitialized) return - - const options = typeof Component === 'function' - ? Component.options - : Component - - options.props = spreadProps(options) - // extract props info - const propsList = Array.isArray(options.props) - ? options.props - : Object.keys(options.props || {}) - hyphenatedPropsList = propsList.map(hyphenate) - camelizedPropsList = propsList.map(camelize) - const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {} - camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => { - map[key] = originalPropsAsObject[propsList[i]] - return map - }, {}) - - // proxy $emit to native DOM events - injectHook(options, 'beforeCreate', function () { - const emit = this.$emit - this.$emit = (name, ...args) => { - this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args)) - return emit.call(this, name, ...args) + class CustomElement extends HTMLElement { + constructor () { + const self = super(); + self.attachShadow({ mode: 'open' }); + + const wrapper = self._wrapper = new Vue({ + name: 'shadow-root', + customElement: self, + shadowRoot: self.shadowRoot, + data () { + return { + props: {}, + slotChildren: [] + } + }, + render (h) { + return h(Component, { + ref: 'inner', + props: this.props + }, this.slotChildren) } - }) - - injectHook(options, 'created', function () { - // sync default props values to wrapper on created - camelizedPropsList.forEach(key => { - this.$root.props[key] = this[key] - }) - }) - - // proxy props as Element properties - camelizedPropsList.forEach(key => { - Object.defineProperty(CustomElement.prototype, key, { - get () { - return this._wrapper.props[key] - }, - set (newVal) { - this._wrapper.props[key] = newVal - }, - enumerable: false, - configurable: true - }) - }) - - isInitialized = true + }); + + // Use MutationObserver to react to future attribute & slot content change + const observer = new MutationObserver(mutations => { + let hasChildrenChange = false; + for (let i = 0; i < mutations.length; i++) { + const m = mutations[i]; + if (isInitialized && m.type === 'attributes' && m.target === self) { + syncAttribute(self, m.attributeName); + } else { + hasChildrenChange = true; + } + } + if (hasChildrenChange) { + wrapper.slotChildren = Object.freeze(toVNodes( + wrapper.$createElement, + self.childNodes + )); + } + }); + observer.observe(self, { + childList: true, + subtree: true, + characterData: true, + attributes: true + }); } - function syncAttribute (el, key) { - const camelized = camelize(key) - const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined - el._wrapper.props[camelized] = convertAttributeValue( - value, - key, - camelizedPropsMap[camelized] - ) + get vueComponent () { + return this._wrapper.$refs.inner } - class CustomElement extends HTMLElement { - constructor () { - const self = super() - self.attachShadow({ mode: 'open' }) - - const wrapper = self._wrapper = new Vue({ - name: 'shadow-root', - customElement: self, - shadowRoot: self.shadowRoot, - data () { - return { - props: {}, - slotChildren: [] - } - }, - render (h) { - return h(Component, { - ref: 'inner', - props: this.props - }, this.slotChildren) - } - }) - - // Use MutationObserver to react to future attribute & slot content change - const observer = new MutationObserver(mutations => { - let hasChildrenChange = false - for (let i = 0; i < mutations.length; i++) { - const m = mutations[i] - if (isInitialized && m.type === 'attributes' && m.target === self) { - syncAttribute(self, m.attributeName) - } else { - hasChildrenChange = true - } - } - if (hasChildrenChange) { - wrapper.slotChildren = Object.freeze(toVNodes( - wrapper.$createElement, - self.childNodes - )) - } - }) - observer.observe(self, { - childList: true, - subtree: true, - characterData: true, - attributes: true - }) - } - - get vueComponent () { - return this._wrapper.$refs.inner - } - - connectedCallback () { - const wrapper = this._wrapper - if (!wrapper._isMounted) { + connectedCallback () { + const wrapper = this._wrapper; + if (!wrapper._isMounted) { // initialize attributes - const syncInitialAttributes = () => { - wrapper.props = getInitialProps(camelizedPropsList) - hyphenatedPropsList.forEach(key => { - syncAttribute(this, key) - }) - } - - if (isInitialized) { - syncInitialAttributes() - } else { - // async & unresolved - Component().then(resolved => { - if (resolved.__esModule || resolved[Symbol.toStringTag] === 'Module') { - resolved = resolved.default - } - initialize(resolved) - syncInitialAttributes() - }) - } - // initialize children - wrapper.slotChildren = Object.freeze(toVNodes( - wrapper.$createElement, - this.childNodes - )) - wrapper.$mount() - this.shadowRoot.appendChild(wrapper.$el) + const syncInitialAttributes = () => { + wrapper.props = getInitialProps(camelizedPropsList); + hyphenatedPropsList.forEach(key => { + syncAttribute(this, key); + }); + }; + + if (isInitialized) { + syncInitialAttributes(); } else { - callHooks(this.vueComponent, 'activated') + // async & unresolved + Component().then(resolved => { + if (resolved.__esModule || resolved[Symbol.toStringTag] === 'Module') { + resolved = resolved.default; + } + initialize(resolved); + syncInitialAttributes(); + }); } - } - - disconnectedCallback () { - callHooks(this.vueComponent, 'deactivated') + // initialize children + wrapper.slotChildren = Object.freeze(toVNodes( + wrapper.$createElement, + this.childNodes + )); + wrapper.$mount(); + this.shadowRoot.appendChild(wrapper.$el); + } else { + callHooks(this.vueComponent, 'activated'); } } - if (!isAsync) { - initialize(Component) + disconnectedCallback () { + callHooks(this.vueComponent, 'deactivated'); } + } - return CustomElement + if (!isAsync) { + initialize(Component); } - return wrap -}()) + return CustomElement +} + +return wrap; + +}()); diff --git a/dist/vue-wc-wrapper.js b/dist/vue-wc-wrapper.js index cf488eb..36ce7f9 100644 --- a/dist/vue-wc-wrapper.js +++ b/dist/vue-wc-wrapper.js @@ -1,32 +1,32 @@ -const camelizeRE = /-(\w)/g +const camelizeRE = /-(\w)/g; const camelize = str => { return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') -} +}; -const hyphenateRE = /\B([A-Z])/g +const hyphenateRE = /\B([A-Z])/g; const hyphenate = str => { return str.replace(hyphenateRE, '-$1').toLowerCase() -} +}; function getInitialProps (propsList) { - const res = {} + const res = {}; propsList.forEach(key => { - res[key] = undefined - }) + res[key] = undefined; + }); return res } function injectHook (options, key, hook) { - options[key] = [].concat(options[key] || []) - options[key].unshift(hook) + options[key] = [].concat(options[key] || []); + options[key].unshift(hook); } function callHooks (vm, hook) { if (vm) { - const hooks = vm.$options[hook] || [] + const hooks = vm.$options[hook] || []; hooks.forEach(hook => { - hook.call(vm) - }) + hook.call(vm); + }); } } @@ -38,8 +38,8 @@ function createCustomEvent (name, args) { }) } -const isBoolean = val => /function Boolean/.test(String(val)) -const isNumber = val => /function Number/.test(String(val)) +const isBoolean = val => /function Boolean/.test(String(val)); +const isNumber = val => /function Number/.test(String(val)); function convertAttributeValue (value, name, { type } = {}) { if (isBoolean(type)) { @@ -51,7 +51,7 @@ function convertAttributeValue (value, name, { type } = {}) { } return value != null } else if (isNumber(type)) { - const parsed = parseFloat(value, 10) + const parsed = parseFloat(value, 10); return isNaN(parsed) ? value : parsed } else { return value @@ -59,9 +59,9 @@ function convertAttributeValue (value, name, { type } = {}) { } function toVNodes (h, children) { - const res = [] + const res = []; for (let i = 0, l = children.length; i < l; i++) { - res.push(toVNode(h, children[i])) + res.push(toVNode(h, children[i])); } return res } @@ -75,10 +75,10 @@ function toVNode (h, node) { domProps: { innerHTML: node.innerHTML } - } + }; if (data.attrs.slot) { - data.slot = data.attrs.slot - delete data.attrs.slot + data.slot = data.attrs.slot; + delete data.attrs.slot; } return h(node.tagName, data) } else { @@ -87,76 +87,77 @@ function toVNode (h, node) { } function getAttributes (node) { - const res = {} + const res = {}; for (let i = 0, l = node.attributes.length; i < l; i++) { - const attr = node.attributes[i] - res[attr.nodeName] = attr.nodeValue + const attr = node.attributes[i]; + res[attr.nodeName] = attr.nodeValue; } return res } function spreadProps (component) { - const result = {} - appendProps(result, component) - console.log(result) + const result = {}; + appendProps(result, component); + console.log(result); return result } function appendProps (result, component) { for (const key in component.props) { - const camelKey = camelize(key) + const camelKey = camelize(key); if (!(camelKey in result)) { - result[camelKey] = component.props[key] + result[camelKey] = component.props[key]; } } if (component.extends) { - appendProps(result, component.extends) + appendProps(result, component.extends); } } function wrap (Vue, Component) { - const isAsync = typeof Component === 'function' && !Component.cid - let isInitialized = false - let hyphenatedPropsList - let camelizedPropsList - let camelizedPropsMap + const isAsync = typeof Component === 'function' && !Component.cid; + let isInitialized = false; + let hyphenatedPropsList; + let camelizedPropsList; + let camelizedPropsMap; function initialize (Component) { if (isInitialized) return const options = typeof Component === 'function' ? Component.options - : Component + : Component; - options.props = spreadProps(options) + //spread props + options.props = spreadProps(options); // extract props info const propsList = Array.isArray(options.props) ? options.props - : Object.keys(options.props || {}) - hyphenatedPropsList = propsList.map(hyphenate) - camelizedPropsList = propsList.map(camelize) - const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {} + : Object.keys(options.props || {}); + hyphenatedPropsList = propsList.map(hyphenate); + camelizedPropsList = propsList.map(camelize); + const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {}; camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => { - map[key] = originalPropsAsObject[propsList[i]] + map[key] = originalPropsAsObject[propsList[i]]; return map - }, {}) + }, {}); // proxy $emit to native DOM events injectHook(options, 'beforeCreate', function () { - const emit = this.$emit + const emit = this.$emit; this.$emit = (name, ...args) => { - this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args)) + this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args)); return emit.call(this, name, ...args) - } - }) + }; + }); injectHook(options, 'created', function () { // sync default props values to wrapper on created camelizedPropsList.forEach(key => { - this.$root.props[key] = this[key] - }) - }) + this.$root.props[key] = this[key]; + }); + }); // proxy props as Element properties camelizedPropsList.forEach(key => { @@ -165,30 +166,30 @@ function wrap (Vue, Component) { return this._wrapper.props[key] }, set (newVal) { - this._wrapper.props[key] = newVal + this._wrapper.props[key] = newVal; }, enumerable: false, configurable: true - }) - }) + }); + }); - isInitialized = true + isInitialized = true; } function syncAttribute (el, key) { - const camelized = camelize(key) - const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined + const camelized = camelize(key); + const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined; el._wrapper.props[camelized] = convertAttributeValue( value, key, camelizedPropsMap[camelized] - ) + ); } class CustomElement extends HTMLElement { constructor () { - const self = super() - self.attachShadow({ mode: 'open' }) + const self = super(); + self.attachShadow({ mode: 'open' }); const wrapper = self._wrapper = new Vue({ name: 'shadow-root', @@ -206,32 +207,32 @@ function wrap (Vue, Component) { props: this.props }, this.slotChildren) } - }) + }); // Use MutationObserver to react to future attribute & slot content change const observer = new MutationObserver(mutations => { - let hasChildrenChange = false + let hasChildrenChange = false; for (let i = 0; i < mutations.length; i++) { - const m = mutations[i] + const m = mutations[i]; if (isInitialized && m.type === 'attributes' && m.target === self) { - syncAttribute(self, m.attributeName) + syncAttribute(self, m.attributeName); } else { - hasChildrenChange = true + hasChildrenChange = true; } } if (hasChildrenChange) { wrapper.slotChildren = Object.freeze(toVNodes( wrapper.$createElement, self.childNodes - )) + )); } - }) + }); observer.observe(self, { childList: true, subtree: true, characterData: true, attributes: true - }) + }); } get vueComponent () { @@ -239,50 +240,50 @@ function wrap (Vue, Component) { } connectedCallback () { - const wrapper = this._wrapper + const wrapper = this._wrapper; if (!wrapper._isMounted) { // initialize attributes const syncInitialAttributes = () => { - wrapper.props = getInitialProps(camelizedPropsList) + wrapper.props = getInitialProps(camelizedPropsList); hyphenatedPropsList.forEach(key => { - syncAttribute(this, key) - }) - } + syncAttribute(this, key); + }); + }; if (isInitialized) { - syncInitialAttributes() + syncInitialAttributes(); } else { // async & unresolved Component().then(resolved => { if (resolved.__esModule || resolved[Symbol.toStringTag] === 'Module') { - resolved = resolved.default + resolved = resolved.default; } - initialize(resolved) - syncInitialAttributes() - }) + initialize(resolved); + syncInitialAttributes(); + }); } // initialize children wrapper.slotChildren = Object.freeze(toVNodes( wrapper.$createElement, this.childNodes - )) - wrapper.$mount() - this.shadowRoot.appendChild(wrapper.$el) + )); + wrapper.$mount(); + this.shadowRoot.appendChild(wrapper.$el); } else { - callHooks(this.vueComponent, 'activated') + callHooks(this.vueComponent, 'activated'); } } disconnectedCallback () { - callHooks(this.vueComponent, 'deactivated') + callHooks(this.vueComponent, 'deactivated'); } } if (!isAsync) { - initialize(Component) + initialize(Component); } return CustomElement } -export default wrap +export default wrap; diff --git a/src/index.js b/src/index.js index e273ea2..88e5a07 100644 --- a/src/index.js +++ b/src/index.js @@ -24,7 +24,8 @@ export default function wrap (Vue, Component) { ? Component.options : Component - options.props = spreadProps(options) + //spread props + options.props = spreadProps(options) // extract props info const propsList = Array.isArray(options.props) ? options.props diff --git a/test/fixtures/spreadedProperties.html b/test/fixtures/spreadedProperties.html new file mode 100644 index 0000000..87a640d --- /dev/null +++ b/test/fixtures/spreadedProperties.html @@ -0,0 +1,41 @@ + + + + From 94fd1b693b4189944fe42ff1970b42dc984f1bdd Mon Sep 17 00:00:00 2001 From: jili Date: Tue, 8 Jan 2019 11:43:51 +0100 Subject: [PATCH 03/17] new test for spread props --- test/fixtures/spreadedProperties.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fixtures/spreadedProperties.html b/test/fixtures/spreadedProperties.html index 87a640d..acd3a3b 100644 --- a/test/fixtures/spreadedProperties.html +++ b/test/fixtures/spreadedProperties.html @@ -10,7 +10,7 @@ }, } } -var Parent1 = { +var Parent1 = { extends: Parent0, props: { p1: { From 4c1088917efb47bc7c1978ef2b5e141ce2437a32 Mon Sep 17 00:00:00 2001 From: jili Date: Tue, 8 Jan 2019 11:49:31 +0100 Subject: [PATCH 04/17] new test --- test/fixtures/spreadedProperties.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fixtures/spreadedProperties.html b/test/fixtures/spreadedProperties.html index acd3a3b..87a640d 100644 --- a/test/fixtures/spreadedProperties.html +++ b/test/fixtures/spreadedProperties.html @@ -10,7 +10,7 @@ }, } } -var Parent1 = { +var Parent1 = { extends: Parent0, props: { p1: { From 3a899b8d827dbd2f41848b8ce85ea0051fa5de2e Mon Sep 17 00:00:00 2001 From: jili Date: Tue, 8 Jan 2019 11:51:05 +0100 Subject: [PATCH 05/17] try lint-staged --- src/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 88e5a07..2dde0eb 100644 --- a/src/index.js +++ b/src/index.js @@ -24,8 +24,8 @@ export default function wrap (Vue, Component) { ? Component.options : Component - //spread props - options.props = spreadProps(options) + // spread props + options.props = spreadProps(options) // extract props info const propsList = Array.isArray(options.props) ? options.props From 91fd583b65bb0b0c4d35944643282f44b34f9a56 Mon Sep 17 00:00:00 2001 From: jili Date: Tue, 8 Jan 2019 12:34:18 +0100 Subject: [PATCH 06/17] fix minxins props --- src/utils.js | 18 ++++++- test/fixtures/spreadedProperties.html | 78 +++++++++++++++------------ 2 files changed, 61 insertions(+), 35 deletions(-) diff --git a/src/utils.js b/src/utils.js index d06640c..2d8d856 100644 --- a/src/utils.js +++ b/src/utils.js @@ -109,8 +109,24 @@ function appendProps (result, component) { result[camelKey] = component.props[key] } } - + if (component.mixins) { + appendMixinsProps(result, component.mixins) + } if (component.extends) { appendProps(result, component.extends) } } + +function appendMixinsProps (result, mixins) { + mixins.forEach(function (mixin) { + if (mixin.props) { + for (const key in mixin.props) { + const camelKey = camelize(key) + if (!(camelKey in result)) { + result[camelKey] = mixin.props[key] + } + } + } + }) +} + diff --git a/test/fixtures/spreadedProperties.html b/test/fixtures/spreadedProperties.html index 87a640d..0c810c9 100644 --- a/test/fixtures/spreadedProperties.html +++ b/test/fixtures/spreadedProperties.html @@ -1,41 +1,51 @@ - + From e3426d92d3ce3256fd1947960c31fc9b87cbb0de Mon Sep 17 00:00:00 2001 From: jili Date: Tue, 8 Jan 2019 12:48:45 +0100 Subject: [PATCH 07/17] fix spread mixin props --- dist/vue-wc-wrapper.global.js | 530 +++++++++++++++++----------------- dist/vue-wc-wrapper.js | 185 ++++++------ 2 files changed, 372 insertions(+), 343 deletions(-) diff --git a/dist/vue-wc-wrapper.global.js b/dist/vue-wc-wrapper.global.js index 6fcaa7f..c1f740b 100644 --- a/dist/vue-wc-wrapper.global.js +++ b/dist/vue-wc-wrapper.global.js @@ -1,294 +1,308 @@ var wrapVueWebComponent = (function () { -'use strict'; - -const camelizeRE = /-(\w)/g; -const camelize = str => { - return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') -}; - -const hyphenateRE = /\B([A-Z])/g; -const hyphenate = str => { - return str.replace(hyphenateRE, '-$1').toLowerCase() -}; - -function getInitialProps (propsList) { - const res = {}; - propsList.forEach(key => { - res[key] = undefined; - }); - return res -} - -function injectHook (options, key, hook) { - options[key] = [].concat(options[key] || []); - options[key].unshift(hook); -} - -function callHooks (vm, hook) { - if (vm) { - const hooks = vm.$options[hook] || []; - hooks.forEach(hook => { - hook.call(vm); - }); + 'use strict' + + const camelizeRE = /-(\w)/g + const camelize = str => { + return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') } -} - -function createCustomEvent (name, args) { - return new CustomEvent(name, { - bubbles: false, - cancelable: false, - detail: args - }) -} - -const isBoolean = val => /function Boolean/.test(String(val)); -const isNumber = val => /function Number/.test(String(val)); - -function convertAttributeValue (value, name, { type } = {}) { - if (isBoolean(type)) { - if (value === 'true' || value === 'false') { - return value === 'true' - } - if (value === '' || value === name) { - return true + + const hyphenateRE = /\B([A-Z])/g + const hyphenate = str => { + return str.replace(hyphenateRE, '-$1').toLowerCase() + } + + function getInitialProps (propsList) { + const res = {} + propsList.forEach(key => { + res[key] = undefined + }) + return res + } + + function injectHook (options, key, hook) { + options[key] = [].concat(options[key] || []) + options[key].unshift(hook) + } + + function callHooks (vm, hook) { + if (vm) { + const hooks = vm.$options[hook] || [] + hooks.forEach(hook => { + hook.call(vm) + }) } - return value != null - } else if (isNumber(type)) { - const parsed = parseFloat(value, 10); - return isNaN(parsed) ? value : parsed - } else { - return value } -} -function toVNodes (h, children) { - const res = []; - for (let i = 0, l = children.length; i < l; i++) { - res.push(toVNode(h, children[i])); + function createCustomEvent (name, args) { + return new CustomEvent(name, { + bubbles: false, + cancelable: false, + detail: args + }) } - return res -} - -function toVNode (h, node) { - if (node.nodeType === 3) { - return node.data.trim() ? node.data : null - } else if (node.nodeType === 1) { - const data = { - attrs: getAttributes(node), - domProps: { - innerHTML: node.innerHTML + + const isBoolean = val => /function Boolean/.test(String(val)) + const isNumber = val => /function Number/.test(String(val)) + + function convertAttributeValue (value, name, { type } = {}) { + if (isBoolean(type)) { + if (value === 'true' || value === 'false') { + return value === 'true' + } + if (value === '' || value === name) { + return true } - }; - if (data.attrs.slot) { - data.slot = data.attrs.slot; - delete data.attrs.slot; + return value != null + } else if (isNumber(type)) { + const parsed = parseFloat(value, 10) + return isNaN(parsed) ? value : parsed + } else { + return value } - return h(node.tagName, data) - } else { - return null } -} -function getAttributes (node) { - const res = {}; - for (let i = 0, l = node.attributes.length; i < l; i++) { - const attr = node.attributes[i]; - res[attr.nodeName] = attr.nodeValue; + function toVNodes (h, children) { + const res = [] + for (let i = 0, l = children.length; i < l; i++) { + res.push(toVNode(h, children[i])) + } + return res } - return res -} - -function spreadProps (component) { - const result = {}; - appendProps(result, component); - console.log(result); - return result -} - -function appendProps (result, component) { - for (const key in component.props) { - const camelKey = camelize(key); - if (!(camelKey in result)) { - result[camelKey] = component.props[key]; + + function toVNode (h, node) { + if (node.nodeType === 3) { + return node.data.trim() ? node.data : null + } else if (node.nodeType === 1) { + const data = { + attrs: getAttributes(node), + domProps: { + innerHTML: node.innerHTML + } + } + if (data.attrs.slot) { + data.slot = data.attrs.slot + delete data.attrs.slot + } + return h(node.tagName, data) + } else { + return null } } - if (component.extends) { - appendProps(result, component.extends); + function getAttributes (node) { + const res = {} + for (let i = 0, l = node.attributes.length; i < l; i++) { + const attr = node.attributes[i] + res[attr.nodeName] = attr.nodeValue + } + return res } -} - -function wrap (Vue, Component) { - const isAsync = typeof Component === 'function' && !Component.cid; - let isInitialized = false; - let hyphenatedPropsList; - let camelizedPropsList; - let camelizedPropsMap; - - function initialize (Component) { - if (isInitialized) return - - const options = typeof Component === 'function' - ? Component.options - : Component; - - //spread props - options.props = spreadProps(options); - // extract props info - const propsList = Array.isArray(options.props) - ? options.props - : Object.keys(options.props || {}); - hyphenatedPropsList = propsList.map(hyphenate); - camelizedPropsList = propsList.map(camelize); - const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {}; - camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => { - map[key] = originalPropsAsObject[propsList[i]]; - return map - }, {}); - - // proxy $emit to native DOM events - injectHook(options, 'beforeCreate', function () { - const emit = this.$emit; - this.$emit = (name, ...args) => { - this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args)); - return emit.call(this, name, ...args) - }; - }); - - injectHook(options, 'created', function () { - // sync default props values to wrapper on created - camelizedPropsList.forEach(key => { - this.$root.props[key] = this[key]; - }); - }); - - // proxy props as Element properties - camelizedPropsList.forEach(key => { - Object.defineProperty(CustomElement.prototype, key, { - get () { - return this._wrapper.props[key] - }, - set (newVal) { - this._wrapper.props[key] = newVal; - }, - enumerable: false, - configurable: true - }); - }); - - isInitialized = true; + + function spreadProps (component) { + const result = {} + appendProps(result, component) + console.log(result) + return result } - function syncAttribute (el, key) { - const camelized = camelize(key); - const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined; - el._wrapper.props[camelized] = convertAttributeValue( - value, - key, - camelizedPropsMap[camelized] - ); + function appendProps (result, component) { + for (const key in component.props) { + const camelKey = camelize(key) + if (!(camelKey in result)) { + result[camelKey] = component.props[key] + } + } + if (component.mixins) { + appendMixinsProps(result, component.mixins) + } + if (component.extends) { + appendProps(result, component.extends) + } } - class CustomElement extends HTMLElement { - constructor () { - const self = super(); - self.attachShadow({ mode: 'open' }); - - const wrapper = self._wrapper = new Vue({ - name: 'shadow-root', - customElement: self, - shadowRoot: self.shadowRoot, - data () { - return { - props: {}, - slotChildren: [] + function appendMixinsProps (result, mixins) { + mixins.forEach(function (mixin) { + if (mixin.props) { + for (const key in mixin.props) { + const camelKey = camelize(key) + if (!(camelKey in result)) { + result[camelKey] = mixin.props[key] } - }, - render (h) { - return h(Component, { - ref: 'inner', - props: this.props - }, this.slotChildren) } - }); - - // Use MutationObserver to react to future attribute & slot content change - const observer = new MutationObserver(mutations => { - let hasChildrenChange = false; - for (let i = 0; i < mutations.length; i++) { - const m = mutations[i]; - if (isInitialized && m.type === 'attributes' && m.target === self) { - syncAttribute(self, m.attributeName); - } else { - hasChildrenChange = true; - } - } - if (hasChildrenChange) { - wrapper.slotChildren = Object.freeze(toVNodes( - wrapper.$createElement, - self.childNodes - )); + } + }) + } + + function wrap (Vue, Component) { + const isAsync = typeof Component === 'function' && !Component.cid + let isInitialized = false + let hyphenatedPropsList + let camelizedPropsList + let camelizedPropsMap + + function initialize (Component) { + if (isInitialized) return + + const options = typeof Component === 'function' + ? Component.options + : Component + + // spread props + options.props = spreadProps(options) + // extract props info + const propsList = Array.isArray(options.props) + ? options.props + : Object.keys(options.props || {}) + hyphenatedPropsList = propsList.map(hyphenate) + camelizedPropsList = propsList.map(camelize) + const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {} + camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => { + map[key] = originalPropsAsObject[propsList[i]] + return map + }, {}) + + // proxy $emit to native DOM events + injectHook(options, 'beforeCreate', function () { + const emit = this.$emit + this.$emit = (name, ...args) => { + this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args)) + return emit.call(this, name, ...args) } - }); - observer.observe(self, { - childList: true, - subtree: true, - characterData: true, - attributes: true - }); + }) + + injectHook(options, 'created', function () { + // sync default props values to wrapper on created + camelizedPropsList.forEach(key => { + this.$root.props[key] = this[key] + }) + }) + + // proxy props as Element properties + camelizedPropsList.forEach(key => { + Object.defineProperty(CustomElement.prototype, key, { + get () { + return this._wrapper.props[key] + }, + set (newVal) { + this._wrapper.props[key] = newVal + }, + enumerable: false, + configurable: true + }) + }) + + isInitialized = true } - get vueComponent () { - return this._wrapper.$refs.inner + function syncAttribute (el, key) { + const camelized = camelize(key) + const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined + el._wrapper.props[camelized] = convertAttributeValue( + value, + key, + camelizedPropsMap[camelized] + ) } - connectedCallback () { - const wrapper = this._wrapper; - if (!wrapper._isMounted) { + class CustomElement extends HTMLElement { + constructor () { + const self = super() + self.attachShadow({ mode: 'open' }) + + const wrapper = self._wrapper = new Vue({ + name: 'shadow-root', + customElement: self, + shadowRoot: self.shadowRoot, + data () { + return { + props: {}, + slotChildren: [] + } + }, + render (h) { + return h(Component, { + ref: 'inner', + props: this.props + }, this.slotChildren) + } + }) + + // Use MutationObserver to react to future attribute & slot content change + const observer = new MutationObserver(mutations => { + let hasChildrenChange = false + for (let i = 0; i < mutations.length; i++) { + const m = mutations[i] + if (isInitialized && m.type === 'attributes' && m.target === self) { + syncAttribute(self, m.attributeName) + } else { + hasChildrenChange = true + } + } + if (hasChildrenChange) { + wrapper.slotChildren = Object.freeze(toVNodes( + wrapper.$createElement, + self.childNodes + )) + } + }) + observer.observe(self, { + childList: true, + subtree: true, + characterData: true, + attributes: true + }) + } + + get vueComponent () { + return this._wrapper.$refs.inner + } + + connectedCallback () { + const wrapper = this._wrapper + if (!wrapper._isMounted) { // initialize attributes - const syncInitialAttributes = () => { - wrapper.props = getInitialProps(camelizedPropsList); - hyphenatedPropsList.forEach(key => { - syncAttribute(this, key); - }); - }; - - if (isInitialized) { - syncInitialAttributes(); - } else { + const syncInitialAttributes = () => { + wrapper.props = getInitialProps(camelizedPropsList) + hyphenatedPropsList.forEach(key => { + syncAttribute(this, key) + }) + } + + if (isInitialized) { + syncInitialAttributes() + } else { // async & unresolved - Component().then(resolved => { - if (resolved.__esModule || resolved[Symbol.toStringTag] === 'Module') { - resolved = resolved.default; - } - initialize(resolved); - syncInitialAttributes(); - }); + Component().then(resolved => { + if (resolved.__esModule || resolved[Symbol.toStringTag] === 'Module') { + resolved = resolved.default + } + initialize(resolved) + syncInitialAttributes() + }) + } + // initialize children + wrapper.slotChildren = Object.freeze(toVNodes( + wrapper.$createElement, + this.childNodes + )) + wrapper.$mount() + this.shadowRoot.appendChild(wrapper.$el) + } else { + callHooks(this.vueComponent, 'activated') } - // initialize children - wrapper.slotChildren = Object.freeze(toVNodes( - wrapper.$createElement, - this.childNodes - )); - wrapper.$mount(); - this.shadowRoot.appendChild(wrapper.$el); - } else { - callHooks(this.vueComponent, 'activated'); + } + + disconnectedCallback () { + callHooks(this.vueComponent, 'deactivated') } } - disconnectedCallback () { - callHooks(this.vueComponent, 'deactivated'); + if (!isAsync) { + initialize(Component) } - } - if (!isAsync) { - initialize(Component); + return CustomElement } - return CustomElement -} - -return wrap; - -}()); + return wrap +}()) diff --git a/dist/vue-wc-wrapper.js b/dist/vue-wc-wrapper.js index 36ce7f9..180d831 100644 --- a/dist/vue-wc-wrapper.js +++ b/dist/vue-wc-wrapper.js @@ -1,32 +1,32 @@ -const camelizeRE = /-(\w)/g; +const camelizeRE = /-(\w)/g const camelize = str => { return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') -}; +} -const hyphenateRE = /\B([A-Z])/g; +const hyphenateRE = /\B([A-Z])/g const hyphenate = str => { return str.replace(hyphenateRE, '-$1').toLowerCase() -}; +} function getInitialProps (propsList) { - const res = {}; + const res = {} propsList.forEach(key => { - res[key] = undefined; - }); + res[key] = undefined + }) return res } function injectHook (options, key, hook) { - options[key] = [].concat(options[key] || []); - options[key].unshift(hook); + options[key] = [].concat(options[key] || []) + options[key].unshift(hook) } function callHooks (vm, hook) { if (vm) { - const hooks = vm.$options[hook] || []; + const hooks = vm.$options[hook] || [] hooks.forEach(hook => { - hook.call(vm); - }); + hook.call(vm) + }) } } @@ -38,8 +38,8 @@ function createCustomEvent (name, args) { }) } -const isBoolean = val => /function Boolean/.test(String(val)); -const isNumber = val => /function Number/.test(String(val)); +const isBoolean = val => /function Boolean/.test(String(val)) +const isNumber = val => /function Number/.test(String(val)) function convertAttributeValue (value, name, { type } = {}) { if (isBoolean(type)) { @@ -51,7 +51,7 @@ function convertAttributeValue (value, name, { type } = {}) { } return value != null } else if (isNumber(type)) { - const parsed = parseFloat(value, 10); + const parsed = parseFloat(value, 10) return isNaN(parsed) ? value : parsed } else { return value @@ -59,9 +59,9 @@ function convertAttributeValue (value, name, { type } = {}) { } function toVNodes (h, children) { - const res = []; + const res = [] for (let i = 0, l = children.length; i < l; i++) { - res.push(toVNode(h, children[i])); + res.push(toVNode(h, children[i])) } return res } @@ -75,10 +75,10 @@ function toVNode (h, node) { domProps: { innerHTML: node.innerHTML } - }; + } if (data.attrs.slot) { - data.slot = data.attrs.slot; - delete data.attrs.slot; + data.slot = data.attrs.slot + delete data.attrs.slot } return h(node.tagName, data) } else { @@ -87,77 +87,92 @@ function toVNode (h, node) { } function getAttributes (node) { - const res = {}; + const res = {} for (let i = 0, l = node.attributes.length; i < l; i++) { - const attr = node.attributes[i]; - res[attr.nodeName] = attr.nodeValue; + const attr = node.attributes[i] + res[attr.nodeName] = attr.nodeValue } return res } function spreadProps (component) { - const result = {}; - appendProps(result, component); - console.log(result); + const result = {} + appendProps(result, component) + console.log(result) return result } function appendProps (result, component) { for (const key in component.props) { - const camelKey = camelize(key); + const camelKey = camelize(key) if (!(camelKey in result)) { - result[camelKey] = component.props[key]; + result[camelKey] = component.props[key] } } - + if (component.mixins) { + appendMixinsProps(result, component.mixins) + } if (component.extends) { - appendProps(result, component.extends); + appendProps(result, component.extends) } } +function appendMixinsProps (result, mixins) { + mixins.forEach(function (mixin) { + if (mixin.props) { + for (const key in mixin.props) { + const camelKey = camelize(key) + if (!(camelKey in result)) { + result[camelKey] = mixin.props[key] + } + } + } + }) +} + function wrap (Vue, Component) { - const isAsync = typeof Component === 'function' && !Component.cid; - let isInitialized = false; - let hyphenatedPropsList; - let camelizedPropsList; - let camelizedPropsMap; + const isAsync = typeof Component === 'function' && !Component.cid + let isInitialized = false + let hyphenatedPropsList + let camelizedPropsList + let camelizedPropsMap function initialize (Component) { if (isInitialized) return const options = typeof Component === 'function' ? Component.options - : Component; + : Component - //spread props - options.props = spreadProps(options); + // spread props + options.props = spreadProps(options) // extract props info const propsList = Array.isArray(options.props) ? options.props - : Object.keys(options.props || {}); - hyphenatedPropsList = propsList.map(hyphenate); - camelizedPropsList = propsList.map(camelize); - const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {}; + : Object.keys(options.props || {}) + hyphenatedPropsList = propsList.map(hyphenate) + camelizedPropsList = propsList.map(camelize) + const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {} camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => { - map[key] = originalPropsAsObject[propsList[i]]; + map[key] = originalPropsAsObject[propsList[i]] return map - }, {}); + }, {}) // proxy $emit to native DOM events injectHook(options, 'beforeCreate', function () { - const emit = this.$emit; + const emit = this.$emit this.$emit = (name, ...args) => { - this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args)); + this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args)) return emit.call(this, name, ...args) - }; - }); + } + }) injectHook(options, 'created', function () { // sync default props values to wrapper on created camelizedPropsList.forEach(key => { - this.$root.props[key] = this[key]; - }); - }); + this.$root.props[key] = this[key] + }) + }) // proxy props as Element properties camelizedPropsList.forEach(key => { @@ -166,30 +181,30 @@ function wrap (Vue, Component) { return this._wrapper.props[key] }, set (newVal) { - this._wrapper.props[key] = newVal; + this._wrapper.props[key] = newVal }, enumerable: false, configurable: true - }); - }); + }) + }) - isInitialized = true; + isInitialized = true } function syncAttribute (el, key) { - const camelized = camelize(key); - const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined; + const camelized = camelize(key) + const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined el._wrapper.props[camelized] = convertAttributeValue( value, key, camelizedPropsMap[camelized] - ); + ) } class CustomElement extends HTMLElement { constructor () { - const self = super(); - self.attachShadow({ mode: 'open' }); + const self = super() + self.attachShadow({ mode: 'open' }) const wrapper = self._wrapper = new Vue({ name: 'shadow-root', @@ -207,32 +222,32 @@ function wrap (Vue, Component) { props: this.props }, this.slotChildren) } - }); + }) // Use MutationObserver to react to future attribute & slot content change const observer = new MutationObserver(mutations => { - let hasChildrenChange = false; + let hasChildrenChange = false for (let i = 0; i < mutations.length; i++) { - const m = mutations[i]; + const m = mutations[i] if (isInitialized && m.type === 'attributes' && m.target === self) { - syncAttribute(self, m.attributeName); + syncAttribute(self, m.attributeName) } else { - hasChildrenChange = true; + hasChildrenChange = true } } if (hasChildrenChange) { wrapper.slotChildren = Object.freeze(toVNodes( wrapper.$createElement, self.childNodes - )); + )) } - }); + }) observer.observe(self, { childList: true, subtree: true, characterData: true, attributes: true - }); + }) } get vueComponent () { @@ -240,50 +255,50 @@ function wrap (Vue, Component) { } connectedCallback () { - const wrapper = this._wrapper; + const wrapper = this._wrapper if (!wrapper._isMounted) { // initialize attributes const syncInitialAttributes = () => { - wrapper.props = getInitialProps(camelizedPropsList); + wrapper.props = getInitialProps(camelizedPropsList) hyphenatedPropsList.forEach(key => { - syncAttribute(this, key); - }); - }; + syncAttribute(this, key) + }) + } if (isInitialized) { - syncInitialAttributes(); + syncInitialAttributes() } else { // async & unresolved Component().then(resolved => { if (resolved.__esModule || resolved[Symbol.toStringTag] === 'Module') { - resolved = resolved.default; + resolved = resolved.default } - initialize(resolved); - syncInitialAttributes(); - }); + initialize(resolved) + syncInitialAttributes() + }) } // initialize children wrapper.slotChildren = Object.freeze(toVNodes( wrapper.$createElement, this.childNodes - )); - wrapper.$mount(); - this.shadowRoot.appendChild(wrapper.$el); + )) + wrapper.$mount() + this.shadowRoot.appendChild(wrapper.$el) } else { - callHooks(this.vueComponent, 'activated'); + callHooks(this.vueComponent, 'activated') } } disconnectedCallback () { - callHooks(this.vueComponent, 'deactivated'); + callHooks(this.vueComponent, 'deactivated') } } if (!isAsync) { - initialize(Component); + initialize(Component) } return CustomElement } -export default wrap; +export default wrap From 82669efc1d9a0689d42b114e12bd399e693eac88 Mon Sep 17 00:00:00 2001 From: jili Date: Tue, 8 Jan 2019 14:27:22 +0100 Subject: [PATCH 08/17] test for spreaded props --- test/fixtures/spreadedProperties.html | 38 ++++++++++++------------- test/test.js | 40 +++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/test/fixtures/spreadedProperties.html b/test/fixtures/spreadedProperties.html index 0c810c9..5f62b7b 100644 --- a/test/fixtures/spreadedProperties.html +++ b/test/fixtures/spreadedProperties.html @@ -2,50 +2,48 @@ - + diff --git a/test/test.js b/test/test.js index 9423d7a..97ed47e 100644 --- a/test/test.js +++ b/test/test.js @@ -16,12 +16,48 @@ test('properties', async () => { el.foo = 234 el.someProp = 'lol' }) - const newFoo = await page.evaluate(() => el.vueComponent.foo) + const newFoo = await page.evaluate(() => el.vueComponent.foo) expect(newFoo).toBe(234) - const newBar = await page.evaluate(() => el.vueComponent.someProp) + const newBar = await page.evaluate(() => el.vueComponent.someProp) expect(newBar).toBe('lol') }) +test('spreadedProperties', async () => { + const { page } = await launchPage(`spreadedProperties`) + + // props from 'extends' + const p0 = await page.evaluate(() => el.p0) + expect(p0).toBe('p0') + + // props from 'extends' + const p1 = await page.evaluate(() => el.p1) + expect(p1).toBe('p1') + + // props from 'mixins' + const m0 = await page.evaluate(() => el.m0) + expect(m0).toBe('m0') + + // props from 'mixins' + const m1 = await page.evaluate(() => el.m1) + expect(m1).toBe('m1') + + // props proxying: set + await page.evaluate(() => { + el.p0 = 'new-p0' + el.p1 = 'new-p1' + el.m0 = 'new-m0' + el.m1 = 'new-m1' + }) + const newP0 = await page.evaluate(() => el.vueComponent.p0) + expect(newP0).toBe('new-p0') + const newP1 = await page.evaluate(() => el.vueComponent.p1) + expect(newP1).toBe('new-p1') + const newM0 = await page.evaluate(() => el.vueComponent.m0) + expect(newM0).toBe('new-m0') + const newM1 = await page.evaluate(() => el.vueComponent.m1) + expect(newM1).toBe('new-m1') +}) + test('attributes', async () => { const { page } = await launchPage(`attributes`) From 8d75625e715e79d99eb0a9ab287a91bb1fd2db08 Mon Sep 17 00:00:00 2001 From: Jiang Li Date: Wed, 9 Jan 2019 14:54:36 +0100 Subject: [PATCH 09/17] check array props --- src/index.js | 6 ++---- src/utils.js | 49 +++++++++++++++++++++++++++++++------------------ test/setup.js | 1 + 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/index.js b/src/index.js index 2dde0eb..4788a1a 100644 --- a/src/index.js +++ b/src/index.js @@ -27,12 +27,10 @@ export default function wrap (Vue, Component) { // spread props options.props = spreadProps(options) // extract props info - const propsList = Array.isArray(options.props) - ? options.props - : Object.keys(options.props || {}) + const propsList = Object.keys(options.props || {}) hyphenatedPropsList = propsList.map(hyphenate) camelizedPropsList = propsList.map(camelize) - const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {} + const originalPropsAsObject = options.props || {} camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => { map[key] = originalPropsAsObject[propsList[i]] return map diff --git a/src/utils.js b/src/utils.js index 2d8d856..f202c68 100644 --- a/src/utils.js +++ b/src/utils.js @@ -97,34 +97,47 @@ function getAttributes (node) { export function spreadProps (component) { const result = {} - appendProps(result, component) - console.log(result) + spreadNext(result, component) return result } -function appendProps (result, component) { - for (const key in component.props) { - const camelKey = camelize(key) - if (!(camelKey in result)) { - result[camelKey] = component.props[key] - } +function spreadNext (result, component) { + if (component.props) { + appendProps(result, component.props) } + if (component.mixins) { - appendMixinsProps(result, component.mixins) + component.mixins.forEach(function (mixin) { + spreadNext(result, mixin) + }) } if (component.extends) { - appendProps(result, component.extends) + spreadNext(result, component.extends) + } +} + +function appendProps (result, props) { + if (Array.isArray(props)) { + processArrayProps(result, props) + } else { + processObjectProps(result, props) } } -function appendMixinsProps (result, mixins) { - mixins.forEach(function (mixin) { - if (mixin.props) { - for (const key in mixin.props) { - const camelKey = camelize(key) - if (!(camelKey in result)) { - result[camelKey] = mixin.props[key] - } +function processObjectProps (result, props) { + for (const key in props) { + const camelKey = camelize(key) + if (!(camelKey in result)) { + result[camelKey] = props[key] + } + } +} +function processArrayProps (result, props) { + props.forEach(function (prop) { + if (typeof prop === 'string') { + const camelKey = camelize(prop) + if (!(camelKey in result)) { + result[camelKey] = undefined } } }) diff --git a/test/setup.js b/test/setup.js index 24e59e1..a69ac1b 100644 --- a/test/setup.js +++ b/test/setup.js @@ -20,6 +20,7 @@ module.exports = async function launchPage (name) { } beforeAll(async () => { + jest.setTimeout(10000) browser = await puppeteer.launch(puppeteerOptions) server = createServer({ root: process.cwd() }) await new Promise((resolve, reject) => { From 2b68079a04b6af5ba549cfc0e4c680b5906b0ccd Mon Sep 17 00:00:00 2001 From: Jiang Li Date: Wed, 9 Jan 2019 15:38:41 +0100 Subject: [PATCH 10/17] more tests for spread props --- dist/vue-wc-wrapper.global.js | 55 ++++++++++++++++----------- dist/vue-wc-wrapper.js | 55 ++++++++++++++++----------- test/fixtures/spreadedProperties.html | 10 +++-- test/test.js | 26 +++++++++++++ 4 files changed, 99 insertions(+), 47 deletions(-) diff --git a/dist/vue-wc-wrapper.global.js b/dist/vue-wc-wrapper.global.js index c1f740b..a22b451 100644 --- a/dist/vue-wc-wrapper.global.js +++ b/dist/vue-wc-wrapper.global.js @@ -100,34 +100,47 @@ var wrapVueWebComponent = (function () { function spreadProps (component) { const result = {} - appendProps(result, component) - console.log(result) + spreadNext(result, component) return result } - function appendProps (result, component) { - for (const key in component.props) { - const camelKey = camelize(key) - if (!(camelKey in result)) { - result[camelKey] = component.props[key] - } + function spreadNext (result, component) { + if (component.props) { + appendProps(result, component.props) } + if (component.mixins) { - appendMixinsProps(result, component.mixins) + component.mixins.forEach(function (mixin) { + spreadNext(result, mixin) + }) } if (component.extends) { - appendProps(result, component.extends) + spreadNext(result, component.extends) } } - function appendMixinsProps (result, mixins) { - mixins.forEach(function (mixin) { - if (mixin.props) { - for (const key in mixin.props) { - const camelKey = camelize(key) - if (!(camelKey in result)) { - result[camelKey] = mixin.props[key] - } + function appendProps (result, props) { + if (Array.isArray(props)) { + processArrayProps(result, props) + } else { + processObjectProps(result, props) + } + } + + function processObjectProps (result, props) { + for (const key in props) { + const camelKey = camelize(key) + if (!(camelKey in result)) { + result[camelKey] = props[key] + } + } + } + function processArrayProps (result, props) { + props.forEach(function (prop) { + if (typeof prop === 'string') { + const camelKey = camelize(prop) + if (!(camelKey in result)) { + result[camelKey] = undefined } } }) @@ -150,12 +163,10 @@ var wrapVueWebComponent = (function () { // spread props options.props = spreadProps(options) // extract props info - const propsList = Array.isArray(options.props) - ? options.props - : Object.keys(options.props || {}) + const propsList = Object.keys(options.props || {}) hyphenatedPropsList = propsList.map(hyphenate) camelizedPropsList = propsList.map(camelize) - const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {} + const originalPropsAsObject = options.props || {} camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => { map[key] = originalPropsAsObject[propsList[i]] return map diff --git a/dist/vue-wc-wrapper.js b/dist/vue-wc-wrapper.js index 180d831..d062ab8 100644 --- a/dist/vue-wc-wrapper.js +++ b/dist/vue-wc-wrapper.js @@ -97,34 +97,47 @@ function getAttributes (node) { function spreadProps (component) { const result = {} - appendProps(result, component) - console.log(result) + spreadNext(result, component) return result } -function appendProps (result, component) { - for (const key in component.props) { - const camelKey = camelize(key) - if (!(camelKey in result)) { - result[camelKey] = component.props[key] - } +function spreadNext (result, component) { + if (component.props) { + appendProps(result, component.props) } + if (component.mixins) { - appendMixinsProps(result, component.mixins) + component.mixins.forEach(function (mixin) { + spreadNext(result, mixin) + }) } if (component.extends) { - appendProps(result, component.extends) + spreadNext(result, component.extends) } } -function appendMixinsProps (result, mixins) { - mixins.forEach(function (mixin) { - if (mixin.props) { - for (const key in mixin.props) { - const camelKey = camelize(key) - if (!(camelKey in result)) { - result[camelKey] = mixin.props[key] - } +function appendProps (result, props) { + if (Array.isArray(props)) { + processArrayProps(result, props) + } else { + processObjectProps(result, props) + } +} + +function processObjectProps (result, props) { + for (const key in props) { + const camelKey = camelize(key) + if (!(camelKey in result)) { + result[camelKey] = props[key] + } + } +} +function processArrayProps (result, props) { + props.forEach(function (prop) { + if (typeof prop === 'string') { + const camelKey = camelize(prop) + if (!(camelKey in result)) { + result[camelKey] = undefined } } }) @@ -147,12 +160,10 @@ function wrap (Vue, Component) { // spread props options.props = spreadProps(options) // extract props info - const propsList = Array.isArray(options.props) - ? options.props - : Object.keys(options.props || {}) + const propsList = Object.keys(options.props || {}) hyphenatedPropsList = propsList.map(hyphenate) camelizedPropsList = propsList.map(camelize) - const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {} + const originalPropsAsObject = options.props || {} camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => { map[key] = originalPropsAsObject[propsList[i]] return map diff --git a/test/fixtures/spreadedProperties.html b/test/fixtures/spreadedProperties.html index 5f62b7b..d920cc3 100644 --- a/test/fixtures/spreadedProperties.html +++ b/test/fixtures/spreadedProperties.html @@ -18,9 +18,12 @@ } } }; + var mixin2 = { + props: ['m2a', 'm2b'] + }; var Parent0 = { - mixins:[mixin0, mixin1], + mixins:[mixin0, mixin1, mixin2], props: { p0: { type: String, @@ -40,10 +43,11 @@ customElements.define('my-element', wrap(Vue, { extends: Parent1, - template: `
{{p0}} {{p1}} {{m0}} {{m1}}
`, + props:['c1', 'c2'], + template: `
{{p0}} {{p1}} {{m0}} {{m1}} {{c1}}{{c2}}{{c3}}{{m2a}} {{m2b}} {{m2c}}
`, })); window.el = document.querySelector('my-element'); - + diff --git a/test/test.js b/test/test.js index 97ed47e..688b406 100644 --- a/test/test.js +++ b/test/test.js @@ -41,12 +41,24 @@ test('spreadedProperties', async () => { const m1 = await page.evaluate(() => el.m1) expect(m1).toBe('m1') + // array props + const c1 = await page.evaluate(() => el.c1) + const c2 = await page.evaluate(() => el.c2) + const m2a = await page.evaluate(() => el.m2a) + const m2b = await page.evaluate(() => el.m2b) + expect(c1).toBe(undefined) + expect(c2).toBe(undefined) + expect(m2a).toBe(undefined) + expect(m2b).toBe(undefined) + // props proxying: set await page.evaluate(() => { el.p0 = 'new-p0' el.p1 = 'new-p1' el.m0 = 'new-m0' el.m1 = 'new-m1' + el.c1 = 'new-c1' + el.m2a = 'new-m2a' }) const newP0 = await page.evaluate(() => el.vueComponent.p0) expect(newP0).toBe('new-p0') @@ -56,6 +68,20 @@ test('spreadedProperties', async () => { expect(newM0).toBe('new-m0') const newM1 = await page.evaluate(() => el.vueComponent.m1) expect(newM1).toBe('new-m1') + const newC1 = await page.evaluate(() => el.vueComponent.c1) + expect(newC1).toBe('new-c1') + const newM2a = await page.evaluate(() => el.vueComponent.m2a) + expect(newM2a).toBe('new-m2a') + + // set via attribute + await page.evaluate(() => { + el.setAttribute('c1', 'foo') + el.setAttribute('m1', 'bar') + el.setAttribute('m2a', 'bla') + }) + expect(await page.evaluate(() => el.c1)).toBe('foo') + expect(await page.evaluate(() => el.m1)).toBe('bar') + expect(await page.evaluate(() => el.m2a)).toBe('bla') }) test('attributes', async () => { From 9c9468b5d21747487b77cd96e5c109911abff960 Mon Sep 17 00:00:00 2001 From: Jiang Li Date: Wed, 9 Jan 2019 15:41:48 +0100 Subject: [PATCH 11/17] new build --- dist/vue-wc-wrapper.global.js | 557 +++++++++++++++++----------------- dist/vue-wc-wrapper.js | 180 +++++------ 2 files changed, 369 insertions(+), 368 deletions(-) diff --git a/dist/vue-wc-wrapper.global.js b/dist/vue-wc-wrapper.global.js index a22b451..ab07537 100644 --- a/dist/vue-wc-wrapper.global.js +++ b/dist/vue-wc-wrapper.global.js @@ -1,319 +1,320 @@ var wrapVueWebComponent = (function () { - 'use strict' - - const camelizeRE = /-(\w)/g - const camelize = str => { - return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') - } - - const hyphenateRE = /\B([A-Z])/g - const hyphenate = str => { - return str.replace(hyphenateRE, '-$1').toLowerCase() - } - - function getInitialProps (propsList) { - const res = {} - propsList.forEach(key => { - res[key] = undefined - }) - return res - } - - function injectHook (options, key, hook) { - options[key] = [].concat(options[key] || []) - options[key].unshift(hook) +'use strict'; + +const camelizeRE = /-(\w)/g; +const camelize = str => { + return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') +}; + +const hyphenateRE = /\B([A-Z])/g; +const hyphenate = str => { + return str.replace(hyphenateRE, '-$1').toLowerCase() +}; + +function getInitialProps (propsList) { + const res = {}; + propsList.forEach(key => { + res[key] = undefined; + }); + return res +} + +function injectHook (options, key, hook) { + options[key] = [].concat(options[key] || []); + options[key].unshift(hook); +} + +function callHooks (vm, hook) { + if (vm) { + const hooks = vm.$options[hook] || []; + hooks.forEach(hook => { + hook.call(vm); + }); } - - function callHooks (vm, hook) { - if (vm) { - const hooks = vm.$options[hook] || [] - hooks.forEach(hook => { - hook.call(vm) - }) +} + +function createCustomEvent (name, args) { + return new CustomEvent(name, { + bubbles: false, + cancelable: false, + detail: args + }) +} + +const isBoolean = val => /function Boolean/.test(String(val)); +const isNumber = val => /function Number/.test(String(val)); + +function convertAttributeValue (value, name, { type } = {}) { + if (isBoolean(type)) { + if (value === 'true' || value === 'false') { + return value === 'true' } + if (value === '' || value === name) { + return true + } + return value != null + } else if (isNumber(type)) { + const parsed = parseFloat(value, 10); + return isNaN(parsed) ? value : parsed + } else { + return value } +} - function createCustomEvent (name, args) { - return new CustomEvent(name, { - bubbles: false, - cancelable: false, - detail: args - }) +function toVNodes (h, children) { + const res = []; + for (let i = 0, l = children.length; i < l; i++) { + res.push(toVNode(h, children[i])); } - - const isBoolean = val => /function Boolean/.test(String(val)) - const isNumber = val => /function Number/.test(String(val)) - - function convertAttributeValue (value, name, { type } = {}) { - if (isBoolean(type)) { - if (value === 'true' || value === 'false') { - return value === 'true' - } - if (value === '' || value === name) { - return true + return res +} + +function toVNode (h, node) { + if (node.nodeType === 3) { + return node.data.trim() ? node.data : null + } else if (node.nodeType === 1) { + const data = { + attrs: getAttributes(node), + domProps: { + innerHTML: node.innerHTML } - return value != null - } else if (isNumber(type)) { - const parsed = parseFloat(value, 10) - return isNaN(parsed) ? value : parsed - } else { - return value + }; + if (data.attrs.slot) { + data.slot = data.attrs.slot; + delete data.attrs.slot; } + return h(node.tagName, data) + } else { + return null } +} - function toVNodes (h, children) { - const res = [] - for (let i = 0, l = children.length; i < l; i++) { - res.push(toVNode(h, children[i])) - } - return res +function getAttributes (node) { + const res = {}; + for (let i = 0, l = node.attributes.length; i < l; i++) { + const attr = node.attributes[i]; + res[attr.nodeName] = attr.nodeValue; } - - function toVNode (h, node) { - if (node.nodeType === 3) { - return node.data.trim() ? node.data : null - } else if (node.nodeType === 1) { - const data = { - attrs: getAttributes(node), - domProps: { - innerHTML: node.innerHTML - } - } - if (data.attrs.slot) { - data.slot = data.attrs.slot - delete data.attrs.slot - } - return h(node.tagName, data) - } else { - return null - } + return res +} + +function spreadProps (component) { + const result = {}; + spreadNext(result, component); + return result +} + +function spreadNext (result, component) { + if (component.props) { + appendProps(result, component.props); } - function getAttributes (node) { - const res = {} - for (let i = 0, l = node.attributes.length; i < l; i++) { - const attr = node.attributes[i] - res[attr.nodeName] = attr.nodeValue - } - return res + if (component.mixins) { + component.mixins.forEach(function (mixin) { + spreadNext(result, mixin); + }); } - - function spreadProps (component) { - const result = {} - spreadNext(result, component) - return result + if (component.extends) { + spreadNext(result, component.extends); } +} - function spreadNext (result, component) { - if (component.props) { - appendProps(result, component.props) - } - - if (component.mixins) { - component.mixins.forEach(function (mixin) { - spreadNext(result, mixin) - }) - } - if (component.extends) { - spreadNext(result, component.extends) - } +function appendProps (result, props) { + if (Array.isArray(props)) { + processArrayProps(result, props); + } else { + processObjectProps(result, props); } +} - function appendProps (result, props) { - if (Array.isArray(props)) { - processArrayProps(result, props) - } else { - processObjectProps(result, props) +function processObjectProps (result, props) { + for (const key in props) { + const camelKey = camelize(key); + if (!(camelKey in result)) { + result[camelKey] = props[key]; } } - - function processObjectProps (result, props) { - for (const key in props) { - const camelKey = camelize(key) +} +function processArrayProps (result, props) { + props.forEach(function (prop) { + if (typeof prop === 'string') { + const camelKey = camelize(prop); if (!(camelKey in result)) { - result[camelKey] = props[key] + result[camelKey] = undefined; } } - } - function processArrayProps (result, props) { - props.forEach(function (prop) { - if (typeof prop === 'string') { - const camelKey = camelize(prop) - if (!(camelKey in result)) { - result[camelKey] = undefined - } - } - }) - } - - function wrap (Vue, Component) { - const isAsync = typeof Component === 'function' && !Component.cid - let isInitialized = false - let hyphenatedPropsList - let camelizedPropsList - let camelizedPropsMap - - function initialize (Component) { - if (isInitialized) return - - const options = typeof Component === 'function' - ? Component.options - : Component - - // spread props - options.props = spreadProps(options) - // extract props info - const propsList = Object.keys(options.props || {}) - hyphenatedPropsList = propsList.map(hyphenate) - camelizedPropsList = propsList.map(camelize) - const originalPropsAsObject = options.props || {} - camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => { - map[key] = originalPropsAsObject[propsList[i]] - return map - }, {}) - - // proxy $emit to native DOM events - injectHook(options, 'beforeCreate', function () { - const emit = this.$emit - this.$emit = (name, ...args) => { - this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args)) - return emit.call(this, name, ...args) - } - }) - - injectHook(options, 'created', function () { + }); +} + +function wrap (Vue, Component) { + const isAsync = typeof Component === 'function' && !Component.cid; + let isInitialized = false; + let hyphenatedPropsList; + let camelizedPropsList; + let camelizedPropsMap; + + function initialize (Component) { + if (isInitialized) return + + const options = typeof Component === 'function' + ? Component.options + : Component; + + // spread props + options.props = spreadProps(options); + // extract props info + const propsList = Object.keys(options.props || {}); + hyphenatedPropsList = propsList.map(hyphenate); + camelizedPropsList = propsList.map(camelize); + const originalPropsAsObject = options.props || {}; + camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => { + map[key] = originalPropsAsObject[propsList[i]]; + return map + }, {}); + + // proxy $emit to native DOM events + injectHook(options, 'beforeCreate', function () { + const emit = this.$emit; + this.$emit = (name, ...args) => { + this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args)); + return emit.call(this, name, ...args) + }; + }); + + injectHook(options, 'created', function () { // sync default props values to wrapper on created - camelizedPropsList.forEach(key => { - this.$root.props[key] = this[key] - }) - }) - - // proxy props as Element properties camelizedPropsList.forEach(key => { - Object.defineProperty(CustomElement.prototype, key, { - get () { - return this._wrapper.props[key] - }, - set (newVal) { - this._wrapper.props[key] = newVal - }, - enumerable: false, - configurable: true - }) - }) - - isInitialized = true - } - - function syncAttribute (el, key) { - const camelized = camelize(key) - const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined - el._wrapper.props[camelized] = convertAttributeValue( - value, - key, - camelizedPropsMap[camelized] - ) - } - - class CustomElement extends HTMLElement { - constructor () { - const self = super() - self.attachShadow({ mode: 'open' }) - - const wrapper = self._wrapper = new Vue({ - name: 'shadow-root', - customElement: self, - shadowRoot: self.shadowRoot, - data () { - return { - props: {}, - slotChildren: [] - } - }, - render (h) { - return h(Component, { - ref: 'inner', - props: this.props - }, this.slotChildren) - } - }) - - // Use MutationObserver to react to future attribute & slot content change - const observer = new MutationObserver(mutations => { - let hasChildrenChange = false - for (let i = 0; i < mutations.length; i++) { - const m = mutations[i] - if (isInitialized && m.type === 'attributes' && m.target === self) { - syncAttribute(self, m.attributeName) - } else { - hasChildrenChange = true - } - } - if (hasChildrenChange) { - wrapper.slotChildren = Object.freeze(toVNodes( - wrapper.$createElement, - self.childNodes - )) - } - }) - observer.observe(self, { - childList: true, - subtree: true, - characterData: true, - attributes: true - }) - } + this.$root.props[key] = this[key]; + }); + }); + + // proxy props as Element properties + camelizedPropsList.forEach(key => { + Object.defineProperty(CustomElement.prototype, key, { + get () { + return this._wrapper.props[key] + }, + set (newVal) { + this._wrapper.props[key] = newVal; + }, + enumerable: false, + configurable: true + }); + }); + + isInitialized = true; + } - get vueComponent () { - return this._wrapper.$refs.inner - } + function syncAttribute (el, key) { + const camelized = camelize(key); + const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined; + el._wrapper.props[camelized] = convertAttributeValue( + value, + key, + camelizedPropsMap[camelized] + ); + } - connectedCallback () { - const wrapper = this._wrapper - if (!wrapper._isMounted) { - // initialize attributes - const syncInitialAttributes = () => { - wrapper.props = getInitialProps(camelizedPropsList) - hyphenatedPropsList.forEach(key => { - syncAttribute(this, key) - }) + class CustomElement extends HTMLElement { + constructor () { + const self = super(); + self.attachShadow({ mode: 'open' }); + + const wrapper = self._wrapper = new Vue({ + name: 'shadow-root', + customElement: self, + shadowRoot: self.shadowRoot, + data () { + return { + props: {}, + slotChildren: [] } - - if (isInitialized) { - syncInitialAttributes() + }, + render (h) { + return h(Component, { + ref: 'inner', + props: this.props + }, this.slotChildren) + } + }); + + // Use MutationObserver to react to future attribute & slot content change + const observer = new MutationObserver(mutations => { + let hasChildrenChange = false; + for (let i = 0; i < mutations.length; i++) { + const m = mutations[i]; + if (isInitialized && m.type === 'attributes' && m.target === self) { + syncAttribute(self, m.attributeName); } else { - // async & unresolved - Component().then(resolved => { - if (resolved.__esModule || resolved[Symbol.toStringTag] === 'Module') { - resolved = resolved.default - } - initialize(resolved) - syncInitialAttributes() - }) + hasChildrenChange = true; } - // initialize children + } + if (hasChildrenChange) { wrapper.slotChildren = Object.freeze(toVNodes( wrapper.$createElement, - this.childNodes - )) - wrapper.$mount() - this.shadowRoot.appendChild(wrapper.$el) - } else { - callHooks(this.vueComponent, 'activated') + self.childNodes + )); } - } + }); + observer.observe(self, { + childList: true, + subtree: true, + characterData: true, + attributes: true + }); + } + + get vueComponent () { + return this._wrapper.$refs.inner + } - disconnectedCallback () { - callHooks(this.vueComponent, 'deactivated') + connectedCallback () { + const wrapper = this._wrapper; + if (!wrapper._isMounted) { + // initialize attributes + const syncInitialAttributes = () => { + wrapper.props = getInitialProps(camelizedPropsList); + hyphenatedPropsList.forEach(key => { + syncAttribute(this, key); + }); + }; + + if (isInitialized) { + syncInitialAttributes(); + } else { + // async & unresolved + Component().then(resolved => { + if (resolved.__esModule || resolved[Symbol.toStringTag] === 'Module') { + resolved = resolved.default; + } + initialize(resolved); + syncInitialAttributes(); + }); + } + // initialize children + wrapper.slotChildren = Object.freeze(toVNodes( + wrapper.$createElement, + this.childNodes + )); + wrapper.$mount(); + this.shadowRoot.appendChild(wrapper.$el); + } else { + callHooks(this.vueComponent, 'activated'); } } - if (!isAsync) { - initialize(Component) + disconnectedCallback () { + callHooks(this.vueComponent, 'deactivated'); } + } - return CustomElement + if (!isAsync) { + initialize(Component); } - return wrap -}()) + return CustomElement +} + +return wrap; + +}()); diff --git a/dist/vue-wc-wrapper.js b/dist/vue-wc-wrapper.js index d062ab8..ba33aef 100644 --- a/dist/vue-wc-wrapper.js +++ b/dist/vue-wc-wrapper.js @@ -1,32 +1,32 @@ -const camelizeRE = /-(\w)/g +const camelizeRE = /-(\w)/g; const camelize = str => { return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') -} +}; -const hyphenateRE = /\B([A-Z])/g +const hyphenateRE = /\B([A-Z])/g; const hyphenate = str => { return str.replace(hyphenateRE, '-$1').toLowerCase() -} +}; function getInitialProps (propsList) { - const res = {} + const res = {}; propsList.forEach(key => { - res[key] = undefined - }) + res[key] = undefined; + }); return res } function injectHook (options, key, hook) { - options[key] = [].concat(options[key] || []) - options[key].unshift(hook) + options[key] = [].concat(options[key] || []); + options[key].unshift(hook); } function callHooks (vm, hook) { if (vm) { - const hooks = vm.$options[hook] || [] + const hooks = vm.$options[hook] || []; hooks.forEach(hook => { - hook.call(vm) - }) + hook.call(vm); + }); } } @@ -38,8 +38,8 @@ function createCustomEvent (name, args) { }) } -const isBoolean = val => /function Boolean/.test(String(val)) -const isNumber = val => /function Number/.test(String(val)) +const isBoolean = val => /function Boolean/.test(String(val)); +const isNumber = val => /function Number/.test(String(val)); function convertAttributeValue (value, name, { type } = {}) { if (isBoolean(type)) { @@ -51,7 +51,7 @@ function convertAttributeValue (value, name, { type } = {}) { } return value != null } else if (isNumber(type)) { - const parsed = parseFloat(value, 10) + const parsed = parseFloat(value, 10); return isNaN(parsed) ? value : parsed } else { return value @@ -59,9 +59,9 @@ function convertAttributeValue (value, name, { type } = {}) { } function toVNodes (h, children) { - const res = [] + const res = []; for (let i = 0, l = children.length; i < l; i++) { - res.push(toVNode(h, children[i])) + res.push(toVNode(h, children[i])); } return res } @@ -75,10 +75,10 @@ function toVNode (h, node) { domProps: { innerHTML: node.innerHTML } - } + }; if (data.attrs.slot) { - data.slot = data.attrs.slot - delete data.attrs.slot + data.slot = data.attrs.slot; + delete data.attrs.slot; } return h(node.tagName, data) } else { @@ -87,103 +87,103 @@ function toVNode (h, node) { } function getAttributes (node) { - const res = {} + const res = {}; for (let i = 0, l = node.attributes.length; i < l; i++) { - const attr = node.attributes[i] - res[attr.nodeName] = attr.nodeValue + const attr = node.attributes[i]; + res[attr.nodeName] = attr.nodeValue; } return res } function spreadProps (component) { - const result = {} - spreadNext(result, component) + const result = {}; + spreadNext(result, component); return result } function spreadNext (result, component) { if (component.props) { - appendProps(result, component.props) + appendProps(result, component.props); } if (component.mixins) { component.mixins.forEach(function (mixin) { - spreadNext(result, mixin) - }) + spreadNext(result, mixin); + }); } if (component.extends) { - spreadNext(result, component.extends) + spreadNext(result, component.extends); } } function appendProps (result, props) { if (Array.isArray(props)) { - processArrayProps(result, props) + processArrayProps(result, props); } else { - processObjectProps(result, props) + processObjectProps(result, props); } } function processObjectProps (result, props) { for (const key in props) { - const camelKey = camelize(key) + const camelKey = camelize(key); if (!(camelKey in result)) { - result[camelKey] = props[key] + result[camelKey] = props[key]; } } } function processArrayProps (result, props) { props.forEach(function (prop) { if (typeof prop === 'string') { - const camelKey = camelize(prop) + const camelKey = camelize(prop); if (!(camelKey in result)) { - result[camelKey] = undefined + result[camelKey] = undefined; } } - }) + }); } function wrap (Vue, Component) { - const isAsync = typeof Component === 'function' && !Component.cid - let isInitialized = false - let hyphenatedPropsList - let camelizedPropsList - let camelizedPropsMap + const isAsync = typeof Component === 'function' && !Component.cid; + let isInitialized = false; + let hyphenatedPropsList; + let camelizedPropsList; + let camelizedPropsMap; function initialize (Component) { if (isInitialized) return const options = typeof Component === 'function' ? Component.options - : Component + : Component; // spread props - options.props = spreadProps(options) + options.props = spreadProps(options); // extract props info - const propsList = Object.keys(options.props || {}) - hyphenatedPropsList = propsList.map(hyphenate) - camelizedPropsList = propsList.map(camelize) - const originalPropsAsObject = options.props || {} + const propsList = Object.keys(options.props || {}); + hyphenatedPropsList = propsList.map(hyphenate); + camelizedPropsList = propsList.map(camelize); + const originalPropsAsObject = options.props || {}; camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => { - map[key] = originalPropsAsObject[propsList[i]] + map[key] = originalPropsAsObject[propsList[i]]; return map - }, {}) + }, {}); // proxy $emit to native DOM events injectHook(options, 'beforeCreate', function () { - const emit = this.$emit + const emit = this.$emit; this.$emit = (name, ...args) => { - this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args)) + this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args)); return emit.call(this, name, ...args) - } - }) + }; + }); injectHook(options, 'created', function () { // sync default props values to wrapper on created camelizedPropsList.forEach(key => { - this.$root.props[key] = this[key] - }) - }) + this.$root.props[key] = this[key]; + }); + }); // proxy props as Element properties camelizedPropsList.forEach(key => { @@ -192,30 +192,30 @@ function wrap (Vue, Component) { return this._wrapper.props[key] }, set (newVal) { - this._wrapper.props[key] = newVal + this._wrapper.props[key] = newVal; }, enumerable: false, configurable: true - }) - }) + }); + }); - isInitialized = true + isInitialized = true; } function syncAttribute (el, key) { - const camelized = camelize(key) - const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined + const camelized = camelize(key); + const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined; el._wrapper.props[camelized] = convertAttributeValue( value, key, camelizedPropsMap[camelized] - ) + ); } class CustomElement extends HTMLElement { constructor () { - const self = super() - self.attachShadow({ mode: 'open' }) + const self = super(); + self.attachShadow({ mode: 'open' }); const wrapper = self._wrapper = new Vue({ name: 'shadow-root', @@ -233,32 +233,32 @@ function wrap (Vue, Component) { props: this.props }, this.slotChildren) } - }) + }); // Use MutationObserver to react to future attribute & slot content change const observer = new MutationObserver(mutations => { - let hasChildrenChange = false + let hasChildrenChange = false; for (let i = 0; i < mutations.length; i++) { - const m = mutations[i] + const m = mutations[i]; if (isInitialized && m.type === 'attributes' && m.target === self) { - syncAttribute(self, m.attributeName) + syncAttribute(self, m.attributeName); } else { - hasChildrenChange = true + hasChildrenChange = true; } } if (hasChildrenChange) { wrapper.slotChildren = Object.freeze(toVNodes( wrapper.$createElement, self.childNodes - )) + )); } - }) + }); observer.observe(self, { childList: true, subtree: true, characterData: true, attributes: true - }) + }); } get vueComponent () { @@ -266,50 +266,50 @@ function wrap (Vue, Component) { } connectedCallback () { - const wrapper = this._wrapper + const wrapper = this._wrapper; if (!wrapper._isMounted) { // initialize attributes const syncInitialAttributes = () => { - wrapper.props = getInitialProps(camelizedPropsList) + wrapper.props = getInitialProps(camelizedPropsList); hyphenatedPropsList.forEach(key => { - syncAttribute(this, key) - }) - } + syncAttribute(this, key); + }); + }; if (isInitialized) { - syncInitialAttributes() + syncInitialAttributes(); } else { // async & unresolved Component().then(resolved => { if (resolved.__esModule || resolved[Symbol.toStringTag] === 'Module') { - resolved = resolved.default + resolved = resolved.default; } - initialize(resolved) - syncInitialAttributes() - }) + initialize(resolved); + syncInitialAttributes(); + }); } // initialize children wrapper.slotChildren = Object.freeze(toVNodes( wrapper.$createElement, this.childNodes - )) - wrapper.$mount() - this.shadowRoot.appendChild(wrapper.$el) + )); + wrapper.$mount(); + this.shadowRoot.appendChild(wrapper.$el); } else { - callHooks(this.vueComponent, 'activated') + callHooks(this.vueComponent, 'activated'); } } disconnectedCallback () { - callHooks(this.vueComponent, 'deactivated') + callHooks(this.vueComponent, 'deactivated'); } } if (!isAsync) { - initialize(Component) + initialize(Component); } return CustomElement } -export default wrap +export default wrap; From 1d4f88467468c15ac4892748af0b36430eb0b61b Mon Sep 17 00:00:00 2001 From: Jiang Li Date: Wed, 9 Jan 2019 15:46:50 +0100 Subject: [PATCH 12/17] set version 1.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f7ec743..831c8ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vue/web-component-wrapper", - "version": "1.2.0", + "version": "1.2.1", "description": "wrap a vue component as a web component.", "main": "dist/vue-wc-wrapper.js", "unpkg": "dist/vue-wc-wrapper.global.js", From 0c1687607008926b343d36501cf663549fcfeec8 Mon Sep 17 00:00:00 2001 From: Jiang Li Date: Wed, 9 Jan 2019 15:54:17 +0100 Subject: [PATCH 13/17] set version 1.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 831c8ed..6abe05e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vue/web-component-wrapper", - "version": "1.2.1", + "version": "1.3.0", "description": "wrap a vue component as a web component.", "main": "dist/vue-wc-wrapper.js", "unpkg": "dist/vue-wc-wrapper.global.js", From 2c03372b1f719c88ef37b11d560ca4ba34ecce63 Mon Sep 17 00:00:00 2001 From: Jiang Li Date: Wed, 9 Jan 2019 16:02:11 +0100 Subject: [PATCH 14/17] clean test --- test/fixtures/spreadedProperties.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fixtures/spreadedProperties.html b/test/fixtures/spreadedProperties.html index d920cc3..8b60b0c 100644 --- a/test/fixtures/spreadedProperties.html +++ b/test/fixtures/spreadedProperties.html @@ -44,7 +44,7 @@ customElements.define('my-element', wrap(Vue, { extends: Parent1, props:['c1', 'c2'], - template: `
{{p0}} {{p1}} {{m0}} {{m1}} {{c1}}{{c2}}{{c3}}{{m2a}} {{m2b}} {{m2c}}
`, + template: `
{{p0}} {{p1}} {{m0}} {{m1}} {{c1}}{{c2}}{{m2a}} {{m2b}}
`, })); window.el = document.querySelector('my-element'); From 87fa7d98c6851245980682188b8a783064c71be3 Mon Sep 17 00:00:00 2001 From: Jiang Li Date: Wed, 9 Jan 2019 16:11:28 +0100 Subject: [PATCH 15/17] test array props in inheritance --- test/fixtures/spreadedProperties.html | 7 +------ test/test.js | 4 +++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/test/fixtures/spreadedProperties.html b/test/fixtures/spreadedProperties.html index 8b60b0c..e2d500f 100644 --- a/test/fixtures/spreadedProperties.html +++ b/test/fixtures/spreadedProperties.html @@ -24,12 +24,7 @@ var Parent0 = { mixins:[mixin0, mixin1, mixin2], - props: { - p0: { - type: String, - default: 'p0' - } - } + props: ['p0'] }; var Parent1 = { extends: Parent0, diff --git a/test/test.js b/test/test.js index 688b406..665d0fd 100644 --- a/test/test.js +++ b/test/test.js @@ -27,7 +27,7 @@ test('spreadedProperties', async () => { // props from 'extends' const p0 = await page.evaluate(() => el.p0) - expect(p0).toBe('p0') + expect(p0).toBe(undefined) // props from 'extends' const p1 = await page.evaluate(() => el.p1) @@ -76,10 +76,12 @@ test('spreadedProperties', async () => { // set via attribute await page.evaluate(() => { el.setAttribute('c1', 'foo') + el.setAttribute('p1', 'foo2') el.setAttribute('m1', 'bar') el.setAttribute('m2a', 'bla') }) expect(await page.evaluate(() => el.c1)).toBe('foo') + expect(await page.evaluate(() => el.p1)).toBe('foo2') expect(await page.evaluate(() => el.m1)).toBe('bar') expect(await page.evaluate(() => el.m2a)).toBe('bla') }) From 2c6a4f1efa1a6d6ac5f0dbc17268e80b1f59a9ea Mon Sep 17 00:00:00 2001 From: jili Date: Thu, 28 Feb 2019 11:42:19 +0100 Subject: [PATCH 16/17] add readme for abas --- abas/README2.md | 16 ++++++++++++++++ abas/WcIconMixin.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 abas/README2.md create mode 100644 abas/WcIconMixin.js diff --git a/abas/README2.md b/abas/README2.md new file mode 100644 index 0000000..37079e2 --- /dev/null +++ b/abas/README2.md @@ -0,0 +1,16 @@ +# This readme is only for abas + +1. Checkout this wrapper repo into the same level of the project what you want to convert. e.g. abas-elements +2. To fix the icons styles + Put 'WcIconMixin.js' file into abas-elements/src/mixins +3. Adjust 'vue.config.js' in abas-elements project by adding alias for the '@vue/web-component-wrapper' + + ''' + configureWebpack: { + resolve: { + alias: { + '@vue/web-component-wrapper': path.join(__dirname, '../vue-web-component-wrapper'), + }, + }, + }, + ''' \ No newline at end of file diff --git a/abas/WcIconMixin.js b/abas/WcIconMixin.js new file mode 100644 index 0000000..fc720dc --- /dev/null +++ b/abas/WcIconMixin.js @@ -0,0 +1,28 @@ +import { dom } from '@fortawesome/fontawesome-svg-core' + +/** + * @mixin + */ +export default { + mounted () { + const id = 'fa-styles' + const shadowRoot = this.getShadowRoot(this) + if (shadowRoot && !shadowRoot.getElementById(id)) { + const faStyles = document.createElement('style') + faStyles.setAttribute('id', id) + faStyles.textContent = dom.css() + shadowRoot.appendChild(faStyles) + } + }, + methods: { + getShadowRoot (obj) { + if (obj.$parent) { + if (obj.$parent.$options && obj.$parent.$options.shadowRoot) { + return obj.$parent.$options.shadowRoot + } + return this.getShadowRoot(obj.$parent) + } + return null + } + } +} From e5fa6abd38b4a05be9b0e952f1e3337fefc4b170 Mon Sep 17 00:00:00 2001 From: jili Date: Thu, 28 Feb 2019 12:23:39 +0100 Subject: [PATCH 17/17] update readme --- abas/README2.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/abas/README2.md b/abas/README2.md index 37079e2..3cbd50c 100644 --- a/abas/README2.md +++ b/abas/README2.md @@ -3,6 +3,8 @@ 1. Checkout this wrapper repo into the same level of the project what you want to convert. e.g. abas-elements 2. To fix the icons styles Put 'WcIconMixin.js' file into abas-elements/src/mixins + Add 'import WcIconMixin from '../mixins/WcIconMixin';' in Icon.vue + Add mixin 'mixins: [WcIconMixin],' in Icon.vue 3. Adjust 'vue.config.js' in abas-elements project by adding alias for the '@vue/web-component-wrapper' ''' @@ -13,4 +15,8 @@ }, }, }, - ''' \ No newline at end of file + ''' +4. add dependency into package.json in block 'devDependencies' + ''' + "eslint-plugin-vue-libs": "^2.1.0" + ''' pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy