提交 82c5a914 编写于 作者: Q qiang

fix(mp): 增加新的解构插槽编译选项 betterScopedSlots,解构插槽内支持使用作用域外部值和复杂表达式

上级 bd35b9ad
......@@ -82,6 +82,41 @@ describe('mp:compiler-mp-weixin', () => {
)
})
it('generate scoped slot with filter', () => {
assertCodegen(
'<my-component><template v-slot="{item}">{{getValue(item)}}<template></my-component>',
'<my-component vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'default\']}}"><block><block wx:if="{{$root.m0}}">{{$root.m1}}</block></block></my-component>',
'with(this){var m0=$hasScopedSlotsParams("551070e6-1");var m1=m0?getValue($getScopedSlotsParams("551070e6-1","default","item")):null;$mp.data=Object.assign({},{$root:{m0:m0,m1:m1}})}',
{
betterScopedSlots: true
}
)
assertCodegen(
'<my-component><template v-slot="item">{{getValue(item.text)}}<template></my-component>',
'<my-component vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'default\']}}"><block><block wx:if="{{$root.m0}}">{{$root.m1}}</block></block></my-component>',
'with(this){var m0=$hasScopedSlotsParams("551070e6-1");var m1=m0?getValue($getScopedSlotsParams("551070e6-1","default").text):null;$mp.data=Object.assign({},{$root:{m0:m0,m1:m1}})}',
{
betterScopedSlots: true
}
)
assertCodegen(
'<view><slot :item="item"><slot></view>',
'<view><block wx:if="{{$slots.default}}"><slot></slot></block><block wx:else><slot></slot></block></view>',
'with(this){$setScopedSlotsParams("default",{"item":item})}',
{
betterScopedSlots: true
}
)
assertCodegen(
'<view><slot v-bind="object"><slot></view>',
'<view><block wx:if="{{$slots.default}}"><slot></slot></block><block wx:else><slot></slot></block></view>',
'with(this){$setScopedSlotsParams("default",object)}',
{
betterScopedSlots: true
}
)
})
it('generate scoped slot', () => {
assertCodegen(
'<slot v-bind:user="user"></slot>',
......@@ -158,4 +193,4 @@ describe('mp:compiler-mp-weixin', () => {
'<test data-custom-hidden="{{!(shown)}}" vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'default\']}}">hello world</test>'
)
})
})
})
......@@ -106,6 +106,9 @@ module.exports = {
METHOD_TO_STRING,
METHOD_RENDER_LIST,
METHOD_RESOLVE_FILTER,
METHOD_RENDER_SLOT,
METHOD_CREATE_EMPTY_VNODE,
METHOD_RESOLVE_SCOPED_SLOTS,
PREFIX_GLOBAL: 'g',
PREFIX_ATTR: 'a',
PREFIX_METHOD: 'm',
......
......@@ -90,6 +90,7 @@ module.exports = function traverse (ast, state) {
const blockStatementBody = []
const objectPropertyArray = []
const initExpressionStatementArray = []
const renderSlotStatementArray = []
// TODO 待重构,至少 filter,method 等实现方式要调整
babelTraverse(ast, visitor, undefined, {
scoped: [],
......@@ -100,7 +101,8 @@ module.exports = function traverse (ast, state) {
identifierArray: identifierArray,
propertyArray: objectPropertyArray,
declarationArray: blockStatementBody,
initExpressionStatementArray: initExpressionStatementArray
initExpressionStatementArray: initExpressionStatementArray,
renderSlotStatementArray
})
if (initExpressionStatementArray.length) {
......@@ -111,10 +113,14 @@ module.exports = function traverse (ast, state) {
blockStatementBody.push(getDataExpressionStatement(objectPropertyArray))
}
if (renderSlotStatementArray.length) {
blockStatementBody.push(...renderSlotStatementArray)
}
reIdentifier(identifierArray)
return t.withStatement(
t.thisExpression(),
t.blockStatement(blockStatementBody)
)
}
}
const t = require('@babel/types')
module.exports = function getRenderSlot (path, state) {
const name = path.get('arguments.0')
const arg2 = path.get('arguments.2')
const arg3 = path.get('arguments.3')
let valueNode
if (arg3) {
// v-bind:object
valueNode = arg3.node
} else if (arg2 && !arg2.isNullLiteral()) {
if (arg2.isObjectExpression()) {
const propertiesPath = arg2.get('properties')
const oldProperties = []
const newProperties = []
propertiesPath.forEach(path => {
const properties = path.get('key').isStringLiteral({ value: 'SLOT_DEFAULT' }) ? oldProperties : newProperties
properties.push(path.node)
})
if (!newProperties.length) {
return
}
valueNode = t.objectExpression(newProperties)
arg2.replaceWith(t.objectExpression(oldProperties))
} else {
valueNode = arg2.node
}
}
if (valueNode) {
state.renderSlotStatementArray.push(t.expressionStatement(t.callExpression(t.identifier('$setScopedSlotsParams'), [t.stringLiteral(name.node.value), valueNode])))
}
// TODO 组件嵌套
path.skip()
}
const t = require('@babel/types')
const {
METHOD_CREATE_EMPTY_VNODE
} = require('../../constants')
function replaceId (path, ids) {
let replaced
path.traverse({
noScope: true,
Identifier (path) {
const name = path.node.name
if (name in ids && path.key !== 'key' && (path.key !== 'property' || path.parent.computed)) {
path.replaceWith(ids[name])
replaced = true
}
}
})
return replaced
}
module.exports = function getResolveScopedSlots (parent, state) {
const properties = parent.get('arguments.0.elements.0.properties')
const fn = properties.find(path => path.get('key').isIdentifier({ name: 'fn' }))
const params = fn.get('value.params.0')
if (!params) {
return
}
const vueId = parent.parentPath.parentPath.get('properties').find(path => path.get('key').isIdentifier({ name: 'attrs' })).get('value').get('properties').find(path => path.get('key').isStringLiteral({ value: 'vue-id' })).get('value').node.value
const slot = properties.find(path => path.get('key').isIdentifier({ name: 'key' })).get('value').node.value
const ids = {}
function updateIds (vueId, slot, value, key) {
const array = [t.stringLiteral(vueId), t.stringLiteral(slot)]
if (key) {
array.push(t.stringLiteral(key))
}
ids[value] = t.callExpression(t.identifier('$getScopedSlotsParams'), array)
}
if (params.isObjectPattern()) {
params.get('properties').forEach(prop => {
updateIds(vueId, slot, prop.get('value').node.name, prop.get('key').node.name)
})
} else if (params.isIdentifier()) {
updateIds(vueId, slot, params.node.name)
}
const fnBody = fn.get('value.body')
if (replaceId(fnBody, ids)) {
const orgin = fnBody.get('body.0.argument')
const elements = orgin.get('elements')
const node = (elements.length === 1 ? elements[0] : orgin).node
const test = t.callExpression(t.identifier('$hasScopedSlotsParams'), [t.stringLiteral(vueId)])
orgin.replaceWith(t.arrayExpression([t.conditionalExpression(test, node, t.callExpression(t.identifier(METHOD_CREATE_EMPTY_VNODE), []))]))
}
}
......@@ -6,6 +6,8 @@ const {
METHOD_RENDER_LIST,
METHOD_BUILT_IN,
METHOD_RESOLVE_FILTER,
METHOD_RENDER_SLOT,
METHOD_RESOLVE_SCOPED_SLOTS,
IDENTIFIER_FILTER,
IDENTIFIER_METHOD,
IDENTIFIER_GLOBAL
......@@ -26,6 +28,8 @@ const traverseData = require('./data')
const traverseRenderList = require('./render-list')
const getMemberExpr = require('./member-expr')
const getRenderSlot = require('./render-slot')
const getResolveScopedSlots = require('./resolve-scoped-slots')
function addStaticClass (path, staticClass) {
const dataPath = path.get('arguments.1')
......@@ -217,6 +221,12 @@ module.exports = {
this
)
)
} else if (this.options.betterScopedSlots) {
if (methodName === METHOD_RESOLVE_SCOPED_SLOTS) {
getResolveScopedSlots(path, this)
} else if (methodName === METHOD_RENDER_SLOT) {
getRenderSlot(path, this)
}
}
break
}
......
......@@ -304,7 +304,7 @@ function traverseRenderSlot (callExprNode, state) {
const slotName = callExprNode.arguments[0].value
let deleteSlotName = false // 标记是否组件 slot 手动指定了 name="default"
if (callExprNode.arguments.length > 2) { // 作用域插槽
if (!state.options.betterScopedSlots && callExprNode.arguments.length > 2) { // 作用域插槽
const props = {}
callExprNode.arguments[2].properties.forEach(property => {
props[property.key.value] = genCode(property.value)
......@@ -369,7 +369,7 @@ function traverseResolveScopedSlots (callExprNode, state) {
})
const slotName = keyProperty.value.value
const returnExprNodes = fnProperty.value.body.body[0].argument
if (!proxyProperty) {
if (!state.options.betterScopedSlots && !proxyProperty) {
const resourcePath = state.options.resourcePath
const ownerName = path.basename(resourcePath, path.extname(resourcePath))
......@@ -510,4 +510,4 @@ function traverseCreateTextVNode (callExprNode, state) {
function traverseCreateEmptyVNode (callExprNode, state) {
return ''
}
}
......@@ -258,6 +258,10 @@ if (platformOptions.usingComponents === true) {
}
}
if (platformOptions.betterScopedSlots) {
process.env.BETTER_SCOPED_SLOTS = true
}
if (
process.env.UNI_USING_COMPONENTS ||
process.env.UNI_PLATFORM === 'h5'
......
......@@ -65,7 +65,8 @@ module.exports = function (content, map) {
const filterModules = parseFilterModules(params && params['filter-modules'])
Object.assign(vueLoaderOptions.options.compilerOptions, {
mp: {
platform: process.env.UNI_PLATFORM
platform: process.env.UNI_PLATFORM,
betterScopedSlots: process.env.BETTER_SCOPED_SLOTS
},
filterModules,
filterTagName,
......@@ -84,4 +85,4 @@ module.exports = function (content, map) {
throw new Error('vue-loader-options parse error')
}
this.callback(null, content, map)
}
}
......@@ -41,11 +41,61 @@ function initEventChannel () {
}
}
function initScopedSlotsParams () {
const center = {}
const parents = {}
Vue.prototype.$hasScopedSlotsParams = function (vueId) {
const has = center[vueId]
if (!has) {
parents[vueId] = this
this.$on('hook:destory', () => {
delete parents[vueId]
})
}
return has
}
Vue.prototype.$getScopedSlotsParams = function (vueId, name, key) {
const data = center[vueId]
if (data) {
const object = data[name] || {}
return key ? object[key] : object
} else {
parents[vueId] = this
this.$on('hook:destory', () => {
delete parents[vueId]
})
}
}
Vue.prototype.$setScopedSlotsParams = function (name, value) {
const vueId = this.$options.propsData.vueId
const object = center[vueId] = center[vueId] || {}
object[name] = value
if (parents[vueId]) {
parents[vueId].$forceUpdate()
}
}
Vue.mixin({
destroyed () {
const propsData = this.$options.propsData
const vueId = propsData && propsData.vueId
if (vueId) {
delete center[vueId]
delete parents[vueId]
}
}
})
}
export default function parseBaseApp (vm, {
mocks,
initRefs
}) {
initEventChannel()
initScopedSlotsParams()
if (vm.$options.store) {
Vue.prototype.$store = vm.$options.store
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册