diff --git a/packages/weex-vue-framework/factory.js b/packages/weex-vue-framework/factory.js index 8bcfb675ba9..fb30d12bbbd 100644 --- a/packages/weex-vue-framework/factory.js +++ b/packages/weex-vue-framework/factory.js @@ -5797,7 +5797,7 @@ function createPatchFunction (backend) { } } - function removeVnodes (parentElm, vnodes, startIdx, endIdx) { + function removeVnodes (vnodes, startIdx, endIdx) { for (; startIdx <= endIdx; ++startIdx) { var ch = vnodes[startIdx]; if (isDef(ch)) { @@ -5908,7 +5908,7 @@ function createPatchFunction (backend) { refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm; addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue); } else if (newStartIdx > newEndIdx) { - removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); + removeVnodes(oldCh, oldStartIdx, oldEndIdx); } } @@ -5985,7 +5985,7 @@ function createPatchFunction (backend) { if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); } addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue); } else if (isDef(oldCh)) { - removeVnodes(elm, oldCh, 0, oldCh.length - 1); + removeVnodes(oldCh, 0, oldCh.length - 1); } else if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); } @@ -6216,7 +6216,7 @@ function createPatchFunction (backend) { // destroy old node if (isDef(parentElm$1)) { - removeVnodes(parentElm$1, [oldVnode], 0, 0); + removeVnodes([oldVnode], 0, 0); } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode); } diff --git a/src/compiler/error-detector.js b/src/compiler/error-detector.js index 22c3b75d3aa..9893aa4a47a 100644 --- a/src/compiler/error-detector.js +++ b/src/compiler/error-detector.js @@ -36,6 +36,8 @@ function checkNode (node: ASTNode, warn: Function) { const range = node.rawAttrsMap[name] if (name === 'v-for') { checkFor(node, `v-for="${value}"`, warn, range) + } else if (name === 'v-slot' || name[0] === '#') { + checkFunctionParameterExpression(value, `${name}="${value}"`, warn, range) } else if (onRE.test(name)) { checkEvent(value, `${name}="${value}"`, warn, range) } else { @@ -111,3 +113,16 @@ function checkExpression (exp: string, text: string, warn: Function, range?: Ran } } } + +function checkFunctionParameterExpression (exp: string, text: string, warn: Function, range?: Range) { + try { + new Function(exp, '') + } catch (e) { + warn( + `invalid function parameter expression: ${e.message} in\n\n` + + ` ${exp}\n\n` + + ` Raw expression: ${text.trim()}\n`, + range + ) + } +} diff --git a/src/compiler/parser/index.js b/src/compiler/parser/index.js index fb258752179..cdeb257eda4 100644 --- a/src/compiler/parser/index.js +++ b/src/compiler/parser/index.js @@ -23,8 +23,8 @@ import { export const onRE = /^@|^v-on:/ export const dirRE = process.env.VBIND_PROP_SHORTHAND - ? /^v-|^@|^:|^\./ - : /^v-|^@|^:/ + ? /^v-|^@|^:|^\.|^#/ + : /^v-|^@|^:|^#/ export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/ export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/ const stripParensRE = /^\(|\)$/g diff --git a/src/core/util/env.js b/src/core/util/env.js index eff1d21dc75..820bacdb405 100644 --- a/src/core/util/env.js +++ b/src/core/util/env.js @@ -87,11 +87,10 @@ if (typeof Set !== 'undefined' && isNative(Set)) { } } -interface SimpleSet { +export interface SimpleSet { has(key: string | number): boolean; add(key: string | number): mixed; clear(): void; } export { _Set } -export type { SimpleSet } diff --git a/src/core/vdom/create-element.js b/src/core/vdom/create-element.js index 46027084b51..ba36e15b12d 100644 --- a/src/core/vdom/create-element.js +++ b/src/core/vdom/create-element.js @@ -98,6 +98,12 @@ export function _createElement ( ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) if (config.isReservedTag(tag)) { // platform built-in elements + if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) { + warn( + `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`, + context + ) + } vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context diff --git a/src/core/vdom/patch.js b/src/core/vdom/patch.js index 9746bb794f8..95a4db47298 100644 --- a/src/core/vdom/patch.js +++ b/src/core/vdom/patch.js @@ -16,6 +16,7 @@ import { SSR_ATTR } from 'shared/constants' import { registerRef } from './modules/ref' import { traverse } from '../observer/traverse' import { activeInstance } from '../instance/lifecycle' +import { inBrowser } from '../util/index'; import { isTextInputType } from 'web/util/element' import { @@ -32,6 +33,8 @@ export const emptyNode = new VNode('', {}, []) const hooks = ['create', 'activate', 'update', 'remove', 'destroy'] +const customElements = inBrowser ? window.customElements : undefined + function sameVnode (a, b) { return ( a.key === b.key && ( @@ -106,6 +109,7 @@ export function createPatchFunction (backend) { function isUnknownElement (vnode, inVPre) { return ( + !(customElements && customElements.get(vnode.tag)) && !inVPre && !vnode.ns && !( @@ -358,7 +362,7 @@ export function createPatchFunction (backend) { } } - function removeVnodes (parentElm, vnodes, startIdx, endIdx) { + function removeVnodes (vnodes, startIdx, endIdx) { for (; startIdx <= endIdx; ++startIdx) { const ch = vnodes[startIdx] if (isDef(ch)) { @@ -469,7 +473,7 @@ export function createPatchFunction (backend) { refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) } else if (newStartIdx > newEndIdx) { - removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx) + removeVnodes(oldCh, oldStartIdx, oldEndIdx) } } @@ -561,7 +565,7 @@ export function createPatchFunction (backend) { if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) } else if (isDef(oldCh)) { - removeVnodes(elm, oldCh, 0, oldCh.length - 1) + removeVnodes(oldCh, 0, oldCh.length - 1) } else if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, '') } @@ -790,7 +794,7 @@ export function createPatchFunction (backend) { // destroy old node if (isDef(parentElm)) { - removeVnodes(parentElm, [oldVnode], 0, 0) + removeVnodes([oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode) } diff --git a/test/e2e/nightwatch.config.js b/test/e2e/nightwatch.config.js index 2004f92b45b..8ec592494fe 100644 --- a/test/e2e/nightwatch.config.js +++ b/test/e2e/nightwatch.config.js @@ -50,7 +50,8 @@ module.exports = { 'desiredCapabilities': { 'browserName': 'phantomjs', 'javascriptEnabled': true, - 'acceptSslCerts': true + 'acceptSslCerts': true, + 'phantomjs.binary.path': require('phantomjs-prebuilt').path } } } diff --git a/test/unit/features/component/component-scoped-slot.spec.js b/test/unit/features/component/component-scoped-slot.spec.js index f8a0f11b7fa..28369814f48 100644 --- a/test/unit/features/component/component-scoped-slot.spec.js +++ b/test/unit/features/component/component-scoped-slot.spec.js @@ -759,6 +759,22 @@ describe('Component scoped slot', () => { }).$mount() expect(`Unexpected mixed usage of different slot syntaxes`).toHaveBeenWarned() }) + + it('should warn invalid parameter expression', () => { + new Vue({ + template: `<foo ${syntax}="1"></foo>`, + components: { Foo } + }).$mount(); + expect('invalid function parameter expression').toHaveBeenWarned() + }) + + it('should allow destructuring props with default value', () => { + new Vue({ + template: `<foo ${syntax}="{ foo = { bar: '1' } }"></foo>`, + components: { Foo } + }).$mount(); + expect('invalid function parameter expression').not.toHaveBeenWarned() + }) } // run tests for both full syntax and shorthand diff --git a/test/unit/features/directives/on.spec.js b/test/unit/features/directives/on.spec.js index a97ddaa8947..b7801a82f22 100644 --- a/test/unit/features/directives/on.spec.js +++ b/test/unit/features/directives/on.spec.js @@ -460,6 +460,20 @@ describe('Directive v-on', () => { expect(spy).toHaveBeenCalled() }) + it('should throw a warning if native modifier is used on native HTML element', () => { + vm = new Vue({ + el, + template: ` + <button @click.native="foo"></button> + `, + methods: { foo: spy }, + }) + + triggerEvent(vm.$el, 'click') + expect(`The .native modifier for v-on is only valid on components but it was used on <button>.`).toHaveBeenWarned() + expect(spy.calls.count()).toBe(0) + }) + it('.once modifier should work with child components', () => { vm = new Vue({ el, diff --git a/types/index.d.ts b/types/index.d.ts index b0e24d767e9..58ceb209e5d 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,9 +1,8 @@ import { Vue } from "./vue"; +import "./umd"; export default Vue; -export as namespace Vue; - export { CreateElement, VueConstructor diff --git a/types/test/options-test.ts b/types/test/options-test.ts index b8bdc098312..28060b7c817 100644 --- a/types/test/options-test.ts +++ b/types/test/options-test.ts @@ -76,10 +76,6 @@ Vue.component('union-prop', { complexUnion: { type: [User, Number] as PropType<User | number> }, kittyUser: Object as PropType<ICat & IUser>, callback: Function as PropType<ConfirmCallback>, - mixed: [RegExp, Array], - object: [Cat, User], - primitive: [String, Number], - regex: RegExp, union: [User, Number] as PropType<User | number> }, data() { @@ -87,10 +83,6 @@ Vue.component('union-prop', { this.complexUnion; this.kittyUser; this.callback(true); - this.mixed; - this.object; - this.primitive; - this.regex.compile; this.union; return { fixedSize: this.union, @@ -98,6 +90,22 @@ Vue.component('union-prop', { } }); +// stopped working since TS 3.4 +// Vue.component('union-prop-with-no-casting', { +// props: { +// mixed: [RegExp, Array], +// object: [Cat, User], +// primitive: [String, Number], +// regex: RegExp +// }, +// data() { +// this.mixed; +// this.object; +// this.primitive; +// this.regex.compile; +// } +// }) + Vue.component('prop-with-primitive-default', { props: { id: { diff --git a/types/test/tsconfig.json b/types/test/tsconfig.json index b816ce07cdc..dab30a608bf 100644 --- a/types/test/tsconfig.json +++ b/types/test/tsconfig.json @@ -14,17 +14,9 @@ "vue": ["../index.d.ts"] } }, - "files": [ - "../index.d.ts", - "../options.d.ts", - "../plugin.d.ts", - "../vnode.d.ts", - "../vue.d.ts", - "options-test.ts", - "plugin-test.ts", - "vue-test.ts", - "augmentation-test.ts", - "ssr-test.ts" + "include": [ + "../*.d.ts", + "*.ts" ], "compileOnSave": false } diff --git a/types/test/umd-test.ts b/types/test/umd-test.ts new file mode 100644 index 00000000000..ffdc7046f92 --- /dev/null +++ b/types/test/umd-test.ts @@ -0,0 +1,7 @@ +const vm = new Vue({ + template: "<div>hi</div>" +}); + +const options: Vue.ComponentOptions<Vue> = { + template: "<div>test</div>" +}; diff --git a/types/umd.d.ts b/types/umd.d.ts new file mode 100644 index 00000000000..618d484e48d --- /dev/null +++ b/types/umd.d.ts @@ -0,0 +1,48 @@ +import * as V from "./index"; +import { + DefaultData, + DefaultProps, + DefaultMethods, + DefaultComputed, + PropsDefinition +} from "./options"; + +// Expose some types for backword compatibility... +declare namespace Vue { + // vue.d.ts + export type CreateElement = V.CreateElement; + export type VueConstructor<V extends Vue = Vue> = V.VueConstructor<V>; + + // options.d.ts + export type Component<Data=DefaultData<never>, Methods=DefaultMethods<never>, Computed=DefaultComputed, Props=DefaultProps> = V.Component<Data, Methods, Computed, Props>; + export type AsyncComponent<Data=DefaultData<never>, Methods=DefaultMethods<never>, Computed=DefaultComputed, Props=DefaultProps> = V.AsyncComponent<Data, Methods, Computed, Props>; + export type ComponentOptions<V extends Vue, Data=DefaultData<V>, Methods=DefaultMethods<V>, Computed=DefaultComputed, PropsDef=PropsDefinition<DefaultProps>, Props=DefaultProps> = V.ComponentOptions<V, Data, Methods, Computed, PropsDef, Props>; + export type FunctionalComponentOptions<Props = DefaultProps, PropDefs = PropsDefinition<Props>> = V.FunctionalComponentOptions<Props, PropDefs>; + export type RenderContext<Props=DefaultProps> = V.RenderContext<Props>; + export type PropType<T> = V.PropType<T>; + export type PropOptions<T=any> = V.PropOptions<T>; + export type ComputedOptions<T> = V.ComputedOptions<T>; + export type WatchHandler<T> = V.WatchHandler<T>; + export type WatchOptions = V.WatchOptions; + export type WatchOptionsWithHandler<T> = V.WatchOptionsWithHandler<T>; + export type DirectiveFunction = V.DirectiveFunction; + export type DirectiveOptions = V.DirectiveOptions; + + // plugin.d.ts + export type PluginFunction<T> = V.PluginFunction<T>; + export type PluginObject<T> = V.PluginObject<T>; + + // vnode.d.ts + export type VNodeChildren = V.VNodeChildren; + export type VNodeChildrenArrayContents = V.VNodeChildrenArrayContents; + export type VNode = V.VNode; + export type VNodeComponentOptions = V.VNodeComponentOptions; + export type VNodeData = V.VNodeData; + export type VNodeDirective = V.VNodeDirective; +} + +declare class Vue extends V.default {} + +export = Vue; + +export as namespace Vue;