diff --git a/pages/api/revalidate.js b/pages/api/revalidate.js
new file mode 100644
index 000000000..a2618b5d4
--- /dev/null
+++ b/pages/api/revalidate.js
@@ -0,0 +1 @@
+export { handler as default } from "src/others/Revalidate.mjs";
diff --git a/pages/try.js b/pages/try.js
index 93d83541a..fe297fd6c 100644
--- a/pages/try.js
+++ b/pages/try.js
@@ -1,12 +1,19 @@
import dynamic from "next/dynamic";
-const Try = dynamic(() => import("src/Try.mjs"), {
+export { getStaticProps } from "src/Try.mjs";
+import Try from "src/Try.mjs";
+
+const Playground = dynamic(() => import("src/Playground.mjs"), {
ssr: false,
- //loading: () =>
Loading...
+ loading: () => Loading...
});
-function Comp() {
- return ;
+function Comp(props) {
+ return (
+
+
+
+ );
}
export default Comp;
diff --git a/src/Playground.res b/src/Playground.res
index 227021602..ae5e937e9 100644
--- a/src/Playground.res
+++ b/src/Playground.res
@@ -915,30 +915,88 @@ module Settings = {
{React.string("ReScript Version")}
{
ReactEvent.Form.preventDefault(evt)
- let id = (evt->ReactEvent.Form.target)["value"]
- onCompilerSelect(id)
+ let id: string = (evt->ReactEvent.Form.target)["value"]
+ switch id->CompilerManagerHook.Semver.parse {
+ | Some(v) => onCompilerSelect(v)
+ | None => ()
+ }
}}>
- {switch readyState.experimentalVersions {
- | [] => React.null
- | experimentalVersions =>
+ {
+ let (experimentalVersions, stableVersions) =
+ readyState.versions->Js.Array2.reduce((acc, item) => {
+ let (lhs, rhs) = acc
+ if item.preRelease->Belt.Option.isSome {
+ Js.Array2.push(lhs, item)
+ } else {
+ Js.Array2.push(rhs, item)
+ }->ignore
+ acc
+ }, ([], []))
+
<>
-
- {Belt.Array.map(experimentalVersions, version =>
-
- )->React.array}
-
+ {switch experimentalVersions {
+ | [] => React.null
+ | experimentalVersions =>
+ let versionByOrder = experimentalVersions->Js.Array2.sortInPlaceWith((a, b) => {
+ let cmp = ({
+ CompilerManagerHook.Semver.major: major,
+ minor,
+ patch,
+ preRelease,
+ }) => {
+ let preRelease = switch preRelease {
+ | Some(preRelease) =>
+ switch preRelease {
+ | Dev(id) => 0 + id
+ | Alpha(id) => 10 + id
+ | Beta(id) => 20 + id
+ | Rc(id) => 30 + id
+ }
+ | None => 0
+ }
+ let number =
+ [major, minor, patch]
+ ->Js.Array2.map(v => v->Belt.Int.toString)
+ ->Js.Array2.joinWith("")
+ ->Belt.Int.fromString
+ ->Belt.Option.getWithDefault(0)
+
+ number + preRelease
+ }
+ cmp(b) - cmp(a)
+ })
+ <>
+
+ {versionByOrder
+ ->Belt.Array.map(version => {
+ let version = CompilerManagerHook.Semver.toString(version)
+
+ })
+ ->React.array}
+
+ >
+ }}
+ {switch stableVersions {
+ | [] => React.null
+ | stableVersions =>
+ Belt.Array.map(stableVersions, version => {
+ let version = CompilerManagerHook.Semver.toString(version)
+
+ })->React.array
+ }}
>
- }}
- {Belt.Array.map(readyState.versions, version =>
-
- )->React.array}
+ }
@@ -1350,29 +1408,29 @@ module App = {
let initialReContent = j`Js.log("Hello Reason 3.6!");`
-/**
-Takes a `versionStr` starting with a "v" and ending in major.minor.patch (e.g.
-"v10.1.0") returns major, minor, patch as an integer tuple if it's actually in
-a x.y.z format, otherwise will return `None`.
-*/
-let parseVersion = (versionStr: string): option<(int, int, int)> => {
- switch versionStr->Js.String2.replace("v", "")->Js.String2.split(".") {
- | [major, minor, patch] =>
- switch (major->Belt.Int.fromString, minor->Belt.Int.fromString, patch->Belt.Int.fromString) {
- | (Some(major), Some(minor), Some(patch)) => Some((major, minor, patch))
- | _ => None
- }
- | _ => None
- }
-}
-
-@react.component
-let make = () => {
+let default = (~props: Try.props) => {
let router = Next.Router.useRouter()
+ let versions =
+ props.versions
+ ->Belt.Array.keepMap(v => v->CompilerManagerHook.Semver.parse)
+ ->Js.Array2.sortInPlaceWith((a, b) => {
+ let cmp = ({CompilerManagerHook.Semver.major: major, minor, patch, _}) => {
+ [major, minor, patch]
+ ->Js.Array2.map(v => v->Belt.Int.toString)
+ ->Js.Array2.joinWith("")
+ ->Belt.Int.fromString
+ ->Belt.Option.getWithDefault(0)
+ }
+ cmp(b) - cmp(a)
+ })
+
+ let lastStableVersion =
+ versions->Js.Array2.find(version => version.preRelease->Belt.Option.isNone)
+
let initialVersion = switch Js.Dict.get(router.query, "version") {
- | Some(version) => Some(version)
- | None => CompilerManagerHook.CdnMeta.versions->Belt.Array.get(0)
+ | Some(version) => version->CompilerManagerHook.Semver.parse
+ | None => lastStableVersion
}
let initialLang = switch Js.Dict.get(router.query, "ext") {
@@ -1386,15 +1444,11 @@ let make = () => {
| (None, Res)
| (None, _) =>
switch initialVersion {
- | Some(initialVersion) =>
- switch parseVersion(initialVersion) {
- | Some((major, minor, _)) =>
- if major >= 10 && minor >= 1 {
- InitialContent.since_10_1
- } else {
- InitialContent.original
- }
- | None => InitialContent.original
+ | Some({CompilerManagerHook.Semver.major: major, minor, _}) =>
+ if major >= 10 && minor >= 1 {
+ InitialContent.since_10_1
+ } else {
+ InitialContent.original
}
| None => InitialContent.original
}
@@ -1408,6 +1462,7 @@ let make = () => {
~initialVersion?,
~initialLang,
~onAction,
+ ~versions,
(),
)
diff --git a/src/Playground.resi b/src/Playground.resi
index 1ca44ce26..72031edb8 100644
--- a/src/Playground.resi
+++ b/src/Playground.resi
@@ -1,2 +1 @@
-@react.component
-let make: unit => React.element
+let default: (~props: Try.props) => React.element
diff --git a/src/Try.res b/src/Try.res
index 8152a69d2..704acd270 100644
--- a/src/Try.res
+++ b/src/Try.res
@@ -1,7 +1,8 @@
-@react.component
-let default = () => {
+let default = (props: {"children": React.element}) => {
let overlayState = React.useState(() => false)
+ let playground = props["children"]
+
<>
@@ -10,8 +11,39 @@ let default = () => {
>
}
+
+type props = {versions: array}
+
+let getStaticProps: Next.GetStaticProps.t = async _ => {
+ let versions = {
+ let response = await Webapi.Fetch.fetch("https://cdn.rescript-lang.org/")
+ let text = await Webapi.Fetch.Response.text(response)
+ text
+ ->Js.String2.split("\n")
+ ->Belt.Array.keepMap(line => {
+ switch line->Js.String2.startsWith("
+ // Adapted from https://semver.org/
+ let semverRe = %re(
+ "/v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?/"
+ )
+ switch Js.Re.exec_(semverRe, line) {
+ | Some(result) =>
+ switch Js.Re.captures(result)->Belt.Array.get(0) {
+ | Some(str) => Js.Nullable.toOption(str)
+ | None => None
+ }
+ | None => None
+ }
+ | false => None
+ }
+ })
+ }
+
+ {"props": {versions: versions}}
+}
diff --git a/src/Try.resi b/src/Try.resi
new file mode 100644
index 000000000..2611af9cf
--- /dev/null
+++ b/src/Try.resi
@@ -0,0 +1,3 @@
+let default: {"children": React.element} => React.element
+type props = {versions: array}
+let getStaticProps: Next.GetStaticProps.t
diff --git a/src/bindings/Webapi.res b/src/bindings/Webapi.res
index 92fa03094..5e92050dc 100644
--- a/src/bindings/Webapi.res
+++ b/src/bindings/Webapi.res
@@ -36,3 +36,12 @@ module Window = {
@scope("window") @val external innerWidth: int = "innerWidth"
@scope("window") @val external innerHeight: int = "innerHeight"
}
+
+module Fetch = {
+ module Response = {
+ type t
+ @send external text: t => promise = "text"
+ }
+
+ @val external fetch: string => promise = "fetch"
+}
diff --git a/src/common/CompilerManagerHook.res b/src/common/CompilerManagerHook.res
index 5044e8130..4f753c5b3 100644
--- a/src/common/CompilerManagerHook.res
+++ b/src/common/CompilerManagerHook.res
@@ -34,28 +34,85 @@ module LoadScript = {
}
}
+module Semver = {
+ type preRelease = Alpha(int) | Beta(int) | Dev(int) | Rc(int)
+
+ type t = {major: int, minor: int, patch: int, preRelease: option}
+
+ /**
+ Takes a `version` string starting with a "v" and ending in major.minor.patch or
+ major.minor.patch-prerelease.identifier (e.g. "v10.1.0" or "v10.1.0-alpha.2")
+ */
+ let parse = (versionStr: string) => {
+ let parsePreRelease = str => {
+ switch str->Js.String2.split("-") {
+ | [_, identifier] =>
+ switch identifier->Js.String2.split(".") {
+ | [name, number] =>
+ switch Belt.Int.fromString(number) {
+ | None => None
+ | Some(buildIdentifier) =>
+ switch name {
+ | "dev" => buildIdentifier->Dev->Some
+ | "beta" => buildIdentifier->Beta->Some
+ | "alpha" => buildIdentifier->Alpha->Some
+ | "rc" => buildIdentifier->Rc->Some
+ | _ => None
+ }
+ }
+ | _ => None
+ }
+ | _ => None
+ }
+ }
+
+ // Some version contain a suffix. Example: v11.0.0-alpha.5, v11.0.0-beta.1
+ let isPrerelease = versionStr->Js.String2.search(%re("/-/")) != -1
+
+ // Get the first part i.e vX.Y.Z
+ let versionNumber =
+ versionStr->Js.String2.split("-")->Belt.Array.get(0)->Belt.Option.getWithDefault(versionStr)
+
+ switch versionNumber->Js.String2.replace("v", "")->Js.String2.split(".") {
+ | [major, minor, patch] =>
+ switch (major->Belt.Int.fromString, minor->Belt.Int.fromString, patch->Belt.Int.fromString) {
+ | (Some(major), Some(minor), Some(patch)) =>
+ let preReleaseIdentifier = if isPrerelease {
+ parsePreRelease(versionStr)
+ } else {
+ None
+ }
+ Some({major, minor, patch, preRelease: preReleaseIdentifier})
+ | _ => None
+ }
+ | _ => None
+ }
+ }
+
+ let toString = ({major, minor, patch, preRelease}) => {
+ let mainVersion = `v${major->Belt.Int.toString}.${minor->Belt.Int.toString}.${patch->Belt.Int.toString}`
+
+ switch preRelease {
+ | None => mainVersion
+ | Some(identifier) =>
+ let identifier = switch identifier {
+ | Dev(number) => `dev.${number->Belt.Int.toString}`
+ | Alpha(number) => `alpha.${number->Belt.Int.toString}`
+ | Beta(number) => `beta.${number->Belt.Int.toString}`
+ | Rc(number) => `rc.${number->Belt.Int.toString}`
+ }
+
+ `${mainVersion}-${identifier}`
+ }
+ }
+}
+
module CdnMeta = {
- // Make sure versions exist on https://cdn.rescript-lang.org
- // [0] = latest
- let versions = [
- "v10.1.2",
- "v10.0.1",
- "v10.0.0",
- "v9.1.2",
- "v9.0.2",
- "v9.0.1",
- "v9.0.0",
- "v8.4.2",
- "v8.3.0-dev.2",
- ]
-
- let experimentalVersions = ["v11.0.0-rc.3", "v11.0.0-beta.4", "v11.0.0-beta.1", "v11.0.0-alpha.5"]
-
- let getCompilerUrl = (version: string): string =>
- j`https://cdn.rescript-lang.org/$version/compiler.js`
-
- let getLibraryCmijUrl = (version: string, libraryName: string): string =>
- j`https://cdn.rescript-lang.org/$version/$libraryName/cmij.js`
+ let getCompilerUrl = (version): string =>
+ `https://cdn.rescript-lang.org/${Semver.toString(version)}/compiler.js`
+
+ let getLibraryCmijUrl = (version, libraryName: string): string =>
+ `https://cdn.rescript-lang.org/${Semver.toString(version)}/${libraryName}/cmij.js`
}
module FinalResult = {
@@ -69,30 +126,23 @@ module FinalResult = {
// This will a given list of libraries to a specific target version of the compiler.
// E.g. starting from v9, @rescript/react instead of reason-react is used.
// If the version can't be parsed, an empty array will be returned.
-let getLibrariesForVersion = (~version: string): array => {
- switch Js.String2.split(version, ".")->Belt.List.fromArray {
- | list{major, ..._rest} =>
- let version =
- Js.String2.replace(major, "v", "")->Belt.Int.fromString->Belt.Option.getWithDefault(0)
-
- let libraries = if version >= 9 {
- ["@rescript/react"]
- } else if version < 9 {
- ["reason-react"]
- } else {
- []
- }
-
- // Since version 11, we ship the compiler-builtins as a separate file, and
- // we also added @rescript/core as a pre-vendored package
- if version >= 11 {
- libraries->Js.Array2.push("@rescript/core")->ignore
- libraries->Js.Array2.push("compiler-builtins")->ignore
- }
+let getLibrariesForVersion = (~version: Semver.t): array => {
+ let libraries = if version.major >= 9 {
+ ["@rescript/react"]
+ } else if version.major < 9 {
+ ["reason-react"]
+ } else {
+ []
+ }
- libraries
- | _ => []
+ // Since version 11, we ship the compiler-builtins as a separate file, and
+ // we also added @rescript/core as a pre-vendored package
+ if version.major >= 11 {
+ libraries->Js.Array2.push("@rescript/core")->ignore
+ libraries->Js.Array2.push("compiler-builtins")->ignore
}
+
+ libraries
}
/*
@@ -108,7 +158,7 @@ let getLibrariesForVersion = (~version: string): array => {
We coupled the compiler / library loading to prevent ppl to try loading compiler / cmij files
separately and cause all kinds of race conditions.
*/
-let attachCompilerAndLibraries = async (~version: string, ~libraries: array, ()): result<
+let attachCompilerAndLibraries = async (~version, ~libraries: array, ()): result<
unit,
array,
> => {
@@ -149,7 +199,7 @@ type error =
| CompilerLoadingError(string)
type selected = {
- id: string, // The id used for loading the compiler bundle (ideally should be the same as compilerVersion)
+ id: Semver.t, // The id used for loading the compiler bundle (ideally should be the same as compilerVersion)
apiVersion: Version.t, // The playground API version in use
compilerVersion: string,
ocamlVersion: string,
@@ -159,8 +209,7 @@ type selected = {
}
type ready = {
- versions: array,
- experimentalVersions: array,
+ versions: array,
selected: selected,
targetLang: Lang.t,
errors: array, // For major errors like bundle loading
@@ -170,12 +219,12 @@ type ready = {
type state =
| Init
| SetupFailed(string)
- | SwitchingCompiler(ready, string) // (ready, targetId, libraries)
+ | SwitchingCompiler(ready, Semver.t) // (ready, targetId, libraries)
| Ready(ready)
| Compiling(ready, (Lang.t, string))
type action =
- | SwitchToCompiler(string) // id
+ | SwitchToCompiler(Semver.t) // id
| SwitchLanguage({lang: Lang.t, code: string})
| Format(string)
| CompileCode(Lang.t, string)
@@ -193,9 +242,10 @@ type action =
// component to give feedback to the user that an action happened (useful in
// cases where the output didn't visually change)
let useCompilerManager = (
- ~initialVersion: option=?,
+ ~initialVersion: option=?,
~initialLang: Lang.t=Res,
~onAction: option unit>=?,
+ ~versions: array,
(),
) => {
let (state, setState) = React.useState(_ => Init)
@@ -334,74 +384,58 @@ let useCompilerManager = (
let updateState = async () => {
switch state {
| Init =>
- switch CdnMeta.versions {
+ switch versions {
| [] => dispatchError(SetupError("No compiler versions found"))
| versions =>
- let latest = versions[0]
-
- // If the provided initialVersion is not available, fall back
- // to "latest"
- let initVersion = switch initialVersion {
+ switch initialVersion {
| Some(version) =>
- let allVersions = Belt.Array.concat(CdnMeta.versions, CdnMeta.experimentalVersions)
- if (
- allVersions->Js.Array2.some(v => {
- version == v
- })
- ) {
- version
- } else {
- latest
- }
- | None => latest
- }
+ // Latest version is already running on @rescript/react
+ let libraries = getLibrariesForVersion(~version)
+
+ switch await attachCompilerAndLibraries(~version, ~libraries, ()) {
+ | Ok() =>
+ let instance = Compiler.make()
+ let apiVersion = apiVersion->Version.fromString
+
+ // Note: The compiler bundle currently defaults to
+ // commonjs when initiating the compiler, but our playground
+ // should default to ES6. So we override the config
+ // and use the `setConfig` function to sync up the
+ // internal compiler state with our playground state.
+ let config = {
+ ...instance->Compiler.getConfig,
+ module_system: "es6",
+ }
+ instance->Compiler.setConfig(config)
+
+ let selected = {
+ id: version,
+ apiVersion,
+ compilerVersion: instance->Compiler.version,
+ ocamlVersion: instance->Compiler.ocamlVersion,
+ config,
+ libraries,
+ instance,
+ }
- // Latest version is already running on @rescript/react
- let libraries = getLibrariesForVersion(~version=initVersion)
-
- switch await attachCompilerAndLibraries(~version=initVersion, ~libraries, ()) {
- | Ok() =>
- let instance = Compiler.make()
- let apiVersion = apiVersion->Version.fromString
-
- // Note: The compiler bundle currently defaults to
- // commonjs when initiating the compiler, but our playground
- // should default to ES6. So we override the config
- // and use the `setConfig` function to sync up the
- // internal compiler state with our playground state.
- let config = {
- ...instance->Compiler.getConfig,
- module_system: "es6",
+ let targetLang =
+ Version.availableLanguages(apiVersion)
+ ->Js.Array2.find(l => l === initialLang)
+ ->Belt.Option.getWithDefault(Version.defaultTargetLang)
+
+ setState(_ => Ready({
+ selected,
+ targetLang,
+ versions,
+ errors: [],
+ result: FinalResult.Nothing,
+ }))
+ | Error(errs) =>
+ let msg = Js.Array2.joinWith(errs, "; ")
+
+ dispatchError(CompilerLoadingError(msg))
}
- instance->Compiler.setConfig(config)
-
- let selected = {
- id: initVersion,
- apiVersion,
- compilerVersion: instance->Compiler.version,
- ocamlVersion: instance->Compiler.ocamlVersion,
- config,
- libraries,
- instance,
- }
-
- let targetLang =
- Version.availableLanguages(apiVersion)
- ->Js.Array2.find(l => l === initialLang)
- ->Belt.Option.getWithDefault(Version.defaultTargetLang)
-
- setState(_ => Ready({
- selected,
- targetLang,
- versions,
- experimentalVersions: CdnMeta.experimentalVersions,
- errors: [],
- result: FinalResult.Nothing,
- }))
- | Error(errs) =>
- let msg = Js.Array2.joinWith(errs, "; ")
-
- dispatchError(CompilerLoadingError(msg))
+ | None => dispatchError(CompilerLoadingError("Cant not found the initial version"))
}
}
| SwitchingCompiler(ready, version) =>
@@ -435,7 +469,6 @@ let useCompilerManager = (
selected,
targetLang: Version.defaultTargetLang,
versions: ready.versions,
- experimentalVersions: ready.experimentalVersions,
errors: [],
result: FinalResult.Nothing,
}))
diff --git a/src/common/CompilerManagerHook.resi b/src/common/CompilerManagerHook.resi
index ca82af5f1..54b534b4c 100644
--- a/src/common/CompilerManagerHook.resi
+++ b/src/common/CompilerManagerHook.resi
@@ -8,13 +8,24 @@ module FinalResult: {
| Nothing
}
-module CdnMeta: {
- /** All available versions on the CDN */
- let versions: array
+module Semver: {
+ type preRelease =
+ | Alpha(int)
+ | Beta(int)
+ | Dev(int)
+ | Rc(int)
+ type t = {
+ major: int,
+ minor: int,
+ patch: int,
+ preRelease: option,
+ }
+ let parse: string => option
+ let toString: t => string
}
type selected = {
- id: string, // The id used for loading the compiler bundle (ideally should be the same as compilerVersion)
+ id: Semver.t, // The id used for loading the compiler bundle (ideally should be the same as compilerVersion)
apiVersion: Version.t, // The playground API version in use
compilerVersion: string,
ocamlVersion: string,
@@ -24,8 +35,7 @@ type selected = {
}
type ready = {
- versions: array,
- experimentalVersions: array,
+ versions: array,
selected: selected,
targetLang: Lang.t,
errors: array, // For major errors like bundle loading
@@ -35,20 +45,21 @@ type ready = {
type state =
| Init
| SetupFailed(string)
- | SwitchingCompiler(ready, string) // (ready, targetId, libraries)
+ | SwitchingCompiler(ready, Semver.t) // (ready, targetId, libraries)
| Ready(ready)
| Compiling(ready, (Lang.t, string))
type action =
- | SwitchToCompiler(string) // id
+ | SwitchToCompiler(Semver.t) // id
| SwitchLanguage({lang: Lang.t, code: string})
| Format(string)
| CompileCode(Lang.t, string)
| UpdateConfig(Config.t)
let useCompilerManager: (
- ~initialVersion: string=?,
+ ~initialVersion: Semver.t=?,
~initialLang: Lang.t=?,
~onAction: action => unit=?,
+ ~versions: array,
unit,
) => (state, action => unit)
diff --git a/src/others/Revalidate.res b/src/others/Revalidate.res
new file mode 100644
index 000000000..f4d6dff4f
--- /dev/null
+++ b/src/others/Revalidate.res
@@ -0,0 +1,37 @@
+module Req = {
+ type req = {query: Js.Dict.t}
+}
+
+module Res = {
+ type res
+
+ @send external revalidate: (res, string) => promise = "revalidate"
+ @send external json: (res, {..}) => res = "json"
+
+ module Status = {
+ type t
+ @send external make: (res, int) => t = "status"
+ @send external send: (t, string) => res = "send"
+ @send external json: (t, {..}) => res = "json"
+ }
+}
+
+@val external process: 'a = "process"
+
+let handler = async (req: Req.req, res: Res.res) => {
+ switch req.query->Js.Dict.get("secret") {
+ | Some(secret) =>
+ if secret !== process["env"]["NEXT_REVALIDATE_SECRET_TOKEN"] {
+ res->Res.Status.make(401)->Res.Status.json({"message": "Invalid secret"})
+ } else {
+ try {
+ let () = await res->Res.revalidate("/try")
+ res->Res.json({"revalidated": true})
+ } catch {
+ | Js.Exn.Error(_) => res->Res.Status.make(500)->Res.Status.send("Error revalidating")
+ }
+ }
+ | None =>
+ res->Res.Status.make(500)->Res.Status.send("Error revalidating, param `secret` not found")
+ }
+}
diff --git a/src/others/Revalidate.resi b/src/others/Revalidate.resi
new file mode 100644
index 000000000..694cd1958
--- /dev/null
+++ b/src/others/Revalidate.resi
@@ -0,0 +1,7 @@
+module Req: {
+ type req
+}
+module Res: {
+ type res
+}
+let handler: (Req.req, Res.res) => promise