diff --git a/packages/taro-api/src/index.js b/packages/taro-api/src/index.js index 94648687299f..6a8ede3d2a52 100644 --- a/packages/taro-api/src/index.js +++ b/packages/taro-api/src/index.js @@ -24,6 +24,8 @@ import { useTitleClick, useOptionMenuClick, usePullIntercept, + useShareTimeline, + useAddToFavorites, useReady, useRouter, options, @@ -57,6 +59,8 @@ const Taro = { useTitleClick, useOptionMenuClick, usePullIntercept, + useShareTimeline, + useAddToFavorites, useReady, useRouter, options, diff --git a/packages/taro-cli/package.json b/packages/taro-cli/package.json index 8d317fd0bdf2..77eb1f80ab6a 100644 --- a/packages/taro-cli/package.json +++ b/packages/taro-cli/package.json @@ -90,7 +90,7 @@ "inquirer": "^5.2.0", "klaw": "^2.1.1", "latest-version": "^5.1.0", - "lodash": "^4.17.5", + "lodash": "4.17.19", "mem-fs": "^1.1.3", "mem-fs-editor": "^4.0.0", "minimatch": "^3.0.4", diff --git a/packages/taro-loader/src/page.ts b/packages/taro-loader/src/page.ts index 80849e81281c..2320ef2a9921 100644 --- a/packages/taro-loader/src/page.ts +++ b/packages/taro-loader/src/page.ts @@ -3,8 +3,14 @@ import { getOptions, stringifyRequest } from 'loader-utils' import * as path from 'path' import { frameworkMeta } from './utils' +interface PageConfig { + content: any + path: string +} + export default function (this: webpack.loader.LoaderContext) { const options = getOptions(this) + const config = getPageConfig(options.config, this.resourcePath) const stringify = (s: string): string => stringifyRequest(this, s) const { isNeedRawLoader } = frameworkMeta[options.framework] // raw is a placeholder loader to locate changed .vue resource @@ -18,7 +24,24 @@ if (typeof PRERENDER !== 'undefined') { }` return `import { createPageConfig } from '@tarojs/runtime' import component from ${stringify(componentPath)} +${config.enableShareTimeline ? 'component.enableShareTimeline = true' : ''} +${config.enableShareAppMessage ? 'component.enableShareAppMessage = true' : ''} var inst = Page(createPageConfig(component, '${options.name}')) ${options.prerender ? prerender : ''} ` } + +function getPageConfig (configs: Record, resourcePath: string) { + const configPath = removeExt(resourcePath) + '.config' + for (const name in configs) { + const config = configs[name] + if (removeExt(configs[name].path) === configPath) { + return config.content + } + } + return {} +} + +function removeExt (file: string) { + return path.join(path.dirname(file), path.basename(file, path.extname(file))) +} diff --git a/packages/taro-mini-runner/src/plugins/MiniPlugin.ts b/packages/taro-mini-runner/src/plugins/MiniPlugin.ts index 00733cee9926..52383fac07f9 100644 --- a/packages/taro-mini-runner/src/plugins/MiniPlugin.ts +++ b/packages/taro-mini-runner/src/plugins/MiniPlugin.ts @@ -232,7 +232,8 @@ export default class TaroMiniPlugin { options: { framework, name: module.name, - prerender: this.prerenderPages.has(module.name) + prerender: this.prerenderPages.has(module.name), + config: this.filesConfig } }) } @@ -653,6 +654,10 @@ export default class TaroMiniPlugin { generateConfigFile (compilation: webpack.compilation.Compilation, filePath: string, config: Config & { component?: boolean }) { const fileConfigName = this.getConfigPath(this.getComponentName(filePath)) + const unOfficalConfigs = ['enableShareAppMessage', 'enableShareTimeline'] + unOfficalConfigs.forEach(item => { + delete config[item] + }) const fileConfigStr = JSON.stringify(config) compilation.assets[fileConfigName] = { size: () => fileConfigStr.length, diff --git a/packages/taro-runtime/src/dsl/common.ts b/packages/taro-runtime/src/dsl/common.ts index 9483c63e1343..0eb3077e2424 100644 --- a/packages/taro-runtime/src/dsl/common.ts +++ b/packages/taro-runtime/src/dsl/common.ts @@ -17,6 +17,7 @@ import { CurrentReconciler } from '../reconciler' const instances = new Map() export function injectPageInstance (inst: Instance, id: string) { + CurrentReconciler.mergePageInstance?.(instances.get(id), inst) instances.set(id, inst) } @@ -86,7 +87,7 @@ export function getOnHideEventKey (path: string) { return path + '.' + 'onHide' } -export function createPageConfig (component: React.ComponentClass, pageName?: string, data?: Record) { +export function createPageConfig (component: any, pageName?: string, data?: Record) { const id = pageName ?? `taro_page_${pageId()}` // 小程序 Page 构造器是一个傲娇小公主,不能把复杂的对象挂载到参数上 let pageElement: TaroRootElement | null = null @@ -175,18 +176,6 @@ export function createPageConfig (component: React.ComponentClass, pageName?: st const path = getPath(id, this.options) return safeExecute(path, 'onPageScroll', options) }, - onShareAppMessage (options) { - const target = options.target - if (target != null) { - const id = target.id - const element = document.getElementById(id) - if (element != null) { - options.target!.dataset = element.dataset - } - } - const path = getPath(id, this.options) - return safeExecute(path, 'onShareAppMessage', options) - }, onResize (options) { const path = getPath(id, this.options) return safeExecute(path, 'onResize', options) @@ -210,6 +199,32 @@ export function createPageConfig (component: React.ComponentClass, pageName?: st onPullIntercept () { const path = getPath(id, this.options) return safeExecute(path, 'onPullIntercept') + }, + onAddToFavorites () { + const path = getPath(id, this.options) + return safeExecute(path, 'onAddToFavorites') + } + } + + // onShareAppMessage 和 onShareTimeline 一样,会影响小程序右上方按钮的选项,因此不能默认注册。 + if (component.onShareAppMessage || component.enableShareAppMessage) { + config.onShareAppMessage = function (options) { + const target = options.target + if (target != null) { + const id = target.id + const element = document.getElementById(id) + if (element != null) { + options.target!.dataset = element.dataset + } + } + const path = getPath(id, this.options) + return safeExecute(path, 'onShareAppMessage', options) + } + } + if (component.onShareTimeline || component.enableShareTimeline) { + config.onShareTimeline = function () { + const path = getPath(id, this.options) + return safeExecute(path, 'onShareTimeline') } } diff --git a/packages/taro-runtime/src/dsl/hooks.ts b/packages/taro-runtime/src/dsl/hooks.ts index 90e566fb7d91..87e212877df0 100644 --- a/packages/taro-runtime/src/dsl/hooks.ts +++ b/packages/taro-runtime/src/dsl/hooks.ts @@ -1,3 +1,4 @@ +import { isFunction, isArray } from '@tarojs/shared' import { PageContext, R as React } from './react' import { getPageInstance, injectPageInstance } from './common' import { PageLifeCycle } from './instance' @@ -19,19 +20,34 @@ const taroHooks = (lifecycle: keyof PageLifeCycle) => { inst = Object.create(null) } + inst = inst! + // callback is immutable but inner function is up to date const callback = (...args: any) => fnRef.current(...args) - if (lifecycle !== 'onShareAppMessage') { - (inst![lifecycle] as any) = [ - ...((inst![lifecycle] as any) || []), - callback - ] + if (lifecycle === 'onShareAppMessage') { + inst[lifecycle] = callback } else { - inst![lifecycle] = callback + if (isFunction(inst[lifecycle])) { + (inst[lifecycle] as any) = [inst[lifecycle], callback] + } else { + (inst[lifecycle] as any) = [ + ...((inst[lifecycle] as any) || []), + callback + ] + } } if (first) { injectPageInstance(inst!, id) } + return () => { + const inst = getPageInstance(id) + const list = inst![lifecycle] + if (list === callback) { + (inst![lifecycle] as any) = undefined + } else if (isArray(list)) { + (inst![lifecycle] as any) = list.filter(item => item !== callback) + } + } }, []) } } @@ -58,6 +74,10 @@ export const useOptionMenuClick = taroHooks('onOptionMenuClick') export const usePullIntercept = taroHooks('onPullIntercept') +export const useShareTimeline = taroHooks('onShareTimeline') + +export const useAddToFavorites = taroHooks('onAddToFavorites') + export const useReady = taroHooks('onReady') export const useRouter = (dynamic = false) => { diff --git a/packages/taro-runtime/src/dsl/instance.ts b/packages/taro-runtime/src/dsl/instance.ts index 40649f7b999c..5b28f1186a7d 100644 --- a/packages/taro-runtime/src/dsl/instance.ts +++ b/packages/taro-runtime/src/dsl/instance.ts @@ -53,6 +53,8 @@ export interface PageLifeCycle extends Show { onPopMenuClick?(): void onReady?(): void onPullIntercept?(): void + onShareTimeline?(): void + onAddToFavorites?(): void eh?(event: MpEvent): void onLoad(options: Record): void onUnload(): void diff --git a/packages/taro-runtime/src/dsl/react.ts b/packages/taro-runtime/src/dsl/react.ts index f90e2e58a5f3..a658461d6b31 100644 --- a/packages/taro-runtime/src/dsl/react.ts +++ b/packages/taro-runtime/src/dsl/react.ts @@ -93,6 +93,23 @@ function setReconciler () { lifecycle = 'componentDidHide' } return instance[lifecycle] as Function + }, + mergePageInstance (prev, next) { + if (!prev || !next) return + + // 子组件使用 lifecycle hooks 注册了生命周期后,会存在 prev,里面是注册的生命周期回调。 + Object.keys(prev).forEach(item => { + if (item === 'onShareAppMessage') { + if (!isFunction(next[item])) next[item] = prev[item] + return + } + + if (isFunction(next[item])) { + next[item] = [next[item], ...prev[item]] + } else { + next[item] = [...(next[item] || []), ...prev[item]] + } + }) } } diff --git a/packages/taro-runtime/src/reconciler.ts b/packages/taro-runtime/src/reconciler.ts index 1a0572322a06..072f497f9959 100644 --- a/packages/taro-runtime/src/reconciler.ts +++ b/packages/taro-runtime/src/reconciler.ts @@ -2,7 +2,9 @@ import type { TaroElement } from './dom/element' import type { TaroText } from './dom/text' import type { DataTree, TaroNode } from './dom/node' import type { TaroRootElement } from './dom/root' -import type { PageInstance } from './dsl/instance' +import type { Instance, PageInstance, PageProps } from './dsl/instance' + +type Inst = Instance export interface Reconciler { // mini apps @@ -26,6 +28,8 @@ export interface Reconciler = { diff --git a/packages/taro/types/taro.hooks.d.ts b/packages/taro/types/taro.hooks.d.ts index 54794085c9c4..1e45d1b215d3 100644 --- a/packages/taro/types/taro.hooks.d.ts +++ b/packages/taro/types/taro.hooks.d.ts @@ -42,6 +42,16 @@ declare namespace Taro { */ function useTabItemTap(callback: (payload: TabItemTapObject) => any): void + /** + * 用户点击右上角菜单“收藏”按钮时的回调。 + */ + function useAddToFavorites(callback: (paload: AddToFavoritesObject) => AddToFavoritesReturnObject): void + + /** + * 用户点击右上角菜单“分享到朋友圈”按钮时的回调。 + */ + function useShareTimeline(callback: () => any): void + /** * 页面初次渲染完成的回调。 * 此时页面已经准备妥当,可以和视图层进行交互。 diff --git a/packages/taro/types/taro.lifecycle.d.ts b/packages/taro/types/taro.lifecycle.d.ts index 269b0fe85403..21a018dd0562 100644 --- a/packages/taro/types/taro.lifecycle.d.ts +++ b/packages/taro/types/taro.lifecycle.d.ts @@ -28,19 +28,19 @@ declare namespace Taro { * 转发事件来源 * `button`:页面内转发按钮 * `menu`:右上角转发菜单 - * + * * @since 1.2.4 */ from?: 'button' | 'menu' | string /** * 如果 `from` 值是 `button`,则 `target` 是触发这次转发事件的 `button`,否则为 `undefined` - * + * * @since 1.2.4 */ target?: object /** * 页面中包含 `` 组件时,返回当前 `` 的 url - * + * * @since 1.6.4 */ webViewUrl?: string @@ -62,7 +62,7 @@ declare namespace Taro { * 支持PNG及JPG * 显示图片长宽比是 5:4 * 默认使用截图 - * + * * @since 1.5.0 */ imageUrl?: string @@ -71,26 +71,44 @@ declare namespace Taro { interface TabItemTapObject { /** * 被点击tabItem的序号,从 0 开始 - * - * @since 1.9.0 */ index: string /** * 被点击tabItem的页面路径 - * - * @since 1.9.0 */ pagePath: string /** * 被点击tabItem的按钮文字 - * - * @since 1.9.0 */ text: string } + interface AddToFavoritesObject { + /** + * 页面中包含web-view组件时,返回当前web-view的url + */ + webviewUrl: string + } + + interface AddToFavoritesReturnObject { + /** + * 自定义标题 + */ + title?: string + + /** + * 自定义图片,显示图片长宽比为 1:1 + */ + imageUrl?: string + + /** + * 自定义query字段 + */ + query?: string + } + type GetDerivedStateFromProps = /** * Returns an update to a component's state based on its new props and old state.