Skip to content

Commit

Permalink
feat: support s-is, see baidu/san#533
Browse files Browse the repository at this point in the history
  • Loading branch information
harttle committed Nov 5, 2020
1 parent f8c2b81 commit df78670
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 55 deletions.
5 changes: 0 additions & 5 deletions src/models/component-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export interface ComponentInfo {
initData?(): any,
getComputedNames (): string[]
getFilterNames (): string[]
getChildComponentRenference (tagName: string): ComponentReference | undefined
}

/**
Expand All @@ -41,10 +40,6 @@ abstract class ComponentInfoImpl<R extends ComponentReference = ComponentReferen
abstract hasMethod (name: string): boolean
abstract getComputedNames (): string[]
abstract getFilterNames (): string[]

getChildComponentRenference (tagName: string): R | undefined {
return this.childComponents.get(tagName)
}
}

export class DynamicComponentInfo extends ComponentInfoImpl<DynamicComponentReference> implements ComponentInfo {
Expand Down
49 changes: 31 additions & 18 deletions src/models/component-reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,42 @@ import { ComponentConstructor } from 'san'
* // ComponentReference { specifier: './b.san', id: 'AComponent' }
* import { AComponent } from './b.san'
*/
export interface ComponentReference {
/**
* 组件所在源文件的相对路径
*/
specifier: string
/**
* 组件在所属 SanSourceFile 中的唯一标识,用来文件间引用组件。
*
* - 默认导出为的 ID 为 default,包括 module.exports = Component, export default Component
* - 其他导出的 ID 为 class 名,对于 ComponentClass(没有 Class 名)为递增数字
* - ID 是语言无关的。不可直接用于目标语言文件中的标识符,后者需要解决名字冲突和标识符合法性的问题,是语言相关的。
*/
id: string
export class ComponentReference {
constructor (
/**
* 组件所在源文件的相对路径
*/
public readonly specifier: string,
/**
* 组件在所属 SanSourceFile 中的唯一标识,用来文件间引用组件。
*
* - 默认导出为的 ID 为 default,包括 module.exports = Component, export default Component
* - 其他导出的 ID 为 class 名,对于 ComponentClass(没有 Class 名)为递增数字
* - ID 是语言无关的。不可直接用于目标语言文件中的标识符,后者需要解决名字冲突和标识符合法性的问题,是语言相关的。
*/
public readonly id: string
) {}

toString () {
const { specifier, id } = this
return `{specifier: "${specifier}", id: "${id}"}`
}
}

/**
* ComponentReference 的特型,用于 ComponentClassParser
*/
export interface DynamicComponentReference extends ComponentReference {
/**
* 从 ComponentClass 解析时,可以引用到子组件的 ComponentClass
*/
componentClass: ComponentConstructor<{}, {}>
export class DynamicComponentReference extends ComponentReference {
constructor (
public readonly specifier: string,
public readonly id: string,
/**
* 从 ComponentClass 解析时,可以引用到子组件的 ComponentClass
*/
public readonly componentClass: ComponentConstructor<{}, {}>
) {
super(specifier, id)
}
}

export function componentID (isDefault: boolean, genID: string | (() => string)) {
Expand Down
12 changes: 7 additions & 5 deletions src/parsers/component-class-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export class ComponentClassParser {

parse (): DynamicSanSourceFile {
const componentInfos = []
const stack: DynamicComponentReference[] = [{ componentClass: this.root, id: '' + this.id++, specifier: '.' }]
const stack: DynamicComponentReference[] = [
new DynamicComponentReference('.', '' + this.id++, this.root)
]
const parsed = new Set()
while (stack.length) {
const { id, componentClass } = stack.pop()!
Expand Down Expand Up @@ -65,11 +67,11 @@ export class ComponentClassParser {
const components: { [key: string]: ComponentConstructor<{}, {}> } = getMember(parentComponentClass, 'components', {})
for (const [tagName, componentClass] of Object.entries(components)) {
// 可能是空,例如 var Foo = defineComponent({components: {foo: Foo}})
children.set(tagName, {
specifier: '.',
id: componentID(componentClass === this.root, () => this.getOrSetID(componentClass)),
children.set(tagName, new DynamicComponentReference(
'.',
componentID(componentClass === this.root, () => this.getOrSetID(componentClass)),
componentClass
})
))
}
return children
}
Expand Down
8 changes: 4 additions & 4 deletions src/parsers/javascript-san-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,14 @@ export class JavaScriptSanParser {

private createChildComponentReference (child: Node): ComponentReference {
if (this.componentIDs.has(child)) {
return { specifier: '.', id: this.componentIDs.get(child)! }
return new ComponentReference('.', this.componentIDs.get(child)!)
}
if (isIdentifier(child)) {
if (this.imports.has(child.name)) {
const [specifier, id] = this.imports.get(child.name)!
return { specifier, id }
return new ComponentReference(specifier, id)
}
return { specifier: '.', id: child.name }
return new ComponentReference('.', child.name)
}
if (this.isCreateComponentLoaderCall(child)) {
const options = child.arguments[0]
Expand All @@ -95,7 +95,7 @@ export class JavaScriptSanParser {

// placeholder 未定义,生成一个默认的组件
const cmpt = this.getOrCreateDefaultLoaderComponent()
return { specifier: '.', id: cmpt.id }
return new ComponentReference('.', cmpt.id)
}
throw new Error(`${location(child)} cannot parse components`)
}
Expand Down
13 changes: 7 additions & 6 deletions src/runtime/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
* - 也不能利用 exports 引用当前文件的其他组件: exports.sanSSRRenderX()
*/
import { SanComponent } from 'san'
import { ComponentReference } from '../models/component-reference'

export interface Resolver {
getRenderer: (id: string, specifier?: string) => Function
getRenderer: (ref: { id: string, specifier?: string }) => Function
setRenderer: (id: string, fn: Function) => void
/**
* 每个组件的每次 render 执行,共用同一个 prototype
Expand All @@ -23,21 +24,21 @@ export interface Resolver {
setPrototype: (id: string, proto: SanComponent<{}>) => void
}

export function createResolver (exports: {[key: string]: any}) {
export function createResolver (exports: {[key: string]: any}): Resolver {
return {
getRenderer: function (id: string, specifier: string = '.') {
getRenderer: function ({ id, specifier = '.' }: Partial<ComponentReference>) {
const mod = specifier === '.' ? exports : require(specifier)
return mod[`sanSSRRender${id}`]
},
setRenderer: function (id: string, fn: Function) {
exports[`sanSSRRender${id}`] = fn
},
getPrototype: function (id: string) {
return this.prototypes[id]
return this['prototypes'][id]
},
setPrototype: function (id: string, proto: any) {
this.prototypes[id] = proto
this['prototypes'][id] = proto
},
prototypes: {}
}
} as Resolver
}
20 changes: 15 additions & 5 deletions src/target-js/compilers/anode-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,23 @@ export class ANodeCompiler<T extends 'none' | 'typed'> {
if (TypeGuards.isATemplateNode(aNode)) return this.compileTemplate(aNode)
if (TypeGuards.isAFragmentNode(aNode)) return this.compileFragment(aNode)

const ref = this.componentInfo.getChildComponentRenference(aNode.tagName)
if (ref) {
return this.compileComponent(aNode, ref, isRootElement)
const childComponentReference = this.generateRef(aNode)
if (childComponentReference) {
return this.compileComponent(aNode, childComponentReference, isRootElement)
}
return this.compileElement(aNode, isRootElement)
}

private generateRef (aNode: ANode) {
if (aNode.directives.is) {
this.emitter.writeLine(`let ref = refs[${expr(aNode.directives.is.value)}];`)
return 'ref'
}
if (this.componentInfo.childComponents.has(aNode.tagName)) {
return this.componentInfo.childComponents.get(aNode.tagName)!.toString()
}
}

private compileText (aNode: ATextNode) {
const { emitter } = this
const shouldEmitComment = TypeGuards.isExprTextNode(aNode.textExpr) && aNode.textExpr.original && !this.ssrOnly
Expand Down Expand Up @@ -173,7 +183,7 @@ export class ANodeCompiler<T extends 'none' | 'typed'> {
this.emitter.writeIf('!noDataOutput', () => this.emitter.writeDataComment())
}

private compileComponent (aNode: ANode, ref: ComponentReference, isRootElement: boolean) {
private compileComponent (aNode: ANode, ref: string, isRootElement: boolean) {
const { emitter } = this

const defaultSourceSlots: ANode[] = []
Expand Down Expand Up @@ -212,7 +222,7 @@ export class ANodeCompiler<T extends 'none' | 'typed'> {

emitter.nextLine('html += ')
emitter.writeFunctionCall(
`runtime.resolver.getRenderer("${ref.id}", "${ref.specifier}")`,
`runtime.resolver.getRenderer(${ref})`,
[this.componentDataCode(aNode), ndo, 'runtime', 'parentCtx', stringifier.str(aNode.tagName) + ', slots']
)
}
Expand Down
12 changes: 9 additions & 3 deletions src/target-js/compilers/renderer-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ export class RendererCompiler {
emitter.writeLine('var html = "";')

this.genComponentContextCode(info)
emitter.writeLine('ctx.instance.data = new runtime.SanData(ctx.data, ctx.instance.computed)')
emitter.writeLine('ctx.instance.parentComponent = parentCtx && parentCtx.instance')
emitter.writeLine('var parentCtx = ctx;')

// instance preraration
Expand All @@ -61,6 +59,7 @@ export class RendererCompiler {
}

// calc computed
// TODO remove ctx.computedNames
emitter.writeFor('var i = 0; i < ctx.computedNames.length; i++', () => {
emitter.writeLine('var name = ctx.computedNames[i];')
emitter.writeLine('data[name] = ctx.instance.computed[name].apply(ctx.instance);')
Expand Down Expand Up @@ -94,9 +93,16 @@ export class RendererCompiler {
private genComponentContextCode (componentInfo: ComponentInfo) {
const { emitter } = this
emitter.writeLine(`let instance = _.createFromPrototype(runtime.resolver.getPrototype("${componentInfo.id}"));`)
emitter.writeLine('instance.data = new runtime.SanData(data, instance.computed)')
emitter.writeLine('instance.parentComponent = parentCtx && parentCtx.instance')

emitter.nextLine('let computedNames = [')
emitter.write(componentInfo.getComputedNames().map(x => `'${x}'`).join(', '))
emitter.feedLine('];')
emitter.writeLine('var ctx = {instance, slots, data, parentCtx, computedNames}')

const refs = [...componentInfo.childComponents.entries()].map(([key, val]) => `"${key}": ${val}`).join(', ')
emitter.writeLine(`let refs = {${refs}}`)

emitter.writeLine('var ctx = {instance, slots, data, parentCtx, computedNames, refs}')
}
}
4 changes: 2 additions & 2 deletions src/target-js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default class ToJSCompiler implements Compiler {
runtime.resolver.setRenderer(id, cc.compileComponentRenderer(info))
}
return (data: any, noDataOutput: boolean = false) => {
const render = runtime.resolver.getRenderer(entryComponentInfo.id)
const render = runtime.resolver.getRenderer({ id: entryComponentInfo.id })
return render(data, noDataOutput, runtime)
}
}
Expand All @@ -93,7 +93,7 @@ export default class ToJSCompiler implements Compiler {
// 导出入口 render 函数
const entryInfo = sourceFile.entryComponentInfo
if (entryInfo) {
emitter.writeLine(`module.exports = Object.assign(sanSSRRuntime.resolver.getRenderer("${entryInfo.id}"), exports)`)
emitter.writeLine(`module.exports = Object.assign(sanSSRRuntime.resolver.getRenderer({id:"${entryInfo.id}"}), exports)`)
}
}

Expand Down
14 changes: 7 additions & 7 deletions src/utils/ts-ast-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,16 @@ export function getChildComponents (clazz: ClassDeclaration, defaultClassDeclara
const childComponentClassName = prop.getInitializerIfKindOrThrow(SyntaxKind.Identifier).getText()
if (importedNames.has(childComponentClassName)) { // 子组件来自外部源文件
const { specifier, named } = importedNames.get(childComponentClassName)!
ret.set(propName, {
ret.set(propName, new ComponentReference(
specifier,
id: componentID(!named, childComponentClassName)
})
componentID(!named, childComponentClassName)
))
} else { // 子组件来自当前源文件
const isDefault = !!defaultClassDeclaration && defaultClassDeclaration.getName() === childComponentClassName
ret.set(propName, {
specifier: '.',
id: componentID(isDefault, childComponentClassName)
})
ret.set(propName, new ComponentReference(
'.',
componentID(isDefault, childComponentClassName)
))
}
}
return ret
Expand Down

0 comments on commit df78670

Please sign in to comment.