提交 302ed498 编写于 作者: fxy060608's avatar fxy060608

wip(mp): mp-alipay

上级 e336f92e
...@@ -4,6 +4,12 @@ import { LINEFEED } from '@dcloudio/uni-shared' ...@@ -4,6 +4,12 @@ import { LINEFEED } from '@dcloudio/uni-shared'
import { normalizeMiniProgramFilename } from '../utils' import { normalizeMiniProgramFilename } from '../utils'
export interface MiniProgramCompilerOptions { export interface MiniProgramCompilerOptions {
event?: {
format(
name: string,
opts: { isCatch?: boolean; isCapture?: boolean; isComponent?: boolean }
): string
}
class: { class: {
/** /**
* 是否支持绑定 array 类型 * 是否支持绑定 array 类型
......
import { tags } from '../src/compiler/options'
import { assert } from './testUtils'
describe('mp-alipay: transform component', () => {
test(`built-in component`, () => {
const code = tags.map((tag) => `<${tag}/>`).join('')
assert(
code,
code,
`(_ctx, _cache) => {
return {}
}`
)
})
})
import { assert } from './testUtils' import { assert } from './testUtils'
describe('compiler: transform ref', () => { describe('mp-alipay: transform ref', () => {
test('without ref', () => { test('without ref', () => {
assert( assert(
`<custom/>`, `<custom/>`,
......
import { isCustomElement, isNativeTag } from '@dcloudio/uni-shared' import { isNativeTag } from '@dcloudio/uni-shared'
import { compile, CompilerOptions } from '@dcloudio/uni-mp-compiler' import { compile, CompilerOptions } from '@dcloudio/uni-mp-compiler'
import { miniProgram, nodeTransforms } from '../src/compiler/options' import {
isCustomElement,
miniProgram,
nodeTransforms,
} from '../src/compiler/options'
export function assert( export function assert(
template: string, template: string,
......
import { assert } from './testUtils'
describe('mp-alipay: transform v-on', () => {
test('basic', () => {
assert(
`<view v-on:click="onClick"/>`,
`<view onTap="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _o(_ctx.onClick) }
}`
)
assert(
`<custom v-on:click="onClick"/>`,
`<custom onClick="{{a}}" v-i="2a9ec0b0-0" onVI="__l"/>`,
`(_ctx, _cache) => {
return { a: _o(_ctx.onClick) }
}`
)
})
test('dynamic arg', () => {
// <view v-on:[event]="handler"/>
})
test('dynamic arg with prefixing', () => {
// <view v-on:[event]="handler"/>
})
test('dynamic arg with complex exp prefixing', () => {
// <view v-on:[event(foo)]="handler"/>
})
test('should wrap as function if expression is inline statement', () => {
assert(
`<view @click="i++"/>`,
`<view onTap="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _o($event => _ctx.i++) }
}`
)
})
test('should handle multiple inline statement', () => {
assert(
`<view @click="foo();bar()"/>`,
`<view onTap="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _o($event => { _ctx.foo(); _ctx.bar(); }) }
}`
)
})
test('should handle multi-line statement', () => {
assert(
`<view @click="\nfoo();\nbar()\n"/>`,
`<view onTap="{{a}}"/>`,
`(_ctx, _cache) => {
with (_ctx) {
const { o: _o } = _Vue
return { a: _o($event => { foo(); bar(); }) }
}
}`,
{ prefixIdentifiers: false, mode: 'function' }
)
})
test('inline statement w/ prefixIdentifiers: true', () => {
assert(
`<view @click="foo($event)"/>`,
`<view onTap="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _o($event => _ctx.foo($event)) }
}`
)
})
test('multiple inline statements w/ prefixIdentifiers: true', () => {
assert(
`<view @click="foo($event);bar()"/>`,
`<view onTap="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _o($event => { _ctx.foo($event); _ctx.bar(); }) }
}`
)
})
test('should NOT wrap as function if expression is already function expression', () => {
assert(
`<view @click="$event => foo($event)"/>`,
`<view onTap="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _o($event => _ctx.foo($event)) }
}`
)
})
test('should NOT wrap as function if expression is already function expression (with newlines)', () => {
assert(
`<view @click="
$event => {
foo($event)
}
"/>`,
`<view onTap="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _o($event => { _ctx.foo($event); }) }
}`
)
})
test('should NOT wrap as function if expression is already function expression (with newlines + function keyword)', () => {
assert(
`<view @click="
function($event) {
foo($event)
}
"/>`,
`<view onTap="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _o(function ($event) { _ctx.foo($event); }) }
}`
)
})
test('should NOT wrap as function if expression is complex member expression', () => {
assert(
`<view @click="a['b' + c]"/>`,
`<view onTap="{{a}}"/>`,
`(_ctx, _cache) => {
with (_ctx) {
const { o: _o } = _Vue
return { a: _o(a['b' + c]) }
}
}`,
{
prefixIdentifiers: false,
mode: 'function',
}
)
})
test('complex member expression w/ prefixIdentifiers: true', () => {
assert(
`<view @click="a['b' + c]"/>`,
`<view onTap="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _o(_ctx.a['b' + _ctx.c]) }
}`
)
})
test('function expression w/ prefixIdentifiers: true', () => {
assert(
`<view @click="e => foo(e)"/>`,
`<view onTap="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _o(e => _ctx.foo(e)) }
}`
)
})
test('case conversion for kebab-case events', () => {
assert(
`<view v-on:foo-bar="onMount"/>`,
`<view onFooBar="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _o(_ctx.onMount) }
}`
)
})
test('case conversion for vnode hooks', () => {
assert(
`<view v-on:vnode-mounted="onMount"/>`,
`<view onVnodeMounted="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _o(_ctx.onMount) }
}`
)
})
describe('cacheHandler', () => {
test('empty handler', () => {
assert(
`<view v-on:click.prevent />`,
`<view catchTap="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _o(() => {}) }
}`
)
})
test('member expression handler', () => {
// <div v-on:click="foo" />
})
test('compound member expression handler', () => {
// <div v-on:click="foo.bar" />
})
test('bail on component member expression handler', () => {
// <comp v-on:click="foo" />
})
test('should not be cached inside v-once', () => {
// <div v-once><div v-on:click="foo"/></div>
})
test('inline function expression handler', () => {
// <div v-on:click="() => foo()" />
})
test('inline async arrow function expression handler', () => {
// <div v-on:click="async () => await foo()" />
})
test('inline async function expression handler', () => {
// <div v-on:click="async function () { await foo() } " />
})
test('inline statement handler', () => {
// <div v-on:click="foo++" />
})
})
})
import { assert } from './testUtils' import { assert } from './testUtils'
describe('compiler: transform v-slot', () => { describe('mp-alipay: transform v-slot', () => {
test('default slot', () => { test('default slot', () => {
assert( assert(
`<custom><template v-slot/></custom>`, `<custom><template v-slot/></custom>`,
......
...@@ -6,7 +6,12 @@ ...@@ -6,7 +6,12 @@
"output": { "output": {
"format": "cjs" "format": "cjs"
}, },
"external": ["@dcloudio/uni-cli-shared", "@dcloudio/uni-mp-vite"] "external": [
"@vue/shared",
"@vue/compiler-core",
"@dcloudio/uni-cli-shared",
"@dcloudio/uni-mp-vite"
]
}, },
{ {
"input": { "input": {
......
import { camelize, capitalize } from '@vue/shared'
import { MiniProgramCompilerOptions } from '@dcloudio/uni-cli-shared'
export const event: MiniProgramCompilerOptions['event'] = {
format(name, { isCatch, isComponent }) {
if (!isComponent && name === 'click') {
name = 'tap'
}
name = eventMap[name] || name
return `${isCatch ? 'catch' : 'on'}${capitalize(camelize(name))}`
},
}
const eventMap: Record<string, string> = {
touchstart: 'touchStart',
touchmove: 'touchMove',
touchend: 'touchEnd',
touchcancel: 'touchCancel',
longtap: 'longTap',
longpress: 'longTap',
transitionend: 'transitionEnd',
animationstart: 'animationStart',
animationiteration: 'animationIteration',
animationend: 'animationEnd',
firstappear: 'firstAppear',
// map
markertap: 'markerTap',
callouttap: 'calloutTap',
controltap: 'controlTap',
regionchange: 'regionChange',
paneltap: 'panelTap',
// scroll-view
scrolltoupper: 'scrollToUpper',
scrolltolower: 'scrollToLower',
// movable-view
changeend: 'changeEnd',
// video
timeupdate: 'timeUpdate',
waiting: 'loading',
fullscreenchange: 'fullScreenChange',
useraction: 'userAction',
renderstart: 'renderStart',
loadedmetadata: 'renderStart',
// swiper
animationfinish: 'animationEnd',
}
import path from 'path' import path from 'path'
import { NodeTypes } from '@vue/compiler-core'
import { import {
COMPONENT_ON_LINK, COMPONENT_ON_LINK,
createTransformComponentLink, createTransformComponentLink,
MiniProgramCompilerOptions, MiniProgramCompilerOptions,
} from '@dcloudio/uni-cli-shared' } from '@dcloudio/uni-cli-shared'
import { UniMiniProgramPluginOptions } from '@dcloudio/uni-mp-vite' import { UniMiniProgramPluginOptions } from '@dcloudio/uni-mp-vite'
import { NodeTypes } from '@vue/compiler-core'
import source from './mini.project.json' import source from './mini.project.json'
import { transformRef } from './transforms/transformRef' import { transformRef } from './transforms/transformRef'
import { event } from './event'
const projectConfigFilename = 'mini.project.json' const projectConfigFilename = 'mini.project.json'
export const miniProgram: MiniProgramCompilerOptions = { export const miniProgram: MiniProgramCompilerOptions = {
event,
class: { class: {
array: false, array: false,
}, },
...@@ -20,10 +23,27 @@ export const miniProgram: MiniProgramCompilerOptions = { ...@@ -20,10 +23,27 @@ export const miniProgram: MiniProgramCompilerOptions = {
}, },
directive: 'a:', directive: 'a:',
} }
// TODO getPhoneNumber 等事件
export const nodeTransforms = [ export const nodeTransforms = [
transformRef, transformRef,
createTransformComponentLink(COMPONENT_ON_LINK, NodeTypes.ATTRIBUTE), createTransformComponentLink(COMPONENT_ON_LINK, NodeTypes.ATTRIBUTE),
] ]
export const tags = [
'lifestyle',
'life-follow',
'contact-button',
'spread',
'error-view',
'poster',
'cashier',
'ix-grid',
'ix-native-grid',
'ix-native-list',
'mkt',
]
export function isCustomElement(tag: string) {
return tags.includes(tag)
}
export const options: UniMiniProgramPluginOptions = { export const options: UniMiniProgramPluginOptions = {
vite: { vite: {
inject: { inject: {
...@@ -52,16 +72,14 @@ export const options: UniMiniProgramPluginOptions = { ...@@ -52,16 +72,14 @@ export const options: UniMiniProgramPluginOptions = {
extname: '.sjs', extname: '.sjs',
lang: 'sjs', lang: 'sjs',
generate(filter, filename) { generate(filter, filename) {
if (filename) { // TODO 标签内的 code 代码需要独立生成一个 sjs 文件
return `<sjs src="${filename}.sjs" module="${filter.name}"/>` // 暂不处理,让开发者自己全部使用 src 引入
} return `<import-sjs name="${filter.name}" from="${filename}.sjs"/>`
return `<sjs module="${filter.name}">
${filter.code}
</sjs>`
}, },
}, },
extname: '.axml', extname: '.axml',
compilerOptions: { compilerOptions: {
isCustomElement,
nodeTransforms, nodeTransforms,
}, },
}, },
......
...@@ -23,6 +23,13 @@ describe('compiler: transform v-on', () => { ...@@ -23,6 +23,13 @@ describe('compiler: transform v-on', () => {
`<view bindtap="{{a}}"/>`, `<view bindtap="{{a}}"/>`,
`(_ctx, _cache) => { `(_ctx, _cache) => {
return { a: _o(_ctx.onClick) } return { a: _o(_ctx.onClick) }
}`
)
assert(
`<custom v-on:click="onClick"/>`,
`<custom bindclick="{{a}}" v-i="2a9ec0b0-0"/>`,
`(_ctx, _cache) => {
return { a: _o(_ctx.onClick) }
}` }`
) )
}) })
......
...@@ -87,13 +87,21 @@ export function baseCompile(template: string, options: CompilerOptions = {}) { ...@@ -87,13 +87,21 @@ export function baseCompile(template: string, options: CompilerOptions = {}) {
{ ast } { ast }
) )
if (options.filename && options.miniProgram?.emitFile) { if (options.filename && options.miniProgram?.emitFile) {
const {
class: clazz,
directive,
emitFile,
event,
slot,
} = options.miniProgram
genTemplate(ast, { genTemplate(ast, {
class: options.miniProgram.class, class: clazz,
scopeId: options.scopeId, scopeId: options.scopeId,
filename: options.filename, filename: options.filename,
directive: options.miniProgram.directive, directive,
emitFile: options.miniProgram.emitFile, emitFile,
slot: options.miniProgram.slot, event,
slot,
}) })
} }
......
...@@ -27,16 +27,25 @@ interface TemplateCodegenContext { ...@@ -27,16 +27,25 @@ interface TemplateCodegenContext {
code: string code: string
directive: string directive: string
scopeId?: string | null scopeId?: string | null
event: MiniProgramCompilerOptions['event']
slot: MiniProgramCompilerOptions['slot'] slot: MiniProgramCompilerOptions['slot']
push(code: string): void push(code: string): void
} }
export function generate( export function generate(
{ children }: RootNode, { children }: RootNode,
{ slot, scopeId, emitFile, filename, directive }: TemplateCodegenOptions {
slot,
event,
scopeId,
emitFile,
filename,
directive,
}: TemplateCodegenOptions
) { ) {
const context: TemplateCodegenContext = { const context: TemplateCodegenContext = {
slot, slot,
event,
code: '', code: '',
scopeId, scopeId,
directive, directive,
...@@ -264,12 +273,12 @@ export function genElementProps( ...@@ -264,12 +273,12 @@ export function genElementProps(
function genOn( function genOn(
prop: DirectiveNode, prop: DirectiveNode,
node: ElementNode, node: ElementNode,
{ push }: TemplateCodegenContext { push, event }: TemplateCodegenContext
) { ) {
const arg = (prop.arg as SimpleExpressionNode).content const arg = (prop.arg as SimpleExpressionNode).content
const exp = prop.exp as SimpleExpressionNode const exp = prop.exp as SimpleExpressionNode
const modifiers = prop.modifiers const modifiers = prop.modifiers
const name = formatMiniProgramEvent(arg, { const name = (event?.format || formatMiniProgramEvent)(arg, {
isCatch: modifiers.includes('stop') || modifiers.includes('prevent'), isCatch: modifiers.includes('stop') || modifiers.includes('prevent'),
isCapture: modifiers.includes('capture'), isCapture: modifiers.includes('capture'),
isComponent: node.tagType === ElementTypes.COMPONENT, isComponent: node.tagType === ElementTypes.COMPONENT,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册