提交 231df55e 编写于 作者: P panyiming.325 提交者: 折腾笔记

feat: 支持飞书小程序

上级 9bea5dc8
......@@ -28,6 +28,10 @@ const PLATFORMS = {
prefix: 'ks',
title: '快手小程序'
},
'mp-lark': {
prefix: 'tt',
title: '飞书小程序'
},
'quickapp-webview': {
prefix: 'qa',
title: '快应用(Webview)版'
......@@ -85,4 +89,4 @@ module.exports = {
})
],
external: ['vue', '@dcloudio/uni-i18n']
}
}
......@@ -19,11 +19,12 @@
"build:mp-alipay": "cross-env UNI_PLATFORM=mp-alipay rollup -c build/rollup.config.mp.js",
"build:mp-toutiao": "cross-env UNI_PLATFORM=mp-toutiao rollup -c build/rollup.config.mp.js",
"build:mp-kuaishou": "cross-env UNI_PLATFORM=mp-kuaishou rollup -c build/rollup.config.mp.js",
"build:mp-lark": "cross-env UNI_PLATFORM=mp-lark rollup -c build/rollup.config.mp.js",
"build:quickapp-webview": "cross-env UNI_PLATFORM=quickapp-webview rollup -c build/rollup.config.mp.js",
"build:mp-weixin:mp": "cross-env UNI_PLATFORM=mp-weixin UNI_MP=true rollup -c build/rollup.config.mp.js",
"build:mp-weixin:wxs": "rollup -c build/rollup.config.wxs.js",
"build:quickapp-native": "cross-env NODE_ENV=development node build/build.qa.js && cross-env NODE_ENV=production node build/build.qa.js",
"build:runtime": "npm run lint && npm run build:mp-weixin && npm run build:mp-qq && npm run build:mp-alipay && npm run build:mp-baidu && npm run build:mp-toutiao && npm run build:app-plus && npm run build:quickapp-webview && npm run build:quickapp-native && npm run build:mp-kuaishou",
"build:runtime": "npm run lint && npm run build:mp-weixin && npm run build:mp-qq && npm run build:mp-alipay && npm run build:mp-baidu && npm run build:mp-toutiao && npm run build:app-plus && npm run build:quickapp-webview && npm run build:quickapp-native && npm run build:mp-kuaishou && npm run build:mp-lark",
"build:stat": "npm run lint && rollup -c build/rollup.config.stat.js",
"build:web-view": "rollup -c build/rollup.config.web-view.js",
"test:cli": "cross-env NODE_ENV=test jest",
......
......@@ -122,7 +122,7 @@ module.exports = {
getShadowCss,
getShadowTemplate (colorType = 'grey') {
let tagName = 'cover-image'
if (process.env.UNI_PLATFORM === 'mp-toutiao') {
if (process.env.UNI_PLATFORM === 'mp-toutiao' || process.env.UNI_PLATFORM === 'mp-lark') {
tagName = 'image'
}
return `<${tagName} src="https://cdn.dcloud.net.cn/img/shadow-${colorType}.png" style="z-index:998;position:fixed;left:0;top:0;width:100%;height:3px;"/>`
......
......@@ -10,6 +10,7 @@ const inlineLimit =
process.env.UNI_PLATFORM === 'mp-qq' ||
process.env.UNI_PLATFORM === 'mp-toutiao' ||
process.env.UNI_PLATFORM === 'mp-kuaishou' ||
process.env.UNI_PLATFORM === 'mp-lark' ||
process.env.UNI_PLATFORM === 'app-plus' // v2需要base64,v3需要rewriteUrl
// mp-weixin,mp-qq,app-plus 非v3(即:需要base64的平台)
......@@ -46,4 +47,4 @@ module.exports = {
}
},
rewriteUrl
}
}
# `uni-mp-lark`
> TODO: description
## Usage
```
const uniMpLark = require('uni-mp-lark');
// TODO: DEMONSTRATE API
```
此差异已折叠。
const compiler = require('@dcloudio/uni-mp-weixin/lib/uni.compiler.js')
const path = require('path')
const t = require('@babel/types')
const crypto = require('crypto')
function generateJsCode (properties = '{}') {
return `tt.createComponent({
generic: true,
props: ${properties},
render: function(){}
})
`
}
function generateCssCode (filename) {
return `@import "./${filename}"
`
}
function getBaseName (ownerName, parentName, slotName, resourcePath) {
const str = `${resourcePath}/${parentName}/${slotName}`
const md5 = crypto.createHash('md5').update(str).digest('hex')
if (process.env.NODE_ENV !== 'development') {
return `m${md5.substring(0, 8)}`
}
return `${ownerName}--${parentName}--${slotName}--${md5.substring(0, 4)}`
}
function hasOwn (obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key)
}
module.exports = Object.assign({}, compiler, {
directive: 'tt:',
resolveScopedSlots (slotName, {
genCode,
generate,
ownerName,
parentName,
parentNode,
resourcePath,
paramExprNode,
returnExprNodes,
traverseExpr
}, state) {
if (!state.scopedSlots) {
state.scopedSlots = {}
}
const baseName = getBaseName(ownerName, parentName, slotName, resourcePath)
let componentName = baseName
if (!hasOwn(state.scopedSlots, baseName)) {
state.scopedSlots[baseName] = 0
}
if (state.scopedSlots[baseName]) {
componentName = baseName + state.scopedSlots[baseName]
}
state.scopedSlots[baseName]++
if (!parentNode.attr.generic) {
parentNode.attr.generic = {}
}
parentNode.attr.generic[slotName] = componentName
// 生成 scopedSlots 文件,包括 json,js, ttml, ttss, 还需要更新 owner 的 usingComponents
if (!state.files) {
state.files = {}
}
const extname = path.extname(resourcePath)
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) { }
// webpack-uni-mp-loader/lib/plugin/generate-component 处理 json 文件还有修改 slot 模版
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 ''
}
})
module.exports = {
options: {
cssVars: {
'--status-bar-height': '25px',
'--window-top': '0px',
'--window-bottom': '0px',
'--window-left': '0px',
'--window-right': '0px'
},
extnames: {
style: '.ttss',
template: '.ttml'
},
subPackages: true,
project: 'project.lark.json'
},
copyWebpackOptions (platformOptions, vueOptions) {
const copyOptions = ['ttcomponents']
global.uniModules.forEach(module => {
copyOptions.push('uni_modules/' + module + '/ttcomponents')
})
return copyOptions
}
}
{
"name": "@dcloudio/uni-mp-lark",
"version": "2.0.0-alpha-32520210827002",
"description": "uni-app mp-lark",
"main": "dist/index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/dcloudio/uni-app.git",
"directory": "packages/uni-mp-lark"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
},
"author": "PerfectPan",
"license": "Apache-2.0",
"uni-app": {
"name": "mp-lark",
"title": "飞书小程序"
}
}
......@@ -79,7 +79,8 @@ export const getPlatformName = () => {
'mp-toutiao': 'tt',
'mp-qq': 'qq',
'quickapp-native': 'qn',
'mp-kuaishou': 'ks'
'mp-kuaishou': 'ks',
'mp-lark': 'lark'
}
return platformList[process.env.VUE_APP_PLATFORM];
}
......
......@@ -94,7 +94,7 @@ module.exports = {
if (options.mp.platform === 'mp-alipay') {
options.modules.push(compilerAlipayModule)
} else if (options.mp.platform === 'mp-toutiao') {
} else if (options.mp.platform === 'mp-toutiao' || options.mp.platform === 'mp-lark') {
options.modules.push(compilerToutiaoModule)
}
......@@ -150,7 +150,7 @@ module.exports = {
delete state.files
// resolve scoped slots
res.generic = state.generic || []
res.generic = state.generic || []
delete state.generic
// define scoped slots
......@@ -290,4 +290,4 @@ at ${resourcePath}.vue:1`)
ssrCompile,
ssrCompileToFunctions,
generateCodeFrame
}
}
......@@ -38,10 +38,9 @@ function processStaticClass (classArrayExpression, staticClassPath, state) {
}
staticClassPath.remove()
}
if (
state.options.platform.name === 'mp-toutiao' ||
state.options.platform.name === 'mp-alipay'
) {
const transPlatform = ['mp-toutiao', 'mp-alipay', 'mp-lark'];
if (transPlatform.includes(state.options.platform.name)) {
// classArrayExpression => binaryExpression
return processClassArrayExpressionElements(classArrayExpression)
}
......
......@@ -71,8 +71,8 @@ function processElement (ast, state, isRoot) {
Object.keys(ast.attr.generic).forEach(scopedSlotName => {
slots.push(scopedSlotName)
})
if (platformName === 'mp-toutiao') {
// 用于字节跳动小程序模拟抽象节点
if (platformName === 'mp-toutiao' || platformName === 'mp-lark') {
// 用于字节跳动|飞书小程序模拟抽象节点
ast.attr.generic = `{{${JSON.stringify(ast.attr.generic)}}}`.replace(/"/g, '\'')
} else {
delete ast.attr.generic
......
......@@ -231,10 +231,10 @@ function traverseDataNode (dataNode, state, node) {
let key
// 自定义组件不支持 hidden 属性
const platform = state.options.platform.name
const platforms = ['mp-weixin', 'mp-qq', 'mp-toutiao']
const platforms = ['mp-weixin', 'mp-qq', 'mp-toutiao', 'mp-lark']
if (isComponent(node.type) && platforms.includes(platform)) {
// 字节跳动小程序自定义属性不会反应在DOM上,只能使用事件格式
key = `${platform === 'mp-toutiao' ? 'bind:-' : ''}${ATTE_DATA_CUSTOM_HIDDEN}`
// 字节跳动|飞书小程序自定义属性不会反应在DOM上,只能使用事件格式
key = `${platform === 'mp-toutiao' || platform === 'mp-lark' ? 'bind:-' : ''}${ATTE_DATA_CUSTOM_HIDDEN}`
} else {
key = 'hidden'
}
......
......@@ -41,7 +41,7 @@ module.exports = (api, options) => {
}
}
const platforms = ['mp-weixin', 'mp-qq', 'mp-baidu', 'mp-alipay', 'mp-toutiao']
const platforms = ['mp-weixin', 'mp-qq', 'mp-baidu', 'mp-alipay', 'mp-toutiao', 'mp-lark']
if (args.subpackage && platforms.includes(process.env.UNI_PLATFORM)) {
process.env.UNI_SUBPACKGE = args.subpackage
}
......
......@@ -17,6 +17,7 @@ module.exports = (api, options, rootOptions) => {
'dev:mp-alipay': 'cross-env NODE_ENV=development UNI_PLATFORM=mp-alipay vue-cli-service uni-build --watch',
'dev:mp-toutiao': 'cross-env NODE_ENV=development UNI_PLATFORM=mp-toutiao vue-cli-service uni-build --watch',
'dev:mp-kuaishou': 'cross-env NODE_ENV=development UNI_PLATFORM=mp-kuaishou vue-cli-service uni-build --watch',
'dev:mp-lark': 'cross-env NODE_ENV=development UNI_PLATFORM=mp-lark vue-cli-service uni-build --watch',
'dev:quickapp-native': 'cross-env NODE_ENV=development UNI_PLATFORM=quickapp-native vue-cli-service uni-build --watch',
'dev:quickapp-webview': 'cross-env NODE_ENV=development UNI_PLATFORM=quickapp-webview vue-cli-service uni-build --watch',
'dev:quickapp-webview-huawei': 'cross-env NODE_ENV=development UNI_PLATFORM=quickapp-webview-huawei vue-cli-service uni-build --watch',
......@@ -28,6 +29,7 @@ module.exports = (api, options, rootOptions) => {
'build:mp-alipay': 'cross-env NODE_ENV=production UNI_PLATFORM=mp-alipay vue-cli-service uni-build',
'build:mp-toutiao': 'cross-env NODE_ENV=production UNI_PLATFORM=mp-toutiao vue-cli-service uni-build',
'build:mp-kuaishou': 'cross-env NODE_ENV=production UNI_PLATFORM=mp-kuaishou vue-cli-service uni-build',
'build:mp-lark': 'cross-env NODE_ENV=production UNI_PLATFORM=mp-lark vue-cli-service uni-build',
'build:quickapp-native': 'cross-env NODE_ENV=production UNI_PLATFORM=quickapp-native vue-cli-service uni-build',
'build:quickapp-webview': 'cross-env NODE_ENV=production UNI_PLATFORM=quickapp-webview vue-cli-service uni-build',
'build:quickapp-webview-huawei': 'cross-env NODE_ENV=production UNI_PLATFORM=quickapp-webview-huawei vue-cli-service uni-build',
......
......@@ -410,7 +410,7 @@ if (process.env.UNI_PLATFORM === 'h5') {
moduleAlias.addAlias('vue-style-loader', '@dcloudio/vue-cli-plugin-uni/packages/h5-vue-style-loader')
}
if (process.env.UNI_PLATFORM === 'mp-toutiao') {
if (process.env.UNI_PLATFORM === 'mp-toutiao' || process.env.UNI_PLATFORM === 'mp-lark') {
// !important 始终带有一个空格
moduleAlias.addAlias(
'postcss-normalize-whitespace',
......
......@@ -47,7 +47,7 @@ module.exports = function generateApp (compilation) {
// 框架预设样式 用于隐藏自定义组件
// TODO 分平台 import 不同 css
const platforms = ['mp-weixin', 'mp-qq', 'mp-toutiao']
const platforms = ['mp-weixin', 'mp-qq', 'mp-toutiao', 'mp-lark']
const presetStyle = platforms.includes(process.env.UNI_PLATFORM) ? '[data-custom-hidden="true"],[bind-data-custom-hidden="true"]{display: none !important;}' : ''
if (compilation.assets[`common/main${ext}`]) { // 是否存在 main.css
......
......@@ -145,7 +145,7 @@ module.exports = function generateComponent (compilation, jsonpFunction = 'webpa
}
}
}
// 处理字节跳动小程序作用域插槽
// 处理字节跳动|飞书小程序作用域插槽
const fixExtname = '.fix'
if (name.endsWith(fixExtname)) {
const source = assets[name].source()
......
......@@ -214,4 +214,4 @@ module.exports = function (content, map) {
}
this.callback(null, '', map)
}
}
......@@ -107,4 +107,4 @@ module.exports = function (content, map) {
})
this.callback(null, '', map)
}
}
module.exports = function (pagesJson, manifestJson) {
const {
app,
project
} = require('../mp')(pagesJson, manifestJson, require('./project.config.json'))
return [app, project]
}
\ No newline at end of file
{
"setting": {
"urlCheck": true,
"es6": false,
"postcss": false,
"minified": false,
"newFeature": true
},
"appid": "testAppId",
"projectname": ""
}
......@@ -29,6 +29,9 @@ import {
import {
initWebviewApi as initKuaishouWebviewApi
} from 'uni-platforms/mp-kuaishou/runtime/web-view'
import {
initWebviewApi as initLarkWebviewApi
} from 'uni-platforms/mp-lark/runtime/web-view'
const UniAppJSBridgeReady = function () {
window.UniAppJSBridge = true
......@@ -47,6 +50,7 @@ const initWebviewApis = [
initToutiaoWebviewApi,
initQuickappWebviewApi,
initKuaishouWebviewApi,
initLarkWebviewApi,
initH5WebviewApi
]
......
import navigateTo from 'uni-helpers/navigate-to'
import redirectTo from '../../../mp-weixin/helpers/redirect-to'
import previewImage from '../../../mp-weixin/helpers/normalize-preview-image'
import getSystemInfo from '../../../mp-weixin/helpers/system-info'
import getUserProfile from '../../../mp-weixin/helpers/get-user-profile'
// 需要做转换的 API 列表
export const protocols = {
navigateTo,
redirectTo,
previewImage,
getSystemInfo,
getSystemInfoSync: getSystemInfo,
getUserProfile,
connectSocket: {
args: {
method: false
}
},
chooseVideo: {
args: {
camera: false
}
},
scanCode: {
args: {
onlyFromCamera: false
}
},
startAccelerometer: {
args: {
interval: false
}
},
showToast: {
args: {
image: false
}
},
showModal: {
args: {
cancelColor: false,
confirmColor: false
}
},
showActionSheet: {
args: {
itemColor: false,
alertText: false
}
},
login: {
args: {
scopes: false,
timeout: false
}
},
getUserInfo: {
args: {
lang: false,
timeout: false
}
}
}
export const todos = []
export const canIUses = []
export default {
oauth: ['lark'],
share: ['lark'],
payment: [''],
push: ['']
}
import '../../mp-weixin/runtime/index'
const isLark = window.tt &&
window.tt.miniProgram &&
/Lark|Feishu/i.test(navigator.userAgent)
export function initWebviewApi (readyCallback) {
if (!isLark) {
return
}
document.addEventListener('DOMContentLoaded', readyCallback)
return window.tt.miniProgram
}
import Vue from 'vue'
import parseBaseApp from '../../../mp-weixin/runtime/wrapper/app-base-parser'
import {
mocks,
initRefs
} from './util'
export default function parseApp (vm) {
Vue.prototype._$fallback = true // 降级(调整原 vue 的部分生命周期,如 created,beforeMount,inject,provide)
Vue.mixin({
created () { // 处理 injections, triggerEvent 是异步,且触发时机很慢,故延迟 relation 设置
if (this.mpType !== 'app') {
if (this.mpType === 'page' && !this.$scope.route && this.$scope.__route__
) {
this.$scope.route = this.$scope.__route__
}
initRefs(this)
this.__init_injections(this)
this.__init_provide(this)
}
}
})
return parseBaseApp(vm, {
mocks,
initRefs: function () {} // attached 时,可能查询不到
})
}
import {
isPage,
initRelation,
handleLink
} from './util'
import {
initSlots,
initVueIds
} from 'uni-wrapper/util'
import parseBaseComponent from '../../../mp-weixin/runtime/wrapper/component-base-parser'
export default function parseComponent (vueOptions) {
const [componentOptions, VueComponent] = parseBaseComponent(vueOptions)
componentOptions.lifetimes.attached = function attached () {
const properties = this.properties
const options = {
mpType: isPage.call(this) ? 'page' : 'component',
mpInstance: this,
propsData: properties
}
initVueIds(properties.vueId, this)
// 初始化 vue 实例
this.$vm = new VueComponent(options)
// 处理$slots,$scopedSlots(暂不支持动态变化$slots)
initSlots(this.$vm, properties.vueSlots)
// 处理父子关系
initRelation.call(this, {
vuePid: this._$vuePid,
mpInstance: this
})
// 触发首次 setData
this.$vm.$mount()
}
// ready 比 handleLink 还早,初始化逻辑放到 handleLink 中
delete componentOptions.lifetimes.ready
componentOptions.methods.__l = handleLink
return componentOptions
}
import {
isPage,
instances,
initRelation
} from './util'
import parseBasePage from '../../../mp-weixin/runtime/wrapper/page-base-parser'
export default function parsePage (vuePageOptions) {
const pageOptions = parseBasePage(vuePageOptions, {
isPage,
initRelation
})
// 页面需要在 ready 中触发,其他组件是在 handleLink 中触发
pageOptions.lifetimes.ready = function ready () {
if (this.$vm && this.$vm.mpType === 'page') {
this.$vm.__call_hook('created')
this.$vm.__call_hook('beforeMount')
this.$vm._isMounted = true
this.$vm.__call_hook('mounted')
this.$vm.__call_hook('onReady')
} else {
this.is && console.warn(this.is + ' is not ready')
}
}
pageOptions.lifetimes.detached = function detached () {
this.$vm && this.$vm.$destroy()
// 清理
const webviewId = this.__webviewId__
webviewId && Object.keys(instances).forEach(key => {
if (key.indexOf(webviewId + '_') === 0) {
delete instances[key]
}
})
}
return pageOptions
}
import {
findVmByVueId,
initRefs
} from '../../../mp-weixin/runtime/wrapper/util'
export { initRefs }
export const mocks = ['__route__', '__webviewId__', '__nodeid__', '__nodeId__']
export const instances = Object.create(null)
export function isPage () {
return this.__nodeid__ === 0 || this.__nodeId__ === 0
}
export function initRelation ({
vuePid,
mpInstance
}) {
// triggerEvent 后,接收事件时机特别晚,已经到了 ready 之后
const nodeId = (mpInstance.__nodeId__ || mpInstance.__nodeid__) + ''
const webviewId = mpInstance.__webviewId__ + ''
instances[webviewId + '_' + nodeId] = mpInstance.$vm
this.triggerEvent('__l', {
vuePid,
nodeId,
webviewId
})
}
export function handleLink ({
detail: {
vuePid,
nodeId,
webviewId
}
}) {
const vm = instances[webviewId + '_' + nodeId]
if (!vm) {
return
}
let parentVm
if (vuePid) {
parentVm = findVmByVueId(this.$vm, vuePid)
}
if (!parentVm) {
parentVm = this.$vm
}
vm.$parent = parentVm
vm.$root = parentVm.$root
parentVm.$children.push(vm)
vm.__call_hook('created')
vm.__call_hook('beforeMount')
vm._isMounted = true
vm.__call_hook('mounted')
vm.__call_hook('onReady')
}
......@@ -111,7 +111,7 @@ export default function parseBaseApp (vm, {
}) {
initEventChannel()
if (__PLATFORM__ === 'mp-weixin' || __PLATFORM__ === 'mp-qq' || __PLATFORM__ === 'mp-toutiao' || __PLATFORM__ ===
'mp-kuaishou' || __PLATFORM__ === 'mp-alipay' || __PLATFORM__ === 'mp-baidu') {
'mp-kuaishou' || __PLATFORM__ === 'mp-alipay' || __PLATFORM__ === 'mp-baidu' || __PLATFORM__ === 'mp-lark') {
initScopedSlotsParams()
}
if (vm.$options.store) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册