From 805c5b6850c5edc3fccc7d0ecd71c60f47d835fd Mon Sep 17 00:00:00 2001 From: k Date: Fri, 13 Oct 2023 20:58:25 +0200 Subject: [PATCH] [samples] add static site generator example --- samples/docs/build-static.hxml | 6 ++++ samples/docs/build.hxml | 31 +------------------ samples/docs/common.hxml | 30 ++++++++++++++++++ samples/docs/makefile | 6 ++++ samples/docs/src/AppContext.hx | 48 +++++++++++++++++++++++++++++ samples/docs/src/Main.hx | 1 - samples/docs/src/StaticGenerator.hx | 40 ++++++++++++++++++++++++ samples/docs/src/comp/App.hx | 41 +++++++++--------------- samples/docs/src/comp/SidePanel.hx | 13 ++++++-- samples/docs/src/data/DocChapter.hx | 20 ++++++++++++ 10 files changed, 176 insertions(+), 60 deletions(-) create mode 100644 samples/docs/build-static.hxml create mode 100644 samples/docs/common.hxml create mode 100644 samples/docs/src/AppContext.hx create mode 100644 samples/docs/src/StaticGenerator.hx diff --git a/samples/docs/build-static.hxml b/samples/docs/build-static.hxml new file mode 100644 index 0000000..327b8b2 --- /dev/null +++ b/samples/docs/build-static.hxml @@ -0,0 +1,6 @@ +#!/usr/bin/env haxe + +common.hxml + +-main StaticGenerator +-js bin/static-gen.js diff --git a/samples/docs/build.hxml b/samples/docs/build.hxml index 301ccaa..85e0ca3 100644 --- a/samples/docs/build.hxml +++ b/samples/docs/build.hxml @@ -1,35 +1,6 @@ #!/usr/bin/env haxe --L react-next --L react-css --L css-types --L classnames --L datetime --L markdown --L event-types --L html-entities --L tink_domspec --L hxnodejs --L yaml +common.hxml --cp src -main Main -js bin/server.js - -# -dce full --D js-es=6 -# -D analyzer-optimize --D message.reporting=pretty - --w -WDeprecatedEnumAbstract - -# Configure react-css --D react.css.out=bin/styles.css --D react.css.base=res/base.css -# -D react.css.sourcemap=styles.css.map - -# Configure react-next --D react-wrap-strict --D react-check-jsxstatic-type --D react-disable-dynamic-components - diff --git a/samples/docs/common.hxml b/samples/docs/common.hxml new file mode 100644 index 0000000..f916ded --- /dev/null +++ b/samples/docs/common.hxml @@ -0,0 +1,30 @@ +-L react-next +-L react-css +-L css-types +-L classnames +-L datetime +-L markdown +-L event-types +-L html-entities +-L tink_domspec +-L hxnodejs +-L yaml + +-cp src + +# -dce full +-D js-es=6 +# -D analyzer-optimize +-D message.reporting=pretty + +-w -WDeprecatedEnumAbstract + +# Configure react-css +-D react.css.out=bin/styles.css +-D react.css.base=res/base.css +# -D react.css.sourcemap=styles.css.map + +# Configure react-next +-D react-wrap-strict +-D react-check-jsxstatic-type +-D react-disable-dynamic-components diff --git a/samples/docs/makefile b/samples/docs/makefile index 32f6f74..d3b6cad 100644 --- a/samples/docs/makefile +++ b/samples/docs/makefile @@ -9,3 +9,9 @@ setup: server: node bin/server.js +static: + haxe build-static.hxml + node bin/static-gen.js + +serve-static: + cd bin/static && python -m http.server 8043 diff --git a/samples/docs/src/AppContext.hx b/samples/docs/src/AppContext.hx new file mode 100644 index 0000000..dc1efdc --- /dev/null +++ b/samples/docs/src/AppContext.hx @@ -0,0 +1,48 @@ +import react.React; +import react.ReactComponent; +import react.ReactContext; +import react.ReactMacro.jsx; +import react.ReactType; + +typedef AppContextData = { + var staticSite:Bool; +} + +typedef AppContextProviderProps = { + var value:AppContextData; +} + +typedef AppContextConsumerProps = { + var children:AppContextData->ReactFragment; +} + +class AppContext { + public static var Context(get, null):ReactContext; + public static var Provider(get, null):ReactTypeOf; + public static var Consumer(get, null):ReactTypeOf; + + static function get_Context() {ensureReady(); return Context;} + static function get_Provider() {ensureReady(); return Provider;} + static function get_Consumer() {ensureReady(); return Consumer;} + + static function ensureReady() @:bypassAccessor { + if (Context == null) { + Context = React.createContext(); + Context.displayName = "AppContext"; + Consumer = Context.Consumer; + Provider = Context.Provider; + } + } + + public static function wrap(Comp:ReactType):ReactType { + return function (props:{}) { + return jsx( + + {value -> + + } + ); + } + + } +} diff --git a/samples/docs/src/Main.hx b/samples/docs/src/Main.hx index c1269eb..7c85dcc 100644 --- a/samples/docs/src/Main.hx +++ b/samples/docs/src/Main.hx @@ -8,7 +8,6 @@ import js.node.http.IncomingMessage; import js.node.http.ServerResponse; import react.ReactDOMServer; -import react.Suspense; import react.ReactMacro.jsx; import comp.App; diff --git a/samples/docs/src/StaticGenerator.hx b/samples/docs/src/StaticGenerator.hx new file mode 100644 index 0000000..9b4f2fb --- /dev/null +++ b/samples/docs/src/StaticGenerator.hx @@ -0,0 +1,40 @@ +import haxe.io.Path; +import js.lib.Promise; +import js.node.Fs.Fs; +import sys.FileSystem; +import sys.io.File; + +import data.DocChapter; +import react.ReactDOMServer; +import react.ReactMacro.jsx; + +import comp.App; + +class StaticGenerator { + static inline var OUT = "bin/static"; + + static function main() { + FileSystem.createDirectory(OUT); + for (f in FileSystem.readDirectory(OUT)) FileSystem.deleteFile(Path.join([OUT, f])); + + loadChapters().then(chapters -> { + chapters.unshift(loadReadme()); + Promise.all(chapters.map(renderChapter)).then(_ -> { + File.copy('bin/styles.css', '$OUT/styles.css'); + Sys.println('Generated html and css files in $OUT'); + }); + }); + } + + static function renderChapter(chapter:DocChapter):Promise { + return new Promise((resolve, reject) -> { + var slug = chapter.slug == "/" ? null : chapter.slug; + var path = '$OUT${slug ?? "/index"}.html'; + var fileWriter = Fs.createWriteStream(path); + var stream = ReactDOMServer.renderToStaticNodeStream(jsx()); + stream.on('end', resolve); + stream.on('error', resolve); + stream.pipe(fileWriter); + }); + } +} diff --git a/samples/docs/src/comp/App.hx b/samples/docs/src/comp/App.hx index 939495c..108f960 100644 --- a/samples/docs/src/comp/App.hx +++ b/samples/docs/src/comp/App.hx @@ -1,14 +1,11 @@ package comp; -import haxe.io.Path; -import js.node.Fs; -import js.lib.Promise; - import comp.layout.*; import data.DocChapter; private typedef PublicProps = { var chapter:Null; + @:optional var staticSite:Bool; } private typedef Props = { @@ -60,7 +57,7 @@ class App extends ReactComponent { .then(chapters -> (_) -> jsx( )) @@ -71,18 +68,6 @@ class App extends ReactComponent { }; } - static function loadChapters():Promise> { - return new Promise((resolve, reject) -> { - Fs.readdir("../../doc", (err, files) -> { - var chapters = (files ?? []) - .map(f -> loadChapter(Path.join(["..", "..", "doc", f]))) - .filter(f -> f != null); - - resolve(sortChapters(chapters)); - }); - }); - } - override function render():ReactFragment { return jsx( @@ -100,19 +85,21 @@ class App extends ReactComponent {
- + + - + - - - -
- - - + + + +
+ + + - + +
diff --git a/samples/docs/src/comp/SidePanel.hx b/samples/docs/src/comp/SidePanel.hx index b94f4d7..e331ac5 100644 --- a/samples/docs/src/comp/SidePanel.hx +++ b/samples/docs/src/comp/SidePanel.hx @@ -1,13 +1,21 @@ package comp; +import AppContext; import data.DocChapter; -private typedef Props = { +private typedef PublicProps = { var chapter:Null; var chapters:Array; } +private typedef Props = { + > PublicProps, + > AppContextData, +} + @:css +@:publicProps(PublicProps) +@:wrap(AppContext.wrap) class SidePanel extends ReactComponent { static var styles:Stylesheet = { '_': { @@ -65,6 +73,7 @@ class SidePanel extends ReactComponent { }; override function render():ReactFragment { + var ext = props.staticSite ? '.html' : ''; return jsx(
Home @@ -73,7 +82,7 @@ class SidePanel extends ReactComponent {
diff --git a/samples/docs/src/data/DocChapter.hx b/samples/docs/src/data/DocChapter.hx index 63df4c9..44d728c 100644 --- a/samples/docs/src/data/DocChapter.hx +++ b/samples/docs/src/data/DocChapter.hx @@ -1,5 +1,7 @@ package data; +import js.lib.Promise; +import js.node.Fs.Fs; import sys.FileSystem; import sys.io.File; @@ -30,6 +32,24 @@ function sortChapters(chapters:Array):Array { return chapters; } +function loadChapters():Promise> { + return new Promise((resolve, reject) -> { + Fs.readdir("../../doc", (err, files) -> { + var chapters = (files ?? []) + .map(f -> loadChapter(Path.join(["..", "..", "doc", f]))) + .filter(f -> f != null); + + resolve(sortChapters(chapters)); + }); + }); +} + +function loadReadme():Null { + var ret = loadChapter(Path.join(["..", "..", "README.md"])); + if (ret != null) ret.slug = '/'; + return ret; +} + function loadChapter(path:String):Null { if (!FileSystem.exists(path)) return null; if (!path.endsWith(".md")) return null;