Skip to content

Commit

Permalink
feat: support attr inherit (#170)
Browse files Browse the repository at this point in the history
* feat: support attr inherit

* feat: support attr inherit

* feat: support attr inherit

* feat: support attr inherit

* feat: support attr inherit

* feat: support attr inherit

* feat: support attr inherit
  • Loading branch information
wunci authored Jan 20, 2024
1 parent 87d2e32 commit 221c90b
Show file tree
Hide file tree
Showing 13 changed files with 358 additions and 130 deletions.
28 changes: 14 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@
"jest": "^27.0.6",
"mkdirp": "^1.0.4",
"mustache": "^4.0.1",
"san": "^3.13.0",
"san-html-cases": "^3.13.3",
"san": "^3.14.1",
"san-html-cases": "^3.14.3",
"san-ssr-target-fake-cmd": "^1.0.0",
"san-ssr-target-fake-esm": "^1.0.0",
"source-map-support": "^0.5.19",
Expand Down
30 changes: 28 additions & 2 deletions src/ast/ts-ast-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
*
* 语法树 Spec: https://ts-morph.com/
*/
import type {
import {
Node, MethodDeclaration, ShorthandPropertyAssignment, PropertyAssignment, ImportDeclaration, ClassDeclaration,
SourceFile, ObjectLiteralExpression
} from 'ts-morph'
import { TypeGuards, SyntaxKind } from 'ts-morph'
import { TypeGuards, SyntaxKind, PropertyDeclaration, ts } from 'ts-morph'
import { TagName } from '../models/component-info'
import { componentID, ComponentReference } from '../models/component-reference'
import { strongParseSanSourceFileOptions } from '../compilers/renderer-options'
Expand Down Expand Up @@ -86,6 +86,32 @@ export function getPropertyStringValue<T extends string> (clazz: ClassDeclaratio
throw new Error(`invalid "${memberName}" property`)
}

export function getPropertyBooleanValue (clazz: ClassDeclaration, memberName: string, defaultValue: boolean) {
const staticProperties = clazz.getStaticProperties()
let value = defaultValue
staticProperties.find(property => {
if (PropertyDeclaration.isPropertyDeclaration(property)) {
const propertyDeclaration = property as PropertyDeclaration
const propertyName = propertyDeclaration.getName()
const initializerNode = propertyDeclaration.getInitializer()

let propertyValue
if (initializerNode && Node.isBooleanLiteral(initializerNode)) {
propertyValue = initializerNode.getLiteralValue() as boolean
}
const result = propertyName === memberName && propertyValue !== undefined
if (result) {
value = propertyValue as boolean
return result
}
}

return false
})

return value
}

export function getPropertyStringArrayValue<T extends string[]> (
clazz: ClassDeclaration,
memberName: string
Expand Down
90 changes: 81 additions & 9 deletions src/compilers/anode-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ export class ANodeCompiler {
): Generator<Statement> {
yield * this.elementCompiler.tagStart(
aNode,
this.componentInfo,
dynamicTagName,
isRootElement ? this.compileRootAttrs : undefined
)
Expand All @@ -239,11 +240,37 @@ export class ANodeCompiler {
/**
* add attrs to root element
*/
private * compileRootAttrs () {
yield new If(BINARY(I('attrs'), '&&', BINARY(I('attrs'), '.', I('length'))), [
createHTMLLiteralAppend(' '),
createHTMLExpressionAppend(new FunctionCall(BINARY(I('attrs'), '.', I('join')), [L(' ')]))
])
private * compileRootAttrs (aNode: AElement, propsAttrAssign: Record<string, unknown>) {
const rootAttrExec = []
if (Object.keys(propsAttrAssign).length) {
rootAttrExec.push(...[
DEF('propsAttr', new MapLiteral(Object.keys(propsAttrAssign).map(name => [I(name), I('1')]))),
new Foreach(I('key'), I('val'), I('attrs'), [
// 如果props已经存在对应属性,则attr重复的属性需要被删除
new If(
BINARY(
I('propsAttr'),
'[]',
// val值示例:a=1,通过split获取到key值
BINARY(new FunctionCall(BINARY(I('val'), '.', I('split')), [L('=')]), '[]', L(0))
),
[
STATEMENT(I('continue'))
]
),
createHTMLExpressionAppend(BINARY(L(' '), '+', I('val')))
])
])
} else {
rootAttrExec.push(...[
createHTMLLiteralAppend(' '),
createHTMLExpressionAppend(new FunctionCall(BINARY(I('attrs'), '.', I('join')), [L(' ')]))
])
}
yield new If(
BINARY(
I('attrs'), '&&', BINARY(I('attrs'), '.', I('length'))
), rootAttrExec)
}

private createDataComment () {
Expand Down Expand Up @@ -305,6 +332,43 @@ export class ANodeCompiler {

const childSlots = I(this.id.next('childSlots'))
yield DEF(childSlots.name, new MapLiteral([]))

// 处理属性合并
if (this.componentInfo.inheritAttrs && aNode.attrs && aNode.attrs.length) {
const attrList = []
const attrListMap = []
for (const attr of aNode.attrs) {
const result = TypeGuards.isExprBoolNode(attr.expr) || attr.expr.value === ''
? L(attr.name)
: BINARY(L(`${attr.name}="`), '+', BINARY(sanExpr(attr.expr), '+', L('"')))
attrList.push([result, false])
attrListMap.push([L(attr.name), L(1)])
}
yield DEF('selfAttrs', new ArrayLiteral(attrList as any))
yield DEF('attrListMap', new MapLiteral(attrListMap as any))
yield DEF('filteredParentAttrs', new ArrayLiteral([]))

yield new Foreach(I('key'), I('val'), I('attrs'), [
// 如果props已经存在对应属性,则attr重复的属性需要被删除
new If(
BINARY(
I('attrListMap'),
'[]',
// val值示例:a=1,通过split获取到key值
BINARY(new FunctionCall(BINARY(I('val'), '.', I('split')), [L('=')]), '[]', L(0))
),
[
STATEMENT(I('continue'))
]
),
STATEMENT(new FunctionCall(BINARY(I('filteredParentAttrs'), '.', I('push')), [I('val')]))
])
yield ASSIGN(
I('attrs'),
new FunctionCall(BINARY(I('selfAttrs'), '.', I('concat')), [I('filteredParentAttrs')])
)
}

if (defaultSlotContents.length) {
yield ASSIGN(
BINARY(childSlots, '[]', L('')),
Expand Down Expand Up @@ -346,8 +410,13 @@ export class ANodeCompiler {
assert(ChildComponentClassName !== '')
mapItems.push([I('ComponentClass'), I(ChildComponentClassName)])
}
if (isRootElement) {

// 传入attrs数据到下一个组件
if (isRootElement || aNode.attrs) {
mapItems.push([I('attrs'), I('attrs')])
}

if (isRootElement) {
mapItems.push([
I('rootOutputData'),
BINARY(BINARY(I('info'), '.', I('rootOutputData')), '||', BINARY(I('ctx'), '.', I('data')))
Expand Down Expand Up @@ -453,9 +522,12 @@ export class ANodeCompiler {
}

private childRenderData (aNode: AElement) {
const propData = new MapLiteral(
aNode.props.map(prop => [L(camelCase(prop.name)), sanExpr(prop.expr)])
)
// 追加$attrs data到组件内获取
const props = aNode.props.map(prop => [L(camelCase(prop.name)), sanExpr(prop.expr)])
aNode.attrs && props.push([L('$attrs'), new MapLiteral(
aNode.attrs.map(attr => [L(attr.name), sanExpr(attr.expr)])
)])
const propData = new MapLiteral(props as any)
const bindDirective = aNode.directives.bind
return bindDirective
? new MapAssign(
Expand Down
25 changes: 21 additions & 4 deletions src/compilers/element-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import { HelperCall, ArrayIncludes, Else, Foreach, If, MapLiteral, Statement } from '../ast/renderer-ast-dfn'
import { sanExpr, OutputType } from './san-expr-compiler'
import assert from 'assert'
import { ComponentInfo } from '../models/component-info'

const BOOL_ATTRIBUTES = ['readonly', 'disabled', 'multiple', 'checked']

Expand All @@ -39,7 +40,12 @@ export class ElementCompiler {
/**
* 编译元素标签头
*/
* tagStart (aNode: AElement, dynamicTagName?: string, beforeEnd?: () => Generator<Statement, void, unknown>) {
* tagStart (
aNode: AElement,
componentInfo: ComponentInfo,
dynamicTagName?: string,
beforeEnd?: (aNode: AElement, propsAttrAssign: Record<string, unknown>) => Generator<Statement, void, unknown>
) {
const props = aNode.props
const bindDirective = aNode.directives.bind
const tagName = aNode.tagName!
Expand All @@ -58,16 +64,26 @@ export class ElementCompiler {
// element properties
const propsIndex = {}
for (const prop of props) propsIndex[prop.name] = prop
for (const prop of props) yield * this.compileProperty(tagName, prop, propsIndex)
const propsAttrAssign = {}
if (componentInfo.inheritAttrs) {
for (const prop of props) {
yield * this.compileProperty(tagName, prop, propsIndex, propsAttrAssign)
}
}
if (bindDirective) yield * this.compileBindProperties(tagName, bindDirective)

if (beforeEnd) yield * beforeEnd()
if (beforeEnd) yield * beforeEnd(aNode, propsAttrAssign)

// element end '>'
yield createHTMLLiteralAppend('>')
}

private * compileProperty (tagName: string, prop: AProperty, propsIndex: { [key: string]: AProperty }) {
private * compileProperty (
tagName: string,
prop: AProperty,
propsIndex: { [key: string]: AProperty },
propsAttrAssign: Record<string, unknown>
) {
if (prop.name === 'slot') return
if (prop.name === 'value') {
if (tagName === 'textarea') return
Expand Down Expand Up @@ -120,6 +136,7 @@ export class ElementCompiler {
}
}
if (this.isLiteral(prop.expr)) {
propsAttrAssign[prop.name] = 1
yield createHTMLLiteralAppend(_.attrFilter(prop.name, prop.expr.value, true))
} else {
yield createHTMLExpressionAppend(
Expand Down
49 changes: 49 additions & 0 deletions src/compilers/renderer-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,31 @@ export class RendererCompiler {
body.push(createDefineWithDefaultValue('tagName', BINARY(I('info'), '.', I('tagName')), L('div')))
body.push(createDefineWithDefaultValue('slots', BINARY(I('info'), '.', I('slots')), EMPTY_MAP))
body.push(createDefineWithDefaultValue('attrs', BINARY(I('info'), '.', I('attrs')), EMPTY_ARRAY))
body.push(DEF('inheritAttrs', L(info.inheritAttrs)))
body.push(DEF('autoFillStyleAndId', L(info.autoFillStyleAndId)))

// 变量判断当前是否屏蔽了属性传递
body.push(new If(
new BinaryExpression(
I('inheritAttrs'),
'===',
L(false)
),
[ASSIGN(BINARY(I('attrs'), '.', I('length')), I('0'))]
))

body.push(new If(
new BinaryExpression(
I('autoFillStyleAndId'),
'===',
L(false)
),
[
ASSIGN(BINARY(I('data'), '.', I('id')), L('')),
ASSIGN(BINARY(I('data'), '.', I('style')), L('')),
ASSIGN(BINARY(I('data'), '.', I('class')), L(''))
]
))

// server render component
if (info.ssrType === 'render-only' || info.ssrType === undefined) {
Expand Down Expand Up @@ -216,6 +241,30 @@ export class RendererCompiler {
body.push(createDefineWithDefaultValue('parentCtx', BINARY(I('info'), '.', I('parentCtx')), NULL))
body.push(createDefineWithDefaultValue('slots', BINARY(I('info'), '.', I('slots')), EMPTY_MAP))
body.push(createDefineWithDefaultValue('attrs', BINARY(I('info'), '.', I('attrs')), EMPTY_ARRAY))
body.push(createDefineWithDefaultValue('inheritAttrs', L(info.inheritAttrs), L(true)))
body.push(createDefineWithDefaultValue('autoFillStyleAndId', L(info.autoFillStyleAndId), L(true)))
// 变量判断当前是否屏蔽了属性传递
body.push(new If(
new BinaryExpression(
I('inheritAttrs'),
'===',
L(false)
),
[ASSIGN(BINARY(I('attrs'), '.', I('length')), I('0'))]
))

body.push(new If(
new BinaryExpression(
I('autoFillStyleAndId'),
'===',
L(false)
),
[
ASSIGN(BINARY(I('data'), '.', I('id')), L('')),
ASSIGN(BINARY(I('data'), '.', I('style')), L('')),
ASSIGN(BINARY(I('data'), '.', I('class')), L(''))
]
))

// helper
body.push(new ImportHelper('_'))
Expand Down
Loading

0 comments on commit 221c90b

Please sign in to comment.