Browse Source

feat(weex): generate "@render" function for weex recycle-list (#6987)

* feat($compiler): support to generate @render function for weex recycle-list

Compile the template twice with different options for weex platform if
the “recyclable” flag is passed. Generate both normal render function
and “@render” function for recycle-list.

Adjust function names and arguments in recycle-list compiler.

* test(weex): add test cases for <recycle-list>
dev
Hanks 7 years ago
committed by Evan You
parent
commit
0c11aa8add
  1. 4
      flow/compiler.js
  2. 18
      src/compiler/codegen/events.js
  3. 24
      src/platforms/weex/compiler/index.js
  4. 36
      src/platforms/weex/compiler/modules/recycle-list/index.js
  5. 2
      src/platforms/weex/compiler/modules/recycle-list/text.js
  6. 2
      src/platforms/weex/compiler/modules/recycle-list/v-bind.js
  7. 2
      src/platforms/weex/compiler/modules/recycle-list/v-for.js
  8. 2
      src/platforms/weex/compiler/modules/recycle-list/v-if.js
  9. 4
      src/platforms/weex/compiler/modules/recycle-list/v-on.js
  10. 12
      test/weex/cases/cases.spec.js
  11. 33
      test/weex/cases/recycle-list/attrs.vdom.js
  12. 23
      test/weex/cases/recycle-list/attrs.vue
  13. 37
      test/weex/cases/recycle-list/text-node.vdom.js
  14. 23
      test/weex/cases/recycle-list/text-node.vue
  15. 34
      test/weex/cases/recycle-list/v-else-if.vdom.js
  16. 23
      test/weex/cases/recycle-list/v-else-if.vue
  17. 28
      test/weex/cases/recycle-list/v-else.vdom.js
  18. 22
      test/weex/cases/recycle-list/v-else.vue
  19. 46
      test/weex/cases/recycle-list/v-for-iterator.vdom.js
  20. 24
      test/weex/cases/recycle-list/v-for-iterator.vue
  21. 32
      test/weex/cases/recycle-list/v-for.vdom.js
  22. 23
      test/weex/cases/recycle-list/v-for.vue
  23. 28
      test/weex/cases/recycle-list/v-if.vdom.js
  24. 22
      test/weex/cases/recycle-list/v-if.vue
  25. 36
      test/weex/cases/recycle-list/v-on-inline.vdom.js
  26. 28
      test/weex/cases/recycle-list/v-on-inline.vue
  27. 24
      test/weex/cases/recycle-list/v-on.vdom.js
  28. 26
      test/weex/cases/recycle-list/v-on.vue

4
flow/compiler.js

@ -17,6 +17,9 @@ declare type CompilerOptions = {
shouldDecodeNewlines?: boolean; shouldDecodeNewlines?: boolean;
shouldDecodeNewlinesForHref?: boolean; shouldDecodeNewlinesForHref?: boolean;
// support <recycle-list> in weex
recyclable?: boolean;
// for ssr optimization compiler // for ssr optimization compiler
scopeId?: string; scopeId?: string;
@ -30,6 +33,7 @@ declare type CompilerOptions = {
declare type CompiledResult = { declare type CompiledResult = {
ast: ?ASTElement; ast: ?ASTElement;
render: string; render: string;
'@render'?: string;
staticRenderFns: Array<string>; staticRenderFns: Array<string>;
stringRenderFns?: Array<string>; stringRenderFns?: Array<string>;
errors?: Array<string>; errors?: Array<string>;

18
src/compiler/codegen/events.js

@ -48,12 +48,18 @@ export function genHandlers (
// Generate handler code with binding params on Weex // Generate handler code with binding params on Weex
function genWeexHandler (params: Array<any>, handlerCode: string) { function genWeexHandler (params: Array<any>, handlerCode: string) {
const wrapperArgs = params.filter(exp => simplePathRE.test(exp) && exp !== '$event') let innerHandlerCode = handlerCode
const handlerParams = wrapperArgs.map(exp => ({ '@binding': exp })) const exps = params.filter(exp => simplePathRE.test(exp) && exp !== '$event')
wrapperArgs.push('$event') const bindings = exps.map(exp => ({ '@binding': exp }))
return '{' + const args = exps.map((exp, i) => {
`handler:function(${wrapperArgs.join(',')}){${handlerCode}},\n` + const key = `$_${i + 1}`
`params:${JSON.stringify(handlerParams)}` + innerHandlerCode = innerHandlerCode.replace(exp, key)
return key
})
args.push('$event')
return '{\n' +
`handler:function(${args.join(',')}){${innerHandlerCode}},\n` +
`params:${JSON.stringify(bindings)}\n` +
'}' '}'
} }

24
src/platforms/weex/compiler/index.js

@ -23,8 +23,28 @@ export const baseOptions: CompilerOptions = {
isReservedTag, isReservedTag,
getTagNamespace, getTagNamespace,
preserveWhitespace: false, preserveWhitespace: false,
recyclable: false,
staticKeys: genStaticKeys(modules) staticKeys: genStaticKeys(modules)
} }
const { compile, compileToFunctions } = createCompiler(baseOptions) const compiler = createCompiler(baseOptions)
export { compile, compileToFunctions }
export function compile (
template: string,
options?: CompilerOptions
): CompiledResult {
let generateAltRender = false
if (options && options.recyclable === true) {
generateAltRender = true
options.recyclable = false
}
const result = compiler.compile(template, options)
// generate @render function for <recycle-list>
if (options && generateAltRender) {
options.recyclable = true
const { render } = compiler.compile(template, options)
result['@render'] = render
}
return result
}

36
src/platforms/weex/compiler/modules/recycle-list/index.js

@ -1,38 +1,42 @@
/* @flow */ /* @flow */
import { transformText } from './text' import { postTransformText } from './text'
import { transformVBind } from './v-bind' import { preTransformVBind } from './v-bind'
import { transformVIf } from './v-if' import { preTransformVIf } from './v-if'
import { transformVFor } from './v-for' import { preTransformVFor } from './v-for'
import { postTransformVOn } from './v-on' import { postTransformVOn } from './v-on'
let currentRecycleList = null let currentRecycleList = null
function shouldCompile (el: ASTElement, options: CompilerOptions) {
return options.recyclable ||
(currentRecycleList && el !== currentRecycleList)
}
function preTransformNode (el: ASTElement, options: CompilerOptions) { function preTransformNode (el: ASTElement, options: CompilerOptions) {
if (el.tag === 'recycle-list') { if (el.tag === 'recycle-list') {
currentRecycleList = el currentRecycleList = el
} }
if (currentRecycleList) { if (shouldCompile(el, options)) {
// TODO preTransformVBind(el, options)
transformVBind(el) preTransformVIf(el, options) // also v-else-if and v-else
transformVIf(el, options) // and v-else-if and v-else preTransformVFor(el, options)
transformVFor(el, options)
} }
} }
function transformNode (el: ASTElement) { function transformNode (el: ASTElement, options: CompilerOptions) {
if (currentRecycleList) { if (shouldCompile(el, options)) {
// TODO // do nothing yet
} }
} }
function postTransformNode (el: ASTElement) { function postTransformNode (el: ASTElement, options: CompilerOptions) {
if (currentRecycleList) { if (shouldCompile(el, options)) {
// <text>: transform children text into value attr // <text>: transform children text into value attr
if (el.tag === 'text') { if (el.tag === 'text') {
transformText(el) postTransformText(el, options)
} }
postTransformVOn(el) postTransformVOn(el, options)
} }
if (el === currentRecycleList) { if (el === currentRecycleList) {
currentRecycleList = null currentRecycleList = null

2
src/platforms/weex/compiler/modules/recycle-list/text.js

@ -13,7 +13,7 @@ function genText (node: ASTNode) {
return JSON.stringify(value) return JSON.stringify(value)
} }
export function transformText (el: ASTElement) { export function postTransformText (el: ASTElement, options: CompilerOptions) {
// weex <text> can only contain text, so the parser // weex <text> can only contain text, so the parser
// always generates a single child. // always generates a single child.
if (el.children.length) { if (el.children.length) {

2
src/platforms/weex/compiler/modules/recycle-list/v-bind.js

@ -8,7 +8,7 @@ function parseAttrName (name: string): string {
return camelize(name.replace(bindRE, '')) return camelize(name.replace(bindRE, ''))
} }
export function transformVBind (el: ASTElement) { export function preTransformVBind (el: ASTElement, options: CompilerOptions) {
for (const attr in el.attrsMap) { for (const attr in el.attrsMap) {
if (bindRE.test(attr)) { if (bindRE.test(attr)) {
const name: string = parseAttrName(attr) const name: string = parseAttrName(attr)

2
src/platforms/weex/compiler/modules/recycle-list/v-for.js

@ -3,7 +3,7 @@
import { forAliasRE, forIteratorRE } from 'compiler/parser/index' import { forAliasRE, forIteratorRE } from 'compiler/parser/index'
import { getAndRemoveAttr } from 'compiler/helpers' import { getAndRemoveAttr } from 'compiler/helpers'
export function transformVFor (el: ASTElement, options: CompilerOptions) { export function preTransformVFor (el: ASTElement, options: CompilerOptions) {
const exp = getAndRemoveAttr(el, 'v-for') const exp = getAndRemoveAttr(el, 'v-for')
if (!exp) { if (!exp) {
return return

2
src/platforms/weex/compiler/modules/recycle-list/v-if.js

@ -18,7 +18,7 @@ function getPrevMatch (el: ASTElement): any {
} }
} }
export function transformVIf (el: ASTElement, options: CompilerOptions) { export function preTransformVIf (el: ASTElement, options: CompilerOptions) {
if (hasConditionDirective(el)) { if (hasConditionDirective(el)) {
let exp let exp
const ifExp = getAndRemoveAttr(el, 'v-if') const ifExp = getAndRemoveAttr(el, 'v-if')

4
src/platforms/weex/compiler/modules/recycle-list/v-on.js

@ -1,6 +1,6 @@
/* @flow */ /* @flow */
const inlineStatementRE = /^\s*([A-Za-z_$0-9\.]+)*\s*\(\s*(([A-Za-z_$0-9\'\"]+)?(\s*,\s*([A-Za-z_$0-9\'\"]+))*)\s*\)$/ const inlineStatementRE = /^\s*([A-Za-z_$0-9\['\."\]]+)*\s*\(\s*(([A-Za-z_$0-9\['\."\]]+)?(\s*,\s*([A-Za-z_$0-9\['\."\]]+))*)\s*\)$/
function parseHandlerParams (handler: ASTElementHandler) { function parseHandlerParams (handler: ASTElementHandler) {
const res = inlineStatementRE.exec(handler.value) const res = inlineStatementRE.exec(handler.value)
@ -9,7 +9,7 @@ function parseHandlerParams (handler: ASTElementHandler) {
} }
} }
export function postTransformVOn (el: ASTElement) { export function postTransformVOn (el: ASTElement, options: CompilerOptions) {
const events: ASTElementHandlers | void = el.events const events: ASTElementHandlers | void = el.events
if (!events) { if (!events) {
return return

12
test/weex/cases/cases.spec.js

@ -68,5 +68,17 @@ describe('Usage', () => {
describe('event', () => { describe('event', () => {
it('click', createEventTestCase('event/click')) it('click', createEventTestCase('event/click'))
}) })
describe('recycle-list', () => {
it('text node', createRenderTestCase('recycle-list/text-node'))
it('attributes', createRenderTestCase('recycle-list/attrs'))
it('v-if', createRenderTestCase('recycle-list/v-if'))
it('v-else', createRenderTestCase('recycle-list/v-else'))
it('v-else-if', createRenderTestCase('recycle-list/v-else-if'))
it('v-for', createRenderTestCase('recycle-list/v-for'))
it('v-for-iterator', createRenderTestCase('recycle-list/v-for-iterator'))
it('v-on', createRenderTestCase('recycle-list/v-on'))
it('v-on-inline', createRenderTestCase('recycle-list/v-on-inline'))
})
}) })

33
test/weex/cases/recycle-list/attrs.vdom.js

@ -0,0 +1,33 @@
({
type: 'recycle-list',
attr: {
listData: [
{ type: 'A', count: 1, source: 'http://whatever.com/x.png' },
{ type: 'A', count: 2, source: 'http://whatever.com/y.png' },
{ type: 'A', count: 3, source: 'http://whatever.com/z.png' }
],
templateKey: 'type',
alias: 'item'
},
children: [{
type: 'cell-slot',
attr: { templateType: 'A' },
children: [{
type: 'image',
attr: {
resize: 'cover',
src: {
'@binding': 'item.source'
}
}
}, {
type: 'text',
attr: {
lines: '3',
count: {
'@binding': 'item.count'
}
}
}]
}]
})

23
test/weex/cases/recycle-list/attrs.vue

@ -0,0 +1,23 @@
<template>
<recycle-list :list-data="longList" template-key="type" alias="item">
<cell-slot template-type="A">
<image resize="cover" :src="item.source">
<text lines="3" v-bind:count="item.count"></text>
</cell-slot>
</recycle-list>
</template>
<script>
module.exports = {
data () {
return {
longList: [
{ type: 'A', count: 1, source: 'http://whatever.com/x.png' },
{ type: 'A', count: 2, source: 'http://whatever.com/y.png' },
{ type: 'A', count: 3, source: 'http://whatever.com/z.png' }
]
}
}
}
</script>

37
test/weex/cases/recycle-list/text-node.vdom.js

@ -0,0 +1,37 @@
({
type: 'recycle-list',
attr: {
listData: [
{ type: 'A', dynamic: 'decimal', two: '2', four: '4' },
{ type: 'A', dynamic: 'binary', two: '10', four: '100' }
],
templateKey: 'type',
alias: 'item'
},
children: [{
type: 'cell-slot',
attr: { templateType: 'A' },
children: [{
type: 'text',
attr: {
value: 'static'
}
}, {
type: 'text',
attr: {
value: { '@binding': 'item.dynamic' }
}
}, {
type: 'text',
attr: {
value: [
'one ',
{ '@binding': 'item.two' },
' three ',
{ '@binding': 'item.four' },
' five'
]
}
}]
}]
})

23
test/weex/cases/recycle-list/text-node.vue

@ -0,0 +1,23 @@
<template>
<recycle-list :list-data="longList" template-key="type" alias="item">
<cell-slot template-type="A">
<text>static</text>
<text>{{item.dynamic}}</text>
<text>one {{item.two}} three {{ item.four }} five</text>
</cell-slot>
</recycle-list>
</template>
<script>
module.exports = {
data () {
return {
longList: [
{ type: 'A', dynamic: 'decimal', two: '2', four: '4' },
{ type: 'A', dynamic: 'binary', two: '10', four: '100' }
]
}
}
}
</script>

34
test/weex/cases/recycle-list/v-else-if.vdom.js

@ -0,0 +1,34 @@
({
type: 'recycle-list',
attr: {
listData: [
{ type: 'A' },
{ type: 'A' }
],
templateKey: 'type',
alias: 'item'
},
children: [{
type: 'cell-slot',
attr: { templateType: 'A' },
children: [{
type: 'image',
attr: {
'[[match]]': 'item.sourceA',
src: { '@binding': 'item.sourceA' }
}
}, {
type: 'image',
attr: {
'[[match]]': '!(item.sourceA) && (item.sourceB)',
src: { '@binding': 'item.sourceB' }
}
}, {
type: 'image',
attr: {
'[[match]]': '!(!(item.sourceA) && (item.sourceB))',
src: { '@binding': 'item.placeholder' }
}
}]
}]
})

23
test/weex/cases/recycle-list/v-else-if.vue

@ -0,0 +1,23 @@
<template>
<recycle-list :list-data="longList" template-key="type" alias="item">
<cell-slot template-type="A">
<image v-if="item.sourceA" :src="item.sourceA"></image>
<image v-else-if="item.sourceB" :src="item.sourceB"></image>
<image v-else :src="item.placeholder"></image>
</cell-slot>
</recycle-list>
</template>
<script>
module.exports = {
data () {
return {
longList: [
{ type: 'A' },
{ type: 'A' }
]
}
}
}
</script>

28
test/weex/cases/recycle-list/v-else.vdom.js

@ -0,0 +1,28 @@
({
type: 'recycle-list',
attr: {
listData: [
{ type: 'A' },
{ type: 'A' }
],
templateKey: 'type',
alias: 'item'
},
children: [{
type: 'cell-slot',
attr: { templateType: 'A' },
children: [{
type: 'image',
attr: {
'[[match]]': 'item.source',
src: { '@binding': 'item.source' }
}
}, {
type: 'image',
attr: {
'[[match]]': '!(item.source)',
src: { '@binding': 'item.placeholder' }
}
}]
}]
})

22
test/weex/cases/recycle-list/v-else.vue

@ -0,0 +1,22 @@
<template>
<recycle-list :list-data="longList" template-key="type" alias="item">
<cell-slot template-type="A">
<image v-if="item.source" :src="item.source"></image>
<image v-else v-bind:src="item.placeholder"></image>
</cell-slot>
</recycle-list>
</template>
<script>
module.exports = {
data () {
return {
longList: [
{ type: 'A' },
{ type: 'A' }
]
}
}
}
</script>

46
test/weex/cases/recycle-list/v-for-iterator.vdom.js

@ -0,0 +1,46 @@
({
type: 'recycle-list',
attr: {
listData: [
{ type: 'A' },
{ type: 'A' }
],
templateKey: 'type',
alias: 'item'
},
children: [{
type: 'cell-slot',
attr: { templateType: 'A' },
children: [{
type: 'div',
attr: {
'[[repeat]]': {
'@expression': 'item.list',
'@index': 'index',
'@alias': 'object'
}
},
children: [{
type: 'text',
attr: {
value: {
'@binding': 'object.name'
}
}
}, {
type: 'text',
attr: {
'[[repeat]]': {
'@expression': 'object',
'@alias': 'v',
'@key': 'k',
'@index': 'i'
},
value: {
'@binding': 'v'
}
}
}]
}]
}]
})

24
test/weex/cases/recycle-list/v-for-iterator.vue

@ -0,0 +1,24 @@
<template>
<recycle-list :list-data="longList" template-key="type" alias="item">
<cell-slot template-type="A">
<div v-for="(object, index) in item.list" :key="index">
<text>{{object.name}}</text>
<text v-for="(v, k, i) in object" :key="k">{{v}}</text>
</div>
</cell-slot>
</recycle-list>
</template>
<script>
module.exports = {
data () {
return {
longList: [
{ type: 'A' },
{ type: 'A' }
]
}
}
}
</script>

32
test/weex/cases/recycle-list/v-for.vdom.js

@ -0,0 +1,32 @@
({
type: 'recycle-list',
attr: {
listData: [
{ type: 'A' },
{ type: 'A' }
],
templateKey: 'type',
alias: 'item'
},
children: [{
type: 'cell-slot',
attr: { templateType: 'A' },
children: [{
type: 'div',
attr: {
'[[repeat]]': {
'@expression': 'item.list',
'@alias': 'panel'
}
},
children: [{
type: 'text',
attr: {
value: {
'@binding': 'panel.label'
}
}
}]
}]
}]
})

23
test/weex/cases/recycle-list/v-for.vue

@ -0,0 +1,23 @@
<template>
<recycle-list :list-data="longList" template-key="type" alias="item">
<cell-slot template-type="A">
<div v-for="panel in item.list" :key="panel.id">
<text>{{panel.label}}</text>
</div>
</cell-slot>
</recycle-list>
</template>
<script>
module.exports = {
data () {
return {
longList: [
{ type: 'A' },
{ type: 'A' }
]
}
}
}
</script>

28
test/weex/cases/recycle-list/v-if.vdom.js

@ -0,0 +1,28 @@
({
type: 'recycle-list',
attr: {
listData: [
{ type: 'A' },
{ type: 'A' }
],
templateKey: 'type',
alias: 'item'
},
children: [{
type: 'cell-slot',
attr: { templateType: 'A' },
children: [{
type: 'image',
attr: {
'[[match]]': 'item.source',
src: { '@binding': 'item.source' }
}
}, {
type: 'text',
attr: {
'[[match]]': '!item.source',
value: 'Title'
}
}]
}]
})

22
test/weex/cases/recycle-list/v-if.vue

@ -0,0 +1,22 @@
<template>
<recycle-list :list-data="longList" template-key="type" alias="item">
<cell-slot template-type="A">
<image v-if="item.source" :src="item.source"></image>
<text v-if="!item.source">Title</text>
</cell-slot>
</recycle-list>
</template>
<script>
module.exports = {
data () {
return {
longList: [
{ type: 'A' },
{ type: 'A' }
]
}
}
}
</script>

36
test/weex/cases/recycle-list/v-on-inline.vdom.js

@ -0,0 +1,36 @@
({
type: 'recycle-list',
attr: {
listData: [
{ type: 'A' },
{ type: 'A' }
],
templateKey: 'type',
alias: 'item'
},
children: [{
type: 'cell-slot',
attr: { templateType: 'A' },
children: [{
type: 'text',
event: ['click', {
type: 'longpress',
params: [{ '@binding': 'item.key' }]
}]
}, {
type: 'text',
event: [{
type: 'appear',
params: [
{ '@binding': 'item.index' },
{ '@binding': 'item.type' }
]
}],
attr: { value: 'Button' }
}, {
type: 'text',
event: [{ type: 'disappear' }],
attr: { value: 'Tips' }
}]
}]
})

28
test/weex/cases/recycle-list/v-on-inline.vue

@ -0,0 +1,28 @@
<template>
<recycle-list :list-data="longList" template-key="type" alias="item">
<cell-slot template-type="A">
<text v-on:click="toggle()" @longpress="toggle(item.key)"></text>
<text @appear="onappear(item.index, 'static', item.type, $event)">Button</text>
<text @disappear="onappear(25, 'static')">Tips</text>
</cell-slot>
</recycle-list>
</template>
<script>
module.exports = {
data () {
return {
longList: [
{ type: 'A' },
{ type: 'A' }
]
}
},
methods: {
hide () {},
toggle () {},
onappear () {}
}
}
</script>

24
test/weex/cases/recycle-list/v-on.vdom.js

@ -0,0 +1,24 @@
({
type: 'recycle-list',
attr: {
listData: [
{ type: 'A' },
{ type: 'A' }
],
templateKey: 'type',
alias: 'item'
},
children: [{
type: 'cell-slot',
attr: { templateType: 'A' },
children: [{
type: 'text',
event: ['click', 'longpress'],
attr: { value: 'A' }
}, {
type: 'text',
event: ['touchend'],
attr: { value: 'B' }
}]
}]
})

26
test/weex/cases/recycle-list/v-on.vue

@ -0,0 +1,26 @@
<template>
<recycle-list :list-data="longList" template-key="type" alias="item">
<cell-slot template-type="A">
<text v-on:click="handler" @longpress="move">A</text>
<text @touchend="move">B</text>
</cell-slot>
</recycle-list>
</template>
<script>
module.exports = {
data () {
return {
longList: [
{ type: 'A' },
{ type: 'A' }
]
}
},
methods: {
handler () {},
move () {}
}
}
</script>
Loading…
Cancel
Save