diff --git a/flow/compiler.js b/flow/compiler.js index a3dc1c920fd..d3c49f6b51a 100644 --- a/flow/compiler.js +++ b/flow/compiler.js @@ -114,6 +114,9 @@ declare type ASTElement = { else?: true; ifConditions?: ASTIfConditions; + locals?: Array<{ alias: string; value: string }>; + localsProcessed?: boolean; + for?: string; forProcessed?: boolean; key?: string; diff --git a/src/compiler/codegen/index.js b/src/compiler/codegen/index.js index 1c912c4d96f..1268e10f223 100644 --- a/src/compiler/codegen/index.js +++ b/src/compiler/codegen/index.js @@ -56,6 +56,8 @@ export function genElement (el: ASTElement, state: CodegenState): string { return genOnce(el, state) } else if (el.for && !el.forProcessed) { return genFor(el, state) + } else if (el.locals && !el.localsProcessed) { + return genLocalVariable(el, state) } else if (el.if && !el.ifProcessed) { return genIf(el, state) } else if (el.tag === 'template' && !el.slotTarget) { @@ -164,6 +166,20 @@ function genIfConditions ( } } +function genLocalVariable (el: ASTElement, state: CodegenState): string { + const locals = el.locals || [] + const localNames: Array = [] + const localValues: Array = [] + for (let i = 0, l = locals.length; i < l; ++i) { + localNames.push(locals[i].alias) + localValues.push(locals[i].value) + } + el.localsProcessed = true + return `((function(${localNames.join(',')}){` + + `return ${genElement(el, state)}` + + `})(${localValues.join(',')}))` +} + export function genFor ( el: any, state: CodegenState, @@ -351,7 +367,9 @@ function genScopedSlot ( `return ${el.tag === 'template' ? el.if ? `${el.if}?${genChildren(el, state) || 'undefined'}:undefined` - : genChildren(el, state) || 'undefined' + : el.locals && !el.localsProcessed + ? genLocalVariableScopeSlot(el, state) + : genChildren(el, state) || 'undefined' : genElement(el, state) }}` return `{key:${key},fn:${fn}}` @@ -373,6 +391,24 @@ function genForScopedSlot ( '})' } +function genLocalVariableScopeSlot ( + el: ASTElement, + state: CodegenState +): string { + const locals = el.locals || [] + const localNames: Array = [] + const localValues: Array = [] + for (let i = 0, l = locals.length; i < l; ++i) { + localNames.push(locals[i].alias) + localValues.push(locals[i].value) + } + + el.localsProcessed = true + return `((function(${localNames.join(',')}){` + + `return ${genChildren(el, state) || 'undefined'}` + + `})(${localValues.join(',')}))` +} + export function genChildren ( el: ASTElement, state: CodegenState, diff --git a/src/compiler/helpers.js b/src/compiler/helpers.js index 1d1feddbf01..58cf3defd4a 100644 --- a/src/compiler/helpers.js +++ b/src/compiler/helpers.js @@ -32,6 +32,10 @@ export function addRawAttr (el: ASTElement, name: string, value: any) { el.attrsList.push({ name, value }) } +export function addLocalVariable (el: ASTElement, alias: string, value: string) { + (el.locals || (el.locals = [])).push({ alias, value }) +} + export function addDirective ( el: ASTElement, name: string, diff --git a/src/compiler/parser/index.js b/src/compiler/parser/index.js index ba1b726870a..16e5a9837b0 100644 --- a/src/compiler/parser/index.js +++ b/src/compiler/parser/index.js @@ -16,10 +16,12 @@ import { addDirective, getBindingAttr, getAndRemoveAttr, - pluckModuleFunction + pluckModuleFunction, + addLocalVariable } from '../helpers' export const onRE = /^@|^v-on:/ +export const localRE = /^v-local:/ export const dirRE = /^v-|^@|^:/ export const forAliasRE = /(.*?)\s+(?:in|of)\s+(.*)/ export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/ @@ -552,6 +554,9 @@ function processAttrs (el) { } else { addAttr(el, name, value) } + } else if (localRE.test(name)) { + name = name.replace(localRE, '') + addLocalVariable(el, name, value) } else if (onRE.test(name)) { // v-on name = name.replace(onRE, '') addHandler(el, name, value, modifiers, false, warn) diff --git a/test/unit/features/directives/local.spec.js b/test/unit/features/directives/local.spec.js new file mode 100644 index 00000000000..c1d2d1ece41 --- /dev/null +++ b/test/unit/features/directives/local.spec.js @@ -0,0 +1,166 @@ +import Vue from 'vue' + +describe('Directive v-local', () => { + it('should work', () => { + const vm = new Vue({ + template: '
{{v.baz}}
', + data: { + foo: { + bar: { + baz: 1 + } + } + } + }).$mount() + expect(vm.$el.innerHTML).toBe('1') + }) + + it('should update', done => { + const vm = new Vue({ + template: '
{{v.baz}}
', + data: { + foo: { + bar: { + baz: 1 + } + } + } + }).$mount() + expect(vm.$el.innerHTML).toBe('1') + vm.foo.bar.baz = 2 + waitForUpdate(() => { + expect(vm.$el.innerHTML).toBe('2') + vm.foo.bar.baz = 'a' + }).then(() => { + expect(vm.$el.innerHTML).toBe('a') + }).then(done) + }) + + it('should work with v-if', done => { + const vm = new Vue({ + template: '
{{v.baz}}
', + data: { + foo: { + bar: { + baz: 1, + show: true + } + } + } + }).$mount() + expect(vm.$el.innerHTML).toBe('1') + vm.foo.bar.show = false + waitForUpdate(() => { + expect(vm.$el.innerHTML).toBe('') + vm.foo.bar.baz = 2 + vm.foo.bar.show = true + }).then(() => { + expect(vm.$el.innerHTML).toBe('2') + }).then(done) + }) + + it('should work with v-for', done => { + const vm = new Vue({ + template: '
{{v.baz}}
', + data: { + items: [ + { + foo: { + bar: { + baz: 1 + } + } + } + ] + } + }).$mount() + expect(vm.$el.innerHTML).toBe('1') + vm.items.push({ + foo: { + bar: { + baz: 2 + } + } + }) + waitForUpdate(() => { + expect(vm.$el.innerHTML).toBe('12') + vm.items.shift() + }).then(() => { + expect(vm.$el.innerHTML).toBe('2') + vm.items = [] + }).then(() => { + expect(vm.$el.innerHTML).toBe('') + }).then(done) + }) + + it('should been called once', () => { + const spy = jasmine.createSpy() + const vm = new Vue({ + template: '
{{v.foo}}-{{v.bar}}
', + data: { + foo: { + bar: { + baz: 1 + } + } + }, + methods: { + getValue () { + spy() + return { + foo: this.foo.bar.baz + 1, + bar: this.foo.bar.baz + 2 + } + } + } + }).$mount() + expect(vm.$el.innerHTML).toBe('2-3') + expect(spy).toHaveBeenCalledTimes(1) + }) + + it('should work with scoped-slot', done => { + const vm = new Vue({ + template: ` + + + + `, + components: { + test: { + data () { + return { + items: [{ + foo: { + bar: { + baz: 1 + } + } + }] + } + }, + template: ` +
+ +
+ ` + } + } + }).$mount() + expect(vm.$el.innerHTML).toBe('1') + vm.$refs.test.items.push({ + foo: { + bar: { + baz: 2 + } + } + }) + waitForUpdate(() => { + expect(vm.$el.innerHTML).toBe('12') + vm.$refs.test.items = [] + }).then(() => { + expect(vm.$el.innerHTML).toBe('') + }).then(done) + }) +}) diff --git a/test/unit/modules/compiler/codegen.spec.js b/test/unit/modules/compiler/codegen.spec.js index b1085a571df..f070b67173b 100644 --- a/test/unit/modules/compiler/codegen.spec.js +++ b/test/unit/modules/compiler/codegen.spec.js @@ -74,6 +74,62 @@ describe('codegen', () => { ) }) + it('generate v-local directive', () => { + assertCodegen( + '
{{value}}
', + `with(this){return ((function(value){return _c('div',{},[_v(_s(value))])})(some.deep.prop))}` + ) + }) + + it('generate multi v-local directive', () => { + assertCodegen( + '
{{foo}}{{bar}}
', + `with(this){return ((function(foo,bar){return _c('div',{},[_v(_s(foo)+_s(bar))])})(some.deep.prop,other.deep.prop))}` + ) + }) + + it('generate v-local directive with v-if', () => { + assertCodegen( + '
{{foo.value}}
', + `with(this){return ((function(foo){return (foo.show)?_c('div',{},[_v(_s(foo.value))]):_e()})(some.deep.prop))}` + ) + }) + + it('generate v-local directive with v-for', () => { + assertCodegen( + '
{{foo}}
', + `with(this){return _c('div',_l((items),function(item){return ((function(foo){return _c('span',{},[_v(_s(foo))])})(item.deep.prop))}))}` + ) + }) + + it('generate v-local directive with other v-local', () => { + assertCodegen( + '
{{bar}}
', + `with(this){return ((function(foo){return _c('div',{},[((function(bar){return _c('span',{},[_v(_s(bar))])})(foo.deep.prop))])})(some.deep.prop))}` + ) + }) + + it('generate v-local directive with scoped-slot', () => { + assertCodegen( + '
{{baz}}
', + `with(this){return _c('foo',{scopedSlots:_u([{key:"default",fn:function(bar){return ((function(baz){return _c('div',{},[_v(_s(baz))])})(bar.deep.prop))}}])})}` + ) + }) + + it('generate v-local directive with template tag', () => { + assertCodegen( + '
', + `with(this){return _c('div',[((function(v){return [_c('span',[_v(_s(v))])]})(some.deep.prop))],2)}` + ) + }) + + it('generate v-local directive with scoped-slot and template tag', () => { + assertCodegen( + '', + `with(this){return _c('test',{scopedSlots:_u([{key:"item",fn:function(props){return ((function(v){return [_c('span',[_v(_s(v))])]})(props.text))}}])})}` + ) + }) + it('generate v-if directive', () => { assertCodegen( '

hello

',