|
|
@@ -58,37 +58,125 @@ export type StructuralDirectiveTransform = (
|
|
|
|
|
|
export type TransformOptions = HackOptions<BaseTransformOptions>
|
|
|
|
|
|
-export interface TransformContext<T extends AllNode = AllNode> {
|
|
|
- node: T
|
|
|
- parent: TransformContext<RootNode | ElementNode> | null
|
|
|
+export class TransformContext<T extends AllNode = AllNode> {
|
|
|
+ parent: TransformContext<RootNode | ElementNode> | null = null
|
|
|
root: TransformContext<RootNode>
|
|
|
- index: number
|
|
|
- block: BlockIRNode
|
|
|
+ index: number = 0
|
|
|
+
|
|
|
+ block: BlockIRNode = this.ir.block
|
|
|
options: Required<
|
|
|
Omit<TransformOptions, 'filename' | keyof CompilerCompatOptions>
|
|
|
>
|
|
|
|
|
|
- template: string
|
|
|
- childrenTemplate: (string | null)[]
|
|
|
- dynamic: IRDynamicInfo
|
|
|
+ template: string = ''
|
|
|
+ childrenTemplate: (string | null)[] = []
|
|
|
+ dynamic: IRDynamicInfo = this.ir.block.dynamic
|
|
|
+
|
|
|
+ inVOnce: boolean = false
|
|
|
+ inVFor: number = 0
|
|
|
+
|
|
|
+ comment: CommentNode[] = []
|
|
|
+ component: Set<string> = this.ir.component
|
|
|
+
|
|
|
+ private globalId = 0
|
|
|
+
|
|
|
+ constructor(
|
|
|
+ private ir: RootIRNode,
|
|
|
+ public node: T,
|
|
|
+ options: TransformOptions = {},
|
|
|
+ ) {
|
|
|
+ this.options = extend({}, defaultOptions, options)
|
|
|
+ this.root = this as TransformContext<RootNode>
|
|
|
+ }
|
|
|
|
|
|
- comment: CommentNode[]
|
|
|
+ enterBlock(
|
|
|
+ ir: TransformContext['block'],
|
|
|
+ isVFor: boolean = false,
|
|
|
+ ): () => void {
|
|
|
+ const { block, template, dynamic, childrenTemplate } = this
|
|
|
+ this.block = ir
|
|
|
+ this.dynamic = ir.dynamic
|
|
|
+ this.template = ''
|
|
|
+ this.childrenTemplate = []
|
|
|
+ isVFor && this.inVFor++
|
|
|
+ return () => {
|
|
|
+ // exit
|
|
|
+ this.registerTemplate()
|
|
|
+ this.block = block
|
|
|
+ this.template = template
|
|
|
+ this.dynamic = dynamic
|
|
|
+ this.childrenTemplate = childrenTemplate
|
|
|
+ isVFor && this.inVFor--
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- inVOnce: boolean
|
|
|
- inVFor: number
|
|
|
+ increaseId = () => this.globalId++
|
|
|
+ reference() {
|
|
|
+ if (this.dynamic.id !== undefined) return this.dynamic.id
|
|
|
+ this.dynamic.flags |= DynamicFlag.REFERENCED
|
|
|
+ return (this.dynamic.id = this.increaseId())
|
|
|
+ }
|
|
|
|
|
|
- component: Set<string>
|
|
|
+ pushTemplate(content: string) {
|
|
|
+ const existing = this.ir.template.findIndex(
|
|
|
+ template => template === content,
|
|
|
+ )
|
|
|
+ if (existing !== -1) return existing
|
|
|
+ this.ir.template.push(content)
|
|
|
+ return this.ir.template.length - 1
|
|
|
+ }
|
|
|
+ registerTemplate() {
|
|
|
+ if (!this.template) return -1
|
|
|
+ const id = this.pushTemplate(this.template)
|
|
|
+ return (this.dynamic.template = id)
|
|
|
+ }
|
|
|
|
|
|
- enterBlock(ir: TransformContext['block'], isVFor?: boolean): () => void
|
|
|
- reference(): number
|
|
|
- increaseId(): number
|
|
|
- pushTemplate(template: string): number
|
|
|
- registerTemplate(customTemplate?: string): number
|
|
|
registerEffect(
|
|
|
expressions: SimpleExpressionNode[],
|
|
|
- ...operation: OperationNode[]
|
|
|
- ): void
|
|
|
- registerOperation(...operations: OperationNode[]): void
|
|
|
+ ...operations: OperationNode[]
|
|
|
+ ) {
|
|
|
+ expressions = expressions.filter(exp => !isConstantExpression(exp))
|
|
|
+ if (this.inVOnce || expressions.length === 0) {
|
|
|
+ return this.registerOperation(...operations)
|
|
|
+ }
|
|
|
+ const existing = this.block.effect.find(e =>
|
|
|
+ isSameExpression(e.expressions, expressions),
|
|
|
+ )
|
|
|
+ if (existing) {
|
|
|
+ existing.operations.push(...operations)
|
|
|
+ } else {
|
|
|
+ this.block.effect.push({
|
|
|
+ expressions,
|
|
|
+ operations,
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ function isSameExpression(
|
|
|
+ a: SimpleExpressionNode[],
|
|
|
+ b: SimpleExpressionNode[],
|
|
|
+ ) {
|
|
|
+ if (a.length !== b.length) return false
|
|
|
+ return a.every((exp, i) => exp.content === b[i].content)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ registerOperation(...node: OperationNode[]) {
|
|
|
+ this.block.operation.push(...node)
|
|
|
+ }
|
|
|
+
|
|
|
+ create<T extends TemplateChildNode>(
|
|
|
+ node: T,
|
|
|
+ index: number,
|
|
|
+ ): TransformContext<T> {
|
|
|
+ return Object.assign(Object.create(TransformContext.prototype), this, {
|
|
|
+ node,
|
|
|
+ parent: this as any,
|
|
|
+ index,
|
|
|
+
|
|
|
+ template: '',
|
|
|
+ childrenTemplate: [],
|
|
|
+ dynamic: genDefaultDynamic(),
|
|
|
+ } satisfies Partial<TransformContext<T>>)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const defaultOptions = {
|
|
|
@@ -115,97 +203,6 @@ const defaultOptions = {
|
|
|
onWarn: defaultOnWarn,
|
|
|
}
|
|
|
|
|
|
-// TODO use class for better perf
|
|
|
-function createRootContext(
|
|
|
- root: RootIRNode,
|
|
|
- node: RootNode,
|
|
|
- options: TransformOptions = {},
|
|
|
-): TransformContext<RootNode> {
|
|
|
- let globalId = 0
|
|
|
-
|
|
|
- const context: TransformContext<RootNode> = {
|
|
|
- node,
|
|
|
- parent: null,
|
|
|
- index: 0,
|
|
|
- root: null!, // set later
|
|
|
- block: root.block,
|
|
|
- enterBlock(ir, inVFor = false) {
|
|
|
- const { block, template, dynamic, childrenTemplate } = this
|
|
|
- this.block = ir
|
|
|
- this.dynamic = ir.dynamic
|
|
|
- this.template = ''
|
|
|
- this.childrenTemplate = []
|
|
|
- inVFor && this.inVFor++
|
|
|
- return () => {
|
|
|
- // exit
|
|
|
- this.registerTemplate()
|
|
|
- this.block = block
|
|
|
- this.template = template
|
|
|
- this.dynamic = dynamic
|
|
|
- this.childrenTemplate = childrenTemplate
|
|
|
- inVFor && this.inVFor--
|
|
|
- }
|
|
|
- },
|
|
|
- options: extend({}, defaultOptions, options),
|
|
|
- dynamic: root.block.dynamic,
|
|
|
- inVOnce: false,
|
|
|
- inVFor: 0,
|
|
|
- comment: [],
|
|
|
- component: root.component,
|
|
|
-
|
|
|
- increaseId: () => globalId++,
|
|
|
- reference() {
|
|
|
- if (this.dynamic.id !== undefined) return this.dynamic.id
|
|
|
- this.dynamic.flags |= DynamicFlag.REFERENCED
|
|
|
- return (this.dynamic.id = this.increaseId())
|
|
|
- },
|
|
|
- registerEffect(expressions, ...operations) {
|
|
|
- expressions = expressions.filter(exp => !isConstantExpression(exp))
|
|
|
- if (this.inVOnce || expressions.length === 0) {
|
|
|
- return this.registerOperation(...operations)
|
|
|
- }
|
|
|
- const existing = this.block.effect.find(e =>
|
|
|
- isSameExpression(e.expressions, expressions),
|
|
|
- )
|
|
|
- if (existing) {
|
|
|
- existing.operations.push(...operations)
|
|
|
- } else {
|
|
|
- this.block.effect.push({
|
|
|
- expressions,
|
|
|
- operations,
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- function isSameExpression(
|
|
|
- a: SimpleExpressionNode[],
|
|
|
- b: SimpleExpressionNode[],
|
|
|
- ) {
|
|
|
- if (a.length !== b.length) return false
|
|
|
- return a.every((exp, i) => exp.content === b[i].content)
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- template: '',
|
|
|
- childrenTemplate: [],
|
|
|
- pushTemplate(content) {
|
|
|
- const existing = root.template.findIndex(template => template === content)
|
|
|
- if (existing !== -1) return existing
|
|
|
- root.template.push(content)
|
|
|
- return root.template.length - 1
|
|
|
- },
|
|
|
- registerTemplate() {
|
|
|
- if (!this.template) return -1
|
|
|
- const id = this.pushTemplate(this.template)
|
|
|
- return (this.dynamic.template = id)
|
|
|
- },
|
|
|
- registerOperation(...node) {
|
|
|
- this.block.operation.push(...node)
|
|
|
- },
|
|
|
- }
|
|
|
- context.root = context
|
|
|
- return context
|
|
|
-}
|
|
|
-
|
|
|
// AST -> IR
|
|
|
export function transform(
|
|
|
root: RootNode,
|
|
|
@@ -229,7 +226,7 @@ export function transform(
|
|
|
},
|
|
|
}
|
|
|
|
|
|
- const context = createRootContext(ir, root, options)
|
|
|
+ const context = new TransformContext(ir, root, options)
|
|
|
|
|
|
transformNode(context)
|
|
|
|