Skip to content

Commit

Permalink
fix(runtime): 修复 Vue3.2+ 版本静态节点渲染报错的问题,fix #10077
Browse files Browse the repository at this point in the history
  • Loading branch information
Chen-jj committed Aug 25, 2021
1 parent 8aba346 commit 086a257
Show file tree
Hide file tree
Showing 13 changed files with 136 additions and 129 deletions.
2 changes: 1 addition & 1 deletion packages/taro-mini-runner/src/webpack/build.conf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ export default (appPath: string, mode, config: Partial<IBuildConfig>): any => {
requestAnimationFrame: ['@tarojs/runtime', 'requestAnimationFrame'],
cancelAnimationFrame: ['@tarojs/runtime', 'cancelAnimationFrame'],
Element: ['@tarojs/runtime', 'TaroElement'],
SVGElement: ['@tarojs/runtime', 'TaroElement']
SVGElement: ['@tarojs/runtime', 'SVGElement']
})

const isCssoEnabled = !((csso && csso.enable === false))
Expand Down
10 changes: 10 additions & 0 deletions packages/taro-mini-runner/src/webpack/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,8 @@ export function getRuntimeConstants (runtime) {
const constants = {
ENABLE_INNER_HTML: true,
ENABLE_ADJACENT_HTML: true,
ENABLE_TEMPLATE_CONTENT: true,
ENABLE_CLONE_NODE: true,
ENABLE_SIZE_APIS: false
}

Expand All @@ -538,5 +540,13 @@ export function getRuntimeConstants (runtime) {
constants.ENABLE_SIZE_APIS = runtime.enableSizeAPIs
}

if (runtime.enableTemplateContent !== undefined) {
constants.ENABLE_TEMPLATE_CONTENT = runtime.enableTemplateContent
}

if (runtime.enableCloneNode !== undefined) {
constants.ENABLE_CLONE_NODE = runtime.enableCloneNode
}

return constants
}
3 changes: 0 additions & 3 deletions packages/taro-runtime/src/constants/identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ const SERVICE_IDENTIFIER = {
TaroTextFactory: 'Factory<TaroText>',
TaroNodeImpl: 'TaroNodeImpl',
TaroElementImpl: 'TaroElementImpl',
InnerHTMLImpl: 'InnerHTMLImpl',
insertAdjacentHTMLImpl: 'insertAdjacentHTMLImpl',
getBoundingClientRectImpl: 'getBoundingClientRectImpl',
Hooks: 'hooks',
onRemoveAttribute: 'onRemoveAttribute',
getLifecycle: 'getLifecycle',
Expand Down
1 change: 1 addition & 0 deletions packages/taro-runtime/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const BODY = 'body'
export const APP = 'app'
export const CONTAINER = 'container'
export const DOCUMENT_ELEMENT_NAME = '#document'
export const DOCUMENT_FRAGMENT = 'document-fragment'
export const ID = 'id'
export const UID = 'uid'
export const CLASS = 'class'
Expand Down
3 changes: 1 addition & 2 deletions packages/taro-runtime/src/container/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { TaroDocument } from '../dom/document'
import { TaroRootElement } from '../dom/root'
import { FormElement } from '../dom/form'
import { ElementNames, InstanceFactory, InstanceNamedFactory } from '../interface'
import domExternal from '../dom-external'
import { Hooks } from '../hooks'
import { DefaultHooksContainer } from './default-hooks'
import processPluginHooks from './plugin-hooks'
Expand Down Expand Up @@ -43,7 +42,7 @@ container.bind<TaroNodeImpl>(SERVICE_IDENTIFIER.TaroNodeImpl).to(TaroNodeImpl).i
container.bind<TaroElementImpl>(SERVICE_IDENTIFIER.TaroElementImpl).to(TaroElementImpl).inSingletonScope()
container.bind<Hooks>(SERVICE_IDENTIFIER.Hooks).to(Hooks).inSingletonScope()

container.load(domExternal, DefaultHooksContainer)
container.load(DefaultHooksContainer)

processPluginHooks(container)

Expand Down
44 changes: 21 additions & 23 deletions packages/taro-runtime/src/dom-external/element-impl.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
import { isFunction, warn } from '@tarojs/shared'
import { inject, injectable, optional } from 'inversify'
import SERVICE_IDENTIFIER from '../constants/identifiers'
import { injectable } from 'inversify'
import { getBoundingClientRectImpl, getTemplateContent } from './element'

import type { Ctx } from '../interface'
import type { getBoundingClientRectImpl } from './element'

declare const ENABLE_SIZE_APIS: boolean
declare const ENABLE_TEMPLATE_CONTENT: boolean

@injectable()
export class TaroElementImpl {
public rectImpl: typeof getBoundingClientRectImpl

constructor (// eslint-disable-next-line @typescript-eslint/indent
@inject(SERVICE_IDENTIFIER.getBoundingClientRectImpl) @optional() rectImpl: typeof getBoundingClientRectImpl
) {
this.rectImpl = rectImpl
}

bind (ctx: Ctx) {
this.bindRect(ctx)
}

bindRect (ctx: Ctx) {
const impl = this.rectImpl
ctx.getBoundingClientRect = async function (...args: any[]) {
if (isFunction(impl)) {
return await impl.apply(ctx, args)
if (ENABLE_SIZE_APIS) {
ctx.getBoundingClientRect = async function (...args: any[]) {
return await getBoundingClientRectImpl.apply(ctx, args)
}

process.env.NODE_ENV !== 'production' && warn(true, '请实现 element.getBoundingClientRect')
return Promise.resolve(null)
}
if (ENABLE_TEMPLATE_CONTENT) {
bindContent(ctx)
}
}
}

function bindContent (ctx: Ctx) {
Object.defineProperty(ctx, 'content', {
configurable: true,
enumerable: true,
get () {
return getTemplateContent(ctx)
}
})
}
17 changes: 17 additions & 0 deletions packages/taro-runtime/src/dom-external/element.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { options } from '../options'
import { ElementNames } from '../interface'
import { DOCUMENT_FRAGMENT } from '../constants'

import type { Ctx } from '../interface'

export function getBoundingClientRectImpl (): Promise<null> {
if (!options.miniGlobal) return Promise.resolve(null)
Expand All @@ -9,3 +13,16 @@ export function getBoundingClientRectImpl (): Promise<null> {
}).exec()
})
}

export function getTemplateContent (ctx: Ctx): string | undefined {
if (ctx.nodeName === 'template') {
const content = ctx._getElement(ElementNames.Element)(DOCUMENT_FRAGMENT)
content.childNodes = ctx.childNodes
ctx.childNodes = [content]
content.parentNode = ctx
content.childNodes.forEach(nodes => {
nodes.parentNode = content
})
return content
}
}
26 changes: 0 additions & 26 deletions packages/taro-runtime/src/dom-external/index.ts

This file was deleted.

74 changes: 33 additions & 41 deletions packages/taro-runtime/src/dom-external/node-impl.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,57 @@
import { isFunction, warn } from '@tarojs/shared'
import { inject, injectable, optional } from 'inversify'
import { inject, injectable } from 'inversify'
import SERVICE_IDENTIFIER from '../constants/identifiers'
import { ElementNames, InstanceNamedFactory } from '../interface'
import { setInnerHTML } from '../dom-external/inner-html/html'
import { cloneNode, insertAdjacentHTMLImpl } from './node'

import type { Ctx, GetDoc } from '../interface'
import type { setInnerHTML } from '../dom-external/inner-html/html'
import type { TaroDocument } from '../dom/document'
import type { insertAdjacentHTMLImpl, IPosition } from './node'
import type { IPosition } from './node'

type SetInnerHTML = typeof setInnerHTML
type InsertAdjacentHTMLImpl = typeof insertAdjacentHTMLImpl
declare const ENABLE_INNER_HTML: boolean
declare const ENABLE_ADJACENT_HTML: boolean
declare const ENABLE_CLONE_NODE: boolean

@injectable()
export class TaroNodeImpl {
ctx: Ctx
public getDoc: GetDoc
public innerHTMLImpl: SetInnerHTML
public adjacentImpl: InsertAdjacentHTMLImpl

constructor (// eslint-disable-next-line @typescript-eslint/indent
@inject(SERVICE_IDENTIFIER.TaroElementFactory) getElement: InstanceNamedFactory,
@inject(SERVICE_IDENTIFIER.InnerHTMLImpl) @optional() innerHTMLImpl: SetInnerHTML,
@inject(SERVICE_IDENTIFIER.insertAdjacentHTMLImpl) @optional() adjacentImpl: InsertAdjacentHTMLImpl
@inject(SERVICE_IDENTIFIER.TaroElementFactory) getElement: InstanceNamedFactory
) {
this.getDoc = () => getElement<TaroDocument>(ElementNames.Document)()
this.innerHTMLImpl = innerHTMLImpl
this.adjacentImpl = adjacentImpl
}

public bind (ctx: Ctx) {
this.ctx = ctx
this.bindInnerHTML()
this.bindAdjacentHTML()
}
const getDoc = this.getDoc

private bindInnerHTML () {
const { ctx, innerHTMLImpl: impl, getDoc } = this
Object.defineProperty(ctx, 'innerHTML', {
configurable: true,
enumerable: true,
set (html: string) {
if (isFunction(impl)) {
impl.call(ctx, ctx, html, getDoc)
} else {
process.env.NODE_ENV !== 'production' && warn(true, '请实现 node.innerHTML')
}
},
get (): string {
return ''
if (ENABLE_INNER_HTML) {
bindInnerHTML(ctx, getDoc)
if (ENABLE_ADJACENT_HTML) {
bindAdjacentHTML(ctx, getDoc)
}
})
}
if (ENABLE_CLONE_NODE) {
ctx.cloneNode = cloneNode.bind(ctx, ctx, getDoc)
}
}
}

private bindAdjacentHTML () {
const { ctx, adjacentImpl: impl, getDoc } = this
ctx.insertAdjacentHTML = function (position: IPosition, html: string) {
if (isFunction(impl)) {
impl.call(ctx, position, html, getDoc)
} else {
process.env.NODE_ENV !== 'production' && warn(true, '请实现 node.insertAdjacentHTML')
}
function bindInnerHTML (ctx, getDoc) {
Object.defineProperty(ctx, 'innerHTML', {
configurable: true,
enumerable: true,
set (html: string) {
setInnerHTML.call(ctx, ctx, html, getDoc)
},
get (): string {
return ''
}
})
}

function bindAdjacentHTML (ctx, getDoc) {
ctx.insertAdjacentHTML = function (position: IPosition, html: string) {
insertAdjacentHTMLImpl.call(ctx, position, html, getDoc)
}
}
38 changes: 38 additions & 0 deletions packages/taro-runtime/src/dom-external/node.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import {
STYLE,
DATASET,
PROPS,
OBJECT
} from '../constants'
import { parser } from '../dom-external/inner-html/parser'
import { GetDoc } from '../interface'
import { NodeType } from '../dom/node_types'

import type { Ctx } from '../interface'

export type IPosition = 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend'

Expand Down Expand Up @@ -37,3 +46,32 @@ export function insertAdjacentHTMLImpl (
}
}
}

export function cloneNode (ctx: Ctx, getDoc, isDeep = false) {
const document = getDoc()
let newNode

if (ctx.nodeType === NodeType.ELEMENT_NODE) {
newNode = document.createElement(ctx.nodeName)
} else if (ctx.nodeType === NodeType.TEXT_NODE) {
newNode = document.createTextNode('')
}

for (const key in this) {
const value: any = this[key]
if ([PROPS, DATASET].includes(key) && typeof value === OBJECT) {
newNode[key] = { ...value }
} else if (key === '_value') {
newNode[key] = value
} else if (key === STYLE) {
newNode.style._value = { ...value._value }
newNode.style._usedStyleProp = new Set(Array.from(value._usedStyleProp))
}
}

if (isDeep) {
newNode.childNodes = ctx.childNodes.map(node => node.cloneNode(true))
}

return newNode
}
42 changes: 9 additions & 33 deletions packages/taro-runtime/src/dom/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ import { hydrate } from '../hydrate'
import { eventSource } from './event-source'
import { ElementNames } from '../interface'
import {
STYLE,
DATASET,
PROPS,
OBJECT
DOCUMENT_FRAGMENT
} from '../constants'

import type { UpdatePayload, InstanceNamedFactory } from '../interface'
Expand Down Expand Up @@ -131,6 +128,14 @@ export class TaroNode extends TaroEventTarget {
}

public insertBefore<T extends TaroNode> (newChild: T, refChild?: TaroNode | null, isReplace?: boolean): T {
if (newChild.nodeName === DOCUMENT_FRAGMENT) {
newChild.childNodes.reduceRight((previousValue, currentValue) => {
this.insertBefore(currentValue, previousValue)
return currentValue
}, refChild)
return newChild
}

newChild.remove()
newChild.parentNode = this
let payload: UpdatePayload
Expand Down Expand Up @@ -212,35 +217,6 @@ export class TaroNode extends TaroEventTarget {
this._root?.enqueueUpdate(payload)
}

public cloneNode (isDeep = false) {
const document = this._getElement<TaroDocument>(ElementNames.Document)()
let newNode

if (this.nodeType === NodeType.ELEMENT_NODE) {
newNode = document.createElement(this.nodeName)
} else if (this.nodeType === NodeType.TEXT_NODE) {
newNode = document.createTextNode('')
}

for (const key in this) {
const value: any = this[key]
if ([PROPS, DATASET].includes(key) && typeof value === OBJECT) {
newNode[key] = { ...value }
} else if (key === '_value') {
newNode[key] = value
} else if (key === STYLE) {
newNode.style._value = { ...value._value }
newNode.style._usedStyleProp = new Set(Array.from(value._usedStyleProp))
}
}

if (isDeep) {
newNode.childNodes = this.childNodes.map(node => node.cloneNode(true))
}

return newNode
}

public contains (node: TaroNode & { id?: string }): boolean {
let isContains = false
this.childNodes.some(childNode => {
Expand Down
4 changes: 4 additions & 0 deletions packages/taro-runtime/src/dom/svg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { TaroElement } from './element'

// for Vue3
export class SVGElement extends TaroElement {}
1 change: 1 addition & 0 deletions packages/taro-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { TaroText } from './dom/text'
export { TaroElement } from './dom/element'
export { TaroRootElement } from './dom/root'
export { FormElement } from './dom/form'
export { SVGElement } from './dom/svg'
export { TaroEvent, createEvent } from './dom/event'
export { createDocument, document } from './bom/document'
export { window } from './bom/window'
Expand Down

0 comments on commit 086a257

Please sign in to comment.