diff --git a/bin/debug-local.js b/bin/debug-local.js index d5ced47b..c0c385b5 100644 --- a/bin/debug-local.js +++ b/bin/debug-local.js @@ -23,6 +23,6 @@ fs.writeFileSync(path.resolve(__dirname, './output.js'), res) const Component = require('./component') const render = require('./output') -const html = render({}, false, null, 'div', {}, { ComponentClass: Component }) +const html = render({}, { ComponentClass: Component }) console.log(html) diff --git a/bin/render-by-source.js b/bin/render-by-source.js index 39488570..364a7cb7 100755 --- a/bin/render-by-source.js +++ b/bin/render-by-source.js @@ -16,6 +16,8 @@ if (fs.existsSync(ssrSpecPath)) { } const render = require(join(caseRoot, `${caseName}/output/ssr.js`)) -const html = render(...getRenderArguments(caseName, caseRoot), { context: ssrSpec && ssrSpec.context }) +const html = render(...getRenderArguments(caseName, caseRoot, { + parentCtx: { context: ssrSpec && ssrSpec.context } +})) process.stdout.write(html) diff --git a/src/ast/renderer-ast-util.ts b/src/ast/renderer-ast-util.ts index 5ab464fb..4eac6716 100644 --- a/src/ast/renderer-ast-util.ts +++ b/src/ast/renderer-ast-util.ts @@ -5,7 +5,7 @@ * 例如:new AssignmentStatement(new Identifier('html'), new Literal('foo')) 可以简写为 ASSIGN(I('html), L('foo)) */ -import { SyntaxKind, SyntaxNode, Block, MapLiteral, UnaryOperator, UnaryExpression, NewExpression, VariableDefinition, ReturnStatement, BinaryOperator, If, Null, Undefined, AssignmentStatement, Statement, Expression, Identifier, ExpressionStatement, BinaryExpression, Literal, TryStatement, CatchClause } from './renderer-ast-dfn' +import { SyntaxKind, SyntaxNode, Block, MapLiteral, UnaryOperator, UnaryExpression, NewExpression, VariableDefinition, ReturnStatement, BinaryOperator, If, Null, Undefined, AssignmentStatement, Statement, Expression, Identifier, ExpressionStatement, BinaryExpression, Literal, TryStatement, CatchClause, ConditionalExpression } from './renderer-ast-dfn' export function createHTMLLiteralAppend (html: string) { return STATEMENT(BINARY(I('html'), '+=', L(html))) @@ -35,6 +35,10 @@ export function createTryStatement (block: Statement[], param: Identifier, body: return new TryStatement(block, new CatchClause(param, body)) } +export function createDefineWithDefaultValue (varName: string, value: Expression, defaultValue: Expression) { + return DEF(varName, new ConditionalExpression(BINARY(value, '==', NULL), defaultValue, value)) +} + export function L (val: any) { return Literal.create(val) } diff --git a/src/compilers/anode-compiler.ts b/src/compilers/anode-compiler.ts index bce61032..c64574c8 100644 --- a/src/compilers/anode-compiler.ts +++ b/src/compilers/anode-compiler.ts @@ -232,13 +232,18 @@ export class ANodeCompiler { } // get and call renderer - const args = [this.childRenderData(aNode), ndo, I('parentCtx'), L(aNode.tagName), childSlots] + const mapItems = [ + [I('noDataOutput'), ndo], + [I('parentCtx'), I('parentCtx')], + [I('tagName'), L(aNode.tagName)], + [I('slots'), childSlots] + ] as ConstructorParameters[0] if (this.useProvidedComponentClass) { assert(ChildComponentClassName !== '') - args.push(new MapLiteral([ - [I('ComponentClass'), I(ChildComponentClassName)] - ])) + mapItems.push([I('ComponentClass'), I(ChildComponentClassName)]) } + + const args = [this.childRenderData(aNode), new MapLiteral(mapItems)] const childRenderCall = new FunctionCall( new ComponentRendererReference(ref, L(aNode.tagName)), args diff --git a/src/compilers/renderer-compiler.ts b/src/compilers/renderer-compiler.ts index eaa66a81..5057f07b 100644 --- a/src/compilers/renderer-compiler.ts +++ b/src/compilers/renderer-compiler.ts @@ -8,7 +8,7 @@ import { ANodeCompiler } from './anode-compiler' import { ComponentInfo } from '../models/component-info' import { RenderOptions } from './renderer-options' import { FunctionDefinition, ComputedCall, Foreach, FunctionCall, MapLiteral, If, CreateComponentInstance, ImportHelper, ComponentReferenceLiteral, ConditionalExpression, BinaryExpression } from '../ast/renderer-ast-dfn' -import { EMPTY_MAP, STATEMENT, NEW, BINARY, ASSIGN, DEF, RETURN, createDefaultValue, L, I, NULL, UNDEFINED, createTryStatement } from '../ast/renderer-ast-util' +import { EMPTY_MAP, STATEMENT, NEW, BINARY, ASSIGN, DEF, RETURN, createDefaultValue, L, I, NULL, UNDEFINED, createTryStatement, createDefineWithDefaultValue } from '../ast/renderer-ast-util' import { IDGenerator } from '../utils/id-generator' import { mergeLiteralAdd } from '../optimizers/merge-literal-add' @@ -28,10 +28,6 @@ export class RendererCompiler { public compileToRenderer (componentInfo: ComponentInfo) { const args = [ DEF('data'), - DEF('noDataOutput', L(false)), - DEF('parentCtx', NULL), - DEF('tagName', L('div')), - DEF('slots', EMPTY_MAP), // 参数太多了,后续要增加的参数统一收敛到这里 DEF('info', L({})) @@ -52,6 +48,10 @@ export class RendererCompiler { } // get params from info + body.push(createDefineWithDefaultValue('noDataOutput', BINARY(I('info'), '.', I('noDataOutput')), L(false))) + body.push(createDefineWithDefaultValue('parentCtx', BINARY(I('info'), '.', I('parentCtx')), NULL)) + body.push(createDefineWithDefaultValue('tagName', BINARY(I('info'), '.', I('tagName')), L('div'))) + body.push(createDefineWithDefaultValue('slots', BINARY(I('info'), '.', I('slots')), EMPTY_MAP)) if (this.options.useProvidedComponentClass) { body.push(DEF('ComponentClass', new BinaryExpression(I('info'), '.', I('ComponentClass')))) } diff --git a/src/fixtures/case.ts b/src/fixtures/case.ts index 05b89681..341df4a6 100644 --- a/src/fixtures/case.ts +++ b/src/fixtures/case.ts @@ -6,6 +6,7 @@ import debugFactory from 'debug' import { compileToRenderer } from '../index' import mkdirp from 'mkdirp' import type { RenderOptions } from '../compilers/renderer-options' +import type { Renderer } from '../models/renderer' const debug = debugFactory('case') export const caseRoots = [ @@ -140,15 +141,17 @@ export function readCaseData (caseName: string, caseRoot: string) { return JSON.parse(readFileSync(dataPath, 'utf8')) } -export function getRenderArguments (caseName: string, caseRoot: string) { +export function getRenderArguments (caseName: string, caseRoot: string, info: Partial['1']> = {}): Parameters { const data = readCaseData(caseName, caseRoot) const noDataOutput = /-ndo$/.test(caseName) - return [data, noDataOutput] + return [data, Object.assign({ + noDataOutput + }, info)] } export function renderOnthefly (caseName: string, caseRoot: string) { const render = compileCaseToRenderer(caseName, caseRoot) const data = readCaseData(caseName, caseRoot) const noDataOutput = /-ndo$/.test(caseName) - return render(data, noDataOutput) + return render(data, { noDataOutput }) } diff --git a/src/models/renderer.ts b/src/models/renderer.ts index 797d328c..5cabdd08 100644 --- a/src/models/renderer.ts +++ b/src/models/renderer.ts @@ -1,6 +1,17 @@ /** * SanProject#compileToRenderer() 输出的 renderer 的类型声明 */ + +import type { ComponentClass } from './component' +import type { GlobalContext } from './global-context' + export interface Renderer { - (data: { [key: string]: any }, noDataOutput?: boolean): string + (data: { [key: string]: any }, info?: { + noDataOutput?: boolean, + parentCtx?: { + context?: GlobalContext + }, + tagName?: string, + ComponentClass?: ComponentClass + }): string } diff --git a/src/target-js/index.ts b/src/target-js/index.ts index 9d53022e..0b58ff95 100644 --- a/src/target-js/index.ts +++ b/src/target-js/index.ts @@ -71,9 +71,9 @@ export default class ToJSCompiler implements TargetCodeGenerator { sanSSRResolver.setPrototype(info.id, info.componentClass.prototype) sanSSRResolver.setRenderer(info.id, resolvedRenderer) } - return (data: any, noDataOutput: boolean = false) => { + return (data, info) => { const render = sanSSRResolver.getRenderer({ id: entryComponentInfo.id }) - return render(data, noDataOutput) + return render(data, info) } } diff --git a/test/e2e.spec.ts b/test/e2e.spec.ts index 0369b767..093d6cac 100644 --- a/test/e2e.spec.ts +++ b/test/e2e.spec.ts @@ -5,6 +5,7 @@ import type { RenderOptions } from '../src/index' import { existsSync } from 'fs' import { execSync } from 'child_process' import type { GlobalContext } from '../src/models/global-context' +import type { Renderer } from '../src/models/renderer' export interface SsrSpecConfig { enabled: { @@ -81,9 +82,9 @@ for (const { caseName, caseRoot } of cases) { ssrSpec.afterHook && ssrSpec.afterHook('jssrc') // render - const render = require(join(caseRoot, caseName, 'output', folderName, 'ssr.js')) + const render = require(join(caseRoot, caseName, 'output', folderName, 'ssr.js')) as Renderer // 测试在 strict mode,因此需要手动传入 require - const got = render(...getRenderArguments(caseName, caseRoot), { context: ssrSpec && ssrSpec.context }) + const got = render(...getRenderArguments(caseName, caseRoot, { parentCtx: { context: ssrSpec && ssrSpec.context } })) const [data, html] = parseSanHTML(got) expect(data).toEqual(expectedData) @@ -102,14 +103,16 @@ for (const { caseName, caseRoot } of cases) { ssrSpec.afterHook && ssrSpec.afterHook('comsrc') // render - const render = require(join(caseRoot, caseName, 'output', folderName, 'ssr.js')) + const render = require(join(caseRoot, caseName, 'output', folderName, 'ssr.js')) as Renderer // 测试在 strict mode,因此需要手动传入 require - const info = {} as any + const info = { + parentCtx: { context: ssrSpec && ssrSpec.context } + } as Parameters[1] if (ssrSpec.compileOptions.useProvidedComponentClass) { info.ComponentClass = require(join(caseRoot, caseName, 'component.js')) } - const got = render(...getRenderArguments(caseName, caseRoot), { context: ssrSpec && ssrSpec.context }, 'div', {}, info) + const got = render(...getRenderArguments(caseName, caseRoot, info)) const [data, html] = parseSanHTML(got) expect(data).toEqual(expectedData) diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index 634d4e65..fc1136db 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -17,7 +17,9 @@ describe('compileToRenderer', function () { const render = compileToRenderer(ComponentClass) expect(render).toBeInstanceOf(Function) - expect(render({}, true)).toEqual('A') + expect(render({}, { + noDataOutput: true + })).toEqual('A') }) it('should compile to a renderer function which accepts data', function () { @@ -25,7 +27,9 @@ describe('compileToRenderer', function () { const render = compileToRenderer(ComponentClass) expect(render).toBeInstanceOf(Function) - expect(render({ name: 'Harttle' }, true)).toEqual('name: Harttle') + expect(render({ name: 'Harttle' }, { + noDataOutput: true + })).toEqual('name: Harttle') }) it('should run inited only in run time', function () { @@ -34,7 +38,9 @@ describe('compileToRenderer', function () { const render = compileToRenderer(ComponentClass) expect(inited).not.toBeCalled() - expect(render({}, true)).toEqual('
a
') + expect(render({}, { + noDataOutput: true + })).toEqual('
a
') expect(inited).toBeCalledTimes(1) }) }) diff --git a/test/unit/models/san-project.spec.ts b/test/unit/models/san-project.spec.ts index 6252d9c4..26d601de 100644 --- a/test/unit/models/san-project.spec.ts +++ b/test/unit/models/san-project.spec.ts @@ -101,7 +101,9 @@ describe('SanProject', function () { const render = proj.compileToRenderer(require(resolve(stubRoot, './name.comp.js'))) expect(render).toBeInstanceOf(Function) - expect(render({ name: 'Harttle' }, true)).toEqual('
name: Harttle
') + expect(render({ name: 'Harttle' }, { + noDataOutput: true + })).toEqual('
name: Harttle
') }) it('the noDataOutput parameter should be optional and default to false', function () { @@ -140,7 +142,9 @@ describe('SanProject', function () { const render = proj.compileToRenderer(require(resolve(stubRoot, './name.comp.js'))) expect(render).toBeInstanceOf(Function) - expect(render({ name: 'Harttle' }, true)).toEqual('
name: Harttle
') + expect(render({ name: 'Harttle' }, { + noDataOutput: true + })).toEqual('
name: Harttle
') }) it('should remove modules', function () {