diff --git a/packages/shared/package.json b/packages/shared/package.json index 9a7d5b7f9ae0..336c54fc04b4 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -16,7 +16,8 @@ "url": "git+https://github.com/NervJS/taro.git" }, "scripts": { - "build": "rollup -c" + "build": "rollup -c", + "dev": "rollup -c -w" }, "bugs": { "url": "https://github.com/NervJS/taro/issues" diff --git a/packages/shared/src/components.ts b/packages/shared/src/components.ts index 160ba428fa04..c425d07e6ebc 100644 --- a/packages/shared/src/components.ts +++ b/packages/shared/src/components.ts @@ -1,13 +1,11 @@ import { Shortcuts } from './shortcuts' -import { toDashed, hasOwn, toCamelCase } from './utils' -import { isBooleanStringLiteral, isNumber } from './is' -const styles = { +export const styles = { style: `i.${Shortcuts.Style}`, class: `i.${Shortcuts.Class}` } -const events = { +export const events = { bindtap: 'eh' } @@ -19,12 +17,13 @@ const touchEvents = { bindLongTap: '' } -const alipayEvents = { - onTap: 'eh', - onTouchMove: 'eh', - onTouchEnd: 'eh', - onTouchCancel: 'eh', - onLongTap: 'eh' +export const specialEvents = new Set([ + 'htouchmove', + 'vtouchmove' +]) + +export function singleQuote (s: string) { + return `'${s}'` } const View = { @@ -371,8 +370,8 @@ const ScrollView = { bindRefresherRefresh: '', bindRefresherRestore: '', bindRefresherAbort: '', - bindScrolltoUpper: '', - bindScrolltoLower: '', + bindScrollToUpper: '', + bindScrollToLower: '', bindScroll: '', animation: '', bindTransitionEnd: '', @@ -382,10 +381,6 @@ const ScrollView = { ...touchEvents } -function singleQuote (s: string) { - return `'${s}'` -} - const Swiper = { 'indicator-dots': 'false', 'indicator-color': singleQuote('rgba(0, 0, 0, .3)'), @@ -453,11 +448,6 @@ const Audio = { bindEnded: '' } -const specialEvents = new Set([ - 'htouchmove', - 'vtouchmove' -]) - const Camera = { mode: singleQuote('normal'), 'device-position': singleQuote('back'), @@ -671,60 +661,6 @@ const Slot = { name: '' } -interface Components { - [key: string]: Record; -} - -export function createMiniComponents (components: Components, buildType: string) { - const result: Components = Object.create(null) - const isAlipay = buildType === 'alipay' - - for (const key in components) { - if (hasOwn(components, key)) { - const component = components[key] - const compName = toDashed(key) - const newComp: Record = Object.create(null) - for (let prop in component) { - if (hasOwn(component, prop)) { - let propValue = component[prop] - if (prop.startsWith('bind') || specialEvents.has(prop)) { - prop = isAlipay ? prop.replace('bind', 'on') : prop.toLowerCase() - if ((buildType === 'weapp' || buildType === 'qq') && prop === 'bindlongtap') { - prop = 'bindlongpress' - } - propValue = 'eh' - } else if (propValue === '') { - propValue = `i.${toCamelCase(prop)}` - } else if (isBooleanStringLiteral(propValue) || isNumber(+propValue)) { - propValue = `i.${toCamelCase(prop)} === undefined ? ${propValue} : i.${toCamelCase(prop)}` - } else { - propValue = `i.${toCamelCase(prop)} || ${propValue || singleQuote('')}` - } - - newComp[prop] = propValue - } - } - if (compName !== 'block') { - Object.assign(newComp, styles, isAlipay ? alipayEvents : events) - } - - if (compName === 'swiper-item') { - delete newComp.style - } - - if (compName === 'slot' || compName === 'slot-view') { - result[compName] = { - slot: 'i.name' - } - } else { - result[compName] = newComp - } - } - } - - return result -} - export const internalComponents = { View, Icon, diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 37363b1ba9e0..9c2c4cfa33c4 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -2,3 +2,4 @@ export * from './is' export { Shortcuts } from './shortcuts' export * from './components' export * from './utils' +export * from './template' diff --git a/packages/shared/src/template.ts b/packages/shared/src/template.ts new file mode 100644 index 000000000000..dd8725fc52d7 --- /dev/null +++ b/packages/shared/src/template.ts @@ -0,0 +1,395 @@ +/** + * 这里我们需要关心的小程序种类有两类: + * 1. 模板递归: + * - 支持:tmpl0 套 tmpl0 + * - 不支持:这就使得我们必须生成多级的模板,tmpl0 套 tmpl1,tmpl1 套 tmpl2…… + * 直到超过阈值 N (N = config.miniapp.baseLevel) tmplN 会套组件 comp,组件 comp 重新再套 tmpl0。 + * 2. 小程序脚本语言(wxs, sjs, etc...): + * - 支持:可以在模板使用函数缩减模板大小或提高性能(存疑),例如判断一个值是不是假值(falsy value)。 + * 将来或许会把数据序列化^1 的操作也放到小程序脚本语言里。 + * - 不支持:使用纯 *xml 语法 + * + * ^1: packages/taro-runtime/src/hydrate.ts +*/ + +import { + internalComponents, + focusComponents, + styles, + events, + specialEvents, + singleQuote +} from './components' +import { Shortcuts } from './shortcuts' +import { isBooleanStringLiteral, isNumber, isFunction } from './is' +import { toCamelCase, toDashed, hasOwn } from './utils' + +interface Component { + nodeName: string; + attributes: Attributes; +} + +interface Components { + [key: string]: Record; +} + +interface ComponentConfig { + includes: Set + exclude: Set + thirdPartyComponents: Map> + includeAll: boolean +} + +export interface IAdapter { + if: string; + else: string; + elseif: string; + for: string; + forItem: string; + forIndex: string; + key: string; + xs?: string, + type: string; +} + +export type Attributes = Record + +const voidElements = new Set([ + 'progress', + 'icon', + 'rich-text', + 'input', + 'textarea', + 'slider', + 'switch', + 'audio', + 'live-player', + 'live-pusher', + 'video' +]) + +const weixinAdapter: IAdapter = { + if: 'wx:if', + else: 'wx:else', + elseif: 'wx:elif', + for: 'wx:for', + forItem: 'wx:for-item', + forIndex: 'wx:for-index', + key: 'wx:key', + xs: 'wxs', + type: 'weapp' +} + +export class BaseTemplate { + protected exportExpr = 'module.exports =' + protected isSupportRecursive: boolean + protected supportXS = false + protected miniComponents: Components + protected modifyCompProps?: (compName: string, target: Record) => Record + protected modifyLoopBody?: (child: string, nodeName: string) => string + protected modifyLoopContainer?: (children: string, nodeName: string) => string + protected modifyTemplateResult?: (res: string, nodeName: string, level: number, children: string) => string + + public Adapter = weixinAdapter + + private buildAttribute (attrs: Attributes, nodeName: string): string { + return Object.keys(attrs) + .map(k => `${k}="${k.startsWith('bind') || k.startsWith('on') ? attrs[k] : `{${this.getAttrValue(attrs[k], k, nodeName)}}`}" `) + .join('') + } + + protected replacePropName (name: string, value: string, _componentName?: string) { + if (value === 'eh') return name.toLowerCase() + return name + } + + protected createMiniComponents (components: Components) { + const result: Components = Object.create(null) + + for (const key in components) { + if (hasOwn(components, key)) { + let component = components[key] + const compName = toDashed(key) + const newComp: Record = Object.create(null) + + if (isFunction(this.modifyCompProps)) { + component = this.modifyCompProps(compName, component) + } + + for (let prop in component) { + if (hasOwn(component, prop)) { + let propValue = component[prop] + if (prop.startsWith('bind') || specialEvents.has(prop)) { + propValue = 'eh' + } else if (propValue === '') { + propValue = `i.${toCamelCase(prop)}` + } else if (isBooleanStringLiteral(propValue) || isNumber(+propValue)) { + propValue = `i.${toCamelCase(prop)}===undefined?${propValue}:i.${toCamelCase(prop)}` + } else { + propValue = `i.${toCamelCase(prop)}||${propValue || singleQuote('')}` + } + + prop = this.replacePropName(prop, propValue, compName) + + newComp[prop] = propValue + } + } + if (compName !== 'block') { + Object.assign(newComp, styles, this.getEvents()) + } + + if (compName === 'swiper-item') { + delete newComp.style + } + + if (compName === 'slot' || compName === 'slot-view') { + result[compName] = { + slot: 'i.name' + } + } else { + result[compName] = newComp + } + } + } + + return result + } + + protected buildBaseTemplate () { + const Adapter = this.Adapter + + return `${this.buildXsTemplate()} +