From 0acd864a107a867ed2f6729cff7e411621cb403d Mon Sep 17 00:00:00 2001 From: SRNV Date: Thu, 29 Oct 2020 18:17:31 +0100 Subject: [PATCH 01/51] feat(wip): start getting the dom graph --- mod.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/mod.ts b/mod.ts index 7868d99..6c1e386 100644 --- a/mod.ts +++ b/mod.ts @@ -3,4 +3,5 @@ import { ModuleGetter } from './src/classes/ModuleGetter.ts'; const component = await ModuleGetter.buildModule({ entrypoint: './examples/hello-app/HelloApp.tsx', }); + console.warn(component); From 81233d2214679c91f1695b20a3deafc572792006 Mon Sep 17 00:00:00 2001 From: SRNV Date: Fri, 30 Oct 2020 13:42:00 +0100 Subject: [PATCH 02/51] feat: start dom tree description --- examples/hello-app/ListForeach.tsx | 20 -------- mod.ts | 8 ++- src/classes/DOMElement.ts | 80 ++++++++++++++++++++++++++++++ src/classes/EonComponent.ts | 34 +++++++++++++ src/classes/ModuleErrors.ts | 2 +- src/classes/ModuleGetter.ts | 28 +++++++---- src/classes/ModuleResolver.ts | 38 ++++++++++++++ src/functions/jsxFactory.ts | 59 ++++++++++++++++++++++ tsconfig.json | 1 - types.d.ts | 10 ++-- 10 files changed, 243 insertions(+), 37 deletions(-) delete mode 100644 examples/hello-app/ListForeach.tsx create mode 100644 src/classes/DOMElement.ts create mode 100644 src/classes/EonComponent.ts create mode 100644 src/classes/ModuleResolver.ts create mode 100644 src/functions/jsxFactory.ts diff --git a/examples/hello-app/ListForeach.tsx b/examples/hello-app/ListForeach.tsx deleted file mode 100644 index ae47679..0000000 --- a/examples/hello-app/ListForeach.tsx +++ /dev/null @@ -1,20 +0,0 @@ -export const name = "AppHello"; -export default function(this: ViewModel, Directive: any) { - return (<> - - - ) -} -export class ViewModel { - public message = "Hello World"; - public array :number[] = [1, 3]; - static props(props: { name: string }) { - return props - } -} diff --git a/mod.ts b/mod.ts index 6c1e386..c9d3917 100644 --- a/mod.ts +++ b/mod.ts @@ -1,7 +1,11 @@ import { ModuleGetter } from './src/classes/ModuleGetter.ts'; +import ModuleResolver from './src/classes/ModuleResolver.ts'; +import './src/functions/jsxFactory.ts'; -const component = await ModuleGetter.buildModule({ +const opts = { entrypoint: './examples/hello-app/HelloApp.tsx', -}); +}; +const module = await ModuleGetter.buildModule(opts); +const component = await ModuleResolver.resolve(module, opts); console.warn(component); diff --git a/src/classes/DOMElement.ts b/src/classes/DOMElement.ts new file mode 100644 index 0000000..ad401a3 --- /dev/null +++ b/src/classes/DOMElement.ts @@ -0,0 +1,80 @@ +/** + * class that participate to the DOM Tree description + */ +export type DOMTreeElement = DOMElement; +interface DOMElementInterface { + /** the parent element of the element, undefined if the element is on top */ + parent?: DOMTreeElement; + /** the children of the element */ + children: DOMTreeElement[]; + /** the name of the element */ + name?: string; + /** the value of the element, defined if it's a textnode */ + value?: any; + /** the type of the element + * 1 for all elements including the fragments + * 3 for textnodes or attributes + */ + nodeType?: 1 | 3 | 11; + /** + * the element is a template and on top of the dom + * or direct child of the top fragment + */ + isTemplate?: boolean; + /** + * the element is a style element and on top of the dom + * or direct child of the top fragment + */ + isStyle?: boolean; + /** the element is on top and it's a fragment element */ + isFragment?: boolean; + /** the element is an attribute and the nodetype is also 3 */ + isAttribute?: boolean; + /** the attributes of the element */ + attributes?: { [k: string]: any }; +} +export default class DOMElement implements DOMElementInterface { + parent: DOMElementInterface['parent']; + children: DOMElementInterface['children']; + name: DOMElementInterface['name']; + nodeType: DOMElementInterface['nodeType']; + value: DOMElementInterface['value']; + isAttribute: DOMElementInterface['isAttribute']; + attributes: DOMElementInterface['attributes']; + constructor(opts: DOMElementInterface) { + const { + nodeType, + parent, + name, + children, + value, + isAttribute, + attributes, + } = opts; + this.nodeType = nodeType; + this.parent = parent; + this.name = name; + this.children = children; + this.value = value; + this.isAttribute = isAttribute; + this.attributes = attributes; + } + get isBoundTextnode(): boolean { + return this.nodeType === 3 && typeof this.value === 'function' && !this.isAttribute; + } + get isTemplate(): boolean { + return this.nodeType === 1 && this.name === 'template' && (!this.parent || this.parent.isFragment); + } + get isStyle(): boolean { + return this.nodeType === 1 && this.name === 'style' && (!this.parent || this.parent.isFragment); + } + get isFragment(): boolean { + return this.nodeType === 11 && this.name === undefined && !this.parent; + } + setParent(parent: DOMTreeElement) { + this.parent = parent; + } + setChild(child: DOMTreeElement) { + this.children.push(child); + } +} \ No newline at end of file diff --git a/src/classes/EonComponent.ts b/src/classes/EonComponent.ts new file mode 100644 index 0000000..7d04529 --- /dev/null +++ b/src/classes/EonComponent.ts @@ -0,0 +1,34 @@ +import DOMElement from './DOMElement.ts'; +import type { EonModule } from './ModuleGetter.ts'; + +export interface EonComponentInterface { + /** uuid */ + uuid?: string; + /** name */ + name?: string; + /** path the component */ + file?: string; + /** the DOM tree of the component */ + template?: DOMElement; + /** component's ViewModel */ + ViewModel?: EonModule['ViewModel']; +} +export default class EonComponent implements EonComponentInterface { + uuid: EonComponentInterface['uuid']; + name: EonComponentInterface['name']; + file: EonComponentInterface['file']; + template: EonComponentInterface['template']; + ViewModel: EonComponentInterface['ViewModel']; + constructor(opts: EonComponentInterface) { + const { + file, + uuid, + template, + ViewModel, + } = opts; + this.file = file; + this.uuid = uuid; + this.template = template; + this.ViewModel = ViewModel; + } +} \ No newline at end of file diff --git a/src/classes/ModuleErrors.ts b/src/classes/ModuleErrors.ts index 9d22bc8..2c564f1 100644 --- a/src/classes/ModuleErrors.ts +++ b/src/classes/ModuleErrors.ts @@ -4,7 +4,7 @@ export abstract class ModuleErrors { static checkDiagnostics(diagnostics: any[]) { if (diagnostics) { - // TODO expose to the end user diagnostics here + // TODO expose to the end user the diagnostics here console.warn(diagnostics) } else { return; diff --git a/src/classes/ModuleGetter.ts b/src/classes/ModuleGetter.ts index 1657a76..cc6119b 100644 --- a/src/classes/ModuleGetter.ts +++ b/src/classes/ModuleGetter.ts @@ -1,27 +1,32 @@ // @deno-types="../../types.d.ts" +import DOMElement from './DOMElement.ts'; import { ModuleErrors } from './ModuleErrors.ts'; -import { path } from './../../deps.ts'; -import { v4 } from "https://deno.land/std@0.67.0/uuid/mod.ts"; +import { path, v4 } from './../../deps.ts'; export interface ModuleGetterOptions { entrypoint: string; } export interface EonModule { name: string; - default?: (vm?: FunctionConstructor) => JSX.Element; - template?: (vm?: FunctionConstructor) => JSX.Element; - ViewModel: FunctionConstructor; + default?: (vm?: T) => DOMElement; + template?: (vm?: T) => DOMElement; + ViewModel: any; [k: string]: any; } export abstract class ModuleGetter { + /** + * creates a new ts file, with the transpiled JSX + * and imports this file with the dynamic import, + * this generate the ES Module with all the exports (named and default) + */ private static async getModule(transpiled: string, opts: ModuleGetterOptions): Promise { const { entrypoint } = opts; - const newPath = path.join(Deno.cwd(), `${entrypoint}.out.eon.${v4.generate()}.js`); + const newPath = path.join(Deno.cwd(), `${entrypoint}.${v4.generate()}.ts`); // TODO import h and hf from a file Deno.writeTextFileSync(newPath, ` - function h() { return true } - function hf() {return false } + // @ts-nocheck + import { h, hf } from '${path.join(import.meta.url, '../../functions/jsxFactory.ts')}'; ${transpiled} `); const module = import(newPath) as unknown as EonModule; @@ -32,11 +37,16 @@ export abstract class ModuleGetter { return module; } static async buildModule(opts: ModuleGetterOptions): Promise { - const { entrypoint } = opts; const transpiled = await ModuleGetter.getTranspiledFile(opts); const module = await ModuleGetter.getModule(transpiled, opts); return module; } + /** + * + * the transpilation is needed + * because we need to set the jsxFactory and the jsxFragmentFactory + * for the jsx transpilation + */ static async getTranspiledFile(opts: ModuleGetterOptions): Promise { const { entrypoint } = opts; const [diagnostics, mod] = await Deno.compile(entrypoint, undefined, { diff --git a/src/classes/ModuleResolver.ts b/src/classes/ModuleResolver.ts new file mode 100644 index 0000000..8437560 --- /dev/null +++ b/src/classes/ModuleResolver.ts @@ -0,0 +1,38 @@ +import type { EonModule } from './ModuleGetter.ts'; +import EonComponent from './EonComponent.ts'; +import type { ModuleGetterOptions } from './ModuleGetter.ts'; +import { v4 } from '../../deps.ts'; + +export default abstract class ModuleResolver { + static async resolve(module: EonModule, opts: ModuleGetterOptions) { + const { entrypoint } = opts; + // get the default DOM Graph + // for this we use the default export or the export named template + // we are waiting for a function + // TODO define what to do if default/template is a string + const { template, ViewModel, name } = module; + const component = new EonComponent({ + file: entrypoint, + uuid: v4.generate(), + ViewModel, + }); + const vm = ViewModel ? new ViewModel() : undefined; + const availableTemplate = module.default || template + const defaultTemplate = + availableTemplate ? + availableTemplate.bind ? + availableTemplate.bind(vm) : + availableTemplate : null; + // start by using the templta + switch (true) { + // default/template is a function + case !!defaultTemplate && typeof defaultTemplate === 'function': + + if (defaultTemplate) { + component.template = defaultTemplate(vm); + } + } + component.name = name; + return component; + } +} \ No newline at end of file diff --git a/src/functions/jsxFactory.ts b/src/functions/jsxFactory.ts new file mode 100644 index 0000000..37eaefe --- /dev/null +++ b/src/functions/jsxFactory.ts @@ -0,0 +1,59 @@ +import type { JSXFactory, JSXFragmentFactory } from '../../types.d.ts'; +import DOMElement from '../classes/DOMElement.ts'; +/** + * jsxFactory + */ +export function h(...args: JSXFactory) { + const [tag, attributes, ...children] = args; + // TODO directives inside attributes + // TODO attributes + const element = new DOMElement({ + name: tag.toString(), + nodeType: 1, + children: [], + attributes, + }); + // assign to the children the parent element + // assign the nodeType to the children + if (children.length) { + children.forEach((child: any) => { + let domelement: DOMElement; + if (child instanceof DOMElement) { + child.parent = element; + element.setChild(child); + } else { + domelement = new DOMElement({ + value: child, + children: [], + }) + domelement.parent = element; + // get the nodetype + if (domelement && typeof domelement === 'string' + || domelement === null + || typeof domelement === 'boolean' + || typeof domelement === 'number' + || typeof domelement === 'function') { + // @ts-ignore + domelement.nodeType = 3; + } + // TODO define what to do about objects + // maybe we can think about a template switch with the objects + // save the domelement + element.setChild(domelement); + } + }); + } + if (typeof tag === 'function' && tag.name === 'hf') { + element.nodeType = 11; + element.name = undefined; + element.isAttribute = false; + } + // TODO identify components + return element; +}; +/** + * jsxFragmentFactory + */ +export function hf(...args: JSXFragmentFactory) { + return args; +}; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 85640fe..6961472 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,5 @@ "compilerOptions": { "jsxFactory": "h", "jsxFragmentFactory": "hf", - "jsx": "react" } } \ No newline at end of file diff --git a/types.d.ts b/types.d.ts index 0a5ce44..e3a387b 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,5 +1,5 @@ /** bind this part of the graph */ -declare type BindedValue = (() => string) +declare type BoundValue = (() => string) | string | number | boolean @@ -14,7 +14,7 @@ declare namespace JSX { children: any; } export interface Element { - children: Element | BindedValue; + children: Element | BoundValue; [k: string]: any; } /** style elements should only accept strings as chlidren */ @@ -23,5 +23,7 @@ declare namespace JSX { } } type Attributes = { [k: string]: any }; -declare function h(tagName: string, attributes: Attributes | null, ...children: any[]): any; -declare function hf(attributes: Attributes | null, ...children: any[]): any; \ No newline at end of file +declare function h(tagName: string | Function, attributes: Attributes | null, ...children: any[]): any; +declare function hf(...children: any[]): any; +type JSXFactory = Parameters; +type JSXFragmentFactory = Parameters; \ No newline at end of file From 68b5660a03d3ec1a76aa9bc56b6d321a94190455 Mon Sep 17 00:00:00 2001 From: SRNV Date: Fri, 30 Oct 2020 18:30:40 +0100 Subject: [PATCH 03/51] wip --- deps.ts | 1 + examples/hello-app/HelloApp.tsx | 21 ++++++---- examples/hello-app/HelloApp2.jsx | 5 +-- src/classes/DOMElement.ts | 3 ++ src/classes/EonComponent.ts | 10 ++--- src/classes/ModuleErrors.ts | 28 ++++++++++++- src/classes/ModuleGetter.ts | 4 +- src/classes/ModuleResolver.ts | 18 ++++++-- src/functions/jsxFactory.ts | 34 +++++++++++++--- types.d.ts | 70 ++++++++++++++++++++++++++++++-- 10 files changed, 162 insertions(+), 32 deletions(-) diff --git a/deps.ts b/deps.ts index 850dbd6..36dd25f 100644 --- a/deps.ts +++ b/deps.ts @@ -1,2 +1,3 @@ export * as path from "https://deno.land/std@0.75.0/path/mod.ts"; export { v4 } from "https://deno.land/std@0.67.0/uuid/mod.ts"; +export * as colors from "https://deno.land/std@0.61.0/fmt/colors.ts"; diff --git a/examples/hello-app/HelloApp.tsx b/examples/hello-app/HelloApp.tsx index a71a610..504afbf 100644 --- a/examples/hello-app/HelloApp.tsx +++ b/examples/hello-app/HelloApp.tsx @@ -1,16 +1,18 @@ -import * as HelloApp2 from './HelloApp2.jsx'; -import * as HelloApp3 from './HelloApp2.jsx'; -import * as HelloApp4 from './HelloApp2.jsx'; +import HelloApp2 from './HelloApp2.jsx'; -export const name = "AppHello"; -export default function(this: ViewModel): JSX.Element { +export default function AppHello(this: VMC): JSX.Element { // what to do about all the things referenced here // maybe it's a helper zone for SSR // but what about SPA // keep in mind, this function is just to do the dom tree return (<> - - ) } diff --git a/mod.ts b/mod.ts index 90ccde4..b6dae39 100644 --- a/mod.ts +++ b/mod.ts @@ -3,6 +3,7 @@ import ModuleResolver from './src/classes/ModuleResolver.ts'; import './src/functions/jsxFactory.ts'; import { ModuleGetterOptions } from './src/classes/ModuleGetter.ts'; import EonComponent from './src/classes/EonComponent.ts'; +import DOMElementRegistry from './src/classes/DOMElementRegistry.ts'; export class Eon { static async define(opts: ModuleGetterOptions): Promise { @@ -30,12 +31,12 @@ export class Eon { } const rootComponent = await render(root); const components = [rootComponent]; - for await (let componentPath of registry) { + for await (const componentPath of registry) { const component = await render(componentPath); components.push(component); } // Eon.mount will set the template of the component - for await (let component of components) { + for await (const component of components) { await Eon.mount(component); } ModuleGetter.typeCheckComponents(); @@ -46,4 +47,4 @@ export class Eon { const components = await Eon.dev('./examples/hello-app/HelloApp.tsx', [ './examples/hello-app/AnotherComponent.tsx' ]) -console.warn(components); \ No newline at end of file +console.warn(DOMElementRegistry.getElementsByNodeType(1)); \ No newline at end of file diff --git a/src/classes/DOMElement.ts b/src/classes/DOMElement.ts index 471651b..a20f29b 100644 --- a/src/classes/DOMElement.ts +++ b/src/classes/DOMElement.ts @@ -1,5 +1,6 @@ import EonComponent from './EonComponent.ts'; import { increment } from '../functions/increment.ts'; +import DOMElementRegistry from './DOMElementRegistry.ts'; /** * class that participate to the DOM Tree description */ @@ -12,7 +13,7 @@ interface DOMElementInterface { /** the name of the element */ name?: string; /** the value of the element, defined if it's a textnode */ - value?: any; + value?: unknown; /** the type of the element * 1 for all elements including the fragments * 2 for attributes @@ -33,7 +34,7 @@ interface DOMElementInterface { /** the element is on top and it's a fragment element */ isFragment?: boolean; /** the attributes of the element */ - attributes?: { [k: string]: any }; + attributes?: { [k: string]: unknown }; /** related component */ component?: EonComponent; } @@ -64,6 +65,7 @@ export default class DOMElement implements DOMElementInterface { this.attributes = attributes; this.component = component; this.id = increment(); + DOMElementRegistry.subscribe(this.uuid, this); } get uuid(): string { const idType = this.isBoundTextnode ? 't' @@ -87,6 +89,15 @@ export default class DOMElement implements DOMElementInterface { get isComponent(): boolean { return this.nodeType === 1 && !!this.component; } + /** returns the component that is using this element */ + get parentComponent(): EonComponent { + let parent = this.parent, domelement: DOMElement; + while (parent) { + domelement = this.parent; + parent = this.parent?.parent; + } + return (domelement || this).component; + } setParent(parent: DOMTreeElement) { this.parent = parent; } diff --git a/src/classes/DOMElementRegistry.ts b/src/classes/DOMElementRegistry.ts new file mode 100644 index 0000000..2ff2f1a --- /dev/null +++ b/src/classes/DOMElementRegistry.ts @@ -0,0 +1,21 @@ +import DOMElement from './DOMElement.ts'; +/** + * class to save all the element used + */ +export default class DOMElementRegistry { + private static registry: Map = new Map(); + public static subscribe(uuid: string, domelement: DOMElement) { + if (!this.registry.has(uuid)) { + this.registry.set(uuid, domelement); + } + } + static get collection(): DOMElement[] { + return Array.from(this.registry.entries()).map(([key, domelement]) => domelement); + } + static getElementsByNodeType(nodeType: number) { + return this.collection.filter((domelement) => domelement && domelement.nodeType === nodeType); + } + static getElementsOfComponent(uuid: string): DOMElement[] { + return this.collection.filter((domelement) => domelement && domelement.parentComponent && domelement.parentComponent.uuid === uuid); + } +} \ No newline at end of file diff --git a/src/classes/ModuleErrors.ts b/src/classes/ModuleErrors.ts index 0a3f04e..04a8735 100644 --- a/src/classes/ModuleErrors.ts +++ b/src/classes/ModuleErrors.ts @@ -3,15 +3,15 @@ import { colors, path } from "../../deps.ts"; * a class to display the errors inside the module */ export abstract class ModuleErrors { - static checkDiagnostics(diagnostics: any[]) { + static checkDiagnostics(diagnostics: unknown[]) { const { blue, red, white, gray, bgRed } = colors; if (diagnostics && diagnostics.length) { let errors = ''; - for (const d of diagnostics) { - const underline = red(`${' '.repeat(d.start.character)}^${'~'.repeat(d.end.character - d.start.character - 1)}`) + for (const d of diagnostics.filter(d => d.start)) { + const start = d.start && d.start.character; + const end = d.end && d.end.character; + const underline = red(`${' '.repeat(start)}^${'~'.repeat(end - start - 1)}`) let sourceline = d && d.sourceLine || ''; - const start = d.start.character; - const end = d.end.character; sourceline = gray(sourceline.substring(0, start)) + bgRed(white(sourceline.substring(start, end))) + gray(sourceline.substring(end)); // add the error errors +=+ `\n\t${blue(d && d.messageText || '')}\n\t${sourceline}\n\t${underline}\n\tat ${blue(d && d.fileName || '')}`; @@ -24,7 +24,7 @@ export abstract class ModuleErrors { return; } } - static error(message: string, opts?: { [k: string]: any }): void { + static error(message: string, opts?: { [k: string]: unknown }): void { const { bgRed, red, bold, yellow } = colors; const m: string = this.message( `${bgRed(" ERROR ")} ${red(message)}`, @@ -32,7 +32,7 @@ export abstract class ModuleErrors { ) as string; throw new Error(m); } - static message(message: string, opts?: { [k: string]: any }): void | string { + static message(message: string, opts?: { [k: string]: unknown }): void | string { const { cyan, bold, white } = colors; const name = bold(cyan(" [Eon] ")); if (opts && opts.returns) { diff --git a/src/classes/ModuleGetter.ts b/src/classes/ModuleGetter.ts index 7e15ec3..95884a4 100644 --- a/src/classes/ModuleGetter.ts +++ b/src/classes/ModuleGetter.ts @@ -12,8 +12,8 @@ export interface EonModule { name: string; default(vm?: T): DOMElement; template?: (vm?: T) => DOMElement; - VMC: any; - [k: string]: any; + VMC: unknown; + [k: string]: unknown; } const sessionUuid = v4.generate(); export abstract class ModuleGetter { @@ -62,8 +62,8 @@ export abstract class ModuleGetter { return result[entrypoint].source; } static async typeCheckComponents(): Promise { - let diagnostics: any[] = [] - for await (let [key, component] of EonComponentRegistry.collection) { + let diagnostics: unknown[] = [] + for await (const [key, component] of EonComponentRegistry.collection) { if (component.file) { const [diags, mod] = await Deno.compile(component.file, undefined, { jsxFactory: "h", @@ -73,6 +73,7 @@ export abstract class ModuleGetter { sourceMap: false, lib: ['dom'], }); + // TODO typecheck props usages if (diags) { diagnostics = [ ...diagnostics, @@ -84,6 +85,6 @@ export abstract class ModuleGetter { // start reporting type errors // throws if defined - ModuleErrors.checkDiagnostics(diagnostics as any[]); + ModuleErrors.checkDiagnostics(diagnostics as unknown[]); } } \ No newline at end of file diff --git a/src/classes/ModuleResolver.ts b/src/classes/ModuleResolver.ts index 5e50cb8..c3913da 100644 --- a/src/classes/ModuleResolver.ts +++ b/src/classes/ModuleResolver.ts @@ -16,8 +16,7 @@ export default abstract class ModuleResolver { file: entrypoint, uuid: v4.generate(), templateFactory: module.default || template, - // @ts-ignore - name , + name, VMC, }); this.checkNameValidity(name, opts); @@ -54,6 +53,11 @@ export default abstract class ModuleResolver { case !!defaultTemplate && typeof defaultTemplate === 'function': if (defaultTemplate) { component.template = defaultTemplate(vm); + if (component.template) { + // set the component of the template + // this allows all element to identify the component + component.template.component = component; + } return true; } } diff --git a/src/functions/jsxFactory.ts b/src/functions/jsxFactory.ts index a1cc2d7..57561c3 100644 --- a/src/functions/jsxFactory.ts +++ b/src/functions/jsxFactory.ts @@ -42,24 +42,23 @@ export function h(...args: JSXFactory) { // assign to the children the parent element // assign the nodeType to the children if (children.length) { - children.forEach((child: any) => { + children.forEach((child: unknown) => { let domelement: DOMElement; if (child instanceof DOMElement) { - child.parent = element; + child.setParent(element); element.setChild(child); } else { domelement = new DOMElement({ value: child, children: [], }) - domelement.parent = element; + domelement.setParent(element); // get the nodetype if (child && typeof child === 'string' || child === null || typeof child === 'boolean' || typeof child === 'number' || child instanceof Function) { - // @ts-ignore domelement.nodeType = 3; } // TODO define what to do about objects @@ -74,10 +73,10 @@ export function h(...args: JSXFactory) { element.name = undefined; } return element; -}; +} /** * jsxFragmentFactory */ export function hf(...args: JSXFragmentFactory) { return args; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/types.d.ts b/types.d.ts index 595066e..815956d 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,9 +1,10 @@ /** bind this part of the graph */ declare type BoundValue = (() => string) + | (() => JSX.IntrinsicElements[]) | string | number | boolean - | any[] + | unknown[] | null; declare namespace JSX { export interface IntrinsicElements { @@ -11,83 +12,83 @@ declare namespace JSX { [k: string]: JSX.Element; } interface ElementChildrenAttribute { - children: any; + children: unknown; } export interface Element extends DOMEventsLVL2 { children?: Element | BoundValue; - [k: string]: any; + [k: string]: unknown; } /** style elements should only accept strings as chlidren */ export interface StyleElement extends Element { children: string; } } -type Attributes = { [k: string]: any } | DOMEventsLVL2; -declare function h(tagName: string | Function, attributes: Attributes | null, ...children: any[]): any; -declare function hf(...children: any[]): any; +type Attributes = { [k: string]: unknown } | DOMEventsLVL2; +declare function h(tagName: string, attributes: Attributes | null, ...children: unknown[]): JSX.Element; +declare function hf(...children: unknown[]): JSX.Element; type JSXFactory = Parameters; type JSXFragmentFactory = Parameters; interface DOMEventsLVL2 { - onabort?: Function; - onanimationcancel?: Function; - onanimationend?: Function; - onanimationiteration?: Function; - onauxclick?: Function; - onblur?: Function; - oncancel?: Function; - oncanplay?: Function; - oncanplaythrough?: Function; - onchange?: Function; - onclick?: Function; - onclose?: Function; - oncontextmenu?: Function; - oncuechange?: Function; - ondblclick?: Function; - ondurationchange?: Function; - onended?: Function; - onerror?: Function; - onfocus?: Function; - onformdata?: Function; - ongotpointercapture?: Function; - oninput?: Function; - oninvalid?: Function; - onkeydown?: Function; - onkeypress?: Function; - onkeyup?: Function; - onload?: Function; - onloadeddata?: Function; - onloadedmetadata?: Function; - onloadend?: Function; - onloadstart?: Function; - onlostpointercapture?: Function; - onmousedown?: Function; - onmouseenter?: Function; - onmouseleave?: Function; - onmousemove?: Function; - onmouseout?: Function; - onmouseover?: Function; - onmouseup?: Function; - onpause?: Function; - onplay?: Function; - onplaying?: Function; - onpointercancel?: Function; - onpointerdown?: Function; - onpointerenter?: Function; - onpointerleave?: Function; - onpointermove?: Function; - onpointerout?: Function; - onpointerover?: Function; - onpointerup?: Function; - onreset?: Function; - onresize?: Function; - onscroll?: Function; - onselect?: Function; - onselectionchange?: Function; - onselectstart?: Function; - onsubmit?: Function; - ontouchcancel?: Function; - ontouchstart?: Function; - ontransitioncancel?: Function; - ontransitionend?: Function; - onwheel?: Function; + onabort?: (...args: unknown[]) => unknown; + onanimationcancel?: (...args: unknown[]) => unknown; + onanimationend?: (...args: unknown[]) => unknown; + onanimationiteration?: (...args: unknown[]) => unknown; + onauxclick?: (...args: unknown[]) => unknown; + onblur?: (...args: unknown[]) => unknown; + oncancel?: (...args: unknown[]) => unknown; + oncanplay?: (...args: unknown[]) => unknown; + oncanplaythrough?: (...args: unknown[]) => unknown; + onchange?: (...args: unknown[]) => unknown; + onclick?: (...args: unknown[]) => unknown; + onclose?: (...args: unknown[]) => unknown; + oncontextmenu?: (...args: unknown[]) => unknown; + oncuechange?: (...args: unknown[]) => unknown; + ondblclick?: (...args: unknown[]) => unknown; + ondurationchange?: (...args: unknown[]) => unknown; + onended?: (...args: unknown[]) => unknown; + onerror?: (...args: unknown[]) => unknown; + onfocus?: (...args: unknown[]) => unknown; + onformdata?: (...args: unknown[]) => unknown; + ongotpointercapture?: (...args: unknown[]) => unknown; + oninput?: (...args: unknown[]) => unknown; + oninvalid?: (...args: unknown[]) => unknown; + onkeydown?: (...args: unknown[]) => unknown; + onkeypress?: (...args: unknown[]) => unknown; + onkeyup?: (...args: unknown[]) => unknown; + onload?: (...args: unknown[]) => unknown; + onloadeddata?: (...args: unknown[]) => unknown; + onloadedmetadata?: (...args: unknown[]) => unknown; + onloadend?: (...args: unknown[]) => unknown; + onloadstart?: (...args: unknown[]) => unknown; + onlostpointercapture?: (...args: unknown[]) => unknown; + onmousedown?: (...args: unknown[]) => unknown; + onmouseenter?: (...args: unknown[]) => unknown; + onmouseleave?: (...args: unknown[]) => unknown; + onmousemove?: (...args: unknown[]) => unknown; + onmouseout?: (...args: unknown[]) => unknown; + onmouseover?: (...args: unknown[]) => unknown; + onmouseup?: (...args: unknown[]) => unknown; + onpause?: (...args: unknown[]) => unknown; + onplay?: (...args: unknown[]) => unknown; + onplaying?: (...args: unknown[]) => unknown; + onpointercancel?: (...args: unknown[]) => unknown; + onpointerdown?: (...args: unknown[]) => unknown; + onpointerenter?: (...args: unknown[]) => unknown; + onpointerleave?: (...args: unknown[]) => unknown; + onpointermove?: (...args: unknown[]) => unknown; + onpointerout?: (...args: unknown[]) => unknown; + onpointerover?: (...args: unknown[]) => unknown; + onpointerup?: (...args: unknown[]) => unknown; + onreset?: (...args: unknown[]) => unknown; + onresize?: (...args: unknown[]) => unknown; + onscroll?: (...args: unknown[]) => unknown; + onselect?: (...args: unknown[]) => unknown; + onselectionchange?: (...args: unknown[]) => unknown; + onselectstart?: (...args: unknown[]) => unknown; + onsubmit?: (...args: unknown[]) => unknown; + ontouchcancel?: (...args: unknown[]) => unknown; + ontouchstart?: (...args: unknown[]) => unknown; + ontransitioncancel?: (...args: unknown[]) => unknown; + ontransitionend?: (...args: unknown[]) => unknown; + onwheel?: (...args: unknown[]) => unknown; } \ No newline at end of file From cdda177657a0aa98d5cae22e6d0981bb0a4d09e2 Mon Sep 17 00:00:00 2001 From: SRNV Date: Mon, 2 Nov 2020 15:35:18 +0100 Subject: [PATCH 12/51] chore: erase todos that are done --- src/functions/jsxFactory.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/functions/jsxFactory.ts b/src/functions/jsxFactory.ts index 57561c3..e1229f8 100644 --- a/src/functions/jsxFactory.ts +++ b/src/functions/jsxFactory.ts @@ -4,7 +4,6 @@ import EonComponentRegistry from '../classes/EonComponentRegistry.ts'; function setAttributes(element: DOMElement, attributes: Attributes) { // TODO directives inside attributes - // TODO attributes const entries = Object.entries(attributes); entries.forEach(([key, value]) => { // if the attribute is a function From 52d11de7460e63ccc4ed845ebeee93c94f74d0bb Mon Sep 17 00:00:00 2001 From: SRNV Date: Mon, 2 Nov 2020 15:45:29 +0100 Subject: [PATCH 13/51] fix: types after lint --- src/classes/DOMElement.ts | 4 ++-- src/classes/ModuleErrors.ts | 22 +++++++++++++++++----- src/classes/ModuleGetter.ts | 1 + src/classes/ModuleResolver.ts | 2 +- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/classes/DOMElement.ts b/src/classes/DOMElement.ts index a20f29b..a3681c3 100644 --- a/src/classes/DOMElement.ts +++ b/src/classes/DOMElement.ts @@ -90,8 +90,8 @@ export default class DOMElement implements DOMElementInterface { return this.nodeType === 1 && !!this.component; } /** returns the component that is using this element */ - get parentComponent(): EonComponent { - let parent = this.parent, domelement: DOMElement; + get parentComponent(): EonComponent | undefined { + let parent = this.parent, domelement: DOMElement | undefined; while (parent) { domelement = this.parent; parent = this.parent?.parent; diff --git a/src/classes/ModuleErrors.ts b/src/classes/ModuleErrors.ts index 04a8735..346048f 100644 --- a/src/classes/ModuleErrors.ts +++ b/src/classes/ModuleErrors.ts @@ -2,19 +2,31 @@ import { colors, path } from "../../deps.ts"; /** * a class to display the errors inside the module */ +interface ModuleErrorsDiagnostic { + start?: { + character: number + }; + end?: { + character: number + }; + sourceLine?: string; + messageText?: string; + fileName?: string; +} export abstract class ModuleErrors { static checkDiagnostics(diagnostics: unknown[]) { const { blue, red, white, gray, bgRed } = colors; if (diagnostics && diagnostics.length) { let errors = ''; - for (const d of diagnostics.filter(d => d.start)) { - const start = d.start && d.start.character; - const end = d.end && d.end.character; + for (const d of diagnostics.filter(d => (d as ModuleErrorsDiagnostic).start)) { + const diag = d as (ModuleErrorsDiagnostic) + const start = diag.start && diag.start.character || 0; + const end = diag.end && diag.end.character || 0; const underline = red(`${' '.repeat(start)}^${'~'.repeat(end - start - 1)}`) - let sourceline = d && d.sourceLine || ''; + let sourceline = diag && diag.sourceLine || ''; sourceline = gray(sourceline.substring(0, start)) + bgRed(white(sourceline.substring(start, end))) + gray(sourceline.substring(end)); // add the error - errors +=+ `\n\t${blue(d && d.messageText || '')}\n\t${sourceline}\n\t${underline}\n\tat ${blue(d && d.fileName || '')}`; + errors +=+ `\n\t${blue(diag && diag.messageText || '')}\n\t${sourceline}\n\t${underline}\n\tat ${blue(diag && diag.fileName || '')}`; } this.error( errors, diff --git a/src/classes/ModuleGetter.ts b/src/classes/ModuleGetter.ts index 95884a4..45127ac 100644 --- a/src/classes/ModuleGetter.ts +++ b/src/classes/ModuleGetter.ts @@ -31,6 +31,7 @@ export abstract class ModuleGetter { ${transpiled} `); const module = import(newPath) as unknown as EonModule; + // @ts-ignore module.then(() => { Deno.removeSync(newPath); }) diff --git a/src/classes/ModuleResolver.ts b/src/classes/ModuleResolver.ts index c3913da..2b419df 100644 --- a/src/classes/ModuleResolver.ts +++ b/src/classes/ModuleResolver.ts @@ -40,7 +40,7 @@ export default abstract class ModuleResolver { */ static setComponentTemplate(component: EonComponent): boolean { const { VMC } = component; - const vm = VMC ? new VMC() : undefined; + const vm = VMC ? new (VMC as FunctionConstructor)() : undefined; const availableTemplate = component.templateFactory; const defaultTemplate = availableTemplate ? From 32f0a4aaee99fef9593f2fca59d0ed2b28c74d2b Mon Sep 17 00:00:00 2001 From: SRNV Date: Mon, 2 Nov 2020 17:49:16 +0100 Subject: [PATCH 14/51] wip: start tests --- deps.ts | 5 ++++- tests/DOMElement.test.ts | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tests/DOMElement.test.ts diff --git a/deps.ts b/deps.ts index 23e8af0..5d7fb44 100644 --- a/deps.ts +++ b/deps.ts @@ -1,3 +1,6 @@ export * as path from "https://deno.land/std@0.75.0/path/mod.ts"; export { v4 } from "https://deno.land/std@0.67.0/uuid/mod.ts"; -export * as colors from "https://deno.land/std@0.61.0/fmt/colors.ts"; \ No newline at end of file +export * as colors from "https://deno.land/std@0.61.0/fmt/colors.ts"; +export { + assertEquals, +} from "https://deno.land/std@0.76.0/testing/asserts.ts"; \ No newline at end of file diff --git a/tests/DOMElement.test.ts b/tests/DOMElement.test.ts new file mode 100644 index 0000000..879199b --- /dev/null +++ b/tests/DOMElement.test.ts @@ -0,0 +1,10 @@ +import DOMElement from '../src/classes/DOMElement.ts'; + +Deno.test('basic: nodeType 1 is an element', () => { + const domelement = new DOMElement({ + nodeType: 1, + children: [], + name: 'div' + }); + console.warn(domelement); +}); From 549b60f84836e1e8085c7ea47e1a3f22c749df20 Mon Sep 17 00:00:00 2001 From: SRNV Date: Tue, 3 Nov 2020 00:42:35 +0100 Subject: [PATCH 15/51] feat: improve tests --- tests/DOMElement.test.ts | 51 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/tests/DOMElement.test.ts b/tests/DOMElement.test.ts index 879199b..1a7d463 100644 --- a/tests/DOMElement.test.ts +++ b/tests/DOMElement.test.ts @@ -1,4 +1,25 @@ import DOMElement from '../src/classes/DOMElement.ts'; +import { assertEquals } from '../deps.ts'; + const template = new DOMElement({ + children: [], + name: 'template', + nodeType: 1, + value: '', + }); + const textnode = new DOMElement({ + children: [], + name: undefined, + nodeType: 3, + parent: template, + value: 'Hello', + }); + const node = new DOMElement({ + children: [], + name: 'div', + nodeType: 1, + parent: template, + value: '', + }); Deno.test('basic: nodeType 1 is an element', () => { const domelement = new DOMElement({ @@ -6,5 +27,33 @@ Deno.test('basic: nodeType 1 is an element', () => { children: [], name: 'div' }); - console.warn(domelement); + assertEquals('div', domelement.name); + assertEquals(false, domelement.isComponent); + assertEquals(false, domelement.isTemplate); + assertEquals(false, domelement.isStyle); + assertEquals(false, domelement.isBoundTextnode); + assertEquals(false, domelement.isFragment); + assertEquals(1, domelement.nodeType); + assertEquals(undefined, domelement.parentComponent); + const child = new DOMElement({ + nodeType: 1, + children: [], + name: 'div' + }); + domelement.setChild(child); + assertEquals(true, domelement.children.includes(child)); + assertEquals(true, domelement.uuid.startsWith('n')); }); + +Deno.test('first letter of DOMElement s uuid sould depend on its nodeType', () => { + assertEquals(true, template.uuid.startsWith('tmp')); + assertEquals(true, node.uuid.startsWith('n')); + assertEquals(true, template.uuid.startsWith('t')); +}); + +Deno.test('type validators of DOMElement are correct', () => { + assertEquals(true, template.isTemplate); + assertEquals(false, template.isStyle); + assertEquals(false, template.isFragment); + assertEquals(false, template.isComponent); +}); \ No newline at end of file From c52b0c1f54c512d0595991e109a8408c22c6fdc9 Mon Sep 17 00:00:00 2001 From: SRNV Date: Tue, 3 Nov 2020 13:43:39 +0100 Subject: [PATCH 16/51] feat: improve tests --- src/classes/EonComponent.ts | 2 +- tests/DOMElement.test.ts | 50 +++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/classes/EonComponent.ts b/src/classes/EonComponent.ts index e441da8..9ef2c2a 100644 --- a/src/classes/EonComponent.ts +++ b/src/classes/EonComponent.ts @@ -7,7 +7,7 @@ export interface EonComponentInterface { uuid?: string; /** name */ name?: string; - /** path the component */ + /** path of the component */ file?: string; /** the DOM tree of the component */ template?: DOMElement; diff --git a/tests/DOMElement.test.ts b/tests/DOMElement.test.ts index 1a7d463..5f3f3b7 100644 --- a/tests/DOMElement.test.ts +++ b/tests/DOMElement.test.ts @@ -1,5 +1,17 @@ import DOMElement from '../src/classes/DOMElement.ts'; import { assertEquals } from '../deps.ts'; + const fragment = new DOMElement({ + children: [], + name: undefined, + nodeType: 11, + }); + const fragmentTemplate = new DOMElement({ + children: [], + name: 'template', + nodeType: 1, + parent: fragment + }); + fragment.children.push(fragmentTemplate); const template = new DOMElement({ children: [], name: 'template', @@ -13,6 +25,13 @@ import { assertEquals } from '../deps.ts'; parent: template, value: 'Hello', }); + const boundTextnode = new DOMElement({ + children: [], + name: undefined, + nodeType: 3, + parent: template, + value: () => 'sdgf', + }); const node = new DOMElement({ children: [], name: 'div', @@ -52,8 +71,39 @@ Deno.test('first letter of DOMElement s uuid sould depend on its nodeType', () = }); Deno.test('type validators of DOMElement are correct', () => { + assertEquals(false, fragment.isTemplate); + assertEquals(false, fragment.isStyle); + assertEquals(true, fragment.isFragment); + assertEquals(false, fragment.isComponent); + assertEquals(false, fragment.isBoundTextnode); + assertEquals(true, template.isTemplate); assertEquals(false, template.isStyle); assertEquals(false, template.isFragment); assertEquals(false, template.isComponent); + assertEquals(false, template.isBoundTextnode); + + assertEquals(true, fragmentTemplate.isTemplate); + assertEquals(false, fragmentTemplate.isStyle); + assertEquals(false, fragmentTemplate.isFragment); + assertEquals(false, fragmentTemplate.isComponent); + assertEquals(false, fragmentTemplate.isBoundTextnode); + + assertEquals(false, textnode.isTemplate); + assertEquals(false, textnode.isStyle); + assertEquals(false, textnode.isFragment); + assertEquals(false, textnode.isComponent); + assertEquals(false, textnode.isBoundTextnode); + + assertEquals(false, boundTextnode.isTemplate); + assertEquals(false, boundTextnode.isStyle); + assertEquals(false, boundTextnode.isFragment); + assertEquals(false, boundTextnode.isComponent); + assertEquals(true, boundTextnode.isBoundTextnode); + + assertEquals(false, node.isTemplate); + assertEquals(false, node.isStyle); + assertEquals(false, node.isFragment); + assertEquals(false, node.isComponent); + assertEquals(false, node.isBoundTextnode); }); \ No newline at end of file From f0fe1742e62461202b84afdeabcf9898f0b05651 Mon Sep 17 00:00:00 2001 From: SRNV Date: Tue, 3 Nov 2020 14:53:08 +0100 Subject: [PATCH 17/51] fix: ignore lint errors for no-namespace and ban-ts-comment --- src/classes/ModuleGetter.ts | 2 + tests/DOMElement.test.ts | 99 ++++++++++++++++++++++--------------- types.d.ts | 2 + 3 files changed, 62 insertions(+), 41 deletions(-) diff --git a/src/classes/ModuleGetter.ts b/src/classes/ModuleGetter.ts index 45127ac..ed87b3c 100644 --- a/src/classes/ModuleGetter.ts +++ b/src/classes/ModuleGetter.ts @@ -31,6 +31,8 @@ export abstract class ModuleGetter { ${transpiled} `); const module = import(newPath) as unknown as EonModule; + // TODO fix this for deno lint and deno run + // deno-lint-ignore ban-ts-comment // @ts-ignore module.then(() => { Deno.removeSync(newPath); diff --git a/tests/DOMElement.test.ts b/tests/DOMElement.test.ts index 5f3f3b7..0541e36 100644 --- a/tests/DOMElement.test.ts +++ b/tests/DOMElement.test.ts @@ -1,44 +1,55 @@ import DOMElement from '../src/classes/DOMElement.ts'; -import { assertEquals } from '../deps.ts'; - const fragment = new DOMElement({ - children: [], - name: undefined, - nodeType: 11, - }); - const fragmentTemplate = new DOMElement({ - children: [], - name: 'template', - nodeType: 1, - parent: fragment - }); - fragment.children.push(fragmentTemplate); - const template = new DOMElement({ - children: [], - name: 'template', - nodeType: 1, - value: '', - }); - const textnode = new DOMElement({ - children: [], - name: undefined, - nodeType: 3, - parent: template, - value: 'Hello', - }); - const boundTextnode = new DOMElement({ - children: [], - name: undefined, - nodeType: 3, - parent: template, - value: () => 'sdgf', - }); - const node = new DOMElement({ - children: [], - name: 'div', - nodeType: 1, - parent: template, - value: '', - }); +import EonComponent from '../src/classes/EonComponent.ts'; +import { assertEquals, v4 } from '../deps.ts'; + +const fragment = new DOMElement({ + children: [], + name: undefined, + nodeType: 11, +}); + +const component = new EonComponent({ + file: `${Deno.cwd()}/tests.tsx`, + uuid: v4.generate(), + name: 'test-component', + templateFactory: () => fragment, +}); +fragment.component = component; + +const fragmentTemplate = new DOMElement({ + children: [], + name: 'template', + nodeType: 1, + parent: fragment +}); +fragment.children.push(fragmentTemplate); +const template = new DOMElement({ + children: [], + name: 'template', + nodeType: 1, + value: '', +}); +const textnode = new DOMElement({ + children: [], + name: undefined, + nodeType: 3, + parent: template, + value: 'Hello', +}); +const boundTextnode = new DOMElement({ + children: [], + name: undefined, + nodeType: 3, + parent: template, + value: () => 'sdgf', +}); +const node = new DOMElement({ + children: [], + name: 'div', + nodeType: 1, + parent: template, + value: '', +}); Deno.test('basic: nodeType 1 is an element', () => { const domelement = new DOMElement({ @@ -64,7 +75,7 @@ Deno.test('basic: nodeType 1 is an element', () => { assertEquals(true, domelement.uuid.startsWith('n')); }); -Deno.test('first letter of DOMElement s uuid sould depend on its nodeType', () => { +Deno.test('first letter of DOMElement\'s uuid sould depend on it\'s nodeType', () => { assertEquals(true, template.uuid.startsWith('tmp')); assertEquals(true, node.uuid.startsWith('n')); assertEquals(true, template.uuid.startsWith('t')); @@ -106,4 +117,10 @@ Deno.test('type validators of DOMElement are correct', () => { assertEquals(false, node.isFragment); assertEquals(false, node.isComponent); assertEquals(false, node.isBoundTextnode); +}); + +Deno.test('elements can access to the component', () => { + assertEquals(true, fragment.component === component); + assertEquals(true, fragmentTemplate.parentComponent === component); + assertEquals('test-component', fragmentTemplate.parentComponent && fragmentTemplate.parentComponent.name); }); \ No newline at end of file diff --git a/types.d.ts b/types.d.ts index 815956d..b3a42dd 100644 --- a/types.d.ts +++ b/types.d.ts @@ -6,6 +6,8 @@ declare type BoundValue = (() => string) | boolean | unknown[] | null; + +// deno-lint-ignore no-namespace declare namespace JSX { export interface IntrinsicElements { style: JSX.StyleElement; From 96ae0de6ed38c380030fb1ec27ecdeb231a673de Mon Sep 17 00:00:00 2001 From: SRNV Date: Tue, 3 Nov 2020 15:24:58 +0100 Subject: [PATCH 18/51] wip --- mod.ts | 4 +++- src/classes/DevServer.ts | 44 +++++++++++++++++++++++++++++++++++++ src/classes/ModuleGetter.ts | 1 + tsconfig.json | 1 + 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/classes/DevServer.ts diff --git a/mod.ts b/mod.ts index b6dae39..9cd738f 100644 --- a/mod.ts +++ b/mod.ts @@ -4,6 +4,7 @@ import './src/functions/jsxFactory.ts'; import { ModuleGetterOptions } from './src/classes/ModuleGetter.ts'; import EonComponent from './src/classes/EonComponent.ts'; import DOMElementRegistry from './src/classes/DOMElementRegistry.ts'; +import DevServer from './src/classes/DevServer.ts'; export class Eon { static async define(opts: ModuleGetterOptions): Promise { @@ -47,4 +48,5 @@ export class Eon { const components = await Eon.dev('./examples/hello-app/HelloApp.tsx', [ './examples/hello-app/AnotherComponent.tsx' ]) -console.warn(DOMElementRegistry.getElementsByNodeType(1)); \ No newline at end of file +console.warn(DOMElementRegistry.getElementsByNodeType(1)); +// DevServer.serveSPA(); \ No newline at end of file diff --git a/src/classes/DevServer.ts b/src/classes/DevServer.ts new file mode 100644 index 0000000..adce6c4 --- /dev/null +++ b/src/classes/DevServer.ts @@ -0,0 +1,44 @@ +/** + * a class to serve SPA/SSR/SSG + * in development environment + */ +function random(min: number, max: number): number { + return Math.round(Math.random() * (max - min)) + min; +} +export default class DevServer { + private static port: number = 3040; + private static HMRport: number = DevServer.port; + private static hostname: string = 'localhost'; + /** + * start development services for Single Page Applications + * TCP server + */ + static async serveSPA() { + this.port = await this.getFreePort(this.port); + const listener = Deno.listen({ hostname: this.hostname, port: this.port }); + console.log(`Listening on ${this.hostname}:${this.port}`); + for await (const conn of listener) { + console.warn(conn); + } + } + private static async getFreePort(port: number): Promise { + try { + const listener = await Deno.listen({ hostname: this.hostname, port: port || this.port }); + + listener.close(); + + return port; + } catch (err) { + if (err instanceof Deno.errors.AddrInUse) { + const newPort = port <= 1023 + ? random(0, 1023) + : port <= 49151 + ? random(1024, 49151) + : random(49152, 65535); + return this.getFreePort(newPort); + } + + throw err; + } + } +} \ No newline at end of file diff --git a/src/classes/ModuleGetter.ts b/src/classes/ModuleGetter.ts index ed87b3c..9f1af96 100644 --- a/src/classes/ModuleGetter.ts +++ b/src/classes/ModuleGetter.ts @@ -59,6 +59,7 @@ export abstract class ModuleGetter { jsxFactory: "h", /** @ts-ignore */ jsxFragmentFactory: "hf", + jsx: "react", sourceMap: false, lib: ['dom'], }); diff --git a/tsconfig.json b/tsconfig.json index 6961472..85640fe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,5 +2,6 @@ "compilerOptions": { "jsxFactory": "h", "jsxFragmentFactory": "hf", + "jsx": "react" } } \ No newline at end of file From b79e0c8a0bdb379f2bef0a3fe65fd4edf2145b8d Mon Sep 17 00:00:00 2001 From: SRNV Date: Tue, 3 Nov 2020 15:26:08 +0100 Subject: [PATCH 19/51] fix: missing jsx parameter --- src/classes/ModuleGetter.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/classes/ModuleGetter.ts b/src/classes/ModuleGetter.ts index ed87b3c..40fe6b6 100644 --- a/src/classes/ModuleGetter.ts +++ b/src/classes/ModuleGetter.ts @@ -72,6 +72,7 @@ export abstract class ModuleGetter { jsxFactory: "h", /** @ts-ignore */ jsxFragmentFactory: "hf", + jsx: "react", types: ['./types.d.ts'], sourceMap: false, lib: ['dom'], From ccd462eb43176643dbea977d031326167cb9485d Mon Sep 17 00:00:00 2001 From: SRNV Date: Tue, 3 Nov 2020 15:33:04 +0100 Subject: [PATCH 20/51] fix: missing parameters --- mod.ts | 1 - src/classes/ModuleGetter.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/mod.ts b/mod.ts index b6dae39..feba679 100644 --- a/mod.ts +++ b/mod.ts @@ -47,4 +47,3 @@ export class Eon { const components = await Eon.dev('./examples/hello-app/HelloApp.tsx', [ './examples/hello-app/AnotherComponent.tsx' ]) -console.warn(DOMElementRegistry.getElementsByNodeType(1)); \ No newline at end of file diff --git a/src/classes/ModuleGetter.ts b/src/classes/ModuleGetter.ts index 40fe6b6..cd43fbc 100644 --- a/src/classes/ModuleGetter.ts +++ b/src/classes/ModuleGetter.ts @@ -60,6 +60,7 @@ export abstract class ModuleGetter { /** @ts-ignore */ jsxFragmentFactory: "hf", sourceMap: false, + jsx: "react", lib: ['dom'], }); return result[entrypoint].source; From b81f55aa413e45cb793a404b23ec56a5e1314f88 Mon Sep 17 00:00:00 2001 From: SRNV Date: Tue, 3 Nov 2020 15:34:39 +0100 Subject: [PATCH 21/51] fix: duplicate identifier --- src/classes/ModuleGetter.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/classes/ModuleGetter.ts b/src/classes/ModuleGetter.ts index 86618ca..cd43fbc 100644 --- a/src/classes/ModuleGetter.ts +++ b/src/classes/ModuleGetter.ts @@ -59,7 +59,6 @@ export abstract class ModuleGetter { jsxFactory: "h", /** @ts-ignore */ jsxFragmentFactory: "hf", - jsx: "react", sourceMap: false, jsx: "react", lib: ['dom'], From a762c0e9ca1e2d1214b04cb9d739b424b54f07c3 Mon Sep 17 00:00:00 2001 From: SRNV Date: Tue, 3 Nov 2020 17:43:39 +0100 Subject: [PATCH 22/51] feat(wip): add patterns and docs to render component declarations --- deps.ts | 3 +- doc/PATTERN_COMPONENT.md | 97 +++++++++++++++++++++++++++ mod.ts | 6 +- src/classes/Bundler.ts | 57 ++++++++++++++++ src/classes/DOMElement.ts | 13 ++++ src/classes/DevServer.ts | 14 ++-- src/classes/EonComponent.ts | 11 +++ src/classes/ModuleErrors.ts | 15 +---- src/classes/Utils.ts | 43 ++++++++++++ src/patterns/README.md | 5 ++ src/patterns/component_declaration.ts | 53 +++++++++++++++ 11 files changed, 296 insertions(+), 21 deletions(-) create mode 100644 doc/PATTERN_COMPONENT.md create mode 100644 src/classes/Bundler.ts create mode 100644 src/classes/Utils.ts create mode 100644 src/patterns/README.md create mode 100644 src/patterns/component_declaration.ts diff --git a/deps.ts b/deps.ts index 5d7fb44..2f91a7f 100644 --- a/deps.ts +++ b/deps.ts @@ -3,4 +3,5 @@ export { v4 } from "https://deno.land/std@0.67.0/uuid/mod.ts"; export * as colors from "https://deno.land/std@0.61.0/fmt/colors.ts"; export { assertEquals, -} from "https://deno.land/std@0.76.0/testing/asserts.ts"; \ No newline at end of file +} from "https://deno.land/std@0.76.0/testing/asserts.ts"; +export { serve } from "https://deno.land/std@0.76.0/http/server.ts"; \ No newline at end of file diff --git a/doc/PATTERN_COMPONENT.md b/doc/PATTERN_COMPONENT.md new file mode 100644 index 0000000..a17eb8b --- /dev/null +++ b/doc/PATTERN_COMPONENT.md @@ -0,0 +1,97 @@ +# Eon Component Declaration's pattern +Eon's reactivity would be quiet inspired by Svelte's Reactivity + +the idea is to use vars (from the template provided by the end user), and to assign value in two time. +for this we would use two functions `component_ctx` and `init`. +the first one contains the second one. + +```js +function component_ctx() { + // element's vars declarations here like: let tmp, n; + // ... + function init() { + // element assignment here + } + return { + init, + }; +} +customElement.define('[component-uuid]', class extends HTMLElement { + connectedCallback() { + super(); + const ctx = component_ctx(); + const template = ctx.init(); + } +}) +``` +# TODO: more documentations here + +```js +import { VMC } from '../[path_to_component].ts'; + +function component_ctx() { + // tmp is a template element + // n3 is a div element + // t4 is a boundtext textnode + let tmp1, + n2, + n3, + t4, + /* boundtextnode with end user's input */ + t4_data_update; + t4_data_prev, + t4_data_next + /* c5 is a component */ + c5, + c5_props_update, + /* and then the component */ + component = new VMC(); + + /* will assign all the nodes inside vars*/ + function init() { + tmp1 = document.createElement('template'); + n3 = document.createElement('div'); + t4 = new Text(' '); + c5 = document.createElement('data-[uuid_sub_component]'); + t4_data_update = () => this.message; + /* using the component's props attribute value */ + c5_props_update = () => ({ + message: this.message + }); + // append childs + tmp1.append(n3); + n3.append(t4); + n3.append(c5); + // TODO attributes and bound attributes + // return the template + return tmp1; + } + /* general updates */ + function update() { + t4_data_next = t4_data_update(component); + if (t4_data2_prev !== t4_data_next) { + t4.data = t4_data_next; + t4_data2_prev = t4_data_next; + } + /* using the component's props attribute value */ + c5.component.props(c5_props_update(component)); + } + return { + component, + init: init.bind(component), + update: update.bind(component) + } +} +customElement.define('data-[uuid_component]', class extends HTMLElement { + constructor() { + super(); + const { init, update, component } = component_ctx(); + let template = init(); + let templateContent = template.content; + this.component = component; + + const shadowRoot = this.attachShadow({mode: 'open'}) + .appendChild(templateContent.cloneNode(true)); + } +}); +``` \ No newline at end of file diff --git a/mod.ts b/mod.ts index 4eea854..62e0f69 100644 --- a/mod.ts +++ b/mod.ts @@ -41,12 +41,12 @@ export class Eon { await Eon.mount(component); } ModuleGetter.typeCheckComponents(); + await DevServer.serveSPA(); return components; } } -const components = await Eon.dev('./examples/hello-app/HelloApp.tsx', [ +await Eon.dev('./examples/hello-app/HelloApp.tsx', [ './examples/hello-app/AnotherComponent.tsx' ]) -console.warn(DOMElementRegistry.getElementsByNodeType(1)); -DevServer.serveSPA(); + diff --git a/src/classes/Bundler.ts b/src/classes/Bundler.ts new file mode 100644 index 0000000..6b9fa1d --- /dev/null +++ b/src/classes/Bundler.ts @@ -0,0 +1,57 @@ +import Utils from './Utils.ts'; +import EonComponentRegistry from './EonComponentRegistry.ts'; +import EonComponent from './EonComponent.ts'; +import { path } from '../../deps.ts'; + +type EonApplication = string; + +export default class Bundler extends Utils { + /** + * start building the application + */ + protected static async buildApplication(): Promise { + let files: { [k: string]: string } = {}; + console.warn(0) + // first get all available components + const components: EonComponent[] = EonComponentRegistry.collection.map(([key, component]) => component); + // create a string for each components + // the string should return a Eon Component Declaration Pattern + // file://doc/PATTERN_COMPONENT.md + components.forEach((component: EonComponent) => { + if (component.file) { + // save the new string into the files used by Deno.bundle + files[component.file as string] = this.createEonComponentDeclaration(component); + console.warn(files[component.file as string]); + } + }); + console.warn(files); + return ''; + } + /** + * creates mirror esm files + * for each component + */ + private static createEonComponentDeclaration(component: EonComponent): string { + let result = ''; + const component_declaration_pattern = Deno.readTextFileSync(`${path.join(import.meta.url, '../patterns/component_declaration.ts')}`); + let pattern = ` + import { VMC } from '"{{ path_to_component }}"'; + ${component_declaration_pattern} + `; + return Bundler.renderPattern(pattern, { + data: { + uuid_component: component.dataUuidForSPA, + path_to_component: component.file as string, + element_vars: 'let tmp;\n', + element_assignment: 'tmp = document.createElement("template");\n', + element_parent_append_childs: 'tmp.append("test");\n', + element_set_attributes: 'tmp.setAttribute("class", "test");\n', + return_root_template: 'return tmp;', + bound_textnodes_updates: '', + bound_attributes_updates: 'tmp.setAttribute("class", tmp.classList.contains("test") ? "container" : "class")', + props_updates: '', + element_destructions: 'tmp.remove(); tmp = null;\n', + } + }); + } +} \ No newline at end of file diff --git a/src/classes/DOMElement.ts b/src/classes/DOMElement.ts index a3681c3..a56aba3 100644 --- a/src/classes/DOMElement.ts +++ b/src/classes/DOMElement.ts @@ -89,6 +89,19 @@ export default class DOMElement implements DOMElementInterface { get isComponent(): boolean { return this.nodeType === 1 && !!this.component; } + get isInSVG(): boolean { + let result = false; + let parent = this.parent, domelement: DOMElement | undefined; + while (parent) { + if (domelement && domelement.name === 'svg' || this.name === 'svg') { + result = true; + break; + } + domelement = this.parent; + parent = this.parent?.parent; + } + return result; + } /** returns the component that is using this element */ get parentComponent(): EonComponent | undefined { let parent = this.parent, domelement: DOMElement | undefined; diff --git a/src/classes/DevServer.ts b/src/classes/DevServer.ts index adce6c4..d66fe14 100644 --- a/src/classes/DevServer.ts +++ b/src/classes/DevServer.ts @@ -1,3 +1,5 @@ +import { serve } from '../../deps.ts'; +import Bundler from './Bundler.ts'; /** * a class to serve SPA/SSR/SSG * in development environment @@ -5,7 +7,7 @@ function random(min: number, max: number): number { return Math.round(Math.random() * (max - min)) + min; } -export default class DevServer { +export default class DevServer extends Bundler { private static port: number = 3040; private static HMRport: number = DevServer.port; private static hostname: string = 'localhost'; @@ -14,11 +16,13 @@ export default class DevServer { * TCP server */ static async serveSPA() { + const application = await DevServer.buildApplication(); + console.warn(2) this.port = await this.getFreePort(this.port); - const listener = Deno.listen({ hostname: this.hostname, port: this.port }); - console.log(`Listening on ${this.hostname}:${this.port}`); - for await (const conn of listener) { - console.warn(conn); + const server = serve({ hostname: this.hostname, port: this.port }); + DevServer.message(`Listening on http://${this.hostname}:${this.port}`); + for await (const req of server) { + req.respond({ body: application }); } } private static async getFreePort(port: number): Promise { diff --git a/src/classes/EonComponent.ts b/src/classes/EonComponent.ts index 9ef2c2a..e63f464 100644 --- a/src/classes/EonComponent.ts +++ b/src/classes/EonComponent.ts @@ -45,4 +45,15 @@ export default class EonComponent implements EonComponentInterface { EonComponentRegistry.subscribe(this.uuid, this); } } + /** + * instead of using the name as component identifier (ex: component-name) + * we will use a pseudo uuid (ex: data-a32dsfpi1) + */ + get dataUuidForSPA(): string { + if (this.uuid) { + return `data-${this.uuid.split('-')[0]}`.toLowerCase(); + } else { + return 'no-uuid'; + } + } } \ No newline at end of file diff --git a/src/classes/ModuleErrors.ts b/src/classes/ModuleErrors.ts index 346048f..6ef4812 100644 --- a/src/classes/ModuleErrors.ts +++ b/src/classes/ModuleErrors.ts @@ -1,4 +1,5 @@ -import { colors, path } from "../../deps.ts"; +import { colors } from "../../deps.ts"; +import Utils from "./Utils.ts"; /** * a class to display the errors inside the module */ @@ -13,7 +14,7 @@ interface ModuleErrorsDiagnostic { messageText?: string; fileName?: string; } -export abstract class ModuleErrors { +export abstract class ModuleErrors extends Utils { static checkDiagnostics(diagnostics: unknown[]) { const { blue, red, white, gray, bgRed } = colors; if (diagnostics && diagnostics.length) { @@ -44,14 +45,4 @@ export abstract class ModuleErrors { ) as string; throw new Error(m); } - static message(message: string, opts?: { [k: string]: unknown }): void | string { - const { cyan, bold, white } = colors; - const name = bold(cyan(" [Eon] ")); - if (opts && opts.returns) { - return `${name} ${message}`; - } else { - console.log(name, message); - return; - } - } } diff --git a/src/classes/Utils.ts b/src/classes/Utils.ts new file mode 100644 index 0000000..499310c --- /dev/null +++ b/src/classes/Utils.ts @@ -0,0 +1,43 @@ +import { colors } from '../../deps.ts'; +interface PatternOptions { + data: { [k: string]: string }; + open?: string; + close?: string; +} +/** + * a class to extend + * delivers some utils methods + */ +export default abstract class Utils { + static message(message: string, opts?: { [k: string]: unknown }): void | string { + const { cyan, bold, white } = colors; + const name = bold(cyan(' [Eon] ')); + if (opts && opts.returns) { + return `${name} ${message}`; + } else { + console.log(name, message); + return; + } + } + protected static renderPattern(pattern: string, options: PatternOptions): string { + let result = pattern; + const { data, open = '"{{', close = '}}"' } = options; + const fn = new Function( + '__value', + ...Object.keys(data), + `try { return eval('('+ __value + ')'); } catch(err) { throw err; }`, + ); + const values = Object.values(data); + while ( + result.indexOf(open) > -1 && result.indexOf(close) > -1 + ) { + const start = result.indexOf(open); + const end = result.indexOf(close) + 2; + const substrContent = result.substring(start + 2, end - 2).trim(); + const partStart = result.substring(0, start); + const partEnd = result.substring(end); + result = partStart + fn(substrContent, ...values) + partEnd; + } + return result; + } +} \ No newline at end of file diff --git a/src/patterns/README.md b/src/patterns/README.md new file mode 100644 index 0000000..eca6980 --- /dev/null +++ b/src/patterns/README.md @@ -0,0 +1,5 @@ +# Patterns Folder + +this folder contains all patterns that would be used for rendering +first choice was made by SRNV to use quote double brackets `"{{ var }}"` +if you're not ok with this please open an issue and propose another way to declare vars \ No newline at end of file diff --git a/src/patterns/component_declaration.ts b/src/patterns/component_declaration.ts new file mode 100644 index 0000000..42979db --- /dev/null +++ b/src/patterns/component_declaration.ts @@ -0,0 +1,53 @@ +// @ts-nocheck +function component_ctx() { + "{{ element_vars /** let n1, n2; */ }}"; + let component = new VMC(); + + /* will assign all the nodes inside vars*/ + function init() { + "{{ element_assignments }}" + // append childs + "{{ element_parent_append_childs /** parent.append(...childs) */ }}" + // set attributes for all elements + "{{ element_set_attributes }}" + // should return the root template + "{{ return_root_template }}" + } + /* general updates */ + function update() { + // all update of bound textnodes + "{{ bound_textnodes_updates }}" + // all update of bound attributes + "{{ bound_attributes_updates }}" + // all component should update their props + "{{ props_updates }}" + } + /** when the component is destroyed */ + function destroy() { + if (VMC.beforeDestroy) { + VMC.beforeDestroy.bind(component)(component); + } + // all elements destructions + // element.remove(); + "{{ element_destructions }}" + } + return { + component, + init: init.bind(component), + update: update.bind(component), + destroy: destroy.bind(component) + } +} +customElement.define('"{{ uuid_component }}"', class extends HTMLElement { + static VMC = VMc; + constructor() { + super(); + const { init, update, component } = component_ctx(); + let template = init(); + let templateContent = template.content; + this.component = component; + + const shadowRoot = this.attachShadow({ mode: 'open' }) + .appendChild(templateContent.cloneNode(true)); + } +}); \ No newline at end of file From 387f8deebb64a2edbaedbfb786b59f765f232507 Mon Sep 17 00:00:00 2001 From: SRNV Date: Tue, 3 Nov 2020 22:48:40 +0100 Subject: [PATCH 23/51] wip --- mod.ts | 1 - src/classes/Bundler.ts | 19 ++--- src/classes/ModuleGetter.ts | 2 +- src/classes/Utils.ts | 9 ++- src/patterns/component_declaration.ts | 101 +++++++++++++------------- 5 files changed, 68 insertions(+), 64 deletions(-) diff --git a/mod.ts b/mod.ts index 62e0f69..184e54d 100644 --- a/mod.ts +++ b/mod.ts @@ -3,7 +3,6 @@ import ModuleResolver from './src/classes/ModuleResolver.ts'; import './src/functions/jsxFactory.ts'; import { ModuleGetterOptions } from './src/classes/ModuleGetter.ts'; import EonComponent from './src/classes/EonComponent.ts'; -import DOMElementRegistry from './src/classes/DOMElementRegistry.ts'; import DevServer from './src/classes/DevServer.ts'; export class Eon { diff --git a/src/classes/Bundler.ts b/src/classes/Bundler.ts index 6b9fa1d..cf562cf 100644 --- a/src/classes/Bundler.ts +++ b/src/classes/Bundler.ts @@ -1,8 +1,7 @@ import Utils from './Utils.ts'; import EonComponentRegistry from './EonComponentRegistry.ts'; import EonComponent from './EonComponent.ts'; -import { path } from '../../deps.ts'; - +import componentDeclartionPattern from '../patterns/component_declaration.ts'; type EonApplication = string; export default class Bundler extends Utils { @@ -10,7 +9,9 @@ export default class Bundler extends Utils { * start building the application */ protected static async buildApplication(): Promise { - let files: { [k: string]: string } = {}; + let files: { [k: string]: string } = { + '/app.ts': '', + }; console.warn(0) // first get all available components const components: EonComponent[] = EonComponentRegistry.collection.map(([key, component]) => component); @@ -21,29 +22,29 @@ export default class Bundler extends Utils { if (component.file) { // save the new string into the files used by Deno.bundle files[component.file as string] = this.createEonComponentDeclaration(component); - console.warn(files[component.file as string]); + files['/app.ts'] += `\nimport '${component.file}';`; } }); console.warn(files); - return ''; + const [diags, emit] = await Deno.bundle('/app.ts', files); + console.warn(emit); + return emit; } /** * creates mirror esm files * for each component */ private static createEonComponentDeclaration(component: EonComponent): string { - let result = ''; - const component_declaration_pattern = Deno.readTextFileSync(`${path.join(import.meta.url, '../patterns/component_declaration.ts')}`); let pattern = ` import { VMC } from '"{{ path_to_component }}"'; - ${component_declaration_pattern} + ${componentDeclartionPattern} `; return Bundler.renderPattern(pattern, { data: { uuid_component: component.dataUuidForSPA, path_to_component: component.file as string, element_vars: 'let tmp;\n', - element_assignment: 'tmp = document.createElement("template");\n', + element_assignments: 'tmp = document.createElement("template");\n', element_parent_append_childs: 'tmp.append("test");\n', element_set_attributes: 'tmp.setAttribute("class", "test");\n', return_root_template: 'return tmp;', diff --git a/src/classes/ModuleGetter.ts b/src/classes/ModuleGetter.ts index cd43fbc..00202e5 100644 --- a/src/classes/ModuleGetter.ts +++ b/src/classes/ModuleGetter.ts @@ -27,7 +27,7 @@ export abstract class ModuleGetter { const newPath = path.join(Deno.cwd(), `${entrypoint}.${sessionUuid}.js`); Deno.writeTextFileSync(newPath, ` // @ts-nocheck - import { h, hf } from '${path.join(import.meta.url, '../../functions/jsxFactory.ts')}'; + import { h, hf } from '${new URL('../functions/jsxFactory.ts', import.meta.url).toString()}'; ${transpiled} `); const module = import(newPath) as unknown as EonModule; diff --git a/src/classes/Utils.ts b/src/classes/Utils.ts index 499310c..4b3024e 100644 --- a/src/classes/Utils.ts +++ b/src/classes/Utils.ts @@ -21,19 +21,20 @@ export default abstract class Utils { } protected static renderPattern(pattern: string, options: PatternOptions): string { let result = pattern; - const { data, open = '"{{', close = '}}"' } = options; + const open = '"{{', close = '}}"'; + const { data } = options; const fn = new Function( '__value', ...Object.keys(data), - `try { return eval('('+ __value + ')'); } catch(err) { throw err; }`, + `try { return eval(__value ); } catch(err) { throw err; }`, ); const values = Object.values(data); while ( result.indexOf(open) > -1 && result.indexOf(close) > -1 ) { const start = result.indexOf(open); - const end = result.indexOf(close) + 2; - const substrContent = result.substring(start + 2, end - 2).trim(); + const end = result.indexOf(close) + 3; + const substrContent = result.substring(start + 3, end - 3).trim(); const partStart = result.substring(0, start); const partEnd = result.substring(end); result = partStart + fn(substrContent, ...values) + partEnd; diff --git a/src/patterns/component_declaration.ts b/src/patterns/component_declaration.ts index 42979db..87c01bd 100644 --- a/src/patterns/component_declaration.ts +++ b/src/patterns/component_declaration.ts @@ -1,53 +1,56 @@ -// @ts-nocheck -function component_ctx() { - "{{ element_vars /** let n1, n2; */ }}"; - let component = new VMC(); +export default ` + function component_ctx() { + "{{ element_vars /** let n1, n2; */ }}" + let component = new VMC(); - /* will assign all the nodes inside vars*/ - function init() { - "{{ element_assignments }}" - // append childs - "{{ element_parent_append_childs /** parent.append(...childs) */ }}" - // set attributes for all elements - "{{ element_set_attributes }}" - // should return the root template - "{{ return_root_template }}" - } - /* general updates */ - function update() { - // all update of bound textnodes - "{{ bound_textnodes_updates }}" - // all update of bound attributes - "{{ bound_attributes_updates }}" - // all component should update their props - "{{ props_updates }}" - } - /** when the component is destroyed */ - function destroy() { - if (VMC.beforeDestroy) { - VMC.beforeDestroy.bind(component)(component); + /* will assign all the nodes inside vars*/ + function init() { + "{{ element_assignments }}" + // append childs + "{{ element_parent_append_childs /** parent.append(...childs) */ }}" + // set attributes for all elements + "{{ element_set_attributes }}" + // should return the root template + "{{ return_root_template }}" + } + /* general updates */ + function update() { + // all update of bound textnodes + "{{ bound_textnodes_updates }}" + // all update of bound attributes + "{{ bound_attributes_updates }}" + // all component should update their props + "{{ props_updates }}" + } + /** when the component is destroyed */ + function destroy() { + if (VMC.beforeDestroy) { + VMC.beforeDestroy.bind(component)(component); + } + // all elements destructions + // element.remove(); + "{{ element_destructions }}" + } + return { + component, + init: init.bind(component), + update: update.bind(component), + destroy: destroy.bind(component) } - // all elements destructions - // element.remove(); - "{{ element_destructions }}" - } - return { - component, - init: init.bind(component), - update: update.bind(component), - destroy: destroy.bind(component) } -} -customElement.define('"{{ uuid_component }}"', class extends HTMLElement { - static VMC = VMc; - constructor() { - super(); - const { init, update, component } = component_ctx(); - let template = init(); - let templateContent = template.content; - this.component = component; + customElements.define('"{{ uuid_component }}"', class extends HTMLElement { + static VMC = VMC; + constructor() { + super(); + const { init, update, component } = component_ctx(); + let template = init(); + // @ts-ignore + let templateContent = template.content; + this.component = component; - const shadowRoot = this.attachShadow({ mode: 'open' }) - .appendChild(templateContent.cloneNode(true)); - } -}); \ No newline at end of file + const shadowRoot = this.attachShadow({ mode: 'open' }) + .appendChild(templateContent.cloneNode(true)); + } + }); + +`; \ No newline at end of file From eecbdac1920359cce6d8dea29b7e7c720d3cfa73 Mon Sep 17 00:00:00 2001 From: SRNV Date: Thu, 5 Nov 2020 00:46:20 +0100 Subject: [PATCH 24/51] wip --- doc/PATTERN_COMPONENT.md | 2 +- examples/hello-app/AnotherComponent.tsx | 13 ++- examples/hello-app/HelloApp.tsx | 7 +- src/classes/Bundler.ts | 58 ------------ src/classes/DOMElement.ts | 113 +++++++++++++++++++++--- src/classes/DOMElementRegistry.ts | 13 +-- src/classes/DOMElementRenderer.ts | 46 ++++++++++ src/classes/DevBundler.ts | 105 ++++++++++++++++++++++ src/classes/DevServer.ts | 14 +-- src/classes/EonComponentRegistry.ts | 7 ++ src/classes/ModuleGetter.ts | 2 +- src/classes/Patterns.ts | 15 ++++ src/functions/jsxFactory.ts | 8 ++ src/patterns/component_declaration.ts | 108 +++++++++++----------- src/patterns/for_directive.ts | 18 ++++ test_swc.ts | 7 ++ 16 files changed, 387 insertions(+), 149 deletions(-) delete mode 100644 src/classes/Bundler.ts create mode 100644 src/classes/DOMElementRenderer.ts create mode 100644 src/classes/DevBundler.ts create mode 100644 src/classes/Patterns.ts create mode 100644 src/patterns/for_directive.ts create mode 100644 test_swc.ts diff --git a/doc/PATTERN_COMPONENT.md b/doc/PATTERN_COMPONENT.md index a17eb8b..fcb40d0 100644 --- a/doc/PATTERN_COMPONENT.md +++ b/doc/PATTERN_COMPONENT.md @@ -51,8 +51,8 @@ function component_ctx() { function init() { tmp1 = document.createElement('template'); n3 = document.createElement('div'); - t4 = new Text(' '); c5 = document.createElement('data-[uuid_sub_component]'); + t4 = new Text(' '); t4_data_update = () => this.message; /* using the component's props attribute value */ c5_props_update = () => ({ diff --git a/examples/hello-app/AnotherComponent.tsx b/examples/hello-app/AnotherComponent.tsx index e055d03..0e2f9a6 100644 --- a/examples/hello-app/AnotherComponent.tsx +++ b/examples/hello-app/AnotherComponent.tsx @@ -2,16 +2,25 @@ export const name = "component-a"; export default function(this: VMC) { return (<> ) } export class VMC { - message = "Hello World"; + message = "Hello World 2"; array = [1, 3]; + complexArray = [1, 3, 2, 9, 0, 6]; static props(this: VMC, props: { message: string }) { this.message = props.message; return props diff --git a/examples/hello-app/HelloApp.tsx b/examples/hello-app/HelloApp.tsx index 65e8170..6e3a9ab 100644 --- a/examples/hello-app/HelloApp.tsx +++ b/examples/hello-app/HelloApp.tsx @@ -1,16 +1,11 @@ export const name = "hello-app"; export default function(this: VMC) { - // what to do about all the things referenced here - // maybe it's a helper zone for SSR - // but what about SPA - // keep in mind, this function is just to do the dom tree return (<>