diff --git a/.gitignore b/.gitignore index 5e56e04..836b1a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /bin +/node_modules \ No newline at end of file diff --git a/dev.hxml b/dev.hxml index dc4f1c4..3fc7233 100644 --- a/dev.hxml +++ b/dev.hxml @@ -2,5 +2,8 @@ -lib travix -cp tests -main Playground ---interp +# --interp +--dce full +-D analyzer-optimize +-js bin/test.js -dce full \ No newline at end of file diff --git a/haxe_libraries/coconut.data.hxml b/haxe_libraries/coconut.data.hxml index 89996cd..3850d8b 100644 --- a/haxe_libraries/coconut.data.hxml +++ b/haxe_libraries/coconut.data.hxml @@ -1,8 +1,7 @@ -# @install: lix --silent download "gh://github.com/MVCoconut/coconut.data#b9dc933bbc8d42c8c144a81cf4b1cc96ffa8740c" into coconut.data/0.12.0/github/b9dc933bbc8d42c8c144a81cf4b1cc96ffa8740c +# @install: lix --silent download "gh://github.com/MVCoconut/coconut.data#8541cb4c07cd32e70229d8b97e16bd8fd4d546f8" into coconut.data/0.12.1/github/8541cb4c07cd32e70229d8b97e16bd8fd4d546f8 -lib tink_anon -lib tink_priority -lib tink_pure -lib tink_state --cp ${HAXE_LIBCACHE}/coconut.data/0.12.0/github/b9dc933bbc8d42c8c144a81cf4b1cc96ffa8740c/src --D coconut.data=0.12.0 ---macro coconut.data.macros.Setup.run() \ No newline at end of file +-cp ${HAXE_LIBCACHE}/coconut.data/0.12.1/github/8541cb4c07cd32e70229d8b97e16bd8fd4d546f8/src +-D coconut.data=0.12.1 \ No newline at end of file diff --git a/haxe_libraries/coconut.ui.hxml b/haxe_libraries/coconut.ui.hxml index 47c6b1b..74583bc 100644 --- a/haxe_libraries/coconut.ui.hxml +++ b/haxe_libraries/coconut.ui.hxml @@ -1,6 +1,6 @@ -# @install: lix --silent download "gh://github.com/MVCoconut/coconut.ui#cd52df4838608e02ad593bf4a5eb222bb5673308" into coconut.ui/0.11.2/github/cd52df4838608e02ad593bf4a5eb222bb5673308 +# @install: lix --silent download "gh://github.com/MVCoconut/coconut.ui#98a1c9d1a07ab54d9d0636f84a12c1969c345e2a" into coconut.ui/0.12.0/github/98a1c9d1a07ab54d9d0636f84a12c1969c345e2a -lib coconut.data -lib tink_anon -lib tink_hxx --cp ${HAXE_LIBCACHE}/coconut.ui/0.11.2/github/cd52df4838608e02ad593bf4a5eb222bb5673308/src --D coconut.ui=0.11.2 \ No newline at end of file +-cp ${HAXE_LIBCACHE}/coconut.ui/0.12.0/github/98a1c9d1a07ab54d9d0636f84a12c1969c345e2a/src +-D coconut.ui=0.12.0 \ No newline at end of file diff --git a/haxe_libraries/tink_anon.hxml b/haxe_libraries/tink_anon.hxml index e69ec21..6046e27 100644 --- a/haxe_libraries/tink_anon.hxml +++ b/haxe_libraries/tink_anon.hxml @@ -1,4 +1,4 @@ --D tink_anon=0.5.0 -# @install: lix --silent download "gh://github.com/haxetink/tink_anon#d6d341c39e086920743e4dac514aae07c6f5003d" into tink_anon/0.5.0/github/d6d341c39e086920743e4dac514aae07c6f5003d +# @install: lix --silent download "gh://github.com/haxetink/tink_anon#0277e6e3f97a7878f1aa9aeeccc4b7be0e9c82bc" into tink_anon/0.7.0/github/0277e6e3f97a7878f1aa9aeeccc4b7be0e9c82bc -lib tink_macro --cp ${HAXE_LIBCACHE}/tink_anon/0.5.0/github/d6d341c39e086920743e4dac514aae07c6f5003d/src +-cp ${HAXE_LIBCACHE}/tink_anon/0.7.0/github/0277e6e3f97a7878f1aa9aeeccc4b7be0e9c82bc/src +-D tink_anon=0.7.0 \ No newline at end of file diff --git a/haxe_libraries/tink_core.hxml b/haxe_libraries/tink_core.hxml index 63761ca..2673982 100644 --- a/haxe_libraries/tink_core.hxml +++ b/haxe_libraries/tink_core.hxml @@ -1,3 +1,3 @@ -# @install: lix --silent download "gh://github.com/haxetink/tink_core#914f3cda113652bfa62226664fae49e17bd1730b" into tink_core/2.0.0-rc.3/github/914f3cda113652bfa62226664fae49e17bd1730b --cp ${HAXE_LIBCACHE}/tink_core/2.0.0-rc.3/github/914f3cda113652bfa62226664fae49e17bd1730b/src --D tink_core=2.0.0-rc.3 \ No newline at end of file +# @install: lix --silent download "gh://github.com/haxetink/tink_core#323f80d2ae63036e5b324dc68775f79e98bde396" into tink_core/2.1.1/github/323f80d2ae63036e5b324dc68775f79e98bde396 +-cp ${HAXE_LIBCACHE}/tink_core/2.1.1/github/323f80d2ae63036e5b324dc68775f79e98bde396/src +-D tink_core=2.1.1 \ No newline at end of file diff --git a/haxe_libraries/tink_hxx.hxml b/haxe_libraries/tink_hxx.hxml index 2739de7..9a6edb0 100644 --- a/haxe_libraries/tink_hxx.hxml +++ b/haxe_libraries/tink_hxx.hxml @@ -1,6 +1,6 @@ -# @install: lix --silent download "gh://github.com/haxetink/tink_hxx#0d6cda883d5ef4c1186dbad476e016b98aad68b8" into tink_hxx/0.25.0/github/0d6cda883d5ef4c1186dbad476e016b98aad68b8 +# @install: lix --silent download "gh://github.com/haxetink/tink_hxx#783a4606516582567d9f6f12505740ba48334c39" into tink_hxx/0.25.0/github/783a4606516582567d9f6f12505740ba48334c39 -lib html-entities -lib tink_anon -lib tink_parse --cp ${HAXE_LIBCACHE}/tink_hxx/0.25.0/github/0d6cda883d5ef4c1186dbad476e016b98aad68b8/src +-cp ${HAXE_LIBCACHE}/tink_hxx/0.25.0/github/783a4606516582567d9f6f12505740ba48334c39/src -D tink_hxx=0.25.0 \ No newline at end of file diff --git a/haxe_libraries/tink_state.hxml b/haxe_libraries/tink_state.hxml index 4e3e943..a60b40b 100644 --- a/haxe_libraries/tink_state.hxml +++ b/haxe_libraries/tink_state.hxml @@ -1,4 +1,4 @@ -# @install: lix --silent download "haxelib:/tink_state#1.0.0-beta.2" into tink_state/1.0.0-beta.2/haxelib +# @install: lix --silent download "gh://github.com/haxetink/tink_state#c693cf38fb4c5e19163ceb3770755fa85e22cec9" into tink_state/1.0.0-beta.3/github/c693cf38fb4c5e19163ceb3770755fa85e22cec9 -lib tink_core --cp ${HAXE_LIBCACHE}/tink_state/1.0.0-beta.2/haxelib/src --D tink_state=1.0.0-beta.2 \ No newline at end of file +-cp ${HAXE_LIBCACHE}/tink_state/1.0.0-beta.3/github/c693cf38fb4c5e19163ceb3770755fa85e22cec9/src +-D tink_state=1.0.0-beta.3 \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..cf9e901 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "coconut.html", + "version": "1.0.0", + "description": "A special coconut backend to render plain HTML, for use in static generators and server side rendering.", + "main": "index.js", + "directories": { + "test": "tests" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "lix": "^15.12.4" + } +} diff --git a/src/coconut/html/FakeCallback.hx b/src/coconut/html/FakeCallback.hx index 6245bdf..0573030 100644 --- a/src/coconut/html/FakeCallback.hx +++ b/src/coconut/html/FakeCallback.hx @@ -1,3 +1,8 @@ package coconut.html; -abstract FakeCallback(Dynamic) {} \ No newline at end of file +@:fromHxx( + transform = coconut.html.FakeCallback.create(_) +) +abstract FakeCallback(Dynamic) { + macro static public function create(e); +} \ No newline at end of file diff --git a/src/coconut/html/FakeCallback.macro.hx b/src/coconut/html/FakeCallback.macro.hx new file mode 100644 index 0000000..2198214 --- /dev/null +++ b/src/coconut/html/FakeCallback.macro.hx @@ -0,0 +1,10 @@ +package coconut.html; + +abstract FakeCallback(Dynamic) { + static function create(e:haxe.macro.Expr) { + return switch e.expr { + case EConst(CString(_)): macro @:pos(e.pos) cast $e; + default: macro null; + } + } +} \ No newline at end of file diff --git a/src/coconut/html/Html.hx b/src/coconut/html/Html.hx index abcfe5e..e212323 100644 --- a/src/coconut/html/Html.hx +++ b/src/coconut/html/Html.hx @@ -2,6 +2,8 @@ package coconut.html; import haxe.DynamicAccess; import coconut.html.RenderResult; +import tink.HtmlString; + using StringTools; @:build(coconut.html.macros.Setup.addTags()) @@ -35,12 +37,13 @@ private class HtmlFragment implements RenderResultObject { private typedef HtmlFragmentAttr = { content:String, ?className:tink.domspec.ClassName, ?tag:String }; +@:transitive abstract AttrValue(Dynamic) from Int from String from Bool from Float { public inline function toString():Null return #if js switch js.Lib.typeof(this) { - case 'string': tink.HtmlString.escape(this); + case 'string': HtmlString.escape(this); case 'boolean' if (this): ''; case 'number': '' + (this:Float); default: null; @@ -50,7 +53,7 @@ abstract AttrValue(Dynamic) from Int from String from Bool from Float { case TBool if (this): ''; case TInt: '' + (this:Int); case TFloat: '' + (this:Float); - case TClass(String): tink.HtmlString.escape(this); + case TClass(String): HtmlString.escape(this); default: null; } #end @@ -58,11 +61,13 @@ abstract AttrValue(Dynamic) from Int from String from Bool from Float { private class Tag implements RenderResultObject { final tag:String; + final isVoid:Bool; final attr:DynamicAccess; final children:Children; - public function new(tag, attr, ?children) { + public function new(tag, isVoid, attr, ?children) { this.tag = tag; + this.isVoid = isVoid; this.attr = attr; this.children = children; } @@ -86,14 +91,12 @@ private class Tag implements RenderResultObject { } } - switch children { - case null: - buf.addRaw('/>'); - default: - buf.addRaw('>'); - for (c in children) - c.renderInto(implicits, buf); - buf.addRaw(''); - } + buf.addRaw('>'); + + if (isVoid) return; + + for (c in children) + c.renderInto(implicits, buf); + buf.addRaw(''); } } diff --git a/src/coconut/html/Implicit.hx b/src/coconut/html/Implicit.hx new file mode 100644 index 0000000..eafc5af --- /dev/null +++ b/src/coconut/html/Implicit.hx @@ -0,0 +1,23 @@ +package coconut.html; + +import tink.htmlstring.HtmlBuffer; +import coconut.ui.internal.ImplicitContext; +import coconut.html.RenderResult.RenderResultObject; + +class Implicit implements RenderResultObject { + + final children:Children; + final defaults:ImplicitValues; + + public function new(attr) { + this.children = attr.children; + this.defaults = attr.defaults; + } + + public function renderInto(implicits:ImplicitContext, buffer:HtmlBuffer) { + var ctx = new ImplicitContext(implicits); + ctx.update(defaults); + for (c in children) + c.renderInto(ctx, buffer); + } +} \ No newline at end of file diff --git a/src/coconut/html/macros/Generator.hx b/src/coconut/html/macros/Generator.hx new file mode 100644 index 0000000..0460609 --- /dev/null +++ b/src/coconut/html/macros/Generator.hx @@ -0,0 +1,133 @@ +package coconut.html.macros; + +#if macro +import haxe.macro.Type; +import tink.hxx.StringAt; +import tink.hxx.Tag; +import haxe.macro.Context; +import haxe.macro.Expr; + +using haxe.macro.Tools; +using StringTools; +using tink.MacroApi; + +class Generator extends tink.hxx.Generator { + + static function unwrap(e:Expr) + return if (e == null) null else switch e.expr { + case EParenthesis(e) | ECheckType(e, _) | ECast(e, _): unwrap(e); + default: e; + } + + static function tUnwrap(e:TypedExpr) + return switch e.expr { + case TParenthesis(e) | TCast(e, _) | TMeta(_, e): tUnwrap(e); + default: e; + } + + override function childList(c:tink.hxx.Node.Children, ?t:Type):{expr:ExprDef, pos:Position} { + return + if (t != null && t.toString() == 'coconut.html.Children' && c == null || c.value.length == 0) macro []; + else super.childList(c, t); + } + + override function invoke(name:StringAt, create:TagCreate, args:Array, pos:Position):{expr:ExprDef, pos:Position} { + + var tagName = name.value; + + switch tink.domspec.Macro.tags[tagName] { + case null: + case tag: + + switch Context.typeExpr(macro @:pos(name.pos) $i{name.value}).expr { + case TField(_, FStatic(_.toString() => 'coconut.html.Html', _)): + default: return super.invoke(name, create, args, pos); + } + + switch unwrap(args[1]).expr { + case EObjectDecl(fields): + var dyn = []; + var stat = []; + + for (f in fields) switch tUnwrap(Context.typeExpr(f.expr)) { + case { expr: TConst(c) }: + switch c { + case TInt(Std.string(_) => s) + | TFloat(s) | TString(_.htmlEscape() => s): + stat.push(' ${f.field}="$s"'); + case TBool(b): + if (b) + stat.push(' ${f.field}'); + default: + } + + case e: + var v = Context.storeTypedExpr(e), + attr = ' ${f.field}'; + + dyn.push( + switch Context.followWithAbstracts(e.t).toString() { + case 'Bool': macro if ($v) $v{attr} else ''; + case 'Int' | 'Float': + attr += '="'; + macro $v{attr} + $v + '"'; + case 'String': + attr += '="'; + macro $v{attr} + tink.HtmlString.escape($v) + '"'; + case t: e.pos.error('Cannot handle ${t.toString()}'); + } + ); + } + + var start = '<${tagName}${stat.join('')}'; + + var noArgs = switch unwrap(args[2]) { + case null, macro null, macro []: true; + default: false; + } + + var end = + if (noArgs) + if (tag.kind == VOID) '>'; + else '>'; + else '>'; + + var open = + switch dyn { + case []: + macro $v{start + end}; + default: + var e = macro $v{start}; + for (a in dyn) + e = macro $e + $a; + macro $e + $v{end}; + } + + open = raw(open); + + if (noArgs) + return open; + else { + var close = macro $v{''}; + close = raw(close); + + return switch unwrap(args[2]) { + case macro [$e]: + macro coconut.html.RenderResult.fragment({}, [$open, $e, $close]); + case e: + macro coconut.html.RenderResult.fragment({}, { + var __open = $open; + $e.prepend(__open).append($close); + }); + } + } + default: + } + } + return super.invoke(name, create, args, pos); + } + + static function raw(e) + return macro coconut.html.RenderResult.raw(new tink.HtmlString($e)); +} +#end \ No newline at end of file diff --git a/src/coconut/html/macros/HXX.hx b/src/coconut/html/macros/HXX.hx index c9b4f29..59ed866 100644 --- a/src/coconut/html/macros/HXX.hx +++ b/src/coconut/html/macros/HXX.hx @@ -2,12 +2,11 @@ package coconut.html.macros; #if macro import coconut.ui.macros.Helpers; -import tink.hxx.*; using tink.MacroApi; class HXX { - static final generator = new Generator(Tag.extractAllFrom(macro coconut.html.Html)); + static final generator = new tink.hxx.Generator(); static public function parse(e) return Helpers.parse(e, generator, 'coconut.html.RenderResult.fragment').as(macro : coconut.html.RenderResult); diff --git a/src/coconut/html/macros/Setup.hx b/src/coconut/html/macros/Setup.hx index 4579cf9..bc80847 100644 --- a/src/coconut/html/macros/Setup.hx +++ b/src/coconut/html/macros/Setup.hx @@ -12,10 +12,6 @@ using tink.MacroApi; class Setup { static function perform() { - tink.hxx.Helpers.setCustomTransformer('coconut.html.FakeCallback', { - reduceType: t -> t, - postprocessor: PUntyped(e -> macro @:pos(e.pos) null), - }); } static inline var NAMELESS = ''; @@ -58,7 +54,7 @@ class Setup { opt: false } ]; - var callArgs = [macro $v{name}, macro cast attr]; + var callArgs = [macro $v{name}, macro $v{tag.kind == VOID}, macro cast attr]; if (tag.kind != VOID) { args.push({ name: 'children', @@ -69,7 +65,7 @@ class Setup { } { args: args, - expr: macro return new Tag($a{callArgs}),//VNode.native($a{callArgs}), + expr: macro return new Tag($a{callArgs}), ret: macro : coconut.html.RenderResult } }) diff --git a/src/coconut/ui/Implicit.hx b/src/coconut/ui/Implicit.hx new file mode 100644 index 0000000..ff7cfd6 --- /dev/null +++ b/src/coconut/ui/Implicit.hx @@ -0,0 +1,3 @@ +package coconut.ui; + +typedef Implicit = coconut.html.Implicit; \ No newline at end of file diff --git a/tests/Playground.hx b/tests/Playground.hx index 55534a4..9f123c7 100644 --- a/tests/Playground.hx +++ b/tests/Playground.hx @@ -1,18 +1,22 @@ import coconut.ui.*; +import coconut.html.Html.*; class Playground { static function main() { - trace(Renderer.renderDocument('Test')); + // trace(Renderer.renderDocument(' 5}>