Skip to content

Commit a71ae31

Browse files
authored
load stdlib runtime in playground (#964)
1 parent fc636af commit a71ae31

File tree

7 files changed

+99
-27
lines changed

7 files changed

+99
-27
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
},
5959
"scripts": {
6060
"dev": "next",
61+
"res:watch": "rescript build -w",
6162
"build": "rescript && npm run update-index && next build",
6263
"test": "node scripts/test-examples.mjs && node scripts/test-hrefs.mjs",
6364
"reanalyze": "reanalyze -all-cmt .",

src/RenderPanel.res

+9-3
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,20 @@ let make = (~compilerState: CompilerManagerHook.state, ~clearLogs, ~runOutput) =
1010
React.useEffect(() => {
1111
if runOutput {
1212
switch compilerState {
13-
| CompilerManagerHook.Ready({result: Comp(Success({js_code}))}) =>
13+
| CompilerManagerHook.Ready({selected, result: Comp(Success({js_code}))}) =>
1414
clearLogs()
1515
open Babel
1616

1717
let ast = Parser.parse(js_code, {sourceType: "module"})
18-
let {entryPointExists, code} = PlaygroundValidator.validate(ast)
18+
let {entryPointExists, code, imports} = PlaygroundValidator.validate(ast)
19+
let imports = imports->Dict.mapValues(path => {
20+
let filename = path->String.sliceToEnd(~start=9) // the part after "./stdlib/"
21+
CompilerManagerHook.CdnMeta.getStdlibRuntimeUrl(selected.id, filename)
22+
})
1923

20-
entryPointExists ? code->wrapReactApp->EvalIFrame.sendOutput : EvalIFrame.sendOutput(code)
24+
entryPointExists
25+
? code->wrapReactApp->EvalIFrame.sendOutput(imports)
26+
: EvalIFrame.sendOutput(code, imports)
2127
setValidReact(_ => entryPointExists)
2228
| _ => ()
2329
}

src/bindings/Babel.res

+66-18
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,48 @@ module Ast = {
1010
@tag("type")
1111
type expression = ObjectExpression({properties: array<objectProperties>})
1212

13-
type variableDeclarator = {
14-
@as("type") type_: string,
15-
id: lval,
16-
init?: Null.t<expression>,
13+
module VariableDeclarator = {
14+
@tag("type")
15+
type t = VariableDeclarator({id: lval, init?: Null.t<expression>})
1716
}
17+
module Specifier = {
18+
@tag("type")
19+
type t =
20+
| ImportSpecifier({local: lval})
21+
| ImportDefaultSpecifier({local: lval})
22+
| ImportNamespaceSpecifier({local: lval})
23+
}
24+
25+
module StringLiteral = {
26+
@tag("type")
27+
type t = StringLiteral({value: string})
28+
}
29+
30+
module VariableDeclaration = {
31+
@tag("type")
32+
type t = VariableDeclaration({kind: string, declarations: array<VariableDeclarator.t>})
33+
}
34+
35+
module ImportDeclaration = {
36+
@tag("type")
37+
type t = ImportDeclaration({specifiers: array<Specifier.t>, source: StringLiteral.t})
38+
}
39+
40+
module Identifier = {
41+
@tag("type")
42+
type t = Identifier({mutable name: string})
43+
}
44+
1845
@tag("type")
19-
type node = VariableDeclaration({kind: string, declarations: array<variableDeclarator>})
20-
type nodePath = {node: node}
46+
type node =
47+
| ...StringLiteral.t
48+
| ...Specifier.t
49+
| ...VariableDeclarator.t
50+
| ...VariableDeclaration.t
51+
| ...ImportDeclaration.t
52+
| ...Identifier.t
53+
54+
type nodePath<'nodeType> = {node: 'nodeType}
2155
}
2256

2357
module Parser = {
@@ -30,7 +64,7 @@ module Traverse = {
3064
}
3165

3266
module Generator = {
33-
@send external remove: Ast.nodePath => unit = "remove"
67+
@send external remove: Ast.nodePath<'nodeType> => unit = "remove"
3468

3569
type t = {code: string}
3670
@module("@babel/generator") external generator: Ast.t => t = "default"
@@ -40,26 +74,42 @@ module PlaygroundValidator = {
4074
type validator = {
4175
entryPointExists: bool,
4276
code: string,
77+
imports: Dict.t<string>,
4378
}
4479

4580
let validate = ast => {
4681
let entryPoint = ref(false)
82+
let imports = Dict.make()
4783

4884
let remove = nodePath => Generator.remove(nodePath)
4985
Traverse.traverse(
5086
ast,
5187
{
52-
"ImportDeclaration": remove,
88+
"ImportDeclaration": (
89+
{
90+
node: ImportDeclaration({specifiers, source: StringLiteral({value: source})}),
91+
} as nodePath: Ast.nodePath<Ast.ImportDeclaration.t>,
92+
) => {
93+
if source->String.startsWith("./stdlib") {
94+
switch specifiers {
95+
| [ImportNamespaceSpecifier({local: Identifier({name})})] =>
96+
imports->Dict.set(name, source)
97+
| _ => ()
98+
}
99+
}
100+
remove(nodePath)
101+
},
53102
"ExportNamedDeclaration": remove,
54-
"VariableDeclaration": (nodePath: Ast.nodePath) => {
55-
switch nodePath.node {
56-
| VariableDeclaration({declarations}) if Array.length(declarations) > 0 =>
103+
"VariableDeclaration": (
104+
{node: VariableDeclaration({declarations})}: Ast.nodePath<Ast.VariableDeclaration.t>,
105+
) => {
106+
if Array.length(declarations) > 0 {
57107
let firstDeclaration = Array.getUnsafe(declarations, 0)
58108

59-
switch (firstDeclaration.id, firstDeclaration.init) {
60-
| (Identifier({name}), Some(init)) if name === "App" =>
61-
switch init->Null.toOption {
62-
| Some(ObjectExpression({properties})) =>
109+
switch firstDeclaration {
110+
| VariableDeclarator({id: Identifier({name}), init}) if name === "App" =>
111+
switch init {
112+
| Value(ObjectExpression({properties})) =>
63113
let foundEntryPoint = properties->Array.find(property => {
64114
switch property {
65115
| ObjectProperty({
@@ -74,12 +124,10 @@ module PlaygroundValidator = {
74124
}
75125
| _ => ()
76126
}
77-
| _ => ()
78127
}
79128
},
80129
},
81130
)
82-
83-
{entryPointExists: entryPoint.contents, code: Generator.generator(ast).code}
131+
{entryPointExists: entryPoint.contents, imports, code: Generator.generator(ast).code}
84132
}
85133
}

src/bindings/Webapi.res

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ module Element = {
2727
@send
2828
external postMessage: (contentWindow, string, ~targetOrigin: string=?) => unit = "postMessage"
2929

30+
@send
31+
external postMessageAny: (contentWindow, 'a, ~targetOrigin: string=?) => unit = "postMessage"
32+
3033
module Style = {
3134
@scope("style") @set external width: (Dom.element, string) => unit = "width"
3235
@scope("style") @set external height: (Dom.element, string) => unit = "height"

src/common/CompilerManagerHook.res

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ module CdnMeta = {
4040

4141
let getLibraryCmijUrl = (version, libraryName: string): string =>
4242
`https://cdn.rescript-lang.org/${Semver.toString(version)}/${libraryName}/cmij.js`
43+
44+
let getStdlibRuntimeUrl = (version, filename) =>
45+
`https://cdn.rescript-lang.org/${Semver.toString(version)}/compiler-builtins/stdlib/${filename}`
4346
}
4447

4548
module FinalResult = {

src/common/CompilerManagerHook.resi

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ type ready = {
2727
result: FinalResult.t,
2828
}
2929

30+
module CdnMeta: {
31+
let getStdlibRuntimeUrl: (Semver.t, string) => string
32+
}
33+
3034
type state =
3135
| Init
3236
| SetupFailed(string)

src/common/EvalIFrame.res

+13-6
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ let srcDoc = `
1818
<script type="importmap">
1919
{
2020
"imports": {
21-
"@jsxImportSource": "https://esm.sh/react@${reactVersion}",
2221
"react-dom/client": "https://esm.sh/react-dom@${reactVersion}/client",
2322
"react": "https://esm.sh/react@${reactVersion}",
2423
"react/jsx-runtime": "https://esm.sh/react@${reactVersion}/jsx-runtime"
@@ -36,11 +35,14 @@ let srcDoc = `
3635
window.JsxRuntime = JsxRuntime;
3736
</script>
3837
<script>
39-
window.addEventListener("message", (event) => {
38+
window.addEventListener("message", async (event) => {
4039
try {
4140
// https://rollupjs.org/troubleshooting/#avoiding-eval
42-
const eval2 = eval;
43-
eval2(event.data);
41+
const imports = {};
42+
for (const [key, path] of Object.entries(event.data.imports)) {
43+
imports[key] = await import(path);
44+
}
45+
(Function(...Object.keys(imports), event.data.code))(...Object.values(imports));
4446
} catch (err) {
4547
console.error(err);
4648
}
@@ -67,15 +69,20 @@ let srcDoc = `
6769
</html>
6870
`
6971

70-
let sendOutput = code => {
72+
type message = {
73+
code: string,
74+
imports: Dict.t<string>,
75+
}
76+
77+
let sendOutput = (code, imports) => {
7178
open Webapi
7279

7380
let frame = Document.document->Element.getElementById("iframe-eval")
7481

7582
switch frame {
7683
| Value(element) =>
7784
switch element->Element.contentWindow {
78-
| Some(win) => win->Element.postMessage(code, ~targetOrigin="*")
85+
| Some(win) => win->Element.postMessageAny({code, imports}, ~targetOrigin="*")
7986
| None => Console.error("contentWindow not found")
8087
}
8188
| Null | Undefined => Console.error("iframe not found")

0 commit comments

Comments
 (0)