Skip to content
This repository has been archived by the owner on Sep 24, 2021. It is now read-only.

Commit

Permalink
Abstracted server render expression result
Browse files Browse the repository at this point in the history
The result of compiling a node to be server rendered is non specific to a language (at templating level, expressions are still js ast nodes). Converted ts module compilation to accept this middle step. Renamed some chef interfaces
  • Loading branch information
kaleidawave committed Oct 24, 2020
1 parent 2a3745f commit 47ebc72
Show file tree
Hide file tree
Showing 24 changed files with 231 additions and 180 deletions.
2 changes: 1 addition & 1 deletion src/builders/client-side-routing.ts
Expand Up @@ -38,7 +38,7 @@ export function setNotFoundRoute(component: Component) {
export function injectRoutes(routerModule: Module): void {

// Use the bundled router and get the router component
const routerComponent: ClassDeclaration = routerModule.classes.find(cls => cls.name!.name === "Router")!;
const routerComponent: ClassDeclaration = routerModule.classes.find(cls => cls.actualName === "Router")!;

// Build up array that map patterns to components and their possible layout
const routePairArray = new ArrayLiteral();
Expand Down
17 changes: 8 additions & 9 deletions src/builders/prism-client.ts
Expand Up @@ -35,7 +35,7 @@ export function treeShakeBundle(runtimeFeatures: IRuntimeFeatures, bundle: Modul
statement instanceof ExportStatement && statement.exported instanceof FunctionDeclaration && statement.exported.name?.name === "createComment"
));

const otherStatements = Module.fromString(fileBundle.get("others.ts")!).statements;
const otherStatements = Module.fromString(fileBundle.get("others.ts")!, "others.ts").statements;
const componentClass = (bundle.statements.find(statement =>
statement instanceof ExportStatement && statement.exported instanceof ClassDeclaration && statement.exported.name?.name === "Component"
) as ExportStatement).exported as ClassDeclaration;
Expand All @@ -53,20 +53,20 @@ export function treeShakeBundle(runtimeFeatures: IRuntimeFeatures, bundle: Modul
componentClass.members = componentClass.members.filter(member => !(
!(member instanceof Comment) && (
member instanceof VariableDeclaration && member.name === "_ifSwapElemCache" ||
member instanceof FunctionDeclaration && member.name?.name === "setElem"
member instanceof FunctionDeclaration && member.actualName === "setElem"
)
));

// Remove conditionalSwap and tryAssignData
bundle.statements = bundle.statements.filter(statement => !(
statement instanceof ExportStatement && statement.exported instanceof FunctionDeclaration &&
["conditionalSwap", "tryAssignData"].includes(statement.exported.name?.name!)
["conditionalSwap", "tryAssignData"].includes(statement.exported.actualName!)
));
}
if (!runtimeFeatures.observableArrays) {
// Remove createObservableArray, isArrayHoley and setLength function
bundle.statements = bundle.statements.filter(statement => !(
statement instanceof ExportStatement && statement.exported instanceof FunctionDeclaration && ["createObservableArray", "isArrayHoley", "setLength"].includes(statement.exported.name?.name!)
statement instanceof ExportStatement && statement.exported instanceof FunctionDeclaration && ["createObservableArray", "isArrayHoley", "setLength"].includes(statement.exported.actualName!)
));
}
if (!runtimeFeatures.svg) {
Expand All @@ -82,7 +82,7 @@ export function treeShakeBundle(runtimeFeatures: IRuntimeFeatures, bundle: Modul
statement.exported.name?.name === "h") as ExportStatement;

(renderFunction.exported as FunctionDeclaration).statements =
(Module.fromString(fileBundle.get("others.ts")!).statements
(Module.fromString(fileBundle.get("others.ts")!, "others.ts").statements
.find(statement =>
statement instanceof FunctionDeclaration &&
statement.name?.name === "h"
Expand All @@ -97,7 +97,7 @@ export function treeShakeBundle(runtimeFeatures: IRuntimeFeatures, bundle: Modul
statement.exported.name?.name === "createObservable"
));

const createObservableObject = Module.fromString(fileBundle.get("others.ts")!)
const createObservableObject = Module.fromString(fileBundle.get("others.ts")!, "others.ts")
.statements.find(statement =>
statement instanceof FunctionDeclaration && statement.name?.name === "createObservableObject"
);
Expand All @@ -115,8 +115,7 @@ export function treeShakeBundle(runtimeFeatures: IRuntimeFeatures, bundle: Modul
* @param clientSideRouting Include the client router module (including injecting routes)
*/
export async function getPrismClient(clientSideRouting: boolean = true): Promise<Module> {
const bundle = new Module();
bundle.filename = "prism.js";
const bundle = new Module("prism.js");
for (const clientLib of clientModuleFilenames) {
const module = Module.fromString(fileBundle.get(clientLib)!, join("bundle", clientLib));
if (clientLib.endsWith("router.ts")) {
Expand All @@ -135,7 +134,7 @@ export async function buildIndexHtml(settings: IFinalPrismSettings): Promise<HTM
// Read the included template or one specified by settings
let document: HTMLDocument;
if (settings.templatePath === defaultTemplateHTML) {
document = HTMLDocument.fromString(fileBundle.get("template.html")!);
document = HTMLDocument.fromString(fileBundle.get("template.html")!, "template.html");
} else {
document = await HTMLDocument.fromFile(settings.absoluteTemplatePath);
}
Expand Down
88 changes: 78 additions & 10 deletions src/builders/server-side-rendering/typescript.ts
Expand Up @@ -17,7 +17,73 @@ import { Component } from "../../component";
import { assignToObjectMap } from "../../helpers";
import { buildMetaTags } from "../../metatags";
import { IFinalPrismSettings } from "../../settings";
import { IServerRenderSettings, serverRenderPrismNode } from "../../templating/builders/server-render";
import { IServerRenderSettings, ServerRenderedChunks, serverRenderPrismNode } from "../../templating/builders/server-render";

function templateLiteralFromServerRenderChunks(serverChunks: ServerRenderedChunks): TemplateLiteral {
const templateLiteral = new TemplateLiteral();
for (const chunk of serverChunks) {
if (typeof chunk === "string") {
templateLiteral.addEntry(chunk);
} else if ("value" in chunk) {
templateLiteral.addEntry(wrapWithEscapeCall(chunk.value));
} else if ("condition" in chunk) {
templateLiteral.addEntry(new Expression({
lhs: chunk.condition as IValue,
operation: Operation.Ternary,
rhs: new ArgumentList([
templateLiteralFromServerRenderChunks(chunk.truthyRenderExpression),
templateLiteralFromServerRenderChunks(chunk.falsyRenderExpression)
])
}))
} else if ("subject" in chunk) {
templateLiteral.addEntry(
new Expression({
lhs: new VariableReference("join", new Expression({
lhs: new VariableReference("map", chunk.subject),
operation: Operation.Call,
rhs: new FunctionDeclaration(
null,
[chunk.variable],
[new ReturnStatement(
templateLiteralFromServerRenderChunks(chunk.childRenderExpression)
)],
{ bound: false }
)
})),
operation: Operation.Call,
rhs: new Value("", Type.string)
})
);
} else if ("func" in chunk) {
// TODO temp fix
if (chunk.args.has("attributes")) {
// @ts-ignore chunk.args isn't used again so can overwrite value to be in ts base...
chunk.args.set("attributes", templateLiteralFromServerRenderChunks(chunk.args.get("attributes")!));
}
templateLiteral.addEntry(
new Expression({
lhs: new VariableReference(chunk.func.actualName!),
operation: Operation.Call,
rhs: chunk.func.buildArgumentListFromArgumentsMap(chunk.args)
})
);
}
}
return templateLiteral;
}

/**
* Wraps IValue in a escapeValue function. The escape value function escapes html
* @param value
* @example `abc` -> `escape(abc)`
*/
function wrapWithEscapeCall(value: IValue): Expression {
return new Expression({
lhs: new VariableReference("escape"),
operation: Operation.Call,
rhs: value
});
}

export function moduleFromServerRenderedChunks(comp: Component, settings: IFinalPrismSettings): void {
comp.serverModule = new Module(join(settings.absoluteServerOutputPath,
Expand Down Expand Up @@ -121,7 +187,9 @@ export function moduleFromServerRenderedChunks(comp: Component, settings: IFinal
}

// Final argument is to add a entry onto the component that is sent attributes
const renderTemplateLiteral = serverRenderPrismNode(componentHtmlTag, comp.templateData.nodeData, ssrSettings, comp.globals);
// TODO do this higher up
const serverRenderChunks = serverRenderPrismNode(componentHtmlTag, comp.templateData.nodeData, ssrSettings, comp.globals);
const renderTemplateLiteral = templateLiteralFromServerRenderChunks(serverRenderChunks);

// TODO would comp work just using the existing slot functionality?
// TODO could do in the page render function
Expand Down Expand Up @@ -171,32 +239,32 @@ export function moduleFromServerRenderedChunks(comp: Component, settings: IFinal
pageRenderArgs.push(...comp.clientGlobals.map(cG => cG[0]));

const pageRenderCall: IValue = new Expression({
lhs: new VariableReference(renderFunction.name!.name!),
lhs: new VariableReference(renderFunction.actualName!),
operation: Operation.Call,
rhs: new ArgumentList(pageRenderArgs)
});

// Build the metadata
let metadataString = new TemplateLiteral();
let metadataString: ServerRenderedChunks = [];
if (comp.title) {
const title = new HTMLElement("title");
const titleTextNode = new TextNode("", title);
assignToObjectMap(comp.templateData.nodeData, titleTextNode, "textNodeValue", comp.title);
title.children.push(titleTextNode);
metadataString.addEntry(
...serverRenderPrismNode(title, comp.templateData.nodeData, ssrSettings).entries
);
metadataString = metadataString.concat(serverRenderPrismNode(title, comp.templateData.nodeData, ssrSettings));
}

if (comp.metadata) {
const { metadataTags, nodeData: metaDataNodeData } = buildMetaTags(comp.metadata)
for (const metaTag of metadataTags) {
metadataString.addEntry(...serverRenderPrismNode(metaTag, metaDataNodeData, ssrSettings, comp.globals).entries);
metadataString.addEntry("\n");
metadataString = metadataString.concat(serverRenderPrismNode(metaTag, metaDataNodeData, ssrSettings, comp.globals))
if (!settings.minify) {
metadataString.push("\n");
}
}
}

const metaDataArg: IValue = (comp.title || comp.metadata) ? metadataString : new Value("", Type.string);
const metaDataArg: IValue = (comp.title || comp.metadata) ? templateLiteralFromServerRenderChunks(metadataString) : new Value("", Type.string);

// Creates "return renderHTML(renderComponent(***))"
const renderAsPage = new ReturnStatement(
Expand Down
21 changes: 11 additions & 10 deletions src/chef/abstract-asts.ts
@@ -1,19 +1,20 @@
import { IFile } from "./filesystem";
import { IConstruct, IRenderOptions, IRenderSettings } from "./helpers";
import { IRenderable, IRenderOptions, IRenderSettings } from "./helpers";

export abstract class AbstractModule<T> implements IFile, IConstruct {
constructor(public filename: string, public statements: Array<T>) { }
abstract render(settings?: Partial<IRenderSettings>, options?: Partial<IRenderOptions>): string;
export interface IModule extends IFile, IRenderable {
statements: Array<any>;

render(settings?: Partial<IRenderSettings>, options?: Partial<IRenderOptions>): string;

abstract writeToFile(settings?: Partial<IRenderSettings>): void;
writeToFile(settings?: Partial<IRenderSettings>): void;

abstract addExport(exportable: AbstractFunctionDeclaration | any): void;
abstract addImport(importName: any, from: string): void;
addExport(exportable: IFunctionDeclaration | any): void;
addImport(importName: any, from: string): void;
}

export abstract class AbstractFunctionDeclaration {
export interface IFunctionDeclaration extends IRenderable {
statements: Array<any>;

abstract buildArgumentListFromArgumentsMap(argumentMap: Map<string, any>): any;
abstract actualName: string | null;
buildArgumentListFromArgumentsMap(argumentMap: Map<string, any>): any;
actualName: string | null;
}
4 changes: 2 additions & 2 deletions src/chef/helpers.ts
Expand Up @@ -35,7 +35,7 @@ export enum ModuleFormat {
}

export enum ScriptLanguages {
Javascript, Typescript
Javascript, Typescript, Rust
}

/**
Expand Down Expand Up @@ -63,7 +63,7 @@ export function makeRenderSettings(partialSettings: Partial<IRenderSettings>): I
return { ...defaultRenderSettings, ...partialSettings };
}

export interface IConstruct {
export interface IRenderable {
render(settings?: Partial<IRenderSettings>, options?: Partial<IRenderOptions>): string;
}

Expand Down
10 changes: 5 additions & 5 deletions src/chef/html/html.ts
@@ -1,9 +1,9 @@
import { TokenReader, ITokenizationSettings, IRenderSettings, makeRenderSettings, defaultRenderSettings, IConstruct, IRenderOptions, IPosition, IParseSettings, defaultParseSettings } from "../helpers";
import { TokenReader, ITokenizationSettings, IRenderSettings, makeRenderSettings, defaultRenderSettings, IRenderable, IRenderOptions, IPosition, IParseSettings, defaultParseSettings } from "../helpers";
import { Module } from "../javascript/components/module";
import { Stylesheet } from "../css/stylesheet";
import { readFile, writeFile, IFile } from "../filesystem";

export abstract class Node implements IConstruct {
export abstract class Node implements IRenderable {
abstract parent: HTMLElement | HTMLDocument | null;
abstract render(settings: IRenderSettings): string;

Expand Down Expand Up @@ -47,7 +47,7 @@ export abstract class Node implements IConstruct {
}
}

export class TextNode extends Node implements IConstruct {
export class TextNode extends Node implements IRenderable {
constructor(
public text = '',
public parent: HTMLElement | null = null,
Expand All @@ -65,7 +65,7 @@ export class TextNode extends Node implements IConstruct {
}
}

export class HTMLElement extends Node implements IConstruct {
export class HTMLElement extends Node implements IRenderable {
// Tags that don't require </...>
static selfClosingTags = new Set(["area", "base", "br", "embed", "hr", "iframe", "img", "input", "link", "meta", "param", "source", "track", "!DOCTYPE"]);

Expand Down Expand Up @@ -236,7 +236,7 @@ export class HTMLElement extends Node implements IConstruct {
}
}

export class HTMLComment extends Node implements IConstruct {
export class HTMLComment extends Node implements IRenderable {
constructor(
public parent: HTMLElement | HTMLDocument,
public comment: string = "",
Expand Down
16 changes: 10 additions & 6 deletions src/chef/javascript/components/constructs/class.ts
@@ -1,4 +1,4 @@
import { TokenReader, IRenderSettings, IConstruct, makeRenderSettings, ScriptLanguages, defaultRenderSettings } from "../../../helpers";
import { TokenReader, IRenderSettings, IRenderable, makeRenderSettings, ScriptLanguages, defaultRenderSettings } from "../../../helpers";
import { commentTokens, JSToken, stringToTokens } from "../../javascript";
import { TypeSignature } from "../types/type-signature";
import { FunctionDeclaration, ArgumentList, GetSet } from "./function";
Expand Down Expand Up @@ -72,7 +72,7 @@ interface IClassSettings {

type ClassMember = VariableDeclaration | FunctionDeclaration | Comment;

export class ClassDeclaration implements IConstruct, IClassSettings {
export class ClassDeclaration implements IRenderable, IClassSettings {
public name?: TypeSignature; // If null is class expression
public isAbstract: boolean;
public base?: TypeSignature;
Expand Down Expand Up @@ -141,7 +141,7 @@ export class ClassDeclaration implements IConstruct, IClassSettings {

if (member.isStatic && member instanceof FunctionDeclaration) {
if (!this.staticMethods) this.staticMethods = new Map();
this.staticMethods.set(member.name!.name!, member);
this.staticMethods.set(member.actualName!, member);
} else if (member.isStatic && member instanceof VariableDeclaration) {
if (!this.staticFields) this.staticFields = new Map();
this.staticFields.set(member.name!, member);
Expand All @@ -150,16 +150,20 @@ export class ClassDeclaration implements IConstruct, IClassSettings {
this.fields.set(member.name, member);
} else if (member.getSet === GetSet.Get) {
if (!this.getters) this.getters = new Map();
this.getters.set(member.name!.name!, member);
this.getters.set(member.actualName!, member);
} else if (member.getSet === GetSet.Set) {
if (!this.setters) this.setters = new Map();
this.setters.set(member.name!.name!, member);
this.setters.set(member.actualName!, member);
} else {
if (!this.methods) this.methods = new Map();
this.methods.set(member.name!.name!, member);
this.methods.set(member.actualName!, member);
}
}

get actualName() {
return this.name?.name;
}

render(settings: IRenderSettings = defaultRenderSettings): string {
settings = makeRenderSettings(settings);
let acc = "";
Expand Down
9 changes: 4 additions & 5 deletions src/chef/javascript/components/constructs/function.ts
@@ -1,4 +1,4 @@
import { TokenReader, IRenderSettings, IConstruct, makeRenderSettings, ScriptLanguages, defaultRenderSettings } from "../../../helpers";
import { TokenReader, IRenderSettings, IRenderable, makeRenderSettings, ScriptLanguages, defaultRenderSettings } from "../../../helpers";
import { JSToken, stringToTokens } from "../../javascript";
import { IValue } from "../value/value";
import { TypeSignature } from "../types/type-signature";
Expand All @@ -9,7 +9,7 @@ import { ClassDeclaration, Decorator } from "./class";
import { VariableDeclaration, VariableContext } from "../statements/variable";
import { ObjectLiteral } from "../value/object";
import { Module } from "../module";
import { AbstractFunctionDeclaration } from "../../../abstract-asts";
import { IFunctionDeclaration } from "../../../abstract-asts";

export const functionPrefixes = [JSToken.Get, JSToken.Set, JSToken.Async];

Expand All @@ -29,7 +29,7 @@ export function parseFunctionParams(reader: TokenReader<JSToken>): Array<Variabl
return params;
}

export class ArgumentList implements IConstruct {
export class ArgumentList implements IRenderable {
constructor(public args: IValue[] = []) { }

render(settings: IRenderSettings = defaultRenderSettings): string {
Expand Down Expand Up @@ -72,7 +72,7 @@ interface FunctionOptions {
isAbstract: boolean, // TODO implement on IClassMember
}

export class FunctionDeclaration extends AbstractFunctionDeclaration implements IConstruct, FunctionOptions {
export class FunctionDeclaration implements IFunctionDeclaration, FunctionOptions {
name?: TypeSignature; // Null signifies anonymous function
returnType?: TypeSignature;
statements: Array<Statements>;
Expand All @@ -98,7 +98,6 @@ export class FunctionDeclaration extends AbstractFunctionDeclaration implements
statements: Array<Statements> = [],
options: Partial<FunctionOptions> = {}
) {
super();
if (name) {
if (typeof name === "string") {
this.name = new TypeSignature({ name });
Expand Down

0 comments on commit 47ebc72

Please sign in to comment.