diff --git a/package.json b/package.json index 271070c67..ef6303763 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ }, "scripts": { "dev": "next", + "res:watch": "rescript build -w", "build": "rescript && npm run update-index && next build", "test": "node scripts/test-examples.mjs && node scripts/test-hrefs.mjs", "reanalyze": "reanalyze -all-cmt .", diff --git a/src/RenderPanel.res b/src/RenderPanel.res index afd14106d..7fa692790 100644 --- a/src/RenderPanel.res +++ b/src/RenderPanel.res @@ -10,14 +10,20 @@ let make = (~compilerState: CompilerManagerHook.state, ~clearLogs, ~runOutput) = React.useEffect(() => { if runOutput { switch compilerState { - | CompilerManagerHook.Ready({result: Comp(Success({js_code}))}) => + | CompilerManagerHook.Ready({selected, result: Comp(Success({js_code}))}) => clearLogs() open Babel let ast = Parser.parse(js_code, {sourceType: "module"}) - let {entryPointExists, code} = PlaygroundValidator.validate(ast) + let {entryPointExists, code, imports} = PlaygroundValidator.validate(ast) + let imports = imports->Dict.mapValues(path => { + let filename = path->String.sliceToEnd(~start=9) // the part after "./stdlib/" + CompilerManagerHook.CdnMeta.getStdlibRuntimeUrl(selected.id, filename) + }) - entryPointExists ? code->wrapReactApp->EvalIFrame.sendOutput : EvalIFrame.sendOutput(code) + entryPointExists + ? code->wrapReactApp->EvalIFrame.sendOutput(imports) + : EvalIFrame.sendOutput(code, imports) setValidReact(_ => entryPointExists) | _ => () } diff --git a/src/bindings/Babel.res b/src/bindings/Babel.res index 2726555e4..93f120809 100644 --- a/src/bindings/Babel.res +++ b/src/bindings/Babel.res @@ -10,14 +10,48 @@ module Ast = { @tag("type") type expression = ObjectExpression({properties: array}) - type variableDeclarator = { - @as("type") type_: string, - id: lval, - init?: Null.t, + module VariableDeclarator = { + @tag("type") + type t = VariableDeclarator({id: lval, init?: Null.t}) } + module Specifier = { + @tag("type") + type t = + | ImportSpecifier({local: lval}) + | ImportDefaultSpecifier({local: lval}) + | ImportNamespaceSpecifier({local: lval}) + } + + module StringLiteral = { + @tag("type") + type t = StringLiteral({value: string}) + } + + module VariableDeclaration = { + @tag("type") + type t = VariableDeclaration({kind: string, declarations: array}) + } + + module ImportDeclaration = { + @tag("type") + type t = ImportDeclaration({specifiers: array, source: StringLiteral.t}) + } + + module Identifier = { + @tag("type") + type t = Identifier({mutable name: string}) + } + @tag("type") - type node = VariableDeclaration({kind: string, declarations: array}) - type nodePath = {node: node} + type node = + | ...StringLiteral.t + | ...Specifier.t + | ...VariableDeclarator.t + | ...VariableDeclaration.t + | ...ImportDeclaration.t + | ...Identifier.t + + type nodePath<'nodeType> = {node: 'nodeType} } module Parser = { @@ -30,7 +64,7 @@ module Traverse = { } module Generator = { - @send external remove: Ast.nodePath => unit = "remove" + @send external remove: Ast.nodePath<'nodeType> => unit = "remove" type t = {code: string} @module("@babel/generator") external generator: Ast.t => t = "default" @@ -40,26 +74,42 @@ module PlaygroundValidator = { type validator = { entryPointExists: bool, code: string, + imports: Dict.t, } let validate = ast => { let entryPoint = ref(false) + let imports = Dict.make() let remove = nodePath => Generator.remove(nodePath) Traverse.traverse( ast, { - "ImportDeclaration": remove, + "ImportDeclaration": ( + { + node: ImportDeclaration({specifiers, source: StringLiteral({value: source})}), + } as nodePath: Ast.nodePath, + ) => { + if source->String.startsWith("./stdlib") { + switch specifiers { + | [ImportNamespaceSpecifier({local: Identifier({name})})] => + imports->Dict.set(name, source) + | _ => () + } + } + remove(nodePath) + }, "ExportNamedDeclaration": remove, - "VariableDeclaration": (nodePath: Ast.nodePath) => { - switch nodePath.node { - | VariableDeclaration({declarations}) if Array.length(declarations) > 0 => + "VariableDeclaration": ( + {node: VariableDeclaration({declarations})}: Ast.nodePath, + ) => { + if Array.length(declarations) > 0 { let firstDeclaration = Array.getUnsafe(declarations, 0) - switch (firstDeclaration.id, firstDeclaration.init) { - | (Identifier({name}), Some(init)) if name === "App" => - switch init->Null.toOption { - | Some(ObjectExpression({properties})) => + switch firstDeclaration { + | VariableDeclarator({id: Identifier({name}), init}) if name === "App" => + switch init { + | Value(ObjectExpression({properties})) => let foundEntryPoint = properties->Array.find(property => { switch property { | ObjectProperty({ @@ -74,12 +124,10 @@ module PlaygroundValidator = { } | _ => () } - | _ => () } }, }, ) - - {entryPointExists: entryPoint.contents, code: Generator.generator(ast).code} + {entryPointExists: entryPoint.contents, imports, code: Generator.generator(ast).code} } } diff --git a/src/bindings/Webapi.res b/src/bindings/Webapi.res index c25856ec2..55d5eecc8 100644 --- a/src/bindings/Webapi.res +++ b/src/bindings/Webapi.res @@ -27,6 +27,9 @@ module Element = { @send external postMessage: (contentWindow, string, ~targetOrigin: string=?) => unit = "postMessage" + @send + external postMessageAny: (contentWindow, 'a, ~targetOrigin: string=?) => unit = "postMessage" + module Style = { @scope("style") @set external width: (Dom.element, string) => unit = "width" @scope("style") @set external height: (Dom.element, string) => unit = "height" diff --git a/src/common/CompilerManagerHook.res b/src/common/CompilerManagerHook.res index 2a68805fa..4236657ef 100644 --- a/src/common/CompilerManagerHook.res +++ b/src/common/CompilerManagerHook.res @@ -40,6 +40,9 @@ module CdnMeta = { let getLibraryCmijUrl = (version, libraryName: string): string => `https://cdn.rescript-lang.org/${Semver.toString(version)}/${libraryName}/cmij.js` + + let getStdlibRuntimeUrl = (version, filename) => + `https://cdn.rescript-lang.org/${Semver.toString(version)}/compiler-builtins/stdlib/${filename}` } module FinalResult = { diff --git a/src/common/CompilerManagerHook.resi b/src/common/CompilerManagerHook.resi index c8c3b4b3e..5e9f8a826 100644 --- a/src/common/CompilerManagerHook.resi +++ b/src/common/CompilerManagerHook.resi @@ -27,6 +27,10 @@ type ready = { result: FinalResult.t, } +module CdnMeta: { + let getStdlibRuntimeUrl: (Semver.t, string) => string +} + type state = | Init | SetupFailed(string) diff --git a/src/common/EvalIFrame.res b/src/common/EvalIFrame.res index 99e3e5bd4..5736363c3 100644 --- a/src/common/EvalIFrame.res +++ b/src/common/EvalIFrame.res @@ -18,7 +18,6 @@ let srcDoc = `