diff --git a/packages/uni-mp-toutiao/lib/uni.compiler.js b/packages/uni-mp-toutiao/lib/uni.compiler.js index cdec14a5ee6cb768e9511434525588414908d9a3..c60a6f546dbcb7a896e1cca3f67db4ea57832f51 100644 --- a/packages/uni-mp-toutiao/lib/uni.compiler.js +++ b/packages/uni-mp-toutiao/lib/uni.compiler.js @@ -1,4 +1,118 @@ const compiler = require('@dcloudio/uni-mp-weixin/lib/uni.compiler.js') +const path = require('path') +const t = require('@babel/types') + +function generateJsCode (properties = '{}') { + return ` +wx.createComponent({ + generic:true, + props: ${properties}, + render: function(){} +}) +` +} + +function generateCssCode (filename) { + return ` +@import "./${filename}" +` +} + +function hasOwn (obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key) +} + module.exports = Object.assign({}, compiler, { - directive: 'tt:' -}) + directive: 'tt:', + resolveScopedSlots (slotName, { + genCode, + generate, + ownerName, + parentName, + parentNode, + resourcePath, + paramExprNode, + returnExprNodes, + traverseExpr + }, state) { + if (!state.scopedSlots) { + state.scopedSlots = {} + } + const baseName = `${ownerName}-${parentName}-${slotName}` + let componentName = baseName + if (!hasOwn(state.scopedSlots, baseName)) { + state.scopedSlots[baseName] = 0 + } + if (state.scopedSlots[baseName]) { + componentName = baseName + state.scopedSlots[baseName] + } + state.scopedSlots[baseName]++ + // parentNode.attr['generic:scoped-slots-' + slotName] = componentName + if (!parentNode.attr.generic) { + parentNode.attr.generic = {} + } + parentNode.attr.generic[slotName] = componentName + + // 生成 scopedSlots 文件,包括 json,js,wxml,wxss,还需要更新 owner 的 usingComponents + if (!state.files) { + state.files = {} + } + const extname = path.extname(resourcePath) + + // TODO 需要存储 resourcePath 相关 json + + const templateFile = resourcePath.replace(ownerName + extname, componentName + extname) + const templateContent = generate(traverseExpr(returnExprNodes, state), state) + + state.files[templateFile] = templateContent + + const jsFile = resourcePath.replace(ownerName + extname, componentName + '.js') + + const objectProperties = [] + + if (t.isObjectPattern(paramExprNode)) { + paramExprNode.properties.forEach(property => { + const key = property.key + const value = property.value + const valueObjectProperties = [ + t.objectProperty(t.identifier('type'), t.nullLiteral()) + ] + if (t.isIdentifier(value)) { + if (value.name !== key.name) { + state.errors.add(`解构插槽 Prop 时,不支持将${key.name}重命名为${value.name},重命名后会影响性能`) + } + } else if (t.isAssignmentPattern(value)) { + valueObjectProperties.push(t.objectProperty(t.identifier('default'), value.right)) + } + objectProperties.push(t.objectProperty(key, t.objectExpression(valueObjectProperties))) + }) + } else { + state.errors.add(`目前仅支持解构插槽 ${paramExprNode.name},如 v-slot="{ user }"`) + } + const jsContent = generateJsCode(genCode(t.objectExpression(objectProperties), true)) + state.files[jsFile] = jsContent + + try { + // TODO 使用 getPlatformExts 在单元测试报错,改从 state.options.platform 判断 + const { getPlatformExts } = require('@dcloudio/uni-cli-shared') + const styleExtname = getPlatformExts().style + const styleFile = resourcePath.replace(ownerName + extname, componentName + styleExtname) + const styleContent = generateCssCode(ownerName + styleExtname) + + state.files[styleFile] = styleContent + } catch (error) { } + + // 用于后续修复字节跳动小程序不支持抽象节点导致的问题 + const fixExtname = '.fix' + const extFile = resourcePath.replace(ownerName + extname, componentName + fixExtname) + state.files[extFile] = `${resourcePath.replace(ownerName + extname, ownerName)},${parentName},${componentName},scoped-slots-${slotName}` + + if (!state.generic) { + state.generic = [] + } + // 存储,方便后续生成 json + state.generic.push(componentName) + + return '' + } +}) diff --git a/packages/uni-template-compiler/lib/template/generate.js b/packages/uni-template-compiler/lib/template/generate.js index f78e04ee0b06af9e6a63e80c910c0307e1f504bb..88a9f34970f0458d56609f3f094129e5106c8556 100644 --- a/packages/uni-template-compiler/lib/template/generate.js +++ b/packages/uni-template-compiler/lib/template/generate.js @@ -65,7 +65,12 @@ function processElement (ast, state, isRoot) { Object.keys(ast.attr.generic).forEach(scopedSlotName => { slots.push(scopedSlotName) }) - delete ast.attr.generic + if (platformName === 'mp-toutiao') { + // 用于字节跳动小程序模拟抽象节点 + ast.attr.generic = `{{${JSON.stringify(ast.attr.generic)}}}`.replace(/"/g, '\'') + } else { + delete ast.attr.generic + } } if (slots.length && platformName !== 'mp-alipay') { // 标记 slots ast.attr['vue-slots'] = '{{[' + slots.reverse().map(slotName => `'${slotName}'`).join(',') + ']}}' diff --git a/packages/webpack-uni-mp-loader/lib/plugin/generate-component.js b/packages/webpack-uni-mp-loader/lib/plugin/generate-component.js index 70812dbad64ca140df80e3c14d11fb1274dc42c7..d943ae6b962bc77e11948f5899d8221db8a647ef 100644 --- a/packages/webpack-uni-mp-loader/lib/plugin/generate-component.js +++ b/packages/webpack-uni-mp-loader/lib/plugin/generate-component.js @@ -6,7 +6,8 @@ const { getPlatformExts } = require('@dcloudio/uni-cli-shared') const { - getComponentSet + getComponentSet, + getJsonFile } = require('@dcloudio/uni-cli-shared/lib/cache') const { @@ -66,6 +67,7 @@ module.exports = function generateComponent (compilation) { const concatenatedModules = modules.filter(module => module.modules) const uniModuleId = modules.find(module => module.resource && normalizePath(module.resource) === uniPath).id const styleImports = {} + const fixSlots = {} Object.keys(assets).forEach(name => { if (components.has(name.replace('.js', ''))) { @@ -147,6 +149,57 @@ module.exports = function generateComponent (compilation) { } } } + // 处理字节跳动小程序作用域插槽 + const fixExtname = '.fix' + if (name.endsWith(fixExtname)) { + const source = assets[name].source() + const [ownerName, parentName, componentName, slotName] = source.split(',') + const json = getJsonFile(ownerName) + if (json) { + const data = JSON.parse(json) + const usingComponents = data.usingComponents || {} + const componentPath = path.relative('/', usingComponents[parentName]) + const slots = fixSlots[componentPath] = fixSlots[componentPath] || {} + const slot = slots[slotName] = slots[slotName] || {} + slot[componentName] = '/' + name.replace(fixExtname, '') + delete assets[name] + + const jsonFile = assets[`${componentPath}.json`] + if (jsonFile) { + const oldSource = jsonFile.__$oldSource || jsonFile.source() + const sourceObj = JSON.parse(oldSource) + Object.values(slots).forEach(components => { + const usingComponents = sourceObj.usingComponents = sourceObj.usingComponents || {} + Object.assign(usingComponents, components) + }) + delete sourceObj.componentGenerics + const source = JSON.stringify(sourceObj, null, 2) + jsonFile.source = function () { + return source + } + jsonFile.__$oldSource = oldSource + } + + const templateFile = assets[`${componentPath}${getPlatformExts().template}`] + if (templateFile) { + const oldSource = templateFile.__$oldSource || templateFile.source() + let templateSource + Object.keys(slots).forEach(name => { + const reg = new RegExp(`<${name} (.+?)>`) + templateSource = oldSource.replace(reg, string => { + const props = string.match(reg)[1] + return Object.keys(slots[name]).map(key => { + return `<${key} ${props}>` + }).join('') + }) + }) + templateFile.source = function () { + return templateSource + } + templateFile.__$oldSource = oldSource + } + } + } }) } if (process.env.UNI_FEATURE_OBSOLETE !== 'false') { diff --git a/src/core/runtime/wrapper/util.js b/src/core/runtime/wrapper/util.js index 35c0fd93bfa3abbcc2843b16ec4ef7175a672139..be9f757528e5bbc21e566d0eeae91a5cc0eebf00 100644 --- a/src/core/runtime/wrapper/util.js +++ b/src/core/runtime/wrapper/util.js @@ -229,6 +229,11 @@ export function initProperties (props, isBehavior = false, file = '') { type: String, value: '' } + // 用于字节跳动小程序模拟抽象节点 + properties.generic = { + type: Object, + value: null + } properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots type: null, value: [],