From df28d3993e4d80ae2388bfd28924171cc4776127 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Sun, 11 Jul 2021 15:57:37 +0100 Subject: [PATCH 01/13] WIP --- src/Playground.mjs | 28 +++++++++++++++++ src/Playground.res | 19 +++++++++++ src/common/Eval.mjs | 77 +++++++++++++++++++++++++++++++++++++++++++++ src/common/Eval.res | 35 +++++++++++++++++++++ 4 files changed, 159 insertions(+) create mode 100644 src/common/Eval.mjs create mode 100644 src/common/Eval.res diff --git a/src/Playground.mjs b/src/Playground.mjs index d551b644e..299cf1ade 100644 --- a/src/Playground.mjs +++ b/src/Playground.mjs @@ -1529,6 +1529,29 @@ function Playground$ControlPanel(Props) { [Symbol.for("name")]: "Format" }); }; + var onRunClick = function (evt) { + evt.preventDefault(); + var getSuccessCompilationResult = function (result) { + if (result.TAG === /* Success */1) { + return result._0; + } + + }; + var x = ready.result; + if (typeof x === "number") { + console.log("nothing"); + return ; + } + if (x.TAG === /* Conv */0) { + console.log("conv"); + return ; + } + Belt_Option.map(getSuccessCompilationResult(x._0), (function (r) { + eval(r.js_code); + + })); + + }; var createShareLink = function (param) { var lang = ready.targetLang; var params = lang >= 2 ? [] : [[ @@ -1551,6 +1574,11 @@ function Playground$ControlPanel(Props) { }, React.createElement(Playground$ControlPanel$Button, { children: "Format", onClick: onFormatClick + })), React.createElement("div", { + className: "mr-2" + }, React.createElement(Playground$ControlPanel$Button, { + children: "Run", + onClick: onRunClick })), React.createElement(Playground$ControlPanel$ShareButton, { createShareLink: createShareLink, actionIndicatorKey: actionIndicatorKey diff --git a/src/Playground.res b/src/Playground.res index b5b6f5d9a..f7139aee4 100644 --- a/src/Playground.res +++ b/src/Playground.res @@ -1176,6 +1176,7 @@ module ControlPanel = { } @val @scope(("window", "location")) external origin: string = "origin" + @val external eval: string => unit = "eval" @react.component let make = ( ~actionIndicatorKey: string, @@ -1194,6 +1195,23 @@ module ControlPanel = { dispatch(Format(editorCode.current)) } + let onRunClick = evt => { + ReactEvent.Mouse.preventDefault(evt) + + let getSuccessCompilationResult = result => + switch result { + | RescriptCompilerApi.CompilationResult.Success(r) => Some(r) + | _ => None + } + + switch ready.result { + | FinalResult.Nothing => Js.log("nothing") + | FinalResult.Comp(x) => + getSuccessCompilationResult(x)->Belt.Option.map(r => eval(r.js_code))->ignore + | FinalResult.Conv(_) => Js.log("conv") + } + } + let createShareLink = () => { let params = switch ready.targetLang { | Res => [] @@ -1216,6 +1234,7 @@ module ControlPanel = {
+
| _ => React.null diff --git a/src/common/Eval.mjs b/src/common/Eval.mjs new file mode 100644 index 000000000..221a63011 --- /dev/null +++ b/src/common/Eval.mjs @@ -0,0 +1,77 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Curry from "rescript/lib/es6/curry.js"; +import * as $$Worker from "../bindings/Worker.mjs"; +import * as Belt_Option from "rescript/lib/es6/belt_Option.js"; + +function make(param) { + return $$Worker.make("./worker"); +} + +var Config = { + make: make +}; + +var EvalWorker = $$Worker.Make(Config); + +function make$1(onChange) { + return { + state: /* Idle */0, + worker: Curry._1(EvalWorker.make, undefined), + onChange: onChange + }; +} + +function getState(m) { + return m.state; +} + +function dispatch(m, $$event) { + var match = m.state; + var match$1; + if (match === 0) { + switch ($$event.TAG | 0) { + case /* Evaluate */0 : + match$1 = [ + /* Evaluating */1, + (function (param) { + + }) + ]; + break; + case /* Success */1 : + case /* Fail */2 : + match$1 = [ + m.state, + undefined + ]; + break; + + } + } else { + match$1 = [ + m.state, + undefined + ]; + } + m.state = match$1[0]; + Curry._1(m.onChange, m); + Belt_Option.map(match$1[1], (function (thunk) { + return Curry._1(thunk, undefined); + })); + +} + +var Machine = { + make: make$1, + getState: getState, + dispatch: dispatch +}; + +export { + Config , + EvalWorker , + Machine , + +} +/* EvalWorker Not a pure module */ diff --git a/src/common/Eval.res b/src/common/Eval.res new file mode 100644 index 000000000..edda99f63 --- /dev/null +++ b/src/common/Eval.res @@ -0,0 +1,35 @@ +type evalResult = result + +module Config = { + type fromWorker = ResultMessage(evalResult) + type fromApp = EvalMessage(string) + let make = () => Worker.make("./worker") +} + +module EvalWorker = Worker.Make(Config) + +type state = Idle | Evaluating(string) | Evaluated(string) | Error(string) +type event = Evaluate(string) | Success(string) | Fail(string) + +let reducer = (state, action) => + switch (state, action) { + | (Idle, Evaluate(_)) => state + } + +let useEval = () => { + let (state, dispatch) = React.useReducer(reducer, Idle) + let (worker, _) = React.useState(() => EvalWorker.make()) + + React.useEffect1(() => Some(() => worker->EvalWorker.App.terminate), []) + + React.useEffect2(() => { + switch state { + | Idle => () + | Evaluating(code) => worker->EvalWorker.App.postMessage(Config.EvalMessage(code)) + } + + None + }, (state, worker)) + + (state, dispatch) +} From b7969a282b1966738f3ccb647f96bc657ee9f61b Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Sun, 11 Jul 2021 18:26:13 +0100 Subject: [PATCH 02/13] WIP --- src/bindings/Worker.resi | 8 +- src/common/Eval.mjs | 191 +++++++++++++++++++++++++++++--------- src/common/Eval.res | 55 +++++++++-- src/common/EvalWorker.mjs | 2 + src/common/EvalWorker.res | 0 5 files changed, 199 insertions(+), 57 deletions(-) create mode 100644 src/common/EvalWorker.mjs create mode 100644 src/common/EvalWorker.res diff --git a/src/bindings/Worker.resi b/src/bindings/Worker.resi index 12b1858d2..0d3e7060f 100644 --- a/src/bindings/Worker.resi +++ b/src/bindings/Worker.resi @@ -13,16 +13,16 @@ module Make: (Config: Config) => include Config module App: { - let postMessage: (worker, fromApp) => unit - let onMessage: (worker, {"data": fromWorker} => unit) => unit + let postMessage: (worker, Config.fromApp) => unit + let onMessage: (worker, {"data": Config.fromWorker} => unit) => unit let onError: (worker, 'a => unit) => unit let terminate: worker => unit } module Worker: { type self - let postMessage: fromWorker => unit - let onMessage: (self, {"data": fromApp} => unit) => unit + let postMessage: Config.fromWorker => unit + let onMessage: (self, {"data": Config.fromApp} => unit) => unit let self: self let importScripts: string => unit } diff --git a/src/common/Eval.mjs b/src/common/Eval.mjs index 221a63011..b81a923ee 100644 --- a/src/common/Eval.mjs +++ b/src/common/Eval.mjs @@ -1,11 +1,11 @@ // Generated by ReScript, PLEASE EDIT WITH CARE import * as Curry from "rescript/lib/es6/curry.js"; +import * as React from "react"; import * as $$Worker from "../bindings/Worker.mjs"; -import * as Belt_Option from "rescript/lib/es6/belt_Option.js"; function make(param) { - return $$Worker.make("./worker"); + return $$Worker.make("./EvalWorker.mjs"); } var Config = { @@ -14,64 +14,169 @@ var Config = { var EvalWorker = $$Worker.Make(Config); -function make$1(onChange) { - return { - state: /* Idle */0, - worker: Curry._1(EvalWorker.make, undefined), - onChange: onChange - }; +var worker = Curry._1(EvalWorker.make, undefined); + +var eventListeners = { + contents: [] +}; + +Curry._2(EvalWorker.App.onMessage, worker, (function (msg) { + eventListeners.contents.forEach(function (listener) { + return Curry._1(listener, msg); + }); + + })); + +function addEventListener(listener) { + eventListeners.contents.push(listener); + } -function getState(m) { - return m.state; +function removeEventListener(listener) { + eventListeners.contents = eventListeners.contents.filter(function (l) { + return l !== listener; + }); + } -function dispatch(m, $$event) { - var match = m.state; - var match$1; - if (match === 0) { - switch ($$event.TAG | 0) { +function workerMessageToAction(message) { + var message$1 = message.result; + var forCode = message.forCode; + if (message$1.TAG === /* Ok */0) { + return { + TAG: 1, + forCode: forCode, + message: message$1._0, + [Symbol.for("name")]: "Success" + }; + } else { + return { + TAG: 2, + forCode: forCode, + message: message$1._0, + [Symbol.for("name")]: "Fail" + }; + } +} + +function reducer(state, action) { + if (typeof state === "number") { + switch (action.TAG | 0) { case /* Evaluate */0 : - match$1 = [ - /* Evaluating */1, - (function (param) { - - }) - ]; - break; + return { + TAG: 0, + _0: action._0, + [Symbol.for("name")]: "Evaluating" + }; case /* Success */1 : case /* Fail */2 : - match$1 = [ - m.state, - undefined - ]; - break; + return state; } } else { - match$1 = [ - m.state, - undefined - ]; + switch (state.TAG | 0) { + case /* Evaluating */0 : + var code = state._0; + switch (action.TAG | 0) { + case /* Evaluate */0 : + return state; + case /* Success */1 : + if (action.forCode === code) { + return { + TAG: 1, + _0: action.message, + [Symbol.for("name")]: "Evaluated" + }; + } else { + return state; + } + case /* Fail */2 : + if (action.forCode === code) { + return { + TAG: 2, + _0: action.message, + [Symbol.for("name")]: "Error" + }; + } else { + return state; + } + + } + case /* Evaluated */1 : + switch (action.TAG | 0) { + case /* Evaluate */0 : + return { + TAG: 0, + _0: action._0, + [Symbol.for("name")]: "Evaluating" + }; + case /* Success */1 : + case /* Fail */2 : + return state; + + } + case /* Error */2 : + switch (action.TAG | 0) { + case /* Evaluate */0 : + return { + TAG: 0, + _0: action._0, + [Symbol.for("name")]: "Evaluating" + }; + case /* Success */1 : + case /* Fail */2 : + return state; + + } + + } } - m.state = match$1[0]; - Curry._1(m.onChange, m); - Belt_Option.map(match$1[1], (function (thunk) { - return Curry._1(thunk, undefined); - })); - } -var Machine = { - make: make$1, - getState: getState, - dispatch: dispatch -}; +function useEval(param) { + var match = React.useReducer(reducer, /* Idle */0); + var dispatch = match[1]; + var state = match[0]; + React.useEffect((function () { + var listener = function (message) { + return Curry._1(dispatch, workerMessageToAction(message.data)); + }; + addEventListener(listener); + return (function (param) { + return removeEventListener(listener); + }); + }), []); + React.useEffect((function () { + if (typeof state !== "number" && state.TAG === /* Evaluating */0) { + Curry._2(EvalWorker.App.postMessage, worker, { + _0: state._0, + [Symbol.for("name")]: "EvalMessage" + }); + } + + }), [state]); + return [ + state, + (function (code) { + return Curry._1(dispatch, { + TAG: 0, + _0: code, + [Symbol.for("name")]: "Evaluate" + }); + }) + ]; +} export { Config , EvalWorker , - Machine , + worker , + eventListeners , + addEventListener , + removeEventListener , + workerMessageToAction , + reducer , + useEval , } /* EvalWorker Not a pure module */ diff --git a/src/common/Eval.res b/src/common/Eval.res index edda99f63..eedb7f1c7 100644 --- a/src/common/Eval.res +++ b/src/common/Eval.res @@ -1,35 +1,70 @@ type evalResult = result module Config = { - type fromWorker = ResultMessage(evalResult) + type fromWorker = ResultMessage({forCode: string, result: evalResult}) type fromApp = EvalMessage(string) - let make = () => Worker.make("./worker") + let make = () => Worker.make("./EvalWorker.mjs") } module EvalWorker = Worker.Make(Config) +let worker = EvalWorker.make() + +let eventListeners = ref([]) + +worker->EvalWorker.App.onMessage(msg => + eventListeners.contents->Js.Array2.forEach(listener => listener(msg)) +) + +let addEventListener = listener => { + eventListeners.contents->Js.Array2.push(listener)->ignore +} +let removeEventListener = listener => { + eventListeners := eventListeners.contents->Js.Array2.filter(l => l !== listener) +} + type state = Idle | Evaluating(string) | Evaluated(string) | Error(string) -type event = Evaluate(string) | Success(string) | Fail(string) +type action = + | Evaluate(string) + | Success({forCode: string, message: string}) + | Fail({forCode: string, message: string}) + +let workerMessageToAction = message => + switch message { + | Config.ResultMessage({forCode, result: Ok(message)}) => + Success({forCode: forCode, message: message}) + | Config.ResultMessage({forCode, result: Error(message)}) => + Fail({forCode: forCode, message: message}) + } let reducer = (state, action) => switch (state, action) { - | (Idle, Evaluate(_)) => state + | (Idle, Evaluate(code)) => Evaluating(code) + | (Evaluating(code), Success({forCode, message})) if forCode === code => Evaluated(message) + | (Evaluating(code), Fail({forCode, message})) if forCode === code => Error(message) + | (Error(_), Evaluate(code)) => Evaluating(code) + | (Evaluated(_), Evaluate(code)) => Evaluating(code) + | _ => state } let useEval = () => { let (state, dispatch) = React.useReducer(reducer, Idle) - let (worker, _) = React.useState(() => EvalWorker.make()) - React.useEffect1(() => Some(() => worker->EvalWorker.App.terminate), []) + React.useEffect1(() => { + let listener = message => message["data"]->workerMessageToAction->dispatch + addEventListener(listener) + + Some(() => removeEventListener(listener)) + }, []) - React.useEffect2(() => { + React.useEffect1(() => { switch state { - | Idle => () | Evaluating(code) => worker->EvalWorker.App.postMessage(Config.EvalMessage(code)) + | _ => () } None - }, (state, worker)) + }, [state]) - (state, dispatch) + (state, code => Evaluate(code)->dispatch) } diff --git a/src/common/EvalWorker.mjs b/src/common/EvalWorker.mjs new file mode 100644 index 000000000..d856702bf --- /dev/null +++ b/src/common/EvalWorker.mjs @@ -0,0 +1,2 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ diff --git a/src/common/EvalWorker.res b/src/common/EvalWorker.res new file mode 100644 index 000000000..e69de29bb From 3d98b18d172e35f2d1f35ec7802d46d0ff327c61 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Sun, 11 Jul 2021 18:37:01 +0100 Subject: [PATCH 03/13] WIP --- src/common/Eval.res | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/Eval.res b/src/common/Eval.res index eedb7f1c7..a6dfc1693 100644 --- a/src/common/Eval.res +++ b/src/common/Eval.res @@ -1,6 +1,7 @@ type evalResult = result module Config = { + // TODO: Worker should send two events: LogMessage and ExceptionMessage type fromWorker = ResultMessage({forCode: string, result: evalResult}) type fromApp = EvalMessage(string) let make = () => Worker.make("./EvalWorker.mjs") From 55b666bfdd0f8ca2fb6eafb2e692baf70b473460 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Mon, 12 Jul 2021 14:55:22 +0100 Subject: [PATCH 04/13] WIP --- src/Playground.mjs | 6 +++-- src/Playground.res | 4 +-- src/bindings/Webapi.mjs | 3 +++ src/bindings/Webapi.res | 11 ++++++-- src/bindings/Worker.mjs | 4 +++ src/bindings/Worker.res | 1 + src/bindings/Worker.resi | 1 + src/common/Eval.mjs | 56 ++++++++++++--------------------------- src/common/Eval.res | 34 ++++++++++-------------- src/common/EvalWorker.mjs | 14 +++++++++- src/common/EvalWorker.res | 1 + 11 files changed, 69 insertions(+), 66 deletions(-) diff --git a/src/Playground.mjs b/src/Playground.mjs index 299cf1ade..e53ac229c 100644 --- a/src/Playground.mjs +++ b/src/Playground.mjs @@ -1,5 +1,6 @@ // Generated by ReScript, PLEASE EDIT WITH CARE +import * as Eval from "./common/Eval.mjs"; import * as Icon from "./components/Icon.mjs"; import * as Meta from "./components/Meta.mjs"; import * as Next from "./bindings/Next.mjs"; @@ -1500,6 +1501,8 @@ function Playground$ControlPanel(Props) { var dispatch = Props.dispatch; var editorCode = Props.editorCode; var router = Next.Router.useRouter(undefined); + var match = Eval.useEval(undefined); + var dispatchEval = match[1]; var children; var exit = 0; if (typeof state === "number") { @@ -1547,8 +1550,7 @@ function Playground$ControlPanel(Props) { return ; } Belt_Option.map(getSuccessCompilationResult(x._0), (function (r) { - eval(r.js_code); - + return Curry._1(dispatchEval, r.js_code); })); }; diff --git a/src/Playground.res b/src/Playground.res index f7139aee4..18dde9fcf 100644 --- a/src/Playground.res +++ b/src/Playground.res @@ -1176,7 +1176,6 @@ module ControlPanel = { } @val @scope(("window", "location")) external origin: string = "origin" - @val external eval: string => unit = "eval" @react.component let make = ( ~actionIndicatorKey: string, @@ -1185,6 +1184,7 @@ module ControlPanel = { ~editorCode: React.ref, ) => { let router = Next.Router.useRouter() + let (evalState, dispatchEval) = Eval.useEval() let children = switch state { | Init => React.string("Initializing...") | SwitchingCompiler(_, _, _) => React.string("Switching Compiler...") @@ -1207,7 +1207,7 @@ module ControlPanel = { switch ready.result { | FinalResult.Nothing => Js.log("nothing") | FinalResult.Comp(x) => - getSuccessCompilationResult(x)->Belt.Option.map(r => eval(r.js_code))->ignore + getSuccessCompilationResult(x)->Belt.Option.map(r => dispatchEval(r.js_code))->ignore | FinalResult.Conv(_) => Js.log("conv") } } diff --git a/src/bindings/Webapi.mjs b/src/bindings/Webapi.mjs index 428f1b7ff..298258a97 100644 --- a/src/bindings/Webapi.mjs +++ b/src/bindings/Webapi.mjs @@ -7,10 +7,13 @@ var ClassList = {}; var $$Element = {}; +var Url = {}; + export { $$Document , ClassList , $$Element , + Url , } /* No side effect */ diff --git a/src/bindings/Webapi.res b/src/bindings/Webapi.res index 7bab84a2f..e831e9b6f 100644 --- a/src/bindings/Webapi.res +++ b/src/bindings/Webapi.res @@ -4,7 +4,7 @@ module Document = { } module ClassList = { - type t; + type t @send external toggle: (t, string) => unit = "toggle" } @@ -15,7 +15,6 @@ module Element = { @get external classList: Dom.element => ClassList.t = "classList" } - type animationFrameId @scope("window") @val @@ -23,3 +22,11 @@ external requestAnimationFrame: (unit => unit) => animationFrameId = "requestAni @scope("window") @val external cancelAnimationFrame: animationFrameId => unit = "cancelAnimationFrame" + +module Url = { + type t + @new external make: string => t = "URL" + @new external makeWithBase: (string, string) => t = "URL" + + @send external toString: t => string = "toString" +} diff --git a/src/bindings/Worker.mjs b/src/bindings/Worker.mjs index 6dcee8b10..f32dc0a5d 100644 --- a/src/bindings/Worker.mjs +++ b/src/bindings/Worker.mjs @@ -39,6 +39,10 @@ function Make(funarg) { importScripts: (function (prim) { importScripts(prim); + }), + addEventListener: (function (prim) { + addEventListener(prim); + }) } }; diff --git a/src/bindings/Worker.res b/src/bindings/Worker.res index 16da7dec0..f11381056 100644 --- a/src/bindings/Worker.res +++ b/src/bindings/Worker.res @@ -27,5 +27,6 @@ module Make = (Config: Config) => { external onMessage: (self, {"data": fromApp} => unit) => unit = "onmessage" @val external self: self = "self" @val external importScripts: string => unit = "importScripts" + @val external addEventListener: ({"data": fromApp} => unit) => unit = "addEventListener" } } diff --git a/src/bindings/Worker.resi b/src/bindings/Worker.resi index 0d3e7060f..93372178e 100644 --- a/src/bindings/Worker.resi +++ b/src/bindings/Worker.resi @@ -25,5 +25,6 @@ module Make: (Config: Config) => let onMessage: (self, {"data": Config.fromApp} => unit) => unit let self: self let importScripts: string => unit + let addEventListener: ({"data": fromApp} => unit) => unit } } diff --git a/src/common/Eval.mjs b/src/common/Eval.mjs index b81a923ee..d90e14d21 100644 --- a/src/common/Eval.mjs +++ b/src/common/Eval.mjs @@ -3,9 +3,11 @@ import * as Curry from "rescript/lib/es6/curry.js"; import * as React from "react"; import * as $$Worker from "../bindings/Worker.mjs"; +import * as Belt_Option from "rescript/lib/es6/belt_Option.js"; +import * as Caml_option from "rescript/lib/es6/caml_option.js"; function make(param) { - return $$Worker.make("./EvalWorker.mjs"); + return $$Worker.make(new URL("./EvalWorker.mjs", import.meta.url).toString()); } var Config = { @@ -14,31 +16,6 @@ var Config = { var EvalWorker = $$Worker.Make(Config); -var worker = Curry._1(EvalWorker.make, undefined); - -var eventListeners = { - contents: [] -}; - -Curry._2(EvalWorker.App.onMessage, worker, (function (msg) { - eventListeners.contents.forEach(function (listener) { - return Curry._1(listener, msg); - }); - - })); - -function addEventListener(listener) { - eventListeners.contents.push(listener); - -} - -function removeEventListener(listener) { - eventListeners.contents = eventListeners.contents.filter(function (l) { - return l !== listener; - }); - -} - function workerMessageToAction(message) { var message$1 = message.result; var forCode = message.forCode; @@ -137,21 +114,26 @@ function useEval(param) { var match = React.useReducer(reducer, /* Idle */0); var dispatch = match[1]; var state = match[0]; + var workerRef = React.useRef(undefined); React.useEffect((function () { - var listener = function (message) { - return Curry._1(dispatch, workerMessageToAction(message.data)); - }; - addEventListener(listener); + workerRef.current = Caml_option.some(Curry._1(EvalWorker.make, undefined)); return (function (param) { - return removeEventListener(listener); + Belt_Option.map(workerRef.current, (function (worker) { + return Curry._1(EvalWorker.App.terminate, worker); + })); + }); }), []); React.useEffect((function () { + var maybeWorker = workerRef.current; if (typeof state !== "number" && state.TAG === /* Evaluating */0) { - Curry._2(EvalWorker.App.postMessage, worker, { - _0: state._0, - [Symbol.for("name")]: "EvalMessage" - }); + var code = state._0; + Belt_Option.map(maybeWorker, (function (worker) { + return Curry._2(EvalWorker.App.postMessage, worker, { + _0: code, + [Symbol.for("name")]: "EvalMessage" + }); + })); } }), [state]); @@ -170,10 +152,6 @@ function useEval(param) { export { Config , EvalWorker , - worker , - eventListeners , - addEventListener , - removeEventListener , workerMessageToAction , reducer , useEval , diff --git a/src/common/Eval.res b/src/common/Eval.res index a6dfc1693..9391aeb82 100644 --- a/src/common/Eval.res +++ b/src/common/Eval.res @@ -1,29 +1,17 @@ type evalResult = result +@val external importMetaUrl: string = "import.meta.url" + module Config = { // TODO: Worker should send two events: LogMessage and ExceptionMessage type fromWorker = ResultMessage({forCode: string, result: evalResult}) type fromApp = EvalMessage(string) - let make = () => Worker.make("./EvalWorker.mjs") + let make = () => + Worker.make(Webapi.Url.makeWithBase("./EvalWorker.mjs", importMetaUrl)->Webapi.Url.toString) } module EvalWorker = Worker.Make(Config) -let worker = EvalWorker.make() - -let eventListeners = ref([]) - -worker->EvalWorker.App.onMessage(msg => - eventListeners.contents->Js.Array2.forEach(listener => listener(msg)) -) - -let addEventListener = listener => { - eventListeners.contents->Js.Array2.push(listener)->ignore -} -let removeEventListener = listener => { - eventListeners := eventListeners.contents->Js.Array2.filter(l => l !== listener) -} - type state = Idle | Evaluating(string) | Evaluated(string) | Error(string) type action = | Evaluate(string) @@ -50,17 +38,23 @@ let reducer = (state, action) => let useEval = () => { let (state, dispatch) = React.useReducer(reducer, Idle) + let workerRef = React.useRef(None) React.useEffect1(() => { - let listener = message => message["data"]->workerMessageToAction->dispatch - addEventListener(listener) + workerRef.current = Some(EvalWorker.make()) - Some(() => removeEventListener(listener)) + Some( + () => workerRef.current->Belt.Option.map(worker => worker->EvalWorker.App.terminate)->ignore, + ) }, []) React.useEffect1(() => { + let maybeWorker = workerRef.current switch state { - | Evaluating(code) => worker->EvalWorker.App.postMessage(Config.EvalMessage(code)) + | Evaluating(code) => + maybeWorker + ->Belt.Option.map(worker => worker->EvalWorker.App.postMessage(Config.EvalMessage(code))) + ->ignore | _ => () } diff --git a/src/common/EvalWorker.mjs b/src/common/EvalWorker.mjs index d856702bf..1ff0b8e64 100644 --- a/src/common/EvalWorker.mjs +++ b/src/common/EvalWorker.mjs @@ -1,2 +1,14 @@ // Generated by ReScript, PLEASE EDIT WITH CARE -/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ + +import * as Eval from "./Eval.mjs"; +import * as Curry from "rescript/lib/es6/curry.js"; + +Curry._1(Eval.EvalWorker.$$Worker.addEventListener, (function (msg) { + console.log(msg); + + })); + +export { + +} +/* Not a pure module */ diff --git a/src/common/EvalWorker.res b/src/common/EvalWorker.res index e69de29bb..078a4c1e7 100644 --- a/src/common/EvalWorker.res +++ b/src/common/EvalWorker.res @@ -0,0 +1 @@ +Eval.EvalWorker.Worker.addEventListener(msg => Js.log(msg)) From a62ef73265cbe8510898e22d27bee6d377ca927e Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Mon, 12 Jul 2021 16:32:49 +0100 Subject: [PATCH 05/13] Set target to es2020 --- next.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/next.config.js b/next.config.js index 4e2e77fa0..8bfcda1ee 100644 --- a/next.config.js +++ b/next.config.js @@ -55,7 +55,7 @@ const config = { test: /\.m?js$/, // v-- currently using an experimental setting with esbuild-loader //use: options.defaultLoaders.babel, - use: [{loader: 'esbuild-loader', options: { loader: 'jsx'}}], + use: [{loader: 'esbuild-loader', options: { loader: 'jsx', target: 'es2020' }}], exclude: /node_modules/, type: "javascript/auto", resolve: { From ce9b1c6cd54f05d08fc7d0aa8b66135770b2441d Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Mon, 12 Jul 2021 16:34:19 +0100 Subject: [PATCH 06/13] WIP --- src/bindings/Worker.mjs | 4 ---- src/bindings/Worker.res | 1 - src/bindings/Worker.resi | 1 - src/common/Eval.mjs | 10 ++++++++-- src/common/Eval.res | 18 +++++++++++------- src/common/EvalWorker.mjs | 16 +++++++++++++--- src/common/EvalWorker.res | 11 ++++++++++- 7 files changed, 42 insertions(+), 19 deletions(-) diff --git a/src/bindings/Worker.mjs b/src/bindings/Worker.mjs index f32dc0a5d..6dcee8b10 100644 --- a/src/bindings/Worker.mjs +++ b/src/bindings/Worker.mjs @@ -39,10 +39,6 @@ function Make(funarg) { importScripts: (function (prim) { importScripts(prim); - }), - addEventListener: (function (prim) { - addEventListener(prim); - }) } }; diff --git a/src/bindings/Worker.res b/src/bindings/Worker.res index f11381056..16da7dec0 100644 --- a/src/bindings/Worker.res +++ b/src/bindings/Worker.res @@ -27,6 +27,5 @@ module Make = (Config: Config) => { external onMessage: (self, {"data": fromApp} => unit) => unit = "onmessage" @val external self: self = "self" @val external importScripts: string => unit = "importScripts" - @val external addEventListener: ({"data": fromApp} => unit) => unit = "addEventListener" } } diff --git a/src/bindings/Worker.resi b/src/bindings/Worker.resi index 93372178e..0d3e7060f 100644 --- a/src/bindings/Worker.resi +++ b/src/bindings/Worker.resi @@ -25,6 +25,5 @@ module Make: (Config: Config) => let onMessage: (self, {"data": Config.fromApp} => unit) => unit let self: self let importScripts: string => unit - let addEventListener: ({"data": fromApp} => unit) => unit } } diff --git a/src/common/Eval.mjs b/src/common/Eval.mjs index d90e14d21..1f44d9199 100644 --- a/src/common/Eval.mjs +++ b/src/common/Eval.mjs @@ -6,6 +6,8 @@ import * as $$Worker from "../bindings/Worker.mjs"; import * as Belt_Option from "rescript/lib/es6/belt_Option.js"; import * as Caml_option from "rescript/lib/es6/caml_option.js"; +var source = "EvalSource"; + function make(param) { return $$Worker.make(new URL("./EvalWorker.mjs", import.meta.url).toString()); } @@ -130,8 +132,11 @@ function useEval(param) { var code = state._0; Belt_Option.map(maybeWorker, (function (worker) { return Curry._2(EvalWorker.App.postMessage, worker, { - _0: code, - [Symbol.for("name")]: "EvalMessage" + source: source, + payload: { + _0: code, + [Symbol.for("name")]: "EvalMessage" + } }); })); } @@ -150,6 +155,7 @@ function useEval(param) { } export { + source , Config , EvalWorker , workerMessageToAction , diff --git a/src/common/Eval.res b/src/common/Eval.res index 9391aeb82..b50e889ff 100644 --- a/src/common/Eval.res +++ b/src/common/Eval.res @@ -2,10 +2,14 @@ type evalResult = result @val external importMetaUrl: string = "import.meta.url" +type workerMessage = ResultMessage({forCode: string, result: evalResult}) +type appMessage = EvalMessage(string) +let source = "EvalSource" + module Config = { // TODO: Worker should send two events: LogMessage and ExceptionMessage - type fromWorker = ResultMessage({forCode: string, result: evalResult}) - type fromApp = EvalMessage(string) + type fromWorker = {source: string, payload: workerMessage} + type fromApp = {source: string, payload: appMessage} let make = () => Worker.make(Webapi.Url.makeWithBase("./EvalWorker.mjs", importMetaUrl)->Webapi.Url.toString) } @@ -20,10 +24,8 @@ type action = let workerMessageToAction = message => switch message { - | Config.ResultMessage({forCode, result: Ok(message)}) => - Success({forCode: forCode, message: message}) - | Config.ResultMessage({forCode, result: Error(message)}) => - Fail({forCode: forCode, message: message}) + | ResultMessage({forCode, result: Ok(message)}) => Success({forCode: forCode, message: message}) + | ResultMessage({forCode, result: Error(message)}) => Fail({forCode: forCode, message: message}) } let reducer = (state, action) => @@ -53,7 +55,9 @@ let useEval = () => { switch state { | Evaluating(code) => maybeWorker - ->Belt.Option.map(worker => worker->EvalWorker.App.postMessage(Config.EvalMessage(code))) + ->Belt.Option.map(worker => + worker->EvalWorker.App.postMessage({source: source, payload: EvalMessage(code)}) + ) ->ignore | _ => () } diff --git a/src/common/EvalWorker.mjs b/src/common/EvalWorker.mjs index 1ff0b8e64..8e3bf452a 100644 --- a/src/common/EvalWorker.mjs +++ b/src/common/EvalWorker.mjs @@ -3,12 +3,22 @@ import * as Eval from "./Eval.mjs"; import * as Curry from "rescript/lib/es6/curry.js"; -Curry._1(Eval.EvalWorker.$$Worker.addEventListener, (function (msg) { - console.log(msg); - +function ignoreOtherMessages(message, f) { + if (message.data.source === Eval.source) { + return Curry._1(f, message.data); + } + +} + +Curry._2(Eval.EvalWorker.$$Worker.onMessage, Eval.EvalWorker.$$Worker.self, (function (msg) { + return ignoreOtherMessages(msg, (function (prim) { + console.log(prim); + + })); })); export { + ignoreOtherMessages , } /* Not a pure module */ diff --git a/src/common/EvalWorker.res b/src/common/EvalWorker.res index 078a4c1e7..d76bf256f 100644 --- a/src/common/EvalWorker.res +++ b/src/common/EvalWorker.res @@ -1 +1,10 @@ -Eval.EvalWorker.Worker.addEventListener(msg => Js.log(msg)) +// Required because workers may receive messages intended for other workers eg. react dev tools +// See https://github.com/facebook/react-devtools/issues/812 +let ignoreOtherMessages = (message: {"data": Eval.Config.fromApp}, f) => + if message["data"].source == Eval.source { + f(message["data"]) + } + +Eval.EvalWorker.Worker.self->Eval.EvalWorker.Worker.onMessage(msg => + msg->ignoreOtherMessages(Js.log) +) From e7a77fe9a52a9c4f85c8a50b19d000d32a4958e1 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Tue, 13 Jul 2021 09:52:52 +0100 Subject: [PATCH 07/13] WIP --- next.config.js | 7 ++----- src/common/Eval.mjs | 2 +- src/common/Eval.res | 3 +-- src/common/EvalWorker.mjs | 3 +++ src/common/EvalWorker.res | 5 ++++- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/next.config.js b/next.config.js index 8bfcda1ee..7fb35cfd8 100644 --- a/next.config.js +++ b/next.config.js @@ -48,21 +48,18 @@ const config = { path: false, }; } - useEsbuildMinify(config); + // We need this additional rule to make sure that mjs files are // correctly detected within our src/ folder config.module.rules.push({ test: /\.m?js$/, - // v-- currently using an experimental setting with esbuild-loader - //use: options.defaultLoaders.babel, - use: [{loader: 'esbuild-loader', options: { loader: 'jsx', target: 'es2020' }}], + use: options.defaultLoaders.babel, exclude: /node_modules/, type: "javascript/auto", resolve: { fullySpecified: false, } }); - config.plugins.push(new ProvidePlugin({ React: "react" })); } return config; }, diff --git a/src/common/Eval.mjs b/src/common/Eval.mjs index 1f44d9199..7128de9ee 100644 --- a/src/common/Eval.mjs +++ b/src/common/Eval.mjs @@ -9,7 +9,7 @@ import * as Caml_option from "rescript/lib/es6/caml_option.js"; var source = "EvalSource"; function make(param) { - return $$Worker.make(new URL("./EvalWorker.mjs", import.meta.url).toString()); + return (new Worker(new URL("./EvalWorker.mjs", import.meta.url))); } var Config = { diff --git a/src/common/Eval.res b/src/common/Eval.res index b50e889ff..efdf33900 100644 --- a/src/common/Eval.res +++ b/src/common/Eval.res @@ -10,8 +10,7 @@ module Config = { // TODO: Worker should send two events: LogMessage and ExceptionMessage type fromWorker = {source: string, payload: workerMessage} type fromApp = {source: string, payload: appMessage} - let make = () => - Worker.make(Webapi.Url.makeWithBase("./EvalWorker.mjs", importMetaUrl)->Webapi.Url.toString) + let make = () => %raw(`new Worker(new URL("./EvalWorker.mjs", import.meta.url))`) } module EvalWorker = Worker.Make(Config) diff --git a/src/common/EvalWorker.mjs b/src/common/EvalWorker.mjs index 8e3bf452a..824f1b59c 100644 --- a/src/common/EvalWorker.mjs +++ b/src/common/EvalWorker.mjs @@ -4,6 +4,7 @@ import * as Eval from "./Eval.mjs"; import * as Curry from "rescript/lib/es6/curry.js"; function ignoreOtherMessages(message, f) { + console.log("ffffff", message); if (message.data.source === Eval.source) { return Curry._1(f, message.data); } @@ -17,6 +18,8 @@ Curry._2(Eval.EvalWorker.$$Worker.onMessage, Eval.EvalWorker.$$Worker.self, (fun })); })); +console.log("hello"); + export { ignoreOtherMessages , diff --git a/src/common/EvalWorker.res b/src/common/EvalWorker.res index d76bf256f..f18e842fd 100644 --- a/src/common/EvalWorker.res +++ b/src/common/EvalWorker.res @@ -1,10 +1,13 @@ // Required because workers may receive messages intended for other workers eg. react dev tools // See https://github.com/facebook/react-devtools/issues/812 -let ignoreOtherMessages = (message: {"data": Eval.Config.fromApp}, f) => +let ignoreOtherMessages = (message: {"data": Eval.Config.fromApp}, f) => { + Js.log2("ffffff", message) if message["data"].source == Eval.source { f(message["data"]) } +} Eval.EvalWorker.Worker.self->Eval.EvalWorker.Worker.onMessage(msg => msg->ignoreOtherMessages(Js.log) ) +Js.log("hello") From f598cd49009aa3a183164e2a372af4bb9e4d642c Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Tue, 13 Jul 2021 10:06:59 +0100 Subject: [PATCH 08/13] Add provide plugin back in --- next.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/next.config.js b/next.config.js index 7fb35cfd8..763c23421 100644 --- a/next.config.js +++ b/next.config.js @@ -60,6 +60,7 @@ const config = { fullySpecified: false, } }); + config.plugins.push(new ProvidePlugin({ React: "react" })); } return config; }, From 4b58ab88fbc58b7938f8aee72fdd4d6b3fe775bc Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Tue, 13 Jul 2021 21:13:31 +0100 Subject: [PATCH 09/13] Track logs in state machine --- src/common/Eval.mjs | 130 +++++++++++++++++++++++--------------------- src/common/Eval.res | 39 ++++++++----- 2 files changed, 93 insertions(+), 76 deletions(-) diff --git a/src/common/Eval.mjs b/src/common/Eval.mjs index 7128de9ee..85b1bbb77 100644 --- a/src/common/Eval.mjs +++ b/src/common/Eval.mjs @@ -25,7 +25,6 @@ function workerMessageToAction(message) { return { TAG: 1, forCode: forCode, - message: message$1._0, [Symbol.for("name")]: "Success" }; } else { @@ -33,82 +32,87 @@ function workerMessageToAction(message) { TAG: 2, forCode: forCode, message: message$1._0, - [Symbol.for("name")]: "Fail" + [Symbol.for("name")]: "Exception" }; } } function reducer(state, action) { if (typeof state === "number") { - switch (action.TAG | 0) { - case /* Evaluate */0 : - return { - TAG: 0, - _0: action._0, - [Symbol.for("name")]: "Evaluating" - }; - case /* Success */1 : - case /* Fail */2 : - return state; - + if (action.TAG === /* Evaluate */0) { + return { + TAG: 0, + code: action._0, + logs: [], + [Symbol.for("name")]: "Evaluating" + }; + } else { + return state; } - } else { - switch (state.TAG | 0) { - case /* Evaluating */0 : - var code = state._0; - switch (action.TAG | 0) { - case /* Evaluate */0 : + } + switch (state.TAG | 0) { + case /* Evaluating */0 : + var logs = state.logs; + var code = state.code; + switch (action.TAG | 0) { + case /* Evaluate */0 : + return state; + case /* Success */1 : + if (action.forCode === code) { + return { + TAG: 1, + logs: logs, + [Symbol.for("name")]: "Evaluated" + }; + } else { return state; - case /* Success */1 : - if (action.forCode === code) { - return { - TAG: 1, - _0: action.message, - [Symbol.for("name")]: "Evaluated" - }; - } else { - return state; - } - case /* Fail */2 : - if (action.forCode === code) { - return { - TAG: 2, - _0: action.message, - [Symbol.for("name")]: "Error" - }; - } else { - return state; - } - - } - case /* Evaluated */1 : - switch (action.TAG | 0) { - case /* Evaluate */0 : + } + case /* Exception */2 : + if (action.forCode === code) { return { - TAG: 0, - _0: action._0, - [Symbol.for("name")]: "Evaluating" + TAG: 2, + logs: logs.concat([action.message]), + [Symbol.for("name")]: "Error" }; - case /* Success */1 : - case /* Fail */2 : + } else { return state; - - } - case /* Error */2 : - switch (action.TAG | 0) { - case /* Evaluate */0 : + } + case /* Log */3 : + if (action.forCode === code) { return { TAG: 0, - _0: action._0, + code: code, + logs: logs.concat([action.message]), [Symbol.for("name")]: "Evaluating" }; - case /* Success */1 : - case /* Fail */2 : + } else { return state; - - } - - } + } + + } + case /* Evaluated */1 : + if (action.TAG === /* Evaluate */0) { + return { + TAG: 0, + code: action._0, + logs: [], + [Symbol.for("name")]: "Evaluating" + }; + } else { + return state; + } + case /* Error */2 : + if (action.TAG === /* Evaluate */0) { + return { + TAG: 0, + code: action._0, + logs: [], + [Symbol.for("name")]: "Evaluating" + }; + } else { + return state; + } + } } @@ -129,8 +133,8 @@ function useEval(param) { React.useEffect((function () { var maybeWorker = workerRef.current; if (typeof state !== "number" && state.TAG === /* Evaluating */0) { - var code = state._0; - Belt_Option.map(maybeWorker, (function (worker) { + var code = state.code; + Belt_Option.forEach(maybeWorker, (function (worker) { return Curry._2(EvalWorker.App.postMessage, worker, { source: source, payload: { diff --git a/src/common/Eval.res b/src/common/Eval.res index efdf33900..c96ad30d1 100644 --- a/src/common/Eval.res +++ b/src/common/Eval.res @@ -1,4 +1,4 @@ -type evalResult = result +type evalResult = result @val external importMetaUrl: string = "import.meta.url" @@ -15,25 +15,37 @@ module Config = { module EvalWorker = Worker.Make(Config) -type state = Idle | Evaluating(string) | Evaluated(string) | Error(string) +type state = + | Idle + | Evaluating({code: string, logs: array}) + | Evaluated({logs: array}) + | Error({logs: array}) type action = | Evaluate(string) - | Success({forCode: string, message: string}) - | Fail({forCode: string, message: string}) + | Success({forCode: string}) + | Exception({forCode: string, message: string}) + | Log({forCode: string, message: string}) let workerMessageToAction = message => switch message { - | ResultMessage({forCode, result: Ok(message)}) => Success({forCode: forCode, message: message}) - | ResultMessage({forCode, result: Error(message)}) => Fail({forCode: forCode, message: message}) + | ResultMessage({forCode, result: Ok()}) => Success({forCode: forCode}) + | ResultMessage({forCode, result: Error(message)}) => + Exception({forCode: forCode, message: message}) } let reducer = (state, action) => switch (state, action) { - | (Idle, Evaluate(code)) => Evaluating(code) - | (Evaluating(code), Success({forCode, message})) if forCode === code => Evaluated(message) - | (Evaluating(code), Fail({forCode, message})) if forCode === code => Error(message) - | (Error(_), Evaluate(code)) => Evaluating(code) - | (Evaluated(_), Evaluate(code)) => Evaluating(code) + | (Idle, Evaluate(code)) => Evaluating({code: code, logs: []}) + | (Evaluating({code, logs}), Success({forCode})) if forCode === code => Evaluated({logs: logs}) + | (Evaluating({code, logs}), Exception({forCode, message})) if forCode === code => + Error({logs: logs->Js.Array2.concat([message])}) + | (Evaluating({code, logs}), Log({forCode, message})) if forCode === code => + Evaluating({ + code: code, + logs: logs->Js.Array2.concat([message]), + }) + | (Error(_), Evaluate(code)) => Evaluating({code: code, logs: []}) + | (Evaluated(_), Evaluate(code)) => Evaluating({code: code, logs: []}) | _ => state } @@ -52,9 +64,10 @@ let useEval = () => { React.useEffect1(() => { let maybeWorker = workerRef.current switch state { - | Evaluating(code) => + | Evaluating({code}) => maybeWorker - ->Belt.Option.map(worker => + ->Belt.Option.forEach(worker => + // TODO: Either posting EvalMessage needs to be idempotent or we need a way of not posting this message when we are already in the Evaluating state worker->EvalWorker.App.postMessage({source: source, payload: EvalMessage(code)}) ) ->ignore From 451bc44e83b44e286dd0bc28689302cbec2dee50 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Thu, 15 Jul 2021 22:34:27 +0100 Subject: [PATCH 10/13] Temporary next config workaround See https://github.com/vercel/next.js/issues/22581#issuecomment-864476385 --- next.config.js | 60 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/next.config.js b/next.config.js index 763c23421..936bce648 100644 --- a/next.config.js +++ b/next.config.js @@ -1,8 +1,9 @@ const { ProvidePlugin } = require('webpack'); -const { ESBuildMinifyPlugin } = require('esbuild-loader'); const bsconfig = require("./bsconfig.json"); -const path = require("path"); +const SSRPlugin = require("next/dist/build/webpack/plugins/nextjs-ssr-import") + .default; +const { dirname, relative, resolve, join } = require("path"); const remarkSlug = require("remark-slug"); const fs = require("fs"); @@ -17,17 +18,42 @@ const withMdx = require("./plugins/next-mdx")({ }); -// esbuild-loader specific features -// See: https://github.com/privatenumber/esbuild-loader-examples/blob/master/examples/next/next.config.js -function useEsbuildMinify(config, options) { - const terserIndex = config.optimization.minimizer.findIndex(minimizer => (minimizer.constructor.name === 'TerserPlugin')); - if (terserIndex > -1) { - config.optimization.minimizer.splice( - terserIndex, - 1, - new ESBuildMinifyPlugin(options), - ); - } +// Unfortunately there isn't an easy way to override the replacement function body, so we +// have to just replace the whole plugin `apply` body. +function patchSsrPlugin(plugin) { + plugin.apply = function apply(compiler) { + compiler.hooks.compilation.tap("NextJsSSRImport", compilation => { + compilation.mainTemplate.hooks.requireEnsure.tap( + "NextJsSSRImport", + (code, chunk) => { + // This is the block that fixes https://github.com/vercel/next.js/issues/22581 + if (!chunk.name) { + return; + } + + // Update to load chunks from our custom chunks directory + const outputPath = resolve("/"); + const pagePath = join("/", dirname(chunk.name)); + const relativePathToBaseDir = relative(pagePath, outputPath); + // Make sure even in windows, the path looks like in unix + // Node.js require system will convert it accordingly + const relativePathToBaseDirNormalized = relativePathToBaseDir.replace( + /\\/g, + "/" + ); + return code + .replace( + 'require("./"', + `require("${relativePathToBaseDirNormalized}/"` + ) + .replace( + "readFile(join(__dirname", + `readFile(join(__dirname, "${relativePathToBaseDirNormalized}"` + ); + } + ); + }); + }; } const isWebpack5 = true; @@ -62,6 +88,14 @@ const config = { }); config.plugins.push(new ProvidePlugin({ React: "react" })); } + + const ssrPlugin = config.plugins.find( + plugin => plugin instanceof SSRPlugin + ); + + if (ssrPlugin) { + patchSsrPlugin(ssrPlugin); + } return config; }, async redirects() { From 6d8e3f57f533510a3453079f4d9ad2d54905b4e3 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Thu, 15 Jul 2021 22:35:00 +0100 Subject: [PATCH 11/13] WIP --- src/common/Eval.mjs | 78 ++++++++++++------------------------ src/common/Eval.res | 83 +++++++++++++++++---------------------- src/common/EvalWorker.mjs | 81 +++++++++++++++++++++++++++++++++++--- src/common/EvalWorker.res | 50 +++++++++++++++++++++-- 4 files changed, 185 insertions(+), 107 deletions(-) diff --git a/src/common/Eval.mjs b/src/common/Eval.mjs index 85b1bbb77..2f5f85784 100644 --- a/src/common/Eval.mjs +++ b/src/common/Eval.mjs @@ -18,25 +18,6 @@ var Config = { var EvalWorker = $$Worker.Make(Config); -function workerMessageToAction(message) { - var message$1 = message.result; - var forCode = message.forCode; - if (message$1.TAG === /* Ok */0) { - return { - TAG: 1, - forCode: forCode, - [Symbol.for("name")]: "Success" - }; - } else { - return { - TAG: 2, - forCode: forCode, - message: message$1._0, - [Symbol.for("name")]: "Exception" - }; - } -} - function reducer(state, action) { if (typeof state === "number") { if (action.TAG === /* Evaluate */0) { @@ -68,21 +49,21 @@ function reducer(state, action) { return state; } case /* Exception */2 : - if (action.forCode === code) { - return { - TAG: 2, - logs: logs.concat([action.message]), - [Symbol.for("name")]: "Error" - }; - } else { + if (action.forCode !== code) { return state; } + var message = action.exn.message; + return { + TAG: 2, + logs: logs.concat([message !== undefined ? message : ""]), + [Symbol.for("name")]: "Error" + }; case /* Log */3 : if (action.forCode === code) { return { TAG: 0, code: code, - logs: logs.concat([action.message]), + logs: logs.concat(action.logArgs), [Symbol.for("name")]: "Evaluating" }; } else { @@ -119,10 +100,13 @@ function reducer(state, action) { function useEval(param) { var match = React.useReducer(reducer, /* Idle */0); var dispatch = match[1]; - var state = match[0]; var workerRef = React.useRef(undefined); React.useEffect((function () { - workerRef.current = Caml_option.some(Curry._1(EvalWorker.make, undefined)); + var worker = Curry._1(EvalWorker.make, undefined); + workerRef.current = Caml_option.some(worker); + Curry._2(EvalWorker.App.onMessage, worker, (function (message) { + return Curry._1(dispatch, message.data.payload); + })); return (function (param) { Belt_Option.map(workerRef.current, (function (worker) { return Curry._1(EvalWorker.App.terminate, worker); @@ -130,30 +114,21 @@ function useEval(param) { }); }), []); - React.useEffect((function () { - var maybeWorker = workerRef.current; - if (typeof state !== "number" && state.TAG === /* Evaluating */0) { - var code = state.code; - Belt_Option.forEach(maybeWorker, (function (worker) { - return Curry._2(EvalWorker.App.postMessage, worker, { - source: source, - payload: { - _0: code, - [Symbol.for("name")]: "EvalMessage" - } - }); - })); - } - - }), [state]); return [ - state, + match[0], (function (code) { - return Curry._1(dispatch, { - TAG: 0, - _0: code, - [Symbol.for("name")]: "Evaluate" - }); + var evaluateAction = { + TAG: 0, + _0: code, + [Symbol.for("name")]: "Evaluate" + }; + Curry._1(dispatch, evaluateAction); + return Belt_Option.forEach(workerRef.current, (function (worker) { + return Curry._2(EvalWorker.App.postMessage, worker, { + source: source, + payload: evaluateAction + }); + })); }) ]; } @@ -162,7 +137,6 @@ export { source , Config , EvalWorker , - workerMessageToAction , reducer , useEval , diff --git a/src/common/Eval.res b/src/common/Eval.res index c96ad30d1..6f6c954f5 100644 --- a/src/common/Eval.res +++ b/src/common/Eval.res @@ -1,48 +1,43 @@ -type evalResult = result - @val external importMetaUrl: string = "import.meta.url" -type workerMessage = ResultMessage({forCode: string, result: evalResult}) -type appMessage = EvalMessage(string) +type state = + | Idle + | Evaluating({code: string, logs: array}) + | Evaluated({logs: array}) + | Error({logs: array}) +type action = + | Evaluate(string) + | Success({forCode: string}) + | Exception({forCode: string, exn: Js.Exn.t}) + | Log({forCode: string, logArgs: array}) + let source = "EvalSource" module Config = { - // TODO: Worker should send two events: LogMessage and ExceptionMessage - type fromWorker = {source: string, payload: workerMessage} - type fromApp = {source: string, payload: appMessage} + type fromWorker = {source: string, payload: action} + type fromApp = {source: string, payload: action} let make = () => %raw(`new Worker(new URL("./EvalWorker.mjs", import.meta.url))`) } module EvalWorker = Worker.Make(Config) -type state = - | Idle - | Evaluating({code: string, logs: array}) - | Evaluated({logs: array}) - | Error({logs: array}) -type action = - | Evaluate(string) - | Success({forCode: string}) - | Exception({forCode: string, message: string}) - | Log({forCode: string, message: string}) - -let workerMessageToAction = message => - switch message { - | ResultMessage({forCode, result: Ok()}) => Success({forCode: forCode}) - | ResultMessage({forCode, result: Error(message)}) => - Exception({forCode: forCode, message: message}) - } - let reducer = (state, action) => switch (state, action) { | (Idle, Evaluate(code)) => Evaluating({code: code, logs: []}) | (Evaluating({code, logs}), Success({forCode})) if forCode === code => Evaluated({logs: logs}) - | (Evaluating({code, logs}), Exception({forCode, message})) if forCode === code => - Error({logs: logs->Js.Array2.concat([message])}) - | (Evaluating({code, logs}), Log({forCode, message})) if forCode === code => + | (Evaluating({code, logs}), Exception({forCode, exn})) if forCode === code => + Error({ + logs: logs->Js.Array2.concat([ + switch exn->Js.Exn.message { + | Some(message) => message->Js.Json.string + | None => ""->Js.Json.string + }, + ]), + }) + | (Evaluating({code, logs}), Log({forCode, logArgs})) if forCode === code => Evaluating({ code: code, - logs: logs->Js.Array2.concat([message]), + logs: logs->Js.Array2.concat(logArgs), }) | (Error(_), Evaluate(code)) => Evaluating({code: code, logs: []}) | (Evaluated(_), Evaluate(code)) => Evaluating({code: code, logs: []}) @@ -54,28 +49,24 @@ let useEval = () => { let workerRef = React.useRef(None) React.useEffect1(() => { - workerRef.current = Some(EvalWorker.make()) + let worker = EvalWorker.make() + workerRef.current = Some(worker) + + worker->EvalWorker.App.onMessage(message => dispatch(message["data"].payload)) Some( () => workerRef.current->Belt.Option.map(worker => worker->EvalWorker.App.terminate)->ignore, ) }, []) - React.useEffect1(() => { - let maybeWorker = workerRef.current - switch state { - | Evaluating({code}) => - maybeWorker - ->Belt.Option.forEach(worker => - // TODO: Either posting EvalMessage needs to be idempotent or we need a way of not posting this message when we are already in the Evaluating state - worker->EvalWorker.App.postMessage({source: source, payload: EvalMessage(code)}) + ( + state, + code => { + let evaluateAction = Evaluate(code) + evaluateAction->dispatch + workerRef.current->Belt.Option.forEach(worker => + worker->EvalWorker.App.postMessage({source: source, payload: evaluateAction}) ) - ->ignore - | _ => () - } - - None - }, [state]) - - (state, code => Evaluate(code)->dispatch) + }, + ) } diff --git a/src/common/EvalWorker.mjs b/src/common/EvalWorker.mjs index 824f1b59c..6e24ea06b 100644 --- a/src/common/EvalWorker.mjs +++ b/src/common/EvalWorker.mjs @@ -4,24 +4,93 @@ import * as Eval from "./Eval.mjs"; import * as Curry from "rescript/lib/es6/curry.js"; function ignoreOtherMessages(message, f) { - console.log("ffffff", message); + console.log("Worker received message: ", message.data); if (message.data.source === Eval.source) { return Curry._1(f, message.data); } } +var evaluateCode = (function (code, handlers) { + console.log("Evaluating code...") + const originalConsole = console; + // TODO: For some reason this isn't capturing logs... + let console = { + ...originalConsole, + log: (...args) => handlers.onConsoleLog(args), + warn: (...args) => handlers.onConsoleWarn(args), + error: (...args) => handlers.onConsoleError(args) + }; + try { + eval(code); + handlers.onDone() + } catch (exn) { + handlers.onException(exn); + } + }); + +function dispatch(action) { + return Curry._1(Eval.EvalWorker.$$Worker.postMessage, { + source: Eval.source, + payload: action + }); +} + Curry._2(Eval.EvalWorker.$$Worker.onMessage, Eval.EvalWorker.$$Worker.self, (function (msg) { - return ignoreOtherMessages(msg, (function (prim) { - console.log(prim); - + return ignoreOtherMessages(msg, (function (fromApp) { + var code = fromApp.payload; + if (code.TAG !== /* Evaluate */0) { + return ; + } + var code$1 = code._0; + return evaluateCode(code$1, { + onConsoleLog: (function (logArgs) { + return dispatch({ + TAG: 3, + forCode: code$1, + logArgs: logArgs, + [Symbol.for("name")]: "Log" + }); + }), + onConsoleWarn: (function (logArgs) { + return dispatch({ + TAG: 3, + forCode: code$1, + logArgs: logArgs, + [Symbol.for("name")]: "Log" + }); + }), + onConsoleError: (function (logArgs) { + return dispatch({ + TAG: 3, + forCode: code$1, + logArgs: logArgs, + [Symbol.for("name")]: "Log" + }); + }), + onException: (function (exn) { + return dispatch({ + TAG: 2, + forCode: code$1, + exn: exn, + [Symbol.for("name")]: "Exception" + }); + }), + onDone: (function (param) { + return dispatch({ + TAG: 1, + forCode: code$1, + [Symbol.for("name")]: "Success" + }); + }) + }); })); })); -console.log("hello"); - export { ignoreOtherMessages , + evaluateCode , + dispatch , } /* Not a pure module */ diff --git a/src/common/EvalWorker.res b/src/common/EvalWorker.res index f18e842fd..f8b308e2a 100644 --- a/src/common/EvalWorker.res +++ b/src/common/EvalWorker.res @@ -1,13 +1,57 @@ // Required because workers may receive messages intended for other workers eg. react dev tools // See https://github.com/facebook/react-devtools/issues/812 let ignoreOtherMessages = (message: {"data": Eval.Config.fromApp}, f) => { - Js.log2("ffffff", message) + Js.log2("Worker received message: ", message["data"]) if message["data"].source == Eval.source { f(message["data"]) } } +type evaluationHandlers = { + onConsoleLog: array => unit, + onConsoleWarn: array => unit, + onConsoleError: array => unit, + onException: Js.Exn.t => unit, + onDone: unit => unit, +} + +let evaluateCode: (string, evaluationHandlers) => unit = %raw(` + function (code, handlers) { + console.log("Evaluating code...") + const originalConsole = console; + // TODO: For some reason this isn't capturing logs... + let console = { + ...originalConsole, + log: (...args) => handlers.onConsoleLog(args), + warn: (...args) => handlers.onConsoleWarn(args), + error: (...args) => handlers.onConsoleError(args) + }; + try { + eval(code); + handlers.onDone() + } catch (exn) { + handlers.onException(exn); + } + } +`) + +let dispatch = action => Eval.EvalWorker.Worker.postMessage({source: Eval.source, payload: action}) + Eval.EvalWorker.Worker.self->Eval.EvalWorker.Worker.onMessage(msg => - msg->ignoreOtherMessages(Js.log) + msg->ignoreOtherMessages(fromApp => + switch fromApp.payload { + | Eval.Evaluate(code) => + evaluateCode( + code, + { + onConsoleLog: logArgs => dispatch(Eval.Log({forCode: code, logArgs: logArgs})), + onConsoleWarn: logArgs => dispatch(Eval.Log({forCode: code, logArgs: logArgs})), + onConsoleError: logArgs => dispatch(Eval.Log({forCode: code, logArgs: logArgs})), + onException: exn => dispatch(Eval.Exception({forCode: code, exn: exn})), + onDone: () => dispatch(Eval.Success({forCode: code})), + }, + ) + | _ => () + } + ) ) -Js.log("hello") From 481072eb680ba0f58fa3dff296d8491ce44b2e32 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Thu, 15 Jul 2021 22:40:41 +0100 Subject: [PATCH 12/13] WIP --- src/common/Eval.mjs | 10 +--- src/common/Eval.res | 10 ++-- src/common/EvalWorker.mjs | 110 ++++++++++++++++---------------------- src/common/EvalWorker.res | 41 ++++++-------- 4 files changed, 68 insertions(+), 103 deletions(-) diff --git a/src/common/Eval.mjs b/src/common/Eval.mjs index 2f5f85784..0b9b30115 100644 --- a/src/common/Eval.mjs +++ b/src/common/Eval.mjs @@ -6,8 +6,6 @@ import * as $$Worker from "../bindings/Worker.mjs"; import * as Belt_Option from "rescript/lib/es6/belt_Option.js"; import * as Caml_option from "rescript/lib/es6/caml_option.js"; -var source = "EvalSource"; - function make(param) { return (new Worker(new URL("./EvalWorker.mjs", import.meta.url))); } @@ -105,7 +103,7 @@ function useEval(param) { var worker = Curry._1(EvalWorker.make, undefined); workerRef.current = Caml_option.some(worker); Curry._2(EvalWorker.App.onMessage, worker, (function (message) { - return Curry._1(dispatch, message.data.payload); + return Curry._1(dispatch, message.data); })); return (function (param) { Belt_Option.map(workerRef.current, (function (worker) { @@ -124,17 +122,13 @@ function useEval(param) { }; Curry._1(dispatch, evaluateAction); return Belt_Option.forEach(workerRef.current, (function (worker) { - return Curry._2(EvalWorker.App.postMessage, worker, { - source: source, - payload: evaluateAction - }); + return Curry._2(EvalWorker.App.postMessage, worker, evaluateAction); })); }) ]; } export { - source , Config , EvalWorker , reducer , diff --git a/src/common/Eval.res b/src/common/Eval.res index 6f6c954f5..7ac5c145a 100644 --- a/src/common/Eval.res +++ b/src/common/Eval.res @@ -11,11 +11,9 @@ type action = | Exception({forCode: string, exn: Js.Exn.t}) | Log({forCode: string, logArgs: array}) -let source = "EvalSource" - module Config = { - type fromWorker = {source: string, payload: action} - type fromApp = {source: string, payload: action} + type fromWorker = action + type fromApp = action let make = () => %raw(`new Worker(new URL("./EvalWorker.mjs", import.meta.url))`) } @@ -52,7 +50,7 @@ let useEval = () => { let worker = EvalWorker.make() workerRef.current = Some(worker) - worker->EvalWorker.App.onMessage(message => dispatch(message["data"].payload)) + worker->EvalWorker.App.onMessage(message => dispatch(message["data"])) Some( () => workerRef.current->Belt.Option.map(worker => worker->EvalWorker.App.terminate)->ignore, @@ -65,7 +63,7 @@ let useEval = () => { let evaluateAction = Evaluate(code) evaluateAction->dispatch workerRef.current->Belt.Option.forEach(worker => - worker->EvalWorker.App.postMessage({source: source, payload: evaluateAction}) + worker->EvalWorker.App.postMessage(evaluateAction) ) }, ) diff --git a/src/common/EvalWorker.mjs b/src/common/EvalWorker.mjs index 6e24ea06b..4b1ece84c 100644 --- a/src/common/EvalWorker.mjs +++ b/src/common/EvalWorker.mjs @@ -3,14 +3,6 @@ import * as Eval from "./Eval.mjs"; import * as Curry from "rescript/lib/es6/curry.js"; -function ignoreOtherMessages(message, f) { - console.log("Worker received message: ", message.data); - if (message.data.source === Eval.source) { - return Curry._1(f, message.data); - } - -} - var evaluateCode = (function (code, handlers) { console.log("Evaluating code...") const originalConsole = console; @@ -29,66 +21,58 @@ var evaluateCode = (function (code, handlers) { } }); -function dispatch(action) { - return Curry._1(Eval.EvalWorker.$$Worker.postMessage, { - source: Eval.source, - payload: action - }); -} +var dispatch = Eval.EvalWorker.$$Worker.postMessage; Curry._2(Eval.EvalWorker.$$Worker.onMessage, Eval.EvalWorker.$$Worker.self, (function (msg) { - return ignoreOtherMessages(msg, (function (fromApp) { - var code = fromApp.payload; - if (code.TAG !== /* Evaluate */0) { - return ; - } - var code$1 = code._0; - return evaluateCode(code$1, { - onConsoleLog: (function (logArgs) { - return dispatch({ - TAG: 3, - forCode: code$1, - logArgs: logArgs, - [Symbol.for("name")]: "Log" - }); - }), - onConsoleWarn: (function (logArgs) { - return dispatch({ - TAG: 3, - forCode: code$1, - logArgs: logArgs, - [Symbol.for("name")]: "Log" - }); - }), - onConsoleError: (function (logArgs) { - return dispatch({ - TAG: 3, - forCode: code$1, - logArgs: logArgs, - [Symbol.for("name")]: "Log" - }); - }), - onException: (function (exn) { - return dispatch({ - TAG: 2, - forCode: code$1, - exn: exn, - [Symbol.for("name")]: "Exception" - }); - }), - onDone: (function (param) { - return dispatch({ - TAG: 1, - forCode: code$1, - [Symbol.for("name")]: "Success" - }); - }) - }); - })); + var code = msg.data; + if (code.TAG !== /* Evaluate */0) { + return ; + } + var code$1 = code._0; + return evaluateCode(code$1, { + onConsoleLog: (function (logArgs) { + return Curry._1(dispatch, { + TAG: 3, + forCode: code$1, + logArgs: logArgs, + [Symbol.for("name")]: "Log" + }); + }), + onConsoleWarn: (function (logArgs) { + return Curry._1(dispatch, { + TAG: 3, + forCode: code$1, + logArgs: logArgs, + [Symbol.for("name")]: "Log" + }); + }), + onConsoleError: (function (logArgs) { + return Curry._1(dispatch, { + TAG: 3, + forCode: code$1, + logArgs: logArgs, + [Symbol.for("name")]: "Log" + }); + }), + onException: (function (exn) { + return Curry._1(dispatch, { + TAG: 2, + forCode: code$1, + exn: exn, + [Symbol.for("name")]: "Exception" + }); + }), + onDone: (function (param) { + return Curry._1(dispatch, { + TAG: 1, + forCode: code$1, + [Symbol.for("name")]: "Success" + }); + }) + }); })); export { - ignoreOtherMessages , evaluateCode , dispatch , diff --git a/src/common/EvalWorker.res b/src/common/EvalWorker.res index f8b308e2a..ac7a314a8 100644 --- a/src/common/EvalWorker.res +++ b/src/common/EvalWorker.res @@ -1,12 +1,3 @@ -// Required because workers may receive messages intended for other workers eg. react dev tools -// See https://github.com/facebook/react-devtools/issues/812 -let ignoreOtherMessages = (message: {"data": Eval.Config.fromApp}, f) => { - Js.log2("Worker received message: ", message["data"]) - if message["data"].source == Eval.source { - f(message["data"]) - } -} - type evaluationHandlers = { onConsoleLog: array => unit, onConsoleWarn: array => unit, @@ -35,23 +26,21 @@ let evaluateCode: (string, evaluationHandlers) => unit = %raw(` } `) -let dispatch = action => Eval.EvalWorker.Worker.postMessage({source: Eval.source, payload: action}) +let dispatch = Eval.EvalWorker.Worker.postMessage Eval.EvalWorker.Worker.self->Eval.EvalWorker.Worker.onMessage(msg => - msg->ignoreOtherMessages(fromApp => - switch fromApp.payload { - | Eval.Evaluate(code) => - evaluateCode( - code, - { - onConsoleLog: logArgs => dispatch(Eval.Log({forCode: code, logArgs: logArgs})), - onConsoleWarn: logArgs => dispatch(Eval.Log({forCode: code, logArgs: logArgs})), - onConsoleError: logArgs => dispatch(Eval.Log({forCode: code, logArgs: logArgs})), - onException: exn => dispatch(Eval.Exception({forCode: code, exn: exn})), - onDone: () => dispatch(Eval.Success({forCode: code})), - }, - ) - | _ => () - } - ) + switch msg["data"] { + | Eval.Evaluate(code) => + evaluateCode( + code, + { + onConsoleLog: logArgs => dispatch(Eval.Log({forCode: code, logArgs: logArgs})), + onConsoleWarn: logArgs => dispatch(Eval.Log({forCode: code, logArgs: logArgs})), + onConsoleError: logArgs => dispatch(Eval.Log({forCode: code, logArgs: logArgs})), + onException: exn => dispatch(Eval.Exception({forCode: code, exn: exn})), + onDone: () => dispatch(Eval.Success({forCode: code})), + }, + ) + | _ => () + } ) From c41ed98fdb6aa8fdc361a8677600352df4710e21 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Fri, 16 Jul 2021 13:58:09 +0100 Subject: [PATCH 13/13] Working logs pane --- src/Playground.mjs | 93 ++++++++++++++++++++++++++------------- src/Playground.res | 30 ++++++++++++- src/common/Eval.mjs | 4 +- src/common/Eval.res | 3 +- src/common/EvalWorker.mjs | 18 ++++---- src/common/EvalWorker.res | 18 ++++---- 6 files changed, 114 insertions(+), 52 deletions(-) diff --git a/src/Playground.mjs b/src/Playground.mjs index e53ac229c..62a748c43 100644 --- a/src/Playground.mjs +++ b/src/Playground.mjs @@ -1423,6 +1423,15 @@ function Playground$Settings(Props) { })))); } +function Playground$Logs(Props) { + var logs = Props.logs; + return React.createElement("ul", undefined, logs.map(function (log, i) { + return React.createElement("li", { + key: i.toString() + }, JSON.stringify(log)); + })); +} + function Playground$ControlPanel$Button(Props) { var children = Props.children; var onClick = Props.onClick; @@ -1500,9 +1509,8 @@ function Playground$ControlPanel(Props) { var state = Props.state; var dispatch = Props.dispatch; var editorCode = Props.editorCode; + var dispatchEval = Props.dispatchEval; var router = Next.Router.useRouter(undefined); - var match = Eval.useEval(undefined); - var dispatchEval = match[1]; var children; var exit = 0; if (typeof state === "number") { @@ -1621,6 +1629,7 @@ function Playground$OutputPanel(Props) { var compilerDispatch = Props.compilerDispatch; var compilerState = Props.compilerState; var editorCode = Props.editorCode; + var evalState = Props.evalState; var prevState = React.useRef(undefined); var prev = prevState.current; var cmCode; @@ -1798,6 +1807,21 @@ function Playground$OutputPanel(Props) { selected = 0; } } + var logs; + if (typeof evalState === "number") { + logs = []; + } else { + switch (evalState.TAG | 0) { + case /* Evaluating */0 : + logs = []; + break; + case /* Evaluated */1 : + case /* Error */2 : + logs = evalState.logs; + break; + + } + } prevSelected.current = selected; var tabs = [ { @@ -1812,6 +1836,12 @@ function Playground$OutputPanel(Props) { } }, errorPane) }, + { + title: "Logs", + content: React.createElement(Playground$Logs, { + logs: logs + }) + }, { title: "Settings", content: React.createElement("div", { @@ -1837,12 +1867,13 @@ var initialResContent = "module Button = {\n @react.component\n let make = (~c function Playground$default(Props) { var router = Next.Router.useRouter(undefined); - var match = Js_dict.get(router.query, "ext"); - var initialLang = match === "re" ? /* Reason */0 : /* Res */2; - var match$1 = Js_dict.get(router.query, "code"); + var match = Eval.useEval(undefined); + var match$1 = Js_dict.get(router.query, "ext"); + var initialLang = match$1 === "re" ? /* Reason */0 : /* Res */2; + var match$2 = Js_dict.get(router.query, "code"); var initialContent; - if (match$1 !== undefined) { - initialContent = LzString.decompressFromEncodedURIComponent(match$1); + if (match$2 !== undefined) { + initialContent = LzString.decompressFromEncodedURIComponent(match$2); } else { switch (initialLang) { case /* Reason */0 : @@ -1855,11 +1886,11 @@ function Playground$default(Props) { } } - var match$2 = React.useState(function () { + var match$3 = React.useState(function () { return 0; }); - var setActionCount = match$2[1]; - var actionCount = match$2[0]; + var setActionCount = match$3[1]; + var actionCount = match$3[0]; var onAction = function (param) { return Curry._1(setActionCount, (function (prev) { if (prev > 1000000) { @@ -1869,32 +1900,32 @@ function Playground$default(Props) { } })); }; - var match$3 = CompilerManagerHook.useCompilerManager(initialLang, onAction, undefined); - var compilerDispatch = match$3[1]; - var compilerState = match$3[0]; + var match$4 = CompilerManagerHook.useCompilerManager(initialLang, onAction, undefined); + var compilerDispatch = match$4[1]; + var compilerState = match$4[0]; var overlayState = React.useState(function () { return false; }); var windowWidth = CodeMirror.useWindowWidth(undefined); - var match$4 = React.useState(function () { + var match$5 = React.useState(function () { }); - var setFocusedRowCol = match$4[1]; + var setFocusedRowCol = match$5[1]; var editorCode = React.useRef(initialContent); if (typeof compilerState !== "number" && compilerState.TAG === /* Ready */2) { var ready = compilerState._0; - var match$5 = ready.result; - if (typeof match$5 === "number") { + var match$6 = ready.result; + if (typeof match$6 === "number") { Curry._1(compilerDispatch, { TAG: 3, _0: ready.targetLang, _1: editorCode.current, [Symbol.for("name")]: "CompileCode" }); - } else if (match$5.TAG === /* Conv */0) { - var match$6 = match$5._0; - if (match$6.TAG === /* Success */0) { - editorCode.current = match$6._0.code; + } else if (match$6.TAG === /* Conv */0) { + var match$7 = match$6._0; + if (match$7.TAG === /* Success */0) { + editorCode.current = match$7._0.code; } } @@ -1927,8 +1958,8 @@ function Playground$default(Props) { if (typeof result === "number") { cmErrors = []; } else if (result.TAG === /* Conv */0) { - var match$7 = result._0; - cmErrors = match$7.TAG === /* Fail */1 ? match$7.details.map(function (param) { + var match$8 = result._0; + cmErrors = match$8.TAG === /* Fail */1 ? match$8.details.map(function (param) { return locMsgToCmError("Error", param); }) : []; } else { @@ -1970,14 +2001,14 @@ function Playground$default(Props) { if (typeof compilerState === "number") { cmHoverHints = []; } else if (compilerState.TAG === /* Ready */2) { - var match$8 = compilerState._0.result; - if (typeof match$8 === "number") { + var match$9 = compilerState._0.result; + if (typeof match$9 === "number") { cmHoverHints = []; - } else if (match$8.TAG === /* Conv */0) { + } else if (match$9.TAG === /* Conv */0) { cmHoverHints = []; } else { - var match$9 = match$8._0; - cmHoverHints = match$9.TAG === /* Success */1 ? match$9._0.type_hints.map(function (hint) { + var match$10 = match$9._0; + cmHoverHints = match$10.TAG === /* Success */1 ? match$10._0.type_hints.map(function (hint) { var start = hint._0.start; var end = hint._0.end; return { @@ -2045,7 +2076,8 @@ function Playground$default(Props) { actionIndicatorKey: String(actionCount), state: compilerState, dispatch: compilerDispatch, - editorCode: editorCode + editorCode: editorCode, + dispatchEval: match[1] }), React.createElement(CodeMirror.make, { errors: cmErrors, hoverHints: cmHoverHints, @@ -2086,7 +2118,8 @@ function Playground$default(Props) { }, React.createElement(Playground$OutputPanel, { compilerDispatch: compilerDispatch, compilerState: compilerState, - editorCode: editorCode + editorCode: editorCode, + evalState: match[0] }), React.createElement("div", { className: "absolute bottom-0 w-full" }, React.createElement(Playground$Statusbar, { diff --git a/src/Playground.res b/src/Playground.res index 18dde9fcf..4d2325030 100644 --- a/src/Playground.res +++ b/src/Playground.res @@ -1101,6 +1101,18 @@ module Settings = { } } +module Logs = { + @react.component + let make = (~logs) => +
    + {logs + ->Js.Array2.mapi((log, i) => +
  • Js.Int.toString}> {log->Js.Json.stringify->React.string}
  • + ) + ->React.array} +
+} + module ControlPanel = { module Button = { @react.component @@ -1182,9 +1194,9 @@ module ControlPanel = { ~state: CompilerManagerHook.state, ~dispatch: CompilerManagerHook.action => unit, ~editorCode: React.ref, + ~dispatchEval: string => unit, ) => { let router = Next.Router.useRouter() - let (evalState, dispatchEval) = Eval.useEval() let children = switch state { | Init => React.string("Initializing...") | SwitchingCompiler(_, _, _) => React.string("Switching Compiler...") @@ -1277,6 +1289,7 @@ module OutputPanel = { ~compilerDispatch, ~compilerState: CompilerManagerHook.state, ~editorCode: React.ref, + ~evalState: Eval.state, ) => { /* We need the prevState to understand different @@ -1385,6 +1398,13 @@ module OutputPanel = { | _ => 0 } + let logs = switch evalState { + | Eval.Error({logs}) + | Eval.Evaluated({logs}) => logs + | Eval.Evaluating(_) + | Idle => [] + } + prevSelected.current = selected let tabs = [ @@ -1393,6 +1413,10 @@ module OutputPanel = { title: "Problems", content:
errorPane
, }, + { + title: "Logs", + content: , + }, { title: "Settings", content:
settingsPane
, @@ -1436,6 +1460,7 @@ let initialReContent = j`Js.log("Hello Reason 3.6!");` @react.component let default = () => { let router = Next.Router.useRouter() + let (evalState, dispatchEval) = Eval.useEval() let initialLang = switch Js.Dict.get(router.query, "ext") { | Some("re") => Api.Lang.Reason @@ -1582,6 +1607,7 @@ let default = () => { state=compilerState dispatch=compilerDispatch editorCode + dispatchEval /> {
1024 ? "56rem" : "100%", ())}> - +
}) | Evaluated({logs: array}) - | Error({logs: array}) + | Error({logs: array, exn: Js.Exn.t}) type action = | Evaluate(string) | Success({forCode: string}) @@ -31,6 +31,7 @@ let reducer = (state, action) => | None => ""->Js.Json.string }, ]), + exn: exn, }) | (Evaluating({code, logs}), Log({forCode, logArgs})) if forCode === code => Evaluating({ diff --git a/src/common/EvalWorker.mjs b/src/common/EvalWorker.mjs index 4b1ece84c..69a64c1a0 100644 --- a/src/common/EvalWorker.mjs +++ b/src/common/EvalWorker.mjs @@ -4,21 +4,21 @@ import * as Eval from "./Eval.mjs"; import * as Curry from "rescript/lib/es6/curry.js"; var evaluateCode = (function (code, handlers) { - console.log("Evaluating code...") - const originalConsole = console; - // TODO: For some reason this isn't capturing logs... - let console = { - ...originalConsole, - log: (...args) => handlers.onConsoleLog(args), - warn: (...args) => handlers.onConsoleWarn(args), - error: (...args) => handlers.onConsoleError(args) - }; + const rawConsole = console; try { + // TODO: For some reason this isn't capturing logs... + const replace = { + log: function (...args) { handlers.onConsoleLog(args) }, + warn: function (...args) { handlers.onConsoleWarn(args) }, + error: function (...args) { handlers.onConsoleError(args) } + }; + self.console = Object.assign({}, rawConsole, replace); eval(code); handlers.onDone() } catch (exn) { handlers.onException(exn); } + self.console = rawConsole }); var dispatch = Eval.EvalWorker.$$Worker.postMessage; diff --git a/src/common/EvalWorker.res b/src/common/EvalWorker.res index ac7a314a8..84adb6ce0 100644 --- a/src/common/EvalWorker.res +++ b/src/common/EvalWorker.res @@ -8,21 +8,21 @@ type evaluationHandlers = { let evaluateCode: (string, evaluationHandlers) => unit = %raw(` function (code, handlers) { - console.log("Evaluating code...") - const originalConsole = console; - // TODO: For some reason this isn't capturing logs... - let console = { - ...originalConsole, - log: (...args) => handlers.onConsoleLog(args), - warn: (...args) => handlers.onConsoleWarn(args), - error: (...args) => handlers.onConsoleError(args) - }; + const rawConsole = console; try { + // TODO: For some reason this isn't capturing logs... + const replace = { + log: function (...args) { handlers.onConsoleLog(args) }, + warn: function (...args) { handlers.onConsoleWarn(args) }, + error: function (...args) { handlers.onConsoleError(args) } + }; + self.console = Object.assign({}, rawConsole, replace); eval(code); handlers.onDone() } catch (exn) { handlers.onException(exn); } + self.console = rawConsole } `)