diff --git a/jscomp/bsb/Docs.md b/jscomp/bsb/Docs.md index a0ccd28e6f..83131852be 100644 --- a/jscomp/bsb/Docs.md +++ b/jscomp/bsb/Docs.md @@ -4,17 +4,13 @@ Bsb is ReScript's build system. User-facing documentations are [here](https://re This directory hosts its implementation. It reads into `bsconfig.json`, uses some BS/OCaml/Reason-specific logic, and generates a [ninja](https://ninja-build.org) build file then calls `ninja` on it. So much of the incremental build and perf work is delegated to Ninja. -There's a `templates/` subdirectory. It's the thing shown when you do `bsb -themes`. To generate a template for the user, it basically picks the chosen template from `templates/` and copy pastes it into the destined user directory while substituting some strings in those templates, like `${bsb:proj-version}` in the `package.json`s. The copy-pasting of folders isn't actually done naively through a call to unix `cp`. It's cleverly achieved through something called ocamlres. Please see more descriptions in `pack-templates.sh`. +There's a `templates/` subdirectory. It's the thing shown when you do `bsb -themes`. To generate a template for the user, it basically picks the chosen template from `templates/` and copy pastes it into the destined user directory while substituting some strings in those templates, like `${bsb:proj-version}` in the `package.json`s. ## Add/edit a template -The content of `templates` is packed into `bsb_templates.ml` automatically when running `pack-templates.sh`, located in this directory. +The content of `templates` is packed into `bsb_templates.ml` automatically when running [pack.js](../../scripts/pack.js). -When adding/editing a template the script needs to be rerun to update the relevant parts in `bsb_templates.ml`. The script uses [`ocamlres`](https://github.com/OCamlPro/ocp-ocamlres), a tool for embedding files inside ocaml executables. You will need to install it with [`opam`](https://opam.ocaml.org/doc/Install.html), ocaml package manager. Follow the [instructions](https://opam.ocaml.org/doc/Install.html) to install `opam`, if you haven't installed it yet. To install `ocp-ocamlres` run: - -```sh -opam install ocp-ocamlres -``` +When adding/editing a template the script needs to be rerun to update the relevant parts in `bsb_templates.ml`. ## Testing a template locally diff --git a/jscomp/bsb/bsb_templates.ml b/jscomp/bsb/bsb_templates.ml index 976525bb7d..05bb3ddde4 100644 --- a/jscomp/bsb/bsb_templates.ml +++ b/jscomp/bsb/bsb_templates.ml @@ -1,1804 +1,1888 @@ -(* This file has been generated by ocp-ocamlres *) -let root = OCamlRes.Res.([ - Dir ("basic", [ - Dir ("src", [ - File ("demo.ml", - "\n\ - \n\ - let () = Js.log \"Hello, BuckleScript\"")]) ; - File ("bsconfig.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"sources\": {\n\ - \ \"dir\" : \"src\",\n\ - \ \"subdirs\" : true\n\ - \ },\n\ - \ \"warnings\": {\n\ - \ \"error\" : \"+101\"\n\ - \ }\n\ - }\n\ - ") ; - File ("package.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"scripts\": {\n\ - \ \"clean\": \"bsb -clean-world\",\n\ - \ \"build\": \"bsb -make-world\",\n\ - \ \"watch\": \"bsb -make-world -w\"\n\ - \ },\n\ - \ \"keywords\": [\n\ - \ \"BuckleScript\"\n\ - \ ],\n\ - \ \"author\": \"\",\n\ - \ \"license\": \"MIT\",\n\ - \ \"devDependencies\": {\n\ - \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ - \ }\n\ - }") ; - File (".gitignore", - "*.exe\n\ - *.obj\n\ - *.out\n\ - *.compile\n\ - *.native\n\ - *.byte\n\ - *.cmo\n\ - *.annot\n\ - *.cmi\n\ - *.cmx\n\ - *.cmt\n\ - *.cmti\n\ - *.cma\n\ - *.a\n\ - *.cmxa\n\ - *.obj\n\ - *~\n\ - *.annot\n\ - *.cmj\n\ - *.bak\n\ - lib/bs\n\ - *.mlast\n\ - *.mliast\n\ - .vscode\n\ - .merlin\n\ - .bsb.lock\n\ - /node_modules/\n\ - ") ; - File ("README.md", - "\n\ - \n\ - # Build\n\ - ```\n\ - npm run build\n\ - ```\n\ - \n\ - # Watch\n\ - \n\ - ```\n\ - npm run watch\n\ - ```\n\ - \n\ - ")]) ; - Dir ("basic-reason", [ - Dir ("src", [ - File ("Demo.re", - "Js.log(\"Hello, ReScript!\");\n\ - ")]) ; - File ("bsconfig.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"sources\": {\n\ - \ \"dir\" : \"src\",\n\ - \ \"subdirs\" : true\n\ - \ },\n\ - \ \"package-specs\": {\n\ - \ \"module\": \"commonjs\",\n\ - \ \"in-source\": true\n\ - \ },\n\ - \ \"suffix\": \".bs.js\",\n\ - \ \"bs-dependencies\": [\n\ - \n\ - \ ],\n\ - \ \"warnings\": {\n\ - \ \"error\" : \"+101\"\n\ - \ },\n\ - \ \"namespace\": true,\n\ - \ \"refmt\": 3\n\ - }\n\ - ") ; - File ("package.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"scripts\": {\n\ - \ \"build\": \"bsb -make-world\",\n\ - \ \"start\": \"bsb -make-world -w\",\n\ - \ \"clean\": \"bsb -clean-world\"\n\ - \ },\n\ - \ \"keywords\": [\n\ - \ \"BuckleScript\"\n\ - \ ],\n\ - \ \"author\": \"\",\n\ - \ \"license\": \"MIT\",\n\ - \ \"devDependencies\": {\n\ - \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ - \ }\n\ - }\n\ - ") ; - File (".gitignore", - ".DS_Store\n\ - .merlin\n\ - .bsb.lock\n\ - npm-debug.log\n\ - /lib/bs/\n\ - /node_modules/\n\ - ") ; - File ("README.md", - "# Basic Reason Template\n\ - \n\ - Hello! This project allows you to quickly get started with ReScript using Reason syntax. If you wanted a more sophisticated version, try the `react` template (`bsb -theme react -init .`).\n\ - \n\ - # Build\n\ - \n\ - ```bash\n\ - # for yarn\n\ - yarn build\n\ - \n\ - # for npm\n\ - npm run build\n\ - ```\n\ - \n\ - # Build + Watch\n\ - \n\ - ```bash\n\ - # for yarn\n\ - yarn start\n\ - \n\ - # for npm\n\ - npm run start\n\ - ```\n\ - \n\ - ")]) ; - Dir ("generator", [ - Dir ("src", [ - File ("test.cpp.ml", - "\n\ - (* \n\ - #define FS_VAL(name,ty) external name : ty = \"\" [@@bs.module \"fs\"]\n\ - \n\ - \n\ - FS_VAL(readdirSync, string -> string array)\n\ - \ *)\n\ - \n\ - \n\ - \ let ocaml = OCAML") ; - File ("demo.ml", - "\n\ - \n\ - let () = Js.log \"Hello, BuckleScript\"")]) ; - File ("bsconfig.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"sources\": {\n\ - \ \"dir\": \"src\",\n\ - \ \"generators\": [{\n\ - \ \"name\": \"cpp\",\n\ - \ \"edge\": [\"test.ml\", \":\", \"test.cpp.ml\"]\n\ - \ }],\n\ - \ \"subdirs\": true \n\ - \ },\n\ - \ \"generators\": [{\n\ - \ \"name\" : \"cpp\",\n\ - \ \"command\": \"sed 's/OCAML/3/' $in > $out\"\n\ - \ }],\n\ - \ \"bs-dependencies\" : [\n\ - \ ]\n\ - }") ; - File ("package.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"scripts\": {\n\ - \ \"clean\": \"bsb -clean-world\",\n\ - \ \"build\": \"bsb -make-world\",\n\ - \ \"watch\": \"bsb -make-world -w\"\n\ - \ },\n\ - \ \"keywords\": [\n\ - \ \"BuckleScript\"\n\ - \ ],\n\ - \ \"author\": \"\",\n\ - \ \"license\": \"MIT\",\n\ - \ \"devDependencies\": {\n\ - \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ - \ }\n\ - }") ; - File (".gitignore", - "*.exe\n\ - *.obj\n\ - *.out\n\ - *.compile\n\ - *.native\n\ - *.byte\n\ - *.cmo\n\ - *.annot\n\ - *.cmi\n\ - *.cmx\n\ - *.cmt\n\ - *.cmti\n\ - *.cma\n\ - *.a\n\ - *.cmxa\n\ - *.obj\n\ - *~\n\ - *.annot\n\ - *.cmj\n\ - *.bak\n\ - lib/bs\n\ - *.mlast\n\ - *.mliast\n\ - .vscode\n\ - .merlin\n\ - .bsb.lock\n\ - /node_modules/\n\ - ") ; - File ("README.md", - "\n\ - \n\ - # Build\n\ - ```\n\ - npm run build\n\ - ```\n\ - \n\ - # Watch\n\ - \n\ - ```\n\ - npm run watch\n\ - ```\n\ - \n\ - \n\ - # Editor\n\ - If you use `vscode`, Press `Windows + Shift + B` it will build automatically")]) ; - Dir ("minimal", [ - Dir ("src", [ File ("main.ml", "")]) ; - File ("bsconfig.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"sources\": {\n\ - \ \"dir\": \"src\",\n\ - \ \"subdirs\": true\n\ - \ }\n\ - }") ; - File ("package.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"scripts\": {\n\ - \ \"clean\": \"bsb -clean-world\",\n\ - \ \"build\": \"bsb -make-world\",\n\ - \ \"start\": \"bsb -make-world -w\"\n\ - \ },\n\ - \ \"keywords\": [\n\ - \ \"BuckleScript\"\n\ - \ ],\n\ - \ \"author\": \"\",\n\ - \ \"license\": \"MIT\",\n\ - \ \"devDependencies\": {\n\ - \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ - \ }\n\ - }") ; - File (".gitignore", - ".DS_Store\n\ - .merlin\n\ - .bsb.lock\n\ - npm-debug.log\n\ - /lib/bs/\n\ - /node_modules/") ; - File ("README.md", - "\n\ - \ # ${bsb:name}")]) ; - Dir ("node", [ - Dir ("src", [ - File ("demo.ml", - "\n\ - \n\ - let () = Js.log \"Hello, BuckleScript\"")]) ; - File ("bsconfig.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"sources\": {\n\ - \ \"dir\": \"src\",\n\ - \ \"subdirs\" : true\n\ - \ },\n\ - \ \"package-specs\": {\n\ - \ \"module\": \"commonjs\",\n\ - \ \"in-source\": true\n\ - \ },\n\ - \ \"suffix\": \".bs.js\",\n\ - \ \"bs-dependencies\": [\n\ - \ ]\n\ - }") ; - File ("package.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"scripts\": {\n\ - \ \"clean\": \"bsb -clean-world\",\n\ - \ \"build\": \"bsb -make-world\",\n\ - \ \"watch\": \"bsb -make-world -w\"\n\ - \ },\n\ - \ \"keywords\": [\n\ - \ \"BuckleScript\"\n\ - \ ],\n\ - \ \"author\": \"\",\n\ - \ \"license\": \"MIT\",\n\ - \ \"devDependencies\": {\n\ - \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ - \ }\n\ - }") ; - File (".gitignore", - "*.exe\n\ - *.obj\n\ - *.out\n\ - *.compile\n\ - *.native\n\ - *.byte\n\ - *.cmo\n\ - *.annot\n\ - *.cmi\n\ - *.cmx\n\ - *.cmt\n\ - *.cmti\n\ - *.cma\n\ - *.a\n\ - *.cmxa\n\ - *.obj\n\ - *~\n\ - *.annot\n\ - *.cmj\n\ - *.bak\n\ - lib/bs\n\ - *.mlast\n\ - *.mliast\n\ - .vscode\n\ - .merlin\n\ - .bsb.lock\n\ - /node_modules/\n\ - ") ; - File ("README.md", - "\n\ - \n\ - # Build\n\ - ```\n\ - npm run build\n\ - ```\n\ - \n\ - # Watch\n\ - \n\ - ```\n\ - npm run watch\n\ - ```\n\ - \n\ - \n\ - # Editor\n\ - If you use `vscode`, Press `Windows + Shift + B` it will build automatically\n\ - ")]) ; - Dir ("react-hooks", [ - Dir ("src", [ - File ("Index.re", - "// Entry point\n\ - \n\ - [@bs.val] external document: Js.t({..}) = \"document\";\n\ - \n\ - // We're using raw DOM manipulations here, to avoid making you read\n\ - // ReasonReact when you might precisely be trying to learn it for the first\n\ - // time through the examples later.\n\ - let style = document##createElement(\"style\");\n\ - document##head##appendChild(style);\n\ - style##innerHTML #= ExampleStyles.style;\n\ - \n\ - let makeContainer = text => {\n\ - \ let container = document##createElement(\"div\");\n\ - \ container##className #= \"container\";\n\ - \n\ - \ let title = document##createElement(\"div\");\n\ - \ title##className #= \"containerTitle\";\n\ - \ title##innerText #= text;\n\ - \n\ - \ let content = document##createElement(\"div\");\n\ - \ content##className #= \"containerContent\";\n\ - \n\ - \ let () = container##appendChild(title);\n\ - \ let () = container##appendChild(content);\n\ - \ let () = document##body##appendChild(container);\n\ - \n\ - \ content;\n\ - };\n\ - \n\ - // All 4 examples.\n\ - ReactDOMRe.render(\n\ - \ \n\ - \ {React.string(\"Hello!\")}\n\ - \ ,\n\ - \ makeContainer(\"Blinking Greeting\"),\n\ - );\n\ - \n\ - ReactDOMRe.render(\n\ - \ ,\n\ - \ makeContainer(\"Reducer From ReactJS Docs\"),\n\ - );\n\ - \n\ - ReactDOMRe.render(\n\ - \ ,\n\ - \ makeContainer(\"Fetched Dog Pictures\"),\n\ - );\n\ - \n\ - ReactDOMRe.render(\n\ - \ ,\n\ - \ makeContainer(\"Reason Using JS Using Reason\"),\n\ - );\n\ - ") ; - File ("ExampleStyles.re", - "let reasonReactBlue = \"#48a9dc\";\n\ - \n\ - // The {j|...|j} feature is just string interpolation, from\n\ - // bucklescript.github.io/docs/en/interop-cheatsheet#string-unicode-interpolation\n\ - // This allows us to conveniently write CSS, together with variables, by\n\ - // constructing a string\n\ - let style = {j|\n\ - \ body {\n\ - \ background-color: rgb(224, 226, 229);\n\ - \ display: flex;\n\ - \ flex-direction: column;\n\ - \ align-items: center;\n\ - \ }\n\ - \ button {\n\ - \ background-color: white;\n\ - \ color: $reasonReactBlue;\n\ - \ box-shadow: 0 0 0 1px $reasonReactBlue;\n\ - \ border: none;\n\ - \ padding: 8px;\n\ - \ font-size: 16px;\n\ - \ }\n\ - \ button:active {\n\ - \ background-color: $reasonReactBlue;\n\ - \ color: white;\n\ - \ }\n\ - \ .container {\n\ - \ margin: 12px 0px;\n\ - \ box-shadow: 0px 4px 16px rgb(200, 200, 200);\n\ - \ width: 720px;\n\ - \ border-radius: 12px;\n\ - \ font-family: sans-serif;\n\ - \ }\n\ - \ .containerTitle {\n\ - \ background-color: rgb(242, 243, 245);\n\ - \ border-radius: 12px 12px 0px 0px;\n\ - \ padding: 12px;\n\ - \ font-weight: bold;\n\ - \ }\n\ - \ .containerContent {\n\ - \ background-color: white;\n\ - \ padding: 16px;\n\ - \ border-radius: 0px 0px 12px 12px;\n\ - \ }\n\ - |j};\n\ - ") ; - Dir ("FetchedDogPictures", [ - File ("FetchedDogPictures.re", - "[@bs.val] external fetch: string => Js.Promise.t('a) = \"fetch\";\n\ - \n\ - type state =\n\ - \ | LoadingDogs\n\ - \ | ErrorFetchingDogs\n\ - \ | LoadedDogs(array(string));\n\ - \n\ - [@react.component]\n\ - let make = () => {\n\ - \ let (state, setState) = React.useState(() => LoadingDogs);\n\ - \n\ - \ // Notice that instead of `useEffect`, we have `useEffect0`. See\n\ - \ // reasonml.github.io/reason-react/docs/en/components#hooks for more info\n\ - \ React.useEffect0(() => {\n\ - \ Js.Promise.(\n\ - \ fetch(\"https://dog.ceo/api/breeds/image/random/3\")\n\ - \ |> then_(response => response##json())\n\ - \ |> then_(jsonResponse => {\n\ - \ setState(_previousState => LoadedDogs(jsonResponse##message));\n\ - \ Js.Promise.resolve();\n\ - \ })\n\ - \ |> catch(_err => {\n\ - \ setState(_previousState => ErrorFetchingDogs);\n\ - \ Js.Promise.resolve();\n\ - \ })\n\ - \ |> ignore\n\ - \ );\n\ - \n\ - \ // Returning None, instead of Some(() => ...), means we don't have any\n\ - \ // cleanup to do before unmounting. That's not 100% true. We should\n\ - \ // technically cancel the promise. Unofortunately, there's currently no\n\ - \ // way to cancel a promise. Promises in general should be way less used\n\ - \ // for React components; but since folks do use them, we provide such an\n\ - \ // example here. In reality, this fetch should just be a plain callback,\n\ - \ // with a cancellation API\n\ - \ None;\n\ - \ });\n\ - \n\ - \ \n\ - \ {switch (state) {\n\ - \ | ErrorFetchingDogs => React.string(\"An error occurred!\")\n\ - \ | LoadingDogs => React.string(\"Loading...\")\n\ - \ | LoadedDogs(dogs) =>\n\ - \ dogs\n\ - \ ->Belt.Array.mapWithIndex((i, dog) => {\n\ - \ let imageStyle =\n\ - \ ReactDOMRe.Style.make(\n\ - \ ~height=\"120px\",\n\ - \ ~width=\"100%\",\n\ - \ ~marginRight=i === Js.Array.length(dogs) - 1 ? \"0px\" : \"8px\",\n\ - \ ~borderRadius=\"8px\",\n\ - \ ~boxShadow=\"0px 4px 16px rgb(200, 200, 200)\",\n\ - \ ~backgroundSize=\"cover\",\n\ - \ ~backgroundImage={j|url($dog)|j},\n\ - \ ~backgroundPosition=\"center\",\n\ - \ (),\n\ - \ );\n\ - \
;\n\ - \ })\n\ - \ ->React.array\n\ - \ }}\n\ - \
;\n\ - };\n\ - ")]) ; - Dir ("BlinkingGreeting", [ - File ("BlinkingGreeting.re", - "[@react.component]\n\ - let make = (~children) => {\n\ - \ let (show, setShow) = React.useState(() => true);\n\ - \n\ - \ // Notice that instead of `useEffect`, we have `useEffect0`. See\n\ - \ // reasonml.github.io/reason-react/docs/en/components#hooks for more info\n\ - \ React.useEffect0(() => {\n\ - \ let id =\n\ - \ Js.Global.setInterval(\n\ - \ () => setShow(previousShow => !previousShow),\n\ - \ 1000,\n\ - \ );\n\ - \n\ - \ Some(() => Js.Global.clearInterval(id));\n\ - \ });\n\ - \n\ - \ let style =\n\ - \ if (show) {\n\ - \ ReactDOMRe.Style.make(~opacity=\"1\", ~transition=\"opacity 1s\", ());\n\ - \ } else {\n\ - \ ReactDOMRe.Style.make(~opacity=\"0\", ~transition=\"opacity 1s\", ());\n\ - \ };\n\ - \n\ - \
children
;\n\ - };\n\ - ")]) ; - Dir ("ReasonUsingJSUsingReason", [ - File ("ReasonUsingJSUsingReason.re", - "// In this Interop example folder, we have:\n\ - // - A ReasonReact component, ReasonReactCard.re\n\ - // - Used by a ReactJS component, ReactJSCard.js\n\ - // - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re (this file)\n\ - // - ReasonUsingJSUsingReason.re is used by Index.re\n\ - \n\ - // All you need to do to use a ReactJS component in ReasonReact, is to write the lines below!\n\ - // reasonml.github.io/reason-react/docs/en/components#import-from-js\n\ - [@react.component] [@bs.module]\n\ - external make: unit => React.element = \"./ReactJSCard\";\n\ - ") ; - File ("ReasonReactCard.re", - "// In this Interop example folder, we have:\n\ - // - A ReasonReact component, ReasonReactCard.re (this file)\n\ - // - Used by a ReactJS component, ReactJSCard.js\n\ - // - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re\n\ - // - ReasonUsingJSUsingReason.re is used by Index.re\n\ - \n\ - [@react.component]\n\ - let make = (~style) => {\n\ - \
{React.string(\"This is a ReasonReact card\")}
;\n\ - };\n\ - ") ; - File ("ReactJSCard.js", - "// In this Interop example folder, we have:\n\ - // - A ReasonReact component, ReasonReactCard.re\n\ - // - Used by a ReactJS component, ReactJSCard.js (this file)\n\ - // - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re\n\ - // - ReasonUsingJSUsingReason.re is used by Index.re\n\ - \n\ - var ReactDOM = require('react-dom');\n\ - var React = require('react');\n\ - \n\ - var ReasonReactCard = require('./ReasonReactCard.bs').make;\n\ - \n\ - var ReactJSComponent = function() {\n\ - \ let backgroundColor = \"rgba(0, 0, 0, 0.05)\";\n\ - \ let padding = \"12px\";\n\ - \n\ - \ // We're not using JSX here, to avoid folks needing to install the related\n\ - \ // React toolchains just for this example.\n\ - \ //
\n\ - \ //
This is a ReactJS card
\n\ - \ // \n\ - \ //
\n\ - \ return React.createElement(\n\ - \ \"div\",\n\ - \ {style: {backgroundColor, padding, borderRadius: \"8px\"}},\n\ - \ React.createElement(\"div\", {style: {marginBottom: \"8px\"}}, \"This is a ReactJS card\"),\n\ - \ React.createElement(ReasonReactCard, {style: {backgroundColor, padding, borderRadius: \"4px\"}}),\n\ - \ )\n\ - };\n\ - ReactJSComponent.displayName = \"MyBanner\";\n\ - \n\ - module.exports = ReactJSComponent;\n\ - ")]) ; - Dir ("ReducerFromReactJSDocs", [ - File ("ReducerFromReactJSDocs.re", - "// This is the ReactJS documentation's useReducer example, directly ported over\n\ - // https://reactjs.org/docs/hooks-reference.html#usereducer\n\ - \n\ - // A little extra we've put, because the ReactJS example has no styling\n\ - let leftButtonStyle = ReactDOMRe.Style.make(~borderRadius=\"4px 0px 0px 4px\", ~width=\"48px\", ());\n\ - let rightButtonStyle = ReactDOMRe.Style.make(~borderRadius=\"0px 4px 4px 0px\", ~width=\"48px\", ());\n\ - let containerStyle = ReactDOMRe.Style.make(~display=\"flex\", ~alignItems=\"center\", ~justifyContent=\"space-between\", ());\n\ - \n\ - // Record and variant need explicit declarations.\n\ - type state = {count: int};\n\ - \n\ - type action =\n\ - \ | Increment\n\ - \ | Decrement;\n\ - \n\ - let initialState = {count: 0};\n\ - \n\ - let reducer = (state, action) => {\n\ - \ switch (action) {\n\ - \ | Increment => {count: state.count + 1}\n\ - \ | Decrement => {count: state.count - 1}\n\ - \ };\n\ - };\n\ - \n\ - [@react.component]\n\ - let make = () => {\n\ - \ let (state, dispatch) = React.useReducer(reducer, initialState);\n\ - \n\ - \ // We can use a fragment here, but we don't, because we want to style the counter\n\ - \
\n\ - \
\n\ - \ {React.string(\"Count: \")}\n\ - \ {React.string(string_of_int(state.count))}\n\ - \
\n\ - \
\n\ - \ \n\ - \ \n\ - \
\n\ - \
;\n\ - };\n\ - ")])]) ; - File ("UNUSED_webpack.config.js", - "const path = require('path');\n\ - \n\ - module.exports = {\n\ - \ entry: './src/Index.bs.js',\n\ - \ // If you ever want to use webpack during development, change 'production'\n\ - \ // to 'development' as per webpack documentation. Again, you don't have to\n\ - \ // use webpack or any other bundler during development! Recheck README if\n\ - \ // you didn't know this\n\ - \ mode: 'production',\n\ - \ output: {\n\ - \ path: path.join(__dirname, \"bundleOutput\"),\n\ - \ filename: 'index.js',\n\ - \ },\n\ - };") ; - File ("bsconfig.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"reason\": {\n\ - \ \"react-jsx\": 3\n\ - \ },\n\ - \ \"sources\": {\n\ - \ \"dir\" : \"src\",\n\ - \ \"subdirs\" : true\n\ - \ },\n\ - \ \"bsc-flags\": [\"-bs-super-errors\", \"-bs-no-version-header\"],\n\ - \ \"package-specs\": [{\n\ - \ \"module\": \"commonjs\",\n\ - \ \"in-source\": true\n\ - \ }],\n\ - \ \"suffix\": \".bs.js\",\n\ - \ \"namespace\": true,\n\ - \ \"bs-dependencies\": [\n\ - \ \"reason-react\"\n\ - \ ],\n\ - \ \"refmt\": 3\n\ - }\n\ - ") ; - File ("watcher.js", - "// This is our simple, robust watcher. It hooks into the BuckleScript build\n\ - // system to listen for build events.\n\ - // See package.json's `start` script and `./node_modules/.bin/bsb --help`\n\ - \n\ - // Btw, if you change this file and reload the page, your browser cache\n\ - // _might_ not pick up the new version. If you're in Chrome, do Force Reload.\n\ - \n\ - var websocketReloader;\n\ - var LAST_SUCCESS_BUILD_STAMP = localStorage.getItem('LAST_SUCCESS_BUILD_STAMP') || 0;\n\ - // package.json's `start` script's `bsb -ws _` means it'll pipe build events\n\ - // through a websocket connection to a default port of 9999. This is\n\ - // configurable, e.g. `-ws 5000`\n\ - var webSocketPort = 9999;\n\ - \n\ - function setUpWebSocket() {\n\ - \ if (websocketReloader == null || websocketReloader.readyState !== 1) {\n\ - \ try {\n\ - \ websocketReloader = new WebSocket(`ws://${window.location.hostname}:${webSocketPort}`);\n\ - \ websocketReloader.onmessage = (message) => {\n\ - \ var newData = JSON.parse(message.data).LAST_SUCCESS_BUILD_STAMP;\n\ - \ if (newData > LAST_SUCCESS_BUILD_STAMP) {\n\ - \ LAST_SUCCESS_BUILD_STAMP = newData;\n\ - \ localStorage.setItem('LAST_SUCCESS_BUILD_STAMP', LAST_SUCCESS_BUILD_STAMP);\n\ - \ // Refresh the page! This will naturally re-run everything,\n\ - \ // including our moduleserve which will re-resolve all the modules.\n\ - \ // No stable build!\n\ - \ location.reload(true);\n\ - \ }\n\ - \n\ - \ }\n\ - \ } catch (exn) {\n\ - \ console.error(\"The watcher tried to connect to web socket, but failed. Here's the message:\");\n\ - \ console.error(exn);\n\ - \ }\n\ - \ }\n\ - };\n\ - \n\ - setUpWebSocket();\n\ - setInterval(setUpWebSocket, 2000);\n\ - ") ; - File ("package.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"scripts\": {\n\ - \ \"build\": \"bsb -make-world\",\n\ - \ \"start\": \"bsb -make-world -w -ws _ \",\n\ - \ \"clean\": \"bsb -clean-world\",\n\ - \ \"server\": \"moduleserve ./ --port 8000\",\n\ - \ \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n\ - \ },\n\ - \ \"keywords\": [\n\ - \ \"BuckleScript\",\n\ - \ \"ReasonReact\",\n\ - \ \"reason-react\"\n\ - \ ],\n\ - \ \"author\": \"\",\n\ - \ \"license\": \"MIT\",\n\ - \ \"dependencies\": {\n\ - \ \"react\": \"^16.8.1\",\n\ - \ \"react-dom\": \"^16.8.1\",\n\ - \ \"reason-react\": \">=0.7.1\"\n\ - \ },\n\ - \ \"devDependencies\": {\n\ - \ \"bs-platform\": \"^${bsb:bs-version}\",\n\ - \ \"moduleserve\": \"^0.9.0\"\n\ - \ }\n\ - }\n\ - ") ; - File (".gitignore", - ".DS_Store\n\ - .merlin\n\ - .bsb.lock\n\ - npm-debug.log\n\ - /lib/bs/\n\ - /node_modules/\n\ - /bundleOutput/") ; - File ("README.md", - "# ReasonReact Template & Examples\n\ - \n\ - This is:\n\ - - A template for your new ReasonReact project.\n\ - - A collection of thin examples illustrating ReasonReact usage.\n\ - - Extra helper documentation for ReasonReact (full ReasonReact docs [here](https://reasonml.github.io/reason-react/)).\n\ - \n\ - `src` contains 4 sub-folders, each an independent, self-contained ReasonReact example. Feel free to delete any of them and shape this into your project! This template's more malleable than you might be used to =).\n\ - \n\ - The point of this template and examples is to let you understand and personally tweak the entirely of it. We **don't** give you an opaque, elaborate mega build setup just to put some boxes on the screen. It strikes to stay transparent, learnable, and simple. You're encouraged to read every file; it's a great feeling, having the full picture of what you're using and being able to touch any part.\n\ - \n\ - ## Run\n\ - \n\ - ```sh\n\ - npm install\n\ - npm run server\n\ - # in a new tab\n\ - npm start\n\ - ```\n\ - \n\ - Open a new web page to `http://localhost:8000/`. Change any `.re` file in `src` to see the page auto-reload. **You don't need any bundler when you're developing**!\n\ - \n\ - **How come we don't need any bundler during development**? We highly encourage you to open up `index.html` to check for yourself!\n\ - \n\ - # Features Used\n\ - \n\ - | | Blinking Greeting | Reducer from ReactJS Docs | Fetch Dog Pictures | Reason Using JS Using Reason |\n\ - |---------------------------|-------------------|---------------------------|--------------------|------------------------------|\n\ - | No props | | \xE2\x9C\x93 | | |\n\ - | Has props | | | | \xE2\x9C\x93 |\n\ - | Children props | \xE2\x9C\x93 | | | |\n\ - | No state | | | | \xE2\x9C\x93 |\n\ - | Has state | \xE2\x9C\x93 | | \xE2\x9C\x93 | |\n\ - | Has state with useReducer | | \xE2\x9C\x93 | | |\n\ - | ReasonReact using ReactJS | | | | \xE2\x9C\x93 |\n\ - | ReactJS using ReasonReact | | | | \xE2\x9C\x93 |\n\ - | useEffect | \xE2\x9C\x93 | | \xE2\x9C\x93 | |\n\ - | Dom attribute | \xE2\x9C\x93 | \xE2\x9C\x93 | | \xE2\x9C\x93 |\n\ - | Styling | \xE2\x9C\x93 | \xE2\x9C\x93 | \xE2\x9C\x93 | \xE2\x9C\x93 |\n\ - | React.array | | | \xE2\x9C\x93 | |\n\ - \n\ - # Bundle for Production\n\ - \n\ - We've included a convenience `UNUSED_webpack.config.js`, in case you want to ship your project to production. You can rename and/or remove that in favor of other bundlers, e.g. Rollup.\n\ - \n\ - We've also provided a barebone `indexProduction.html`, to serve your bundle.\n\ - \n\ - ```sh\n\ - npm install webpack webpack-cli\n\ - # rename file\n\ - mv UNUSED_webpack.config.js webpack.config.js\n\ - # call webpack to bundle for production\n\ - ./node_modules/.bin/webpack\n\ - open indexProduction.html\n\ - ```\n\ - \n\ - # Handle Routing Yourself\n\ - \n\ - To serve the files, this template uses a minimal dependency called `moduleserve`. A URL such as `localhost:8000/scores/john` resolves to the file `scores/john.html`. If you'd like to override this and handle URL resolution yourself, change the `server` command in `package.json` from `moduleserve ./ --port 8000` to `moduleserve ./ --port 8000 --spa` (for \"single page application\"). This will make `moduleserve` serve the default `index.html` for any URL. Since `index.html` loads `Index.bs.js`, you can grab hold of the URL in the corresponding `Index.re` and do whatever you want.\n\ - \n\ - By the way, ReasonReact comes with a small [router](https://reasonml.github.io/reason-react/docs/en/router) you might be interested in.\n\ - ") ; - File ("indexProduction.html", - "\n\ - \n\ - \n\ - \ \n\ - \ ReasonReact Examples\n\ - \n\ - \n\ - \ \n\ - \n\ - \n\ - ") ; - File ("index.html", - "\n\ - \n\ - \n\ - \ \n\ - \ ReasonReact Examples\n\ - \n\ - \n\ - \ \n\ - \n\ - \ \n\ - \ \n\ - \ \n\ - \ \n\ - \n\ - \n\ - ")]) ; - Dir ("react-starter", [ - Dir ("src", [ - File ("Index.re", - "[%bs.raw {|require(\"./index.css\")|}];\n\ - \n\ - ReactDOMRe.renderToElementWithId(, \"root\");\n\ - ") ; - File ("index.css", - "body {\n\ - \ margin: 0;\n\ - \ font-family: -apple-system, system-ui, \"Segoe UI\", Helvetica, Arial,\n\ - \ sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n\ - }\n\ - \n\ - main {\n\ - \ padding: 20px;\n\ - }\n\ - \n\ - .counter {\n\ - \ padding: 20px;\n\ - \ display: inline-block;\n\ - }\n\ - ") ; - File ("App.re", - "type state = {count: int};\n\ - \n\ - type action =\n\ - \ | Increment\n\ - \ | Decrement;\n\ - \n\ - let initialState = {count: 0};\n\ - \n\ - let reducer = (state, action) =>\n\ - \ switch (action) {\n\ - \ | Increment => {count: state.count + 1}\n\ - \ | Decrement => {count: state.count - 1}\n\ - \ };\n\ - \n\ - [@react.component]\n\ - let make = () => {\n\ - \ let (state, dispatch) = React.useReducer(reducer, initialState);\n\ - \n\ - \
\n\ - \ {React.string(\"Simple counter with reducer\")}\n\ - \
\n\ - \ \n\ - \ \n\ - \ {state.count |> string_of_int |> React.string}\n\ - \ \n\ - \ \n\ - \
\n\ - \
;\n\ - };\n\ - ") ; - File ("index.html", - "\n\ - \n\ - \ \n\ - \ \n\ - \ Reason react starter\n\ - \ \n\ - \ \n\ - \
\n\ - \ \n\ - \ \n\ - \n\ - ")]) ; - File ("bsconfig.json", - "{\n\ - \ \"name\": \"reason-react-starter\",\n\ - \ \"reason\": {\n\ - \ \"react-jsx\": 3\n\ - \ },\n\ - \ \"sources\": {\n\ - \ \"dir\": \"src\",\n\ - \ \"subdirs\": true\n\ - \ },\n\ - \ \"bsc-flags\": [\"-bs-super-errors\", \"-bs-no-version-header\"],\n\ - \ \"package-specs\": [\n\ - \ {\n\ - \ \"module\": \"commonjs\",\n\ - \ \"in-source\": true\n\ - \ }\n\ - \ ],\n\ - \ \"suffix\": \".bs.js\",\n\ - \ \"namespace\": true,\n\ - \ \"bs-dependencies\": [\"reason-react\"],\n\ - \ \"refmt\": 3\n\ - }\n\ - ") ; - File ("package.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"scripts\": {\n\ - \ \"build\": \"bsb -make-world\",\n\ - \ \"start\": \"bsb -make-world -w -ws _ \",\n\ - \ \"clean\": \"bsb -clean-world\",\n\ - \ \"webpack\": \"webpack -w\",\n\ - \ \"webpack:production\": \"NODE_ENV=production webpack\",\n\ - \ \"server\": \"webpack-dev-server\",\n\ - \ \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n\ - \ },\n\ - \ \"keywords\": [\n\ - \ \"BuckleScript\",\n\ - \ \"ReasonReact\",\n\ - \ \"reason-react\"\n\ - \ ],\n\ - \ \"author\": \"\",\n\ - \ \"license\": \"MIT\",\n\ - \ \"dependencies\": {\n\ - \ \"react\": \"^17.0.1\",\n\ - \ \"react-dom\": \"^17.0.1\",\n\ - \ \"reason-react\": \"^0.9.1\"\n\ - \ },\n\ - \ \"devDependencies\": {\n\ - \ \"bs-platform\": \"^${bsb:bs-version}\",\n\ - \ \"css-loader\": \"^5.0.0\",\n\ - \ \"html-webpack-plugin\": \"^4.5.0\",\n\ - \ \"style-loader\": \"^2.0.0\",\n\ - \ \"webpack\": \"^4.44.2\",\n\ - \ \"webpack-cli\": \"^3.3.12\",\n\ - \ \"webpack-dev-server\": \"^3.11.0\"\n\ - \ }\n\ - }\n\ - ") ; - File (".gitignore", - ".DS_Store\n\ - .merlin\n\ - .bsb.lock\n\ - npm-debug.log\n\ - /lib/bs/\n\ - /node_modules/\n\ - *.bs.js\n\ - ") ; - File ("README.md", - "# Reason react starter\n\ - \n\ - ## Run Project\n\ - \n\ - ```sh\n\ - npm install\n\ - npm start\n\ - # in another tab\n\ - npm run server\n\ - ```\n\ - \n\ - View the app in the browser at http://localhost:8000. Running in this environment provides hot reloading and support for routing; just edit and save the file and the browser will automatically refresh.\n\ - \n\ - To use a port other than 8000 set the `PORT` environment variable (`PORT=8080 npm run server`).\n\ - \n\ - ## Build for Production\n\ - \n\ - ```sh\n\ - npm run clean\n\ - npm run build\n\ - npm run webpack:production\n\ - ```\n\ - \n\ - This will replace the development artifact `build/Index.js` for an optimized version as well as copy `src/index.html` into `build/`. You can then deploy the contents of the `build` directory (`index.html` and `Index.js`).\n\ - \n\ - **To enable dead code elimination**, change `bsconfig.json`'s `package-specs` `module` from `\"commonjs\"` to `\"es6\"`. Then re-run the above 2 commands. This will allow Webpack to remove unused code.\n\ - ") ; - File ("webpack.config.js", - "const path = require(\"path\")\n\ - const HtmlWebpackPlugin = require(\"html-webpack-plugin\")\n\ - const outputDir = path.join(__dirname, \"build/\")\n\ - \n\ - const isProd = process.env.NODE_ENV === \"production\"\n\ - \n\ - module.exports = {\n\ - \ entry: \"./src/Index.bs.js\",\n\ - \ mode: isProd ? \"production\" : \"development\",\n\ - \ devtool: \"source-map\",\n\ - \ output: {\n\ - \ path: outputDir,\n\ - \ filename: \"Index.js\"\n\ - \ },\n\ - \ plugins: [\n\ - \ new HtmlWebpackPlugin({\n\ - \ template: \"src/index.html\",\n\ - \ inject: false\n\ - \ })\n\ - \ ],\n\ - \ devServer: {\n\ - \ compress: true,\n\ - \ contentBase: outputDir,\n\ - \ port: process.env.PORT || 8000,\n\ - \ historyApiFallback: true\n\ - \ },\n\ - \ module: {\n\ - \ rules: [\n\ - \ {\n\ - \ test: /\\.css$/,\n\ - \ use: [\"style-loader\", \"css-loader\"]\n\ - \ }\n\ - \ ]\n\ - \ }\n\ - }\n\ - ")]) ; - Dir ("tea", [ - Dir ("src", [ - File ("main.ml", - "\n\ - \n\ - \n\ - Js.Global.setTimeout\n\ - \ (fun _ -> \n\ - \ Demo.main (Web.Document.getElementById \"my-element\") () \n\ - \ |. ignore\n\ - \ ) \n\ - 0") ; - File ("demo.ml", - "(* This line opens the Tea.App modules into the current scope for Program access functions and types *)\n\ - open Tea.App\n\ - \n\ - (* This opens the Elm-style virtual-dom functions and types into the current scope *)\n\ - open Tea.Html\n\ - \n\ - (* Let's create a new type here to be our main message type that is passed around *)\n\ - type msg =\n\ - \ | Increment (* This will be our message to increment the counter *)\n\ - \ | Decrement (* This will be our message to decrement the counter *)\n\ - \ | Reset (* This will be our message to reset the counter to 0 *)\n\ - \ | Set of int (* This will be our message to set the counter to a specific value *)\n\ - \ [@@bs.deriving {accessors}] (* This is a nice quality-of-life addon from Bucklescript, it will generate function names for each constructor name, optional, but nice to cut down on code, this is unused in this example but good to have regardless *)\n\ - \n\ - (* This is optional for such a simple example, but it is good to have an `init` function to define your initial model default values, the model for Counter is just an integer *)\n\ - let init () = 4\n\ - \n\ - (* This is the central message handler, it takes the model as the first argument *)\n\ - let update model = function (* These should be simple enough to be self-explanatory, mutate the model based on the message, easy to read and follow *)\n\ - \ | Increment -> model + 1\n\ - \ | Decrement -> model - 1\n\ - \ | Reset -> 0\n\ - \ | Set v -> v\n\ - \n\ - (* This is just a helper function for the view, a simple function that returns a button based on some argument *)\n\ - let view_button title msg =\n\ - \ button\n\ - \ [ onClick msg\n\ - \ ]\n\ - \ [ text title\n\ - \ ]\n\ - \n\ - (* This is the main callback to generate the virtual-dom.\n\ - \ This returns a virtual-dom node that becomes the view, only changes from call-to-call are set on the real DOM for efficiency, this is also only called once per frame even with many messages sent in within that frame, otherwise does nothing *)\n\ - let view model =\n\ - \ div\n\ - \ []\n\ - \ [ span\n\ - \ [ style \"text-weight\" \"bold\" ]\n\ - \ [ text (string_of_int model) ]\n\ - \ ; br []\n\ - \ ; view_button \"Increment\" Increment\n\ - \ ; br []\n\ - \ ; view_button \"Decrement\" Decrement\n\ - \ ; br []\n\ - \ ; view_button \"Set to 2\" (Set 42)\n\ - \ ; br []\n\ - \ ; if model <> 0 then view_button \"Reset\" Reset else noNode\n\ - \ ]\n\ - \n\ - (* This is the main function, it can be named anything you want but `main` is traditional.\n\ - \ The Program returned here has a set of callbacks that can easily be called from\n\ - \ Bucklescript or from javascript for running this main attached to an element,\n\ - \ or even to pass a message into the event loop. You can even expose the\n\ - \ constructors to the messages to javascript via the above [@@bs.deriving {accessors}]\n\ - \ attribute on the `msg` type or manually, that way even javascript can use it safely. *)\n\ - let main =\n\ - \ beginnerProgram { (* The beginnerProgram just takes a set model state and the update and view functions *)\n\ - \ model = init (); (* Since model is a set value here, we call our init function to generate that value *)\n\ - \ update;\n\ - \ view;\n\ - \ }")]) ; - File ("loader.js", - "/* Copyright (C) 2018 Authors of BuckleScript\n\ - \ * \n\ - \ * This program is free software: you can redistribute it and/or modify\n\ - \ * it under the terms of the GNU Lesser General Public License as published by\n\ - \ * the Free Software Foundation, either version 3 of the License, or\n\ - \ * (at your option) any later version.\n\ - \ *\n\ - \ * In addition to the permissions granted to you by the LGPL, you may combine\n\ - \ * or link a \"work that uses the Library\" with a publicly distributed version\n\ - \ * of this file to produce a combined library or application, then distribute\n\ - \ * that combined work under the terms of your choosing, with no requirement\n\ - \ * to comply with the obligations normally placed on you by section 4 of the\n\ - \ * LGPL version 3 (or the corresponding section of a later version of the LGPL\n\ - \ * should you choose to use a later version).\n\ - \ *\n\ - \ * This program is distributed in the hope that it will be useful,\n\ - \ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n\ - \ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\ - \ * GNU Lesser General Public License for more details.\n\ - \ * \n\ - \ * You should have received a copy of the GNU Lesser General Public License\n\ - \ * along with this program; if not, write to the Free Software\n\ - \ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */\n\ - \n\ - \n\ - \n\ - //@ts-check\n\ - \n\ - // @ts-ignore\n\ - window.process = { env: { NODE_ENV: 'dev' } }\n\ - \n\ - \n\ - // local to getPath\n\ - var relativeElement = document.createElement(\"a\");\n\ - var baseElement = document.createElement(\"base\");\n\ - document.head.appendChild(baseElement);\n\ - \n\ - export function BsGetPath(id, parent) {\n\ - \ var oldPath = baseElement.href\n\ - \ baseElement.href = parent\n\ - \ relativeElement.href = id\n\ - \ var result = relativeElement.href\n\ - \ baseElement.href = oldPath\n\ - \ return result\n\ - }\n\ - /**\n\ - \ * \n\ - \ * Given current link and its parent, return the new link\n\ - \ * @param {string} id \n\ - \ * @param {string} parent \n\ - \ * @return {string}\n\ - \ */\n\ - function getPathWithJsSuffix(id, parent) {\n\ - \ var oldPath = baseElement.href\n\ - \ baseElement.href = parent\n\ - \ relativeElement.href = id\n\ - \ var result = addSuffixJsIfNot(relativeElement.href)\n\ - \ baseElement.href = oldPath\n\ - \ return result\n\ - }\n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} x \n\ - \ */\n\ - function addSuffixJsIfNot(x) {\n\ - \ if (x.endsWith('.js')) {\n\ - \ return x\n\ - \ } else {\n\ - \ return x + '.js'\n\ - \ }\n\ - }\n\ - \n\ - \n\ - var falsePromise = Promise.resolve(false)\n\ - var fetchConfig = {'cache' : 'no-cache'}\n\ - // package.json semantics\n\ - // a string to module object \n\ - // from url -> module object \n\ - // Modules : Map \n\ - // fetch the link:\n\ - // - if it is already fetched before, return the stored promise\n\ - // otherwise create the promise which will be filled with the text if successful\n\ - // or filled with boolean false when failed\n\ - var MODULES = new Map()\n\ - function cachedFetch(link) {\n\ - \ // console.info(link)\n\ - \ var linkResult = MODULES.get(link)\n\ - \ if (linkResult) {\n\ - \ return linkResult\n\ - \ } else {\n\ - \ var p = fetch(link, fetchConfig)\n\ - \ .then(resp => {\n\ - \ if (resp.ok) {\n\ - \ return resp.text()\n\ - \ } else {\n\ - \ return falsePromise\n\ - \ }\n\ - \ })\n\ - \n\ - \ MODULES.set(link, p)\n\ - \ return p\n\ - \ }\n\ - }\n\ - \n\ - // from location id -> url \n\ - // There are two rounds of caching:\n\ - // 1. if location and relative path is hit, no need to run \n\ - // 2. if location and relative path is not hit, but the resolved link is hit, no need \n\ - // for network request\n\ - /**\n\ - \ * @type {Map > > }\n\ - \ */\n\ - var IDLocations = new Map()\n\ - \n\ - /**\n\ - \ * @type {Map > }\n\ - \ */\n\ - var SyncedIDLocations = new Map()\n\ - // Its value is an object \n\ - // { link : String }\n\ - // We will first mark it when visiting (to avoid duplicated computation)\n\ - // and populate its link later\n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} id \n\ - \ * @param {string} location \n\ - \ */\n\ - function getIdLocation(id, location) {\n\ - \ var idMap = IDLocations.get(location)\n\ - \ if (idMap) {\n\ - \ return idMap.get(id)\n\ - \ }\n\ - }\n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} id \n\ - \ * @param {string} location \n\ - \ */\n\ - function getIdLocationSync(id, location) {\n\ - \ var idMap = SyncedIDLocations.get(location)\n\ - \ if (idMap) {\n\ - \ return idMap.get(id)\n\ - \ }\n\ - }\n\ - \n\ - function countIDLocations() {\n\ - \ var count = 0\n\ - \ for (let [k, vv] of IDLocations) {\n\ - \ for (let [kv, v] of vv) {\n\ - \ count += 1\n\ - \ }\n\ - \ }\n\ - \ console.log(count, 'modules loaded')\n\ - }\n\ - \n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} id \n\ - \ * @param {string} location \n\ - \ * @param {Function} cb \n\ - \ * @returns Promise\n\ - \ */\n\ - function visitIdLocation(id, location, cb) {\n\ - \ var result;\n\ - \ var idMap = IDLocations.get(location)\n\ - \ if (idMap && (result = idMap.get(id))) {\n\ - \ return result\n\ - \ }\n\ - \ else {\n\ - \ result = new Promise(resolve => {\n\ - \ return (cb()).then(res => {\n\ - \ var idMap = SyncedIDLocations.get(location)\n\ - \ if (idMap) {\n\ - \ idMap.set(id, res)\n\ - \ } else {\n\ - \ SyncedIDLocations.set(location, new Map([[id, res]]))\n\ - \ }\n\ - \ return resolve(res)\n\ - \ })\n\ - \ })\n\ - \ if (idMap) {\n\ - \ idMap.set(id, result)\n\ - \ }\n\ - \ else {\n\ - \ IDLocations.set(location, new Map([[id, result]]))\n\ - \ }\n\ - \ return result\n\ - \ }\n\ - }\n\ - \n\ - \n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} text \n\ - \ * @return {string[]}\n\ - \ */\n\ - function getDeps(text) {\n\ - \ var deps = []\n\ - \ text.replace(/(\\/\\*[\\w\\W]*?\\*\\/|\\/\\/[^\\n]*|[.$]r)|\\brequire\\s*\\(\\s*[\"']([^\"']*)[\"']\\s*\\)/g, function (_, ignore, id) {\n\ - \ if (!ignore) deps.push(id);\n\ - \ });\n\ - \ return deps;\n\ - }\n\ - \n\ - \n\ - \n\ - // By using a named \"eval\" most browsers will execute in the global scope.\n\ - // http://www.davidflanagan.com/2010/12/global-eval-in.html\n\ - var globalEval = eval;\n\ - \n\ - // function parentURL(url) {\n\ - // if (url.endsWith('/')) {\n\ - // return url + '../'\n\ - // } else {\n\ - // return url + '/../'\n\ - // }\n\ - // }\n\ - \n\ - \n\ - \n\ - // loader.js:23 http://localhost:8080/node_modules/react-dom/cjs/react-dom.development.js/..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//../ fbjs/lib/containsNode Promise\xC2\xA0{}\n\ - // 23:10:02.884 loader.js:23 http://localhost:8080/node_modules/react-dom/cjs/react-dom.development.js/..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//../ fbjs/lib/invariant Promise\xC2\xA0{}\n\ - \n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} id \n\ - \ * @param {string} parent \n\ - \ */\n\ - function getParentModulePromise(id, parent) {\n\ - \ var parentLink = BsGetPath('..', parent)\n\ - \ if (parentLink === parent) {\n\ - \ return falsePromise\n\ - \ }\n\ - \ return getPackageJsPromise(id, parentLink)\n\ - }\n\ - // In the beginning\n\ - // it is `resolveModule('./main.js', '')\n\ - // return the promise of link and text \n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} id \n\ - \ */\n\ - function getPackageName(id) {\n\ - \ var index = id.indexOf('/')\n\ - \ if (id[0] === '@') index = id.indexOf('/', index + 1)\n\ - \ if (index === -1) {\n\ - \ return id\n\ - \ }\n\ - \ return id.substring(0, index)\n\ - }\n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} s \n\ - \ * @param {string} text \n\ - \ * @returns {undefined | string }\n\ - \ */\n\ - function isJustAPackageAndHasMainField(s,text){\n\ - \ if(s.indexOf('/') >= 0){\n\ - \ return \n\ + +type node = + | Dir of string * node list + | File of string * string +let root = ([ + Dir("basic",[ + File(".gitignore", + "*.exe\n\ + *.obj\n\ + *.out\n\ + *.compile\n\ + *.native\n\ + *.byte\n\ + *.cmo\n\ + *.annot\n\ + *.cmi\n\ + *.cmx\n\ + *.cmt\n\ + *.cmti\n\ + *.cma\n\ + *.a\n\ + *.cmxa\n\ + *.obj\n\ + *~\n\ + *.annot\n\ + *.cmj\n\ + *.bak\n\ + lib/bs\n\ + *.mlast\n\ + *.mliast\n\ + .vscode\n\ + .merlin\n\ + .bsb.lock\n\ + /node_modules/\n\ + " + ); + File("README.md", + "\n\ + \n\ + # Build\n\ + ```\n\ + npm run build\n\ + ```\n\ + \n\ + # Watch\n\ + \n\ + ```\n\ + npm run watch\n\ + ```\n\ + \n\ + " + ); + File("bsconfig.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"sources\": {\n\ + \ \"dir\" : \"src\",\n\ + \ \"subdirs\" : true\n\ + \ },\n\ + \ \"warnings\": {\n\ + \ \"error\" : \"+101\"\n\ + \ }\n\ + }\n\ + " + ); + File("package.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"scripts\": {\n\ + \ \"clean\": \"bsb -clean-world\",\n\ + \ \"build\": \"bsb -make-world\",\n\ + \ \"watch\": \"bsb -make-world -w\"\n\ + \ },\n\ + \ \"keywords\": [\n\ + \ \"BuckleScript\"\n\ + \ ],\n\ + \ \"author\": \"\",\n\ + \ \"license\": \"MIT\",\n\ + \ \"devDependencies\": {\n\ + \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ + \ }\n\ + }" + ); + Dir("src",[ + File("demo.ml", + "\n\ + \n\ + let () = Js.log \"Hello, BuckleScript\"" + ) + ]) + ]); + Dir("basic-reason",[ + File(".gitignore", + ".DS_Store\n\ + .merlin\n\ + .bsb.lock\n\ + npm-debug.log\n\ + /lib/bs/\n\ + /node_modules/\n\ + " + ); + File("README.md", + "# Basic Reason Template\n\ + \n\ + Hello! This project allows you to quickly get started with ReScript using Reason syntax. If you wanted a more sophisticated version, try the `react` template (`bsb -theme react -init .`).\n\ + \n\ + # Build\n\ + \n\ + ```bash\n\ + # for yarn\n\ + yarn build\n\ + \n\ + # for npm\n\ + npm run build\n\ + ```\n\ + \n\ + # Build + Watch\n\ + \n\ + ```bash\n\ + # for yarn\n\ + yarn start\n\ + \n\ + # for npm\n\ + npm run start\n\ + ```\n\ + \n\ + " + ); + File("bsconfig.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"sources\": {\n\ + \ \"dir\" : \"src\",\n\ + \ \"subdirs\" : true\n\ + \ },\n\ + \ \"package-specs\": {\n\ + \ \"module\": \"commonjs\",\n\ + \ \"in-source\": true\n\ + \ },\n\ + \ \"suffix\": \".bs.js\",\n\ + \ \"bs-dependencies\": [\n\ + \n\ + \ ],\n\ + \ \"warnings\": {\n\ + \ \"error\" : \"+101\"\n\ + \ },\n\ + \ \"namespace\": true,\n\ + \ \"refmt\": 3\n\ + }\n\ + " + ); + File("package.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"scripts\": {\n\ + \ \"build\": \"bsb -make-world\",\n\ + \ \"start\": \"bsb -make-world -w\",\n\ + \ \"clean\": \"bsb -clean-world\"\n\ + \ },\n\ + \ \"keywords\": [\n\ + \ \"BuckleScript\"\n\ + \ ],\n\ + \ \"author\": \"\",\n\ + \ \"license\": \"MIT\",\n\ + \ \"devDependencies\": {\n\ + \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ + \ }\n\ + }\n\ + " + ); + Dir("src",[ + File("Demo.re", + "Js.log(\"Hello, ReScript!\");\n\ + " + ) + ]) + ]); + Dir("generator",[ + File(".gitignore", + "*.exe\n\ + *.obj\n\ + *.out\n\ + *.compile\n\ + *.native\n\ + *.byte\n\ + *.cmo\n\ + *.annot\n\ + *.cmi\n\ + *.cmx\n\ + *.cmt\n\ + *.cmti\n\ + *.cma\n\ + *.a\n\ + *.cmxa\n\ + *.obj\n\ + *~\n\ + *.annot\n\ + *.cmj\n\ + *.bak\n\ + lib/bs\n\ + *.mlast\n\ + *.mliast\n\ + .vscode\n\ + .merlin\n\ + .bsb.lock\n\ + /node_modules/\n\ + " + ); + File("README.md", + "\n\ + \n\ + # Build\n\ + ```\n\ + npm run build\n\ + ```\n\ + \n\ + # Watch\n\ + \n\ + ```\n\ + npm run watch\n\ + ```\n\ + \n\ + \n\ + # Editor\n\ + If you use `vscode`, Press `Windows + Shift + B` it will build automatically" + ); + File("bsconfig.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"sources\": {\n\ + \ \"dir\": \"src\",\n\ + \ \"generators\": [{\n\ + \ \"name\": \"cpp\",\n\ + \ \"edge\": [\"test.ml\", \":\", \"test.cpp.ml\"]\n\ + \ }],\n\ + \ \"subdirs\": true \n\ + \ },\n\ + \ \"generators\": [{\n\ + \ \"name\" : \"cpp\",\n\ + \ \"command\": \"sed 's/OCAML/3/' $in > $out\"\n\ + \ }],\n\ + \ \"bs-dependencies\" : [\n\ + \ ]\n\ + }" + ); + File("package.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"scripts\": {\n\ + \ \"clean\": \"bsb -clean-world\",\n\ + \ \"build\": \"bsb -make-world\",\n\ + \ \"watch\": \"bsb -make-world -w\"\n\ + \ },\n\ + \ \"keywords\": [\n\ + \ \"BuckleScript\"\n\ + \ ],\n\ + \ \"author\": \"\",\n\ + \ \"license\": \"MIT\",\n\ + \ \"devDependencies\": {\n\ + \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ + \ }\n\ + }" + ); + Dir("src",[ + File("demo.ml", + "\n\ + \n\ + let () = Js.log \"Hello, BuckleScript\"" + ); + File("test.cpp.ml", + "\n\ + (* \n\ + #define FS_VAL(name,ty) external name : ty = \"\" [@@bs.module \"fs\"]\n\ + \n\ + \n\ + FS_VAL(readdirSync, string -> string array)\n\ + \ *)\n\ + \n\ + \n\ + \ let ocaml = OCAML" + ) + ]) + ]); + Dir("minimal",[ + File(".gitignore", + ".DS_Store\n\ + .merlin\n\ + .bsb.lock\n\ + npm-debug.log\n\ + /lib/bs/\n\ + /node_modules/" + ); + File("README.md", + "\n\ + \ # ${bsb:name}" + ); + File("bsconfig.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"sources\": {\n\ + \ \"dir\": \"src\",\n\ + \ \"subdirs\": true\n\ + \ }\n\ + }" + ); + File("package.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"scripts\": {\n\ + \ \"clean\": \"bsb -clean-world\",\n\ + \ \"build\": \"bsb -make-world\",\n\ + \ \"start\": \"bsb -make-world -w\"\n\ + \ },\n\ + \ \"keywords\": [\n\ + \ \"BuckleScript\"\n\ + \ ],\n\ + \ \"author\": \"\",\n\ + \ \"license\": \"MIT\",\n\ + \ \"devDependencies\": {\n\ + \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ + \ }\n\ + }" + ); + Dir("src",[ + File("main.ml", + "" + ) + ]) + ]); + Dir("node",[ + File(".gitignore", + "*.exe\n\ + *.obj\n\ + *.out\n\ + *.compile\n\ + *.native\n\ + *.byte\n\ + *.cmo\n\ + *.annot\n\ + *.cmi\n\ + *.cmx\n\ + *.cmt\n\ + *.cmti\n\ + *.cma\n\ + *.a\n\ + *.cmxa\n\ + *.obj\n\ + *~\n\ + *.annot\n\ + *.cmj\n\ + *.bak\n\ + lib/bs\n\ + *.mlast\n\ + *.mliast\n\ + .vscode\n\ + .merlin\n\ + .bsb.lock\n\ + /node_modules/\n\ + " + ); + File("README.md", + "\n\ + \n\ + # Build\n\ + ```\n\ + npm run build\n\ + ```\n\ + \n\ + # Watch\n\ + \n\ + ```\n\ + npm run watch\n\ + ```\n\ + \n\ + \n\ + # Editor\n\ + If you use `vscode`, Press `Windows + Shift + B` it will build automatically\n\ + " + ); + File("bsconfig.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"sources\": {\n\ + \ \"dir\": \"src\",\n\ + \ \"subdirs\" : true\n\ + \ },\n\ + \ \"package-specs\": {\n\ + \ \"module\": \"commonjs\",\n\ + \ \"in-source\": true\n\ + \ },\n\ + \ \"suffix\": \".bs.js\",\n\ + \ \"bs-dependencies\": [\n\ + \ ]\n\ + }" + ); + File("package.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"scripts\": {\n\ + \ \"clean\": \"bsb -clean-world\",\n\ + \ \"build\": \"bsb -make-world\",\n\ + \ \"watch\": \"bsb -make-world -w\"\n\ + \ },\n\ + \ \"keywords\": [\n\ + \ \"BuckleScript\"\n\ + \ ],\n\ + \ \"author\": \"\",\n\ + \ \"license\": \"MIT\",\n\ + \ \"devDependencies\": {\n\ + \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ + \ }\n\ + }" + ); + Dir("src",[ + File("demo.ml", + "\n\ + \n\ + let () = Js.log \"Hello, BuckleScript\"" + ) + ]) + ]); + Dir("react-hooks",[ + File(".gitignore", + ".DS_Store\n\ + .merlin\n\ + .bsb.lock\n\ + npm-debug.log\n\ + /lib/bs/\n\ + /node_modules/\n\ + /bundleOutput/" + ); + File("README.md", + "# ReasonReact Template & Examples\n\ + \n\ + This is:\n\ + - A template for your new ReasonReact project.\n\ + - A collection of thin examples illustrating ReasonReact usage.\n\ + - Extra helper documentation for ReasonReact (full ReasonReact docs [here](https://reasonml.github.io/reason-react/)).\n\ + \n\ + `src` contains 4 sub-folders, each an independent, self-contained ReasonReact example. Feel free to delete any of them and shape this into your project! This template's more malleable than you might be used to =).\n\ + \n\ + The point of this template and examples is to let you understand and personally tweak the entirely of it. We **don't** give you an opaque, elaborate mega build setup just to put some boxes on the screen. It strikes to stay transparent, learnable, and simple. You're encouraged to read every file; it's a great feeling, having the full picture of what you're using and being able to touch any part.\n\ + \n\ + ## Run\n\ + \n\ + ```sh\n\ + npm install\n\ + npm run server\n\ + # in a new tab\n\ + npm start\n\ + ```\n\ + \n\ + Open a new web page to `http://localhost:8000/`. Change any `.re` file in `src` to see the page auto-reload. **You don't need any bundler when you're developing**!\n\ + \n\ + **How come we don't need any bundler during development**? We highly encourage you to open up `index.html` to check for yourself!\n\ + \n\ + # Features Used\n\ + \n\ + | | Blinking Greeting | Reducer from ReactJS Docs | Fetch Dog Pictures | Reason Using JS Using Reason |\n\ + |---------------------------|-------------------|---------------------------|--------------------|------------------------------|\n\ + | No props | | ✓ | | |\n\ + | Has props | | | | ✓ |\n\ + | Children props | ✓ | | | |\n\ + | No state | | | | ✓ |\n\ + | Has state | ✓ | | ✓ | |\n\ + | Has state with useReducer | | ✓ | | |\n\ + | ReasonReact using ReactJS | | | | ✓ |\n\ + | ReactJS using ReasonReact | | | | ✓ |\n\ + | useEffect | ✓ | | ✓ | |\n\ + | Dom attribute | ✓ | ✓ | | ✓ |\n\ + | Styling | ✓ | ✓ | ✓ | ✓ |\n\ + | React.array | | | ✓ | |\n\ + \n\ + # Bundle for Production\n\ + \n\ + We've included a convenience `UNUSED_webpack.config.js`, in case you want to ship your project to production. You can rename and/or remove that in favor of other bundlers, e.g. Rollup.\n\ + \n\ + We've also provided a barebone `indexProduction.html`, to serve your bundle.\n\ + \n\ + ```sh\n\ + npm install webpack webpack-cli\n\ + # rename file\n\ + mv UNUSED_webpack.config.js webpack.config.js\n\ + # call webpack to bundle for production\n\ + ./node_modules/.bin/webpack\n\ + open indexProduction.html\n\ + ```\n\ + \n\ + # Handle Routing Yourself\n\ + \n\ + To serve the files, this template uses a minimal dependency called `moduleserve`. A URL such as `localhost:8000/scores/john` resolves to the file `scores/john.html`. If you'd like to override this and handle URL resolution yourself, change the `server` command in `package.json` from `moduleserve ./ --port 8000` to `moduleserve ./ --port 8000 --spa` (for \"single page application\"). This will make `moduleserve` serve the default `index.html` for any URL. Since `index.html` loads `Index.bs.js`, you can grab hold of the URL in the corresponding `Index.re` and do whatever you want.\n\ + \n\ + By the way, ReasonReact comes with a small [router](https://reasonml.github.io/reason-react/docs/en/router) you might be interested in.\n\ + " + ); + File("UNUSED_webpack.config.js", + "const path = require('path');\n\ + \n\ + module.exports = {\n\ + \ entry: './src/Index.bs.js',\n\ + \ // If you ever want to use webpack during development, change 'production'\n\ + \ // to 'development' as per webpack documentation. Again, you don't have to\n\ + \ // use webpack or any other bundler during development! Recheck README if\n\ + \ // you didn't know this\n\ + \ mode: 'production',\n\ + \ output: {\n\ + \ path: path.join(__dirname, \"bundleOutput\"),\n\ + \ filename: 'index.js',\n\ + \ },\n\ + };" + ); + File("bsconfig.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"reason\": {\n\ + \ \"react-jsx\": 3\n\ + \ },\n\ + \ \"sources\": {\n\ + \ \"dir\" : \"src\",\n\ + \ \"subdirs\" : true\n\ + \ },\n\ + \ \"bsc-flags\": [\"-bs-super-errors\", \"-bs-no-version-header\"],\n\ + \ \"package-specs\": [{\n\ + \ \"module\": \"commonjs\",\n\ + \ \"in-source\": true\n\ + \ }],\n\ + \ \"suffix\": \".bs.js\",\n\ + \ \"namespace\": true,\n\ + \ \"bs-dependencies\": [\n\ + \ \"reason-react\"\n\ + \ ],\n\ + \ \"refmt\": 3\n\ + }\n\ + " + ); + File("index.html", + "\n\ + \n\ + \n\ + \ \n\ + \ ReasonReact Examples\n\ + \n\ + \n\ + \ \n\ + \n\ + \ \n\ + \ \n\ + \ \n\ + \ \n\ + \n\ + \n\ + " + ); + File("indexProduction.html", + "\n\ + \n\ + \n\ + \ \n\ + \ ReasonReact Examples\n\ + \n\ + \n\ + \ \n\ + \n\ + \n\ + " + ); + File("package.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"scripts\": {\n\ + \ \"build\": \"bsb -make-world\",\n\ + \ \"start\": \"bsb -make-world -w -ws _ \",\n\ + \ \"clean\": \"bsb -clean-world\",\n\ + \ \"server\": \"moduleserve ./ --port 8000\",\n\ + \ \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n\ + \ },\n\ + \ \"keywords\": [\n\ + \ \"BuckleScript\",\n\ + \ \"ReasonReact\",\n\ + \ \"reason-react\"\n\ + \ ],\n\ + \ \"author\": \"\",\n\ + \ \"license\": \"MIT\",\n\ + \ \"dependencies\": {\n\ + \ \"react\": \"^16.8.1\",\n\ + \ \"react-dom\": \"^16.8.1\",\n\ + \ \"reason-react\": \">=0.7.1\"\n\ + \ },\n\ + \ \"devDependencies\": {\n\ + \ \"bs-platform\": \"^${bsb:bs-version}\",\n\ + \ \"moduleserve\": \"^0.9.0\"\n\ + \ }\n\ + }\n\ + " + ); + Dir("src",[ + Dir("BlinkingGreeting",[ + File("BlinkingGreeting.re", + "[@react.component]\n\ + let make = (~children) => {\n\ + \ let (show, setShow) = React.useState(() => true);\n\ + \n\ + \ // Notice that instead of `useEffect`, we have `useEffect0`. See\n\ + \ // reasonml.github.io/reason-react/docs/en/components#hooks for more info\n\ + \ React.useEffect0(() => {\n\ + \ let id =\n\ + \ Js.Global.setInterval(\n\ + \ () => setShow(previousShow => !previousShow),\n\ + \ 1000,\n\ + \ );\n\ + \n\ + \ Some(() => Js.Global.clearInterval(id));\n\ + \ });\n\ + \n\ + \ let style =\n\ + \ if (show) {\n\ + \ ReactDOMRe.Style.make(~opacity=\"1\", ~transition=\"opacity 1s\", ());\n\ \ } else {\n\ - \ var mainField; \n\ - \ try {\n\ - \ mainField = JSON.parse(text).main\n\ - \ }catch(_){\n\ - \ }\n\ - \ if(mainField === undefined){\n\ - \ return \n\ - \ } else {\n\ - \ return mainField\n\ - \ }\n\ - \ }\n\ - }\n\ - function getPackageJsPromise(id, parent) {\n\ - \ var idNodeModulesPrefix = './node_modules/' + id\n\ - \ var link = getPathWithJsSuffix(idNodeModulesPrefix, parent)\n\ - \ if (parent.endsWith('node_modules/')) {\n\ - \ // impossible that `node_modules/node_modules/xx/x\n\ - \ // return falsePromise\n\ - \ return getParentModulePromise(id, parent)\n\ - \ }\n\ - \n\ - \ var packageJson = BsGetPath(`./node_modules/${getPackageName(id)}/package.json`, parent)\n\ - \n\ - \ return cachedFetch(packageJson)\n\ - \ .then(\n\ - \ function (text) {\n\ - \ if (text !== false) {\n\ - \ var mainField; \n\ - \ if( (mainField = isJustAPackageAndHasMainField(id, text)) !== undefined){\n\ - \ var packageLink = BsGetPath(addSuffixJsIfNot(`./node_modules/${id}/${mainField}`), parent)\n\ - \ return cachedFetch(packageLink)\n\ - \ .then(function(text){\n\ - \ if(text !== false){\n\ - \ return {text, link : packageLink}\n\ - \ } else {\n\ - \ return getParentModulePromise(id,parent)\n\ - \ }\n\ - \ })\n\ - \n\ - \ } else {\n\ - \ // package indeed exist\n\ - \ return cachedFetch(link).then(function (text) {\n\ - \ if (text !== false) {\n\ - \ return { text, link }\n\ - \ } else if (!id.endsWith('.js')) {\n\ - \ var linkNew = getPathWithJsSuffix(idNodeModulesPrefix + `/index.js`, parent)\n\ - \ return cachedFetch(linkNew)\n\ - \ .then(function (text) {\n\ - \ if (text !== false) {\n\ - \ return { text, link: linkNew }\n\ - \ } else {\n\ - \ return getParentModulePromise(id, parent)\n\ - \ }\n\ - \ })\n\ - \n\ - \ } else {\n\ - \ return getParentModulePromise(id, parent)\n\ - \ }\n\ - \ })\n\ - \ }\n\ - \ }\n\ - \ else {\n\ - \ return getParentModulePromise(id, parent)\n\ - \ }\n\ - \ }\n\ - \ )\n\ - \n\ - \n\ - }\n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} id \n\ - \ * @param {string} parent \n\ - \ * can return Promise , false means\n\ - \ * this module can not be resolved\n\ - \ */\n\ - function getModulePromise(id, parent) {\n\ - \ var done = getIdLocation(id, parent)\n\ - \ if (!done) {\n\ - \ return visitIdLocation(id, parent, function () {\n\ - \ if (id[0] != '.') { // package path\n\ - \ return getPackageJsPromise(id, parent)\n\ - \ } else { // relative path, one shot resolve \n\ - \ let link = getPathWithJsSuffix(id, parent)\n\ - \ return cachedFetch(link).then(\n\ - \ function (text) {\n\ - \ if (text !== false) {\n\ - \ return { text, link }\n\ - \ } else if (!id.endsWith('.js')){ \n\ - \ // could be \"./dir\"\n\ - \ var newLink = getPathWithJsSuffix( id +\"/index.js\",parent)\n\ - \ return cachedFetch(newLink)\n\ - \ .then(function(text){\n\ - \ if(text !== false){\n\ - \ return{text, link : newLink }\n\ - \ } else {\n\ - \ throw new Error(` ${id} : ${parent} could not be resolved`)\n\ - \ }\n\ - \ })\n\ - \ }\n\ - \ else {\n\ - \ throw new Error(` ${id} : ${parent} could not be resolved`)\n\ - \ }\n\ - \ }\n\ - \ )\n\ - \ }\n\ - \ })\n\ - \ } else {\n\ - \ return done\n\ - \ }\n\ - }\n\ - \n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} id \n\ - \ * @param {string} parent \n\ - \ * @returns {Promise}\n\ - \ */\n\ - function getAll(id, parent) {\n\ - \ return getModulePromise(id, parent)\n\ - \ .then(function (obj) {\n\ - \ if (obj) {\n\ - \ var deps = getDeps(obj.text)\n\ - \ return Promise.all(deps.map(x => getAll(x, obj.link)))\n\ - \ } else {\n\ - \ throw new Error(`${id}@${parent} was not resolved successfully`)\n\ - \ }\n\ - \ })\n\ - };\n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} text \n\ - \ * @param {string} parent \n\ - \ * @returns {Promise}\n\ - \ */\n\ - function getAllFromText(text, parent) {\n\ - \ var deps = getDeps(text)\n\ - \ return Promise.all(deps.map(x => getAll(x, parent)))\n\ - }\n\ - \n\ - var evaluatedModules = new Map()\n\ - \n\ - function loadSync(id, parent) {\n\ - \ var baseOrModule = getIdLocationSync(id, parent)\n\ - \ if (baseOrModule && baseOrModule.link !== undefined) {\n\ - \ if(evaluatedModules.has(baseOrModule.link)){\n\ - \ return evaluatedModules.get(baseOrModule.link).exports\n\ - \ }\n\ - \ if (!baseOrModule.exports) {\n\ - \ baseOrModule.exports = {}\n\ - \ globalEval(`(function(require,exports,module){${baseOrModule.text}\\n})//# sourceURL=${baseOrModule.link}`)(\n\ - \ function require(id) {\n\ - \ return loadSync(id, baseOrModule.link);\n\ - \ }, // require\n\ - \ baseOrModule.exports = {}, // exports\n\ - \ baseOrModule // module\n\ - \ );\n\ - \ }\n\ - \ if(!evaluatedModules.has(baseOrModule.link)){\n\ - \ evaluatedModules.set(baseOrModule.link,baseOrModule)\n\ - \ }\n\ - \ return baseOrModule.exports\n\ - \ } else {\n\ - \ throw new Error(`${id} : ${parent} could not be resolved`)\n\ - \ }\n\ - }\n\ - \n\ - \n\ - function genEvalName() {\n\ - \ return \"eval-\" + ((\"\" + Math.random()).substr(2, 5))\n\ - }\n\ - /**\n\ - \ * \n\ - \ * @param {string} text \n\ - \ * @param {string} link\n\ - \ * In this case [text] evaluated result will not be cached\n\ - \ */\n\ - function loadTextSync(text, link) {\n\ - \ var baseOrModule = { exports: {}, text, link }\n\ - \ globalEval(`(function(require,exports,module){${baseOrModule.text}\\n})//# sourceURL=${baseOrModule.link}/${genEvalName()}.js`)(\n\ - \ function require(id) {\n\ - \ return loadSync(id, baseOrModule.link);\n\ - \ }, // require\n\ - \ baseOrModule.exports, // exports\n\ - \ baseOrModule // module\n\ + \ ReactDOMRe.Style.make(~opacity=\"0\", ~transition=\"opacity 1s\", ());\n\ + \ };\n\ + \n\ + \
children
;\n\ + };\n\ + " + ) + ]); + File("ExampleStyles.re", + "let reasonReactBlue = \"#48a9dc\";\n\ + \n\ + // The {j|...|j} feature is just string interpolation, from\n\ + // bucklescript.github.io/docs/en/interop-cheatsheet#string-unicode-interpolation\n\ + // This allows us to conveniently write CSS, together with variables, by\n\ + // constructing a string\n\ + let style = {j|\n\ + \ body {\n\ + \ background-color: rgb(224, 226, 229);\n\ + \ display: flex;\n\ + \ flex-direction: column;\n\ + \ align-items: center;\n\ + \ }\n\ + \ button {\n\ + \ background-color: white;\n\ + \ color: $reasonReactBlue;\n\ + \ box-shadow: 0 0 0 1px $reasonReactBlue;\n\ + \ border: none;\n\ + \ padding: 8px;\n\ + \ font-size: 16px;\n\ + \ }\n\ + \ button:active {\n\ + \ background-color: $reasonReactBlue;\n\ + \ color: white;\n\ + \ }\n\ + \ .container {\n\ + \ margin: 12px 0px;\n\ + \ box-shadow: 0px 4px 16px rgb(200, 200, 200);\n\ + \ width: 720px;\n\ + \ border-radius: 12px;\n\ + \ font-family: sans-serif;\n\ + \ }\n\ + \ .containerTitle {\n\ + \ background-color: rgb(242, 243, 245);\n\ + \ border-radius: 12px 12px 0px 0px;\n\ + \ padding: 12px;\n\ + \ font-weight: bold;\n\ + \ }\n\ + \ .containerContent {\n\ + \ background-color: white;\n\ + \ padding: 16px;\n\ + \ border-radius: 0px 0px 12px 12px;\n\ + \ }\n\ + |j};\n\ + " + ); + Dir("FetchedDogPictures",[ + File("FetchedDogPictures.re", + "[@bs.val] external fetch: string => Js.Promise.t('a) = \"fetch\";\n\ + \n\ + type state =\n\ + \ | LoadingDogs\n\ + \ | ErrorFetchingDogs\n\ + \ | LoadedDogs(array(string));\n\ + \n\ + [@react.component]\n\ + let make = () => {\n\ + \ let (state, setState) = React.useState(() => LoadingDogs);\n\ + \n\ + \ // Notice that instead of `useEffect`, we have `useEffect0`. See\n\ + \ // reasonml.github.io/reason-react/docs/en/components#hooks for more info\n\ + \ React.useEffect0(() => {\n\ + \ Js.Promise.(\n\ + \ fetch(\"https://dog.ceo/api/breeds/image/random/3\")\n\ + \ |> then_(response => response##json())\n\ + \ |> then_(jsonResponse => {\n\ + \ setState(_previousState => LoadedDogs(jsonResponse##message));\n\ + \ Js.Promise.resolve();\n\ + \ })\n\ + \ |> catch(_err => {\n\ + \ setState(_previousState => ErrorFetchingDogs);\n\ + \ Js.Promise.resolve();\n\ + \ })\n\ + \ |> ignore\n\ \ );\n\ - \ return baseOrModule.exports\n\ - }\n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} text \n\ - \ */\n\ - function BSloadText(text) {\n\ - \ console.time(\"Loading\")\n\ - \ var parent = BsGetPath(\".\", document.baseURI)\n\ - \ return getAllFromText(text, parent).then(function () {\n\ - \ var result = loadTextSync(text, parent)\n\ - \ console.timeEnd(\"Loading\")\n\ - \ return result\n\ - \ })\n\ - };\n\ - \n\ - \n\ - function load(id, parent) {\n\ - \ return getAll(id, parent).then(function () {\n\ - \ return loadSync(id, parent)\n\ - \ })\n\ - \n\ - };\n\ - \n\ - \n\ - export function BSload(id) {\n\ - \ var parent = BsGetPath(\".\", document.baseURI)\n\ - \ return load(id, parent)\n\ - }\n\ - \n\ - export var BSLoader = {\n\ - \ loadText: BSloadText,\n\ - \ load: BSload,\n\ - \ SyncedIDLocations: SyncedIDLocations\n\ - };\n\ - \n\ - window.BSLoader = BSLoader;\n\ - \n\ - var main = document.querySelector('script[data-main]')\n\ - if (main) {\n\ - \ BSload(main.dataset.main)\n\ - }\n\ - ") ; - File ("bsconfig.json", - "{\n\ - \ \"name\": \"tea\",\n\ - \ \"version\": \"0.1.0\",\n\ - \ \"sources\": {\n\ - \ \"dir\" : \"src\",\n\ - \ \"subdirs\" : true\n\ - \ },\n\ - \ \"package-specs\": {\n\ - \ \"module\": \"commonjs\",\n\ - \ \"in-source\": true\n\ - \ },\n\ - \ \"suffix\": \".bs.js\",\n\ - \ \"bs-dependencies\": [\n\ - \ \"bucklescript-tea\"\n\ - \ ]\n\ - }\n\ - ") ; - File ("watcher.js", - "\n\ - \n\ - var wsReloader;\n\ - var LAST_SUCCESS_BUILD_STAMP = (localStorage.getItem('LAST_SUCCESS_BUILD_STAMP') || 0)\n\ - var WS_PORT = 9999; // configurable\n\ - \n\ - function setUpWebScoket() {\n\ - \ if (wsReloader == null || wsReloader.readyState !== 1) {\n\ - \ try {\n\ - \ wsReloader = new WebSocket(`ws://${window.location.hostname}:${WS_PORT}`)\n\ - \ wsReloader.onmessage = (msg) => {\n\ - \ var newData = JSON.parse(msg.data).LAST_SUCCESS_BUILD_STAMP\n\ - \ if (newData > LAST_SUCCESS_BUILD_STAMP) {\n\ - \ LAST_SUCCESS_BUILD_STAMP = newData\n\ - \ localStorage.setItem('LAST_SUCCESS_BUILD_STAMP', LAST_SUCCESS_BUILD_STAMP)\n\ - \ location.reload(true)\n\ - \ }\n\ - \n\ - \ }\n\ - \ } catch (exn) {\n\ - \ console.error(\"web socket failed connect\")\n\ - \ }\n\ - \ }\n\ - };\n\ - \n\ - setUpWebScoket();\n\ - setInterval(setUpWebScoket, 2000);") ; - File ("package.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"scripts\": {\n\ - \ \"clean\": \"bsb -clean-world\",\n\ - \ \"build\": \"bsb -make-world\",\n\ - \ \"watch\": \"bsb -make-world -w -ws _\"\n\ - \ },\n\ - \ \"keywords\": [\n\ - \ \"BuckleScript\"\n\ - \ ],\n\ - \ \"author\": \"\",\n\ - \ \"license\": \"MIT\",\n\ - \ \"devDependencies\": {\n\ - \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ - \ },\n\ - \ \"dependencies\": {\n\ - \ \"bucklescript-tea\": \"^0.7.4\"\n\ - \ }\n\ - }\n\ - ") ; - File ("README.md", - "\n\ - \n\ - # Build\n\ - ```\n\ - npm run build\n\ - ```\n\ - \n\ - # Watch\n\ - \n\ - ```\n\ - npm run watch\n\ - ```\n\ - \n\ - create a http-server\n\ - \n\ - ```\n\ - npm install -g http-server\n\ - ```\n\ - \n\ - Edit the file and see the changes automatically reloaded in the browser\n\ - ") ; - File ("index.html", - "\n\ - \n\ - \ \n\ - \ \n\ - \ \n\ - \ \n\ - \ \n\ - \ \n\ - \ Bucklescript TEA Starter Kit\n\ - \ \n\ - \ \n\ - \n\ - \n\ - \ \n\ - \
\n\ - \ \n\ - \ \n\ - \ \n\ - \ \n\ - ")]) -]) + \n\ + \ // Returning None, instead of Some(() => ...), means we don't have any\n\ + \ // cleanup to do before unmounting. That's not 100% true. We should\n\ + \ // technically cancel the promise. Unofortunately, there's currently no\n\ + \ // way to cancel a promise. Promises in general should be way less used\n\ + \ // for React components; but since folks do use them, we provide such an\n\ + \ // example here. In reality, this fetch should just be a plain callback,\n\ + \ // with a cancellation API\n\ + \ None;\n\ + \ });\n\ + \n\ + \ \n\ + \ {switch (state) {\n\ + \ | ErrorFetchingDogs => React.string(\"An error occurred!\")\n\ + \ | LoadingDogs => React.string(\"Loading...\")\n\ + \ | LoadedDogs(dogs) =>\n\ + \ dogs\n\ + \ ->Belt.Array.mapWithIndex((i, dog) => {\n\ + \ let imageStyle =\n\ + \ ReactDOMRe.Style.make(\n\ + \ ~height=\"120px\",\n\ + \ ~width=\"100%\",\n\ + \ ~marginRight=i === Js.Array.length(dogs) - 1 ? \"0px\" : \"8px\",\n\ + \ ~borderRadius=\"8px\",\n\ + \ ~boxShadow=\"0px 4px 16px rgb(200, 200, 200)\",\n\ + \ ~backgroundSize=\"cover\",\n\ + \ ~backgroundImage={j|url($dog)|j},\n\ + \ ~backgroundPosition=\"center\",\n\ + \ (),\n\ + \ );\n\ + \
;\n\ + \ })\n\ + \ ->React.array\n\ + \ }}\n\ + \
;\n\ + };\n\ + " + ) + ]); + File("Index.re", + "// Entry point\n\ + \n\ + [@bs.val] external document: Js.t({..}) = \"document\";\n\ + \n\ + // We're using raw DOM manipulations here, to avoid making you read\n\ + // ReasonReact when you might precisely be trying to learn it for the first\n\ + // time through the examples later.\n\ + let style = document##createElement(\"style\");\n\ + document##head##appendChild(style);\n\ + style##innerHTML #= ExampleStyles.style;\n\ + \n\ + let makeContainer = text => {\n\ + \ let container = document##createElement(\"div\");\n\ + \ container##className #= \"container\";\n\ + \n\ + \ let title = document##createElement(\"div\");\n\ + \ title##className #= \"containerTitle\";\n\ + \ title##innerText #= text;\n\ + \n\ + \ let content = document##createElement(\"div\");\n\ + \ content##className #= \"containerContent\";\n\ + \n\ + \ let () = container##appendChild(title);\n\ + \ let () = container##appendChild(content);\n\ + \ let () = document##body##appendChild(container);\n\ + \n\ + \ content;\n\ + };\n\ + \n\ + // All 4 examples.\n\ + ReactDOMRe.render(\n\ + \ \n\ + \ {React.string(\"Hello!\")}\n\ + \ ,\n\ + \ makeContainer(\"Blinking Greeting\"),\n\ + );\n\ + \n\ + ReactDOMRe.render(\n\ + \ ,\n\ + \ makeContainer(\"Reducer From ReactJS Docs\"),\n\ + );\n\ + \n\ + ReactDOMRe.render(\n\ + \ ,\n\ + \ makeContainer(\"Fetched Dog Pictures\"),\n\ + );\n\ + \n\ + ReactDOMRe.render(\n\ + \ ,\n\ + \ makeContainer(\"Reason Using JS Using Reason\"),\n\ + );\n\ + " + ); + Dir("ReasonUsingJSUsingReason",[ + File("ReactJSCard.js", + "// In this Interop example folder, we have:\n\ + // - A ReasonReact component, ReasonReactCard.re\n\ + // - Used by a ReactJS component, ReactJSCard.js (this file)\n\ + // - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re\n\ + // - ReasonUsingJSUsingReason.re is used by Index.re\n\ + \n\ + var ReactDOM = require('react-dom');\n\ + var React = require('react');\n\ + \n\ + var ReasonReactCard = require('./ReasonReactCard.bs').make;\n\ + \n\ + var ReactJSComponent = function() {\n\ + \ let backgroundColor = \"rgba(0, 0, 0, 0.05)\";\n\ + \ let padding = \"12px\";\n\ + \n\ + \ // We're not using JSX here, to avoid folks needing to install the related\n\ + \ // React toolchains just for this example.\n\ + \ //
\n\ + \ //
This is a ReactJS card
\n\ + \ // \n\ + \ //
\n\ + \ return React.createElement(\n\ + \ \"div\",\n\ + \ {style: {backgroundColor, padding, borderRadius: \"8px\"}},\n\ + \ React.createElement(\"div\", {style: {marginBottom: \"8px\"}}, \"This is a ReactJS card\"),\n\ + \ React.createElement(ReasonReactCard, {style: {backgroundColor, padding, borderRadius: \"4px\"}}),\n\ + \ )\n\ + };\n\ + ReactJSComponent.displayName = \"MyBanner\";\n\ + \n\ + module.exports = ReactJSComponent;\n\ + " + ); + File("ReasonReactCard.re", + "// In this Interop example folder, we have:\n\ + // - A ReasonReact component, ReasonReactCard.re (this file)\n\ + // - Used by a ReactJS component, ReactJSCard.js\n\ + // - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re\n\ + // - ReasonUsingJSUsingReason.re is used by Index.re\n\ + \n\ + [@react.component]\n\ + let make = (~style) => {\n\ + \
{React.string(\"This is a ReasonReact card\")}
;\n\ + };\n\ + " + ); + File("ReasonUsingJSUsingReason.re", + "// In this Interop example folder, we have:\n\ + // - A ReasonReact component, ReasonReactCard.re\n\ + // - Used by a ReactJS component, ReactJSCard.js\n\ + // - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re (this file)\n\ + // - ReasonUsingJSUsingReason.re is used by Index.re\n\ + \n\ + // All you need to do to use a ReactJS component in ReasonReact, is to write the lines below!\n\ + // reasonml.github.io/reason-react/docs/en/components#import-from-js\n\ + [@react.component] [@bs.module]\n\ + external make: unit => React.element = \"./ReactJSCard\";\n\ + " + ) + ]); + Dir("ReducerFromReactJSDocs",[ + File("ReducerFromReactJSDocs.re", + "// This is the ReactJS documentation's useReducer example, directly ported over\n\ + // https://reactjs.org/docs/hooks-reference.html#usereducer\n\ + \n\ + // A little extra we've put, because the ReactJS example has no styling\n\ + let leftButtonStyle = ReactDOMRe.Style.make(~borderRadius=\"4px 0px 0px 4px\", ~width=\"48px\", ());\n\ + let rightButtonStyle = ReactDOMRe.Style.make(~borderRadius=\"0px 4px 4px 0px\", ~width=\"48px\", ());\n\ + let containerStyle = ReactDOMRe.Style.make(~display=\"flex\", ~alignItems=\"center\", ~justifyContent=\"space-between\", ());\n\ + \n\ + // Record and variant need explicit declarations.\n\ + type state = {count: int};\n\ + \n\ + type action =\n\ + \ | Increment\n\ + \ | Decrement;\n\ + \n\ + let initialState = {count: 0};\n\ + \n\ + let reducer = (state, action) => {\n\ + \ switch (action) {\n\ + \ | Increment => {count: state.count + 1}\n\ + \ | Decrement => {count: state.count - 1}\n\ + \ };\n\ + };\n\ + \n\ + [@react.component]\n\ + let make = () => {\n\ + \ let (state, dispatch) = React.useReducer(reducer, initialState);\n\ + \n\ + \ // We can use a fragment here, but we don't, because we want to style the counter\n\ + \
\n\ + \
\n\ + \ {React.string(\"Count: \")}\n\ + \ {React.string(string_of_int(state.count))}\n\ + \
\n\ + \
\n\ + \ \n\ + \ \n\ + \
\n\ + \
;\n\ + };\n\ + " + ) + ]) + ]); + File("watcher.js", + "// This is our simple, robust watcher. It hooks into the BuckleScript build\n\ + // system to listen for build events.\n\ + // See package.json's `start` script and `./node_modules/.bin/bsb --help`\n\ + \n\ + // Btw, if you change this file and reload the page, your browser cache\n\ + // _might_ not pick up the new version. If you're in Chrome, do Force Reload.\n\ + \n\ + var websocketReloader;\n\ + var LAST_SUCCESS_BUILD_STAMP = localStorage.getItem('LAST_SUCCESS_BUILD_STAMP') || 0;\n\ + // package.json's `start` script's `bsb -ws _` means it'll pipe build events\n\ + // through a websocket connection to a default port of 9999. This is\n\ + // configurable, e.g. `-ws 5000`\n\ + var webSocketPort = 9999;\n\ + \n\ + function setUpWebSocket() {\n\ + \ if (websocketReloader == null || websocketReloader.readyState !== 1) {\n\ + \ try {\n\ + \ websocketReloader = new WebSocket(`ws://${window.location.hostname}:${webSocketPort}`);\n\ + \ websocketReloader.onmessage = (message) => {\n\ + \ var newData = JSON.parse(message.data).LAST_SUCCESS_BUILD_STAMP;\n\ + \ if (newData > LAST_SUCCESS_BUILD_STAMP) {\n\ + \ LAST_SUCCESS_BUILD_STAMP = newData;\n\ + \ localStorage.setItem('LAST_SUCCESS_BUILD_STAMP', LAST_SUCCESS_BUILD_STAMP);\n\ + \ // Refresh the page! This will naturally re-run everything,\n\ + \ // including our moduleserve which will re-resolve all the modules.\n\ + \ // No stable build!\n\ + \ location.reload(true);\n\ + \ }\n\ + \n\ + \ }\n\ + \ } catch (exn) {\n\ + \ console.error(\"The watcher tried to connect to web socket, but failed. Here's the message:\");\n\ + \ console.error(exn);\n\ + \ }\n\ + \ }\n\ + };\n\ + \n\ + setUpWebSocket();\n\ + setInterval(setUpWebSocket, 2000);\n\ + " + ) + ]); + Dir("react-starter",[ + File(".gitignore", + ".DS_Store\n\ + .merlin\n\ + .bsb.lock\n\ + npm-debug.log\n\ + /lib/bs/\n\ + /node_modules/\n\ + *.bs.js\n\ + " + ); + File("README.md", + "# Reason react starter\n\ + \n\ + ## Run Project\n\ + \n\ + ```sh\n\ + npm install\n\ + npm start\n\ + # in another tab\n\ + npm run server\n\ + ```\n\ + \n\ + View the app in the browser at http://localhost:8000. Running in this environment provides hot reloading and support for routing; just edit and save the file and the browser will automatically refresh.\n\ + \n\ + To use a port other than 8000 set the `PORT` environment variable (`PORT=8080 npm run server`).\n\ + \n\ + ## Build for Production\n\ + \n\ + ```sh\n\ + npm run clean\n\ + npm run build\n\ + npm run webpack:production\n\ + ```\n\ + \n\ + This will replace the development artifact `build/Index.js` for an optimized version as well as copy `src/index.html` into `build/`. You can then deploy the contents of the `build` directory (`index.html` and `Index.js`).\n\ + \n\ + **To enable dead code elimination**, change `bsconfig.json`'s `package-specs` `module` from `\"commonjs\"` to `\"es6\"`. Then re-run the above 2 commands. This will allow Webpack to remove unused code.\n\ + " + ); + File("bsconfig.json", + "{\n\ + \ \"name\": \"reason-react-starter\",\n\ + \ \"reason\": {\n\ + \ \"react-jsx\": 3\n\ + \ },\n\ + \ \"sources\": {\n\ + \ \"dir\": \"src\",\n\ + \ \"subdirs\": true\n\ + \ },\n\ + \ \"bsc-flags\": [\"-bs-super-errors\", \"-bs-no-version-header\"],\n\ + \ \"package-specs\": [\n\ + \ {\n\ + \ \"module\": \"commonjs\",\n\ + \ \"in-source\": true\n\ + \ }\n\ + \ ],\n\ + \ \"suffix\": \".bs.js\",\n\ + \ \"namespace\": true,\n\ + \ \"bs-dependencies\": [\"reason-react\"],\n\ + \ \"refmt\": 3\n\ + }\n\ + " + ); + File("package.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"scripts\": {\n\ + \ \"build\": \"bsb -make-world\",\n\ + \ \"start\": \"bsb -make-world -w -ws _ \",\n\ + \ \"clean\": \"bsb -clean-world\",\n\ + \ \"webpack\": \"webpack -w\",\n\ + \ \"webpack:production\": \"NODE_ENV=production webpack\",\n\ + \ \"server\": \"webpack-dev-server\",\n\ + \ \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n\ + \ },\n\ + \ \"keywords\": [\n\ + \ \"BuckleScript\",\n\ + \ \"ReasonReact\",\n\ + \ \"reason-react\"\n\ + \ ],\n\ + \ \"author\": \"\",\n\ + \ \"license\": \"MIT\",\n\ + \ \"dependencies\": {\n\ + \ \"react\": \"^17.0.1\",\n\ + \ \"react-dom\": \"^17.0.1\",\n\ + \ \"reason-react\": \"^0.9.1\"\n\ + \ },\n\ + \ \"devDependencies\": {\n\ + \ \"bs-platform\": \"^${bsb:bs-version}\",\n\ + \ \"css-loader\": \"^5.0.0\",\n\ + \ \"html-webpack-plugin\": \"^4.5.0\",\n\ + \ \"style-loader\": \"^2.0.0\",\n\ + \ \"webpack\": \"^4.44.2\",\n\ + \ \"webpack-cli\": \"^3.3.12\",\n\ + \ \"webpack-dev-server\": \"^3.11.0\"\n\ + \ }\n\ + }\n\ + " + ); + Dir("src",[ + File("App.re", + "type state = {count: int};\n\ + \n\ + type action =\n\ + \ | Increment\n\ + \ | Decrement;\n\ + \n\ + let initialState = {count: 0};\n\ + \n\ + let reducer = (state, action) =>\n\ + \ switch (action) {\n\ + \ | Increment => {count: state.count + 1}\n\ + \ | Decrement => {count: state.count - 1}\n\ + \ };\n\ + \n\ + [@react.component]\n\ + let make = () => {\n\ + \ let (state, dispatch) = React.useReducer(reducer, initialState);\n\ + \n\ + \
\n\ + \ {React.string(\"Simple counter with reducer\")}\n\ + \
\n\ + \ \n\ + \ \n\ + \ {state.count |> string_of_int |> React.string}\n\ + \ \n\ + \ \n\ + \
\n\ + \
;\n\ + };\n\ + " + ); + File("Index.re", + "[%bs.raw {|require(\"./index.css\")|}];\n\ + \n\ + ReactDOMRe.renderToElementWithId(, \"root\");\n\ + " + ); + File("index.css", + "body {\n\ + \ margin: 0;\n\ + \ font-family: -apple-system, system-ui, \"Segoe UI\", Helvetica, Arial,\n\ + \ sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n\ + }\n\ + \n\ + main {\n\ + \ padding: 20px;\n\ + }\n\ + \n\ + .counter {\n\ + \ padding: 20px;\n\ + \ display: inline-block;\n\ + }\n\ + " + ); + File("index.html", + "\n\ + \n\ + \ \n\ + \ \n\ + \ Reason react starter\n\ + \ \n\ + \ \n\ + \
\n\ + \ \n\ + \ \n\ + \n\ + " + ) + ]); + File("webpack.config.js", + "const path = require(\"path\")\n\ + const HtmlWebpackPlugin = require(\"html-webpack-plugin\")\n\ + const outputDir = path.join(__dirname, \"build/\")\n\ + \n\ + const isProd = process.env.NODE_ENV === \"production\"\n\ + \n\ + module.exports = {\n\ + \ entry: \"./src/Index.bs.js\",\n\ + \ mode: isProd ? \"production\" : \"development\",\n\ + \ devtool: \"source-map\",\n\ + \ output: {\n\ + \ path: outputDir,\n\ + \ filename: \"Index.js\"\n\ + \ },\n\ + \ plugins: [\n\ + \ new HtmlWebpackPlugin({\n\ + \ template: \"src/index.html\",\n\ + \ inject: false\n\ + \ })\n\ + \ ],\n\ + \ devServer: {\n\ + \ compress: true,\n\ + \ contentBase: outputDir,\n\ + \ port: process.env.PORT || 8000,\n\ + \ historyApiFallback: true\n\ + \ },\n\ + \ module: {\n\ + \ rules: [\n\ + \ {\n\ + \ test: /\\.css$/,\n\ + \ use: [\"style-loader\", \"css-loader\"]\n\ + \ }\n\ + \ ]\n\ + \ }\n\ + }\n\ + " + ) + ]); + Dir("tea",[ + File("README.md", + "\n\ + \n\ + # Build\n\ + ```\n\ + npm run build\n\ + ```\n\ + \n\ + # Watch\n\ + \n\ + ```\n\ + npm run watch\n\ + ```\n\ + \n\ + create a http-server\n\ + \n\ + ```\n\ + npm install -g http-server\n\ + ```\n\ + \n\ + Edit the file and see the changes automatically reloaded in the browser\n\ + " + ); + File("bsconfig.json", + "{\n\ + \ \"name\": \"tea\",\n\ + \ \"version\": \"0.1.0\",\n\ + \ \"sources\": {\n\ + \ \"dir\" : \"src\",\n\ + \ \"subdirs\" : true\n\ + \ },\n\ + \ \"package-specs\": {\n\ + \ \"module\": \"commonjs\",\n\ + \ \"in-source\": true\n\ + \ },\n\ + \ \"suffix\": \".bs.js\",\n\ + \ \"bs-dependencies\": [\n\ + \ \"bucklescript-tea\"\n\ + \ ]\n\ + }\n\ + " + ); + File("index.html", + "\n\ + \n\ + \ \n\ + \ \n\ + \ \n\ + \ \n\ + \ \n\ + \ \n\ + \ Bucklescript TEA Starter Kit\n\ + \ \n\ + \ \n\ + \n\ + \n\ + \ \n\ + \
\n\ + \ \n\ + \ \n\ + \ \n\ + \ \n\ + " + ); + File("loader.js", + "/* Copyright (C) 2018 Authors of BuckleScript\n\ + \ * \n\ + \ * This program is free software: you can redistribute it and/or modify\n\ + \ * it under the terms of the GNU Lesser General Public License as published by\n\ + \ * the Free Software Foundation, either version 3 of the License, or\n\ + \ * (at your option) any later version.\n\ + \ *\n\ + \ * In addition to the permissions granted to you by the LGPL, you may combine\n\ + \ * or link a \"work that uses the Library\" with a publicly distributed version\n\ + \ * of this file to produce a combined library or application, then distribute\n\ + \ * that combined work under the terms of your choosing, with no requirement\n\ + \ * to comply with the obligations normally placed on you by section 4 of the\n\ + \ * LGPL version 3 (or the corresponding section of a later version of the LGPL\n\ + \ * should you choose to use a later version).\n\ + \ *\n\ + \ * This program is distributed in the hope that it will be useful,\n\ + \ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n\ + \ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\ + \ * GNU Lesser General Public License for more details.\n\ + \ * \n\ + \ * You should have received a copy of the GNU Lesser General Public License\n\ + \ * along with this program; if not, write to the Free Software\n\ + \ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */\n\ + \n\ + \n\ + \n\ + //@ts-check\n\ + \n\ + // @ts-ignore\n\ + window.process = { env: { NODE_ENV: 'dev' } }\n\ + \n\ + \n\ + // local to getPath\n\ + var relativeElement = document.createElement(\"a\");\n\ + var baseElement = document.createElement(\"base\");\n\ + document.head.appendChild(baseElement);\n\ + \n\ + export function BsGetPath(id, parent) {\n\ + \ var oldPath = baseElement.href\n\ + \ baseElement.href = parent\n\ + \ relativeElement.href = id\n\ + \ var result = relativeElement.href\n\ + \ baseElement.href = oldPath\n\ + \ return result\n\ + }\n\ + /**\n\ + \ * \n\ + \ * Given current link and its parent, return the new link\n\ + \ * @param {string} id \n\ + \ * @param {string} parent \n\ + \ * @return {string}\n\ + \ */\n\ + function getPathWithJsSuffix(id, parent) {\n\ + \ var oldPath = baseElement.href\n\ + \ baseElement.href = parent\n\ + \ relativeElement.href = id\n\ + \ var result = addSuffixJsIfNot(relativeElement.href)\n\ + \ baseElement.href = oldPath\n\ + \ return result\n\ + }\n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} x \n\ + \ */\n\ + function addSuffixJsIfNot(x) {\n\ + \ if (x.endsWith('.js')) {\n\ + \ return x\n\ + \ } else {\n\ + \ return x + '.js'\n\ + \ }\n\ + }\n\ + \n\ + \n\ + var falsePromise = Promise.resolve(false)\n\ + var fetchConfig = {'cache' : 'no-cache'}\n\ + // package.json semantics\n\ + // a string to module object \n\ + // from url -> module object \n\ + // Modules : Map \n\ + // fetch the link:\n\ + // - if it is already fetched before, return the stored promise\n\ + // otherwise create the promise which will be filled with the text if successful\n\ + // or filled with boolean false when failed\n\ + var MODULES = new Map()\n\ + function cachedFetch(link) {\n\ + \ // console.info(link)\n\ + \ var linkResult = MODULES.get(link)\n\ + \ if (linkResult) {\n\ + \ return linkResult\n\ + \ } else {\n\ + \ var p = fetch(link, fetchConfig)\n\ + \ .then(resp => {\n\ + \ if (resp.ok) {\n\ + \ return resp.text()\n\ + \ } else {\n\ + \ return falsePromise\n\ + \ }\n\ + \ })\n\ + \n\ + \ MODULES.set(link, p)\n\ + \ return p\n\ + \ }\n\ + }\n\ + \n\ + // from location id -> url \n\ + // There are two rounds of caching:\n\ + // 1. if location and relative path is hit, no need to run \n\ + // 2. if location and relative path is not hit, but the resolved link is hit, no need \n\ + // for network request\n\ + /**\n\ + \ * @type {Map > > }\n\ + \ */\n\ + var IDLocations = new Map()\n\ + \n\ + /**\n\ + \ * @type {Map > }\n\ + \ */\n\ + var SyncedIDLocations = new Map()\n\ + // Its value is an object \n\ + // { link : String }\n\ + // We will first mark it when visiting (to avoid duplicated computation)\n\ + // and populate its link later\n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} id \n\ + \ * @param {string} location \n\ + \ */\n\ + function getIdLocation(id, location) {\n\ + \ var idMap = IDLocations.get(location)\n\ + \ if (idMap) {\n\ + \ return idMap.get(id)\n\ + \ }\n\ + }\n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} id \n\ + \ * @param {string} location \n\ + \ */\n\ + function getIdLocationSync(id, location) {\n\ + \ var idMap = SyncedIDLocations.get(location)\n\ + \ if (idMap) {\n\ + \ return idMap.get(id)\n\ + \ }\n\ + }\n\ + \n\ + function countIDLocations() {\n\ + \ var count = 0\n\ + \ for (let [k, vv] of IDLocations) {\n\ + \ for (let [kv, v] of vv) {\n\ + \ count += 1\n\ + \ }\n\ + \ }\n\ + \ console.log(count, 'modules loaded')\n\ + }\n\ + \n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} id \n\ + \ * @param {string} location \n\ + \ * @param {Function} cb \n\ + \ * @returns Promise\n\ + \ */\n\ + function visitIdLocation(id, location, cb) {\n\ + \ var result;\n\ + \ var idMap = IDLocations.get(location)\n\ + \ if (idMap && (result = idMap.get(id))) {\n\ + \ return result\n\ + \ }\n\ + \ else {\n\ + \ result = new Promise(resolve => {\n\ + \ return (cb()).then(res => {\n\ + \ var idMap = SyncedIDLocations.get(location)\n\ + \ if (idMap) {\n\ + \ idMap.set(id, res)\n\ + \ } else {\n\ + \ SyncedIDLocations.set(location, new Map([[id, res]]))\n\ + \ }\n\ + \ return resolve(res)\n\ + \ })\n\ + \ })\n\ + \ if (idMap) {\n\ + \ idMap.set(id, result)\n\ + \ }\n\ + \ else {\n\ + \ IDLocations.set(location, new Map([[id, result]]))\n\ + \ }\n\ + \ return result\n\ + \ }\n\ + }\n\ + \n\ + \n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} text \n\ + \ * @return {string[]}\n\ + \ */\n\ + function getDeps(text) {\n\ + \ var deps = []\n\ + \ text.replace(/(\\/\\*[\\w\\W]*?\\*\\/|\\/\\/[^\\n]*|[.$]r)|\\brequire\\s*\\(\\s*[\"']([^\"']*)[\"']\\s*\\)/g, function (_, ignore, id) {\n\ + \ if (!ignore) deps.push(id);\n\ + \ });\n\ + \ return deps;\n\ + }\n\ + \n\ + \n\ + \n\ + // By using a named \"eval\" most browsers will execute in the global scope.\n\ + // http://www.davidflanagan.com/2010/12/global-eval-in.html\n\ + var globalEval = eval;\n\ + \n\ + // function parentURL(url) {\n\ + // if (url.endsWith('/')) {\n\ + // return url + '../'\n\ + // } else {\n\ + // return url + '/../'\n\ + // }\n\ + // }\n\ + \n\ + \n\ + \n\ + // loader.js:23 http://localhost:8080/node_modules/react-dom/cjs/react-dom.development.js/..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//../ fbjs/lib/containsNode Promise {}\n\ + // 23:10:02.884 loader.js:23 http://localhost:8080/node_modules/react-dom/cjs/react-dom.development.js/..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//../ fbjs/lib/invariant Promise {}\n\ + \n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} id \n\ + \ * @param {string} parent \n\ + \ */\n\ + function getParentModulePromise(id, parent) {\n\ + \ var parentLink = BsGetPath('..', parent)\n\ + \ if (parentLink === parent) {\n\ + \ return falsePromise\n\ + \ }\n\ + \ return getPackageJsPromise(id, parentLink)\n\ + }\n\ + // In the beginning\n\ + // it is `resolveModule('./main.js', '')\n\ + // return the promise of link and text \n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} id \n\ + \ */\n\ + function getPackageName(id) {\n\ + \ var index = id.indexOf('/')\n\ + \ if (id[0] === '@') index = id.indexOf('/', index + 1)\n\ + \ if (index === -1) {\n\ + \ return id\n\ + \ }\n\ + \ return id.substring(0, index)\n\ + }\n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} s \n\ + \ * @param {string} text \n\ + \ * @returns {undefined | string }\n\ + \ */\n\ + function isJustAPackageAndHasMainField(s,text){\n\ + \ if(s.indexOf('/') >= 0){\n\ + \ return \n\ + \ } else {\n\ + \ var mainField; \n\ + \ try {\n\ + \ mainField = JSON.parse(text).main\n\ + \ }catch(_){\n\ + \ }\n\ + \ if(mainField === undefined){\n\ + \ return \n\ + \ } else {\n\ + \ return mainField\n\ + \ }\n\ + \ }\n\ + }\n\ + function getPackageJsPromise(id, parent) {\n\ + \ var idNodeModulesPrefix = './node_modules/' + id\n\ + \ var link = getPathWithJsSuffix(idNodeModulesPrefix, parent)\n\ + \ if (parent.endsWith('node_modules/')) {\n\ + \ // impossible that `node_modules/node_modules/xx/x\n\ + \ // return falsePromise\n\ + \ return getParentModulePromise(id, parent)\n\ + \ }\n\ + \n\ + \ var packageJson = BsGetPath(`./node_modules/${getPackageName(id)}/package.json`, parent)\n\ + \n\ + \ return cachedFetch(packageJson)\n\ + \ .then(\n\ + \ function (text) {\n\ + \ if (text !== false) {\n\ + \ var mainField; \n\ + \ if( (mainField = isJustAPackageAndHasMainField(id, text)) !== undefined){\n\ + \ var packageLink = BsGetPath(addSuffixJsIfNot(`./node_modules/${id}/${mainField}`), parent)\n\ + \ return cachedFetch(packageLink)\n\ + \ .then(function(text){\n\ + \ if(text !== false){\n\ + \ return {text, link : packageLink}\n\ + \ } else {\n\ + \ return getParentModulePromise(id,parent)\n\ + \ }\n\ + \ })\n\ + \n\ + \ } else {\n\ + \ // package indeed exist\n\ + \ return cachedFetch(link).then(function (text) {\n\ + \ if (text !== false) {\n\ + \ return { text, link }\n\ + \ } else if (!id.endsWith('.js')) {\n\ + \ var linkNew = getPathWithJsSuffix(idNodeModulesPrefix + `/index.js`, parent)\n\ + \ return cachedFetch(linkNew)\n\ + \ .then(function (text) {\n\ + \ if (text !== false) {\n\ + \ return { text, link: linkNew }\n\ + \ } else {\n\ + \ return getParentModulePromise(id, parent)\n\ + \ }\n\ + \ })\n\ + \n\ + \ } else {\n\ + \ return getParentModulePromise(id, parent)\n\ + \ }\n\ + \ })\n\ + \ }\n\ + \ }\n\ + \ else {\n\ + \ return getParentModulePromise(id, parent)\n\ + \ }\n\ + \ }\n\ + \ )\n\ + \n\ + \n\ + }\n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} id \n\ + \ * @param {string} parent \n\ + \ * can return Promise , false means\n\ + \ * this module can not be resolved\n\ + \ */\n\ + function getModulePromise(id, parent) {\n\ + \ var done = getIdLocation(id, parent)\n\ + \ if (!done) {\n\ + \ return visitIdLocation(id, parent, function () {\n\ + \ if (id[0] != '.') { // package path\n\ + \ return getPackageJsPromise(id, parent)\n\ + \ } else { // relative path, one shot resolve \n\ + \ let link = getPathWithJsSuffix(id, parent)\n\ + \ return cachedFetch(link).then(\n\ + \ function (text) {\n\ + \ if (text !== false) {\n\ + \ return { text, link }\n\ + \ } else if (!id.endsWith('.js')){ \n\ + \ // could be \"./dir\"\n\ + \ var newLink = getPathWithJsSuffix( id +\"/index.js\",parent)\n\ + \ return cachedFetch(newLink)\n\ + \ .then(function(text){\n\ + \ if(text !== false){\n\ + \ return{text, link : newLink }\n\ + \ } else {\n\ + \ throw new Error(` ${id} : ${parent} could not be resolved`)\n\ + \ }\n\ + \ })\n\ + \ }\n\ + \ else {\n\ + \ throw new Error(` ${id} : ${parent} could not be resolved`)\n\ + \ }\n\ + \ }\n\ + \ )\n\ + \ }\n\ + \ })\n\ + \ } else {\n\ + \ return done\n\ + \ }\n\ + }\n\ + \n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} id \n\ + \ * @param {string} parent \n\ + \ * @returns {Promise}\n\ + \ */\n\ + function getAll(id, parent) {\n\ + \ return getModulePromise(id, parent)\n\ + \ .then(function (obj) {\n\ + \ if (obj) {\n\ + \ var deps = getDeps(obj.text)\n\ + \ return Promise.all(deps.map(x => getAll(x, obj.link)))\n\ + \ } else {\n\ + \ throw new Error(`${id}@${parent} was not resolved successfully`)\n\ + \ }\n\ + \ })\n\ + };\n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} text \n\ + \ * @param {string} parent \n\ + \ * @returns {Promise}\n\ + \ */\n\ + function getAllFromText(text, parent) {\n\ + \ var deps = getDeps(text)\n\ + \ return Promise.all(deps.map(x => getAll(x, parent)))\n\ + }\n\ + \n\ + var evaluatedModules = new Map()\n\ + \n\ + function loadSync(id, parent) {\n\ + \ var baseOrModule = getIdLocationSync(id, parent)\n\ + \ if (baseOrModule && baseOrModule.link !== undefined) {\n\ + \ if(evaluatedModules.has(baseOrModule.link)){\n\ + \ return evaluatedModules.get(baseOrModule.link).exports\n\ + \ }\n\ + \ if (!baseOrModule.exports) {\n\ + \ baseOrModule.exports = {}\n\ + \ globalEval(`(function(require,exports,module){${baseOrModule.text}\\n})//# sourceURL=${baseOrModule.link}`)(\n\ + \ function require(id) {\n\ + \ return loadSync(id, baseOrModule.link);\n\ + \ }, // require\n\ + \ baseOrModule.exports = {}, // exports\n\ + \ baseOrModule // module\n\ + \ );\n\ + \ }\n\ + \ if(!evaluatedModules.has(baseOrModule.link)){\n\ + \ evaluatedModules.set(baseOrModule.link,baseOrModule)\n\ + \ }\n\ + \ return baseOrModule.exports\n\ + \ } else {\n\ + \ throw new Error(`${id} : ${parent} could not be resolved`)\n\ + \ }\n\ + }\n\ + \n\ + \n\ + function genEvalName() {\n\ + \ return \"eval-\" + ((\"\" + Math.random()).substr(2, 5))\n\ + }\n\ + /**\n\ + \ * \n\ + \ * @param {string} text \n\ + \ * @param {string} link\n\ + \ * In this case [text] evaluated result will not be cached\n\ + \ */\n\ + function loadTextSync(text, link) {\n\ + \ var baseOrModule = { exports: {}, text, link }\n\ + \ globalEval(`(function(require,exports,module){${baseOrModule.text}\\n})//# sourceURL=${baseOrModule.link}/${genEvalName()}.js`)(\n\ + \ function require(id) {\n\ + \ return loadSync(id, baseOrModule.link);\n\ + \ }, // require\n\ + \ baseOrModule.exports, // exports\n\ + \ baseOrModule // module\n\ + \ );\n\ + \ return baseOrModule.exports\n\ + }\n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} text \n\ + \ */\n\ + function BSloadText(text) {\n\ + \ console.time(\"Loading\")\n\ + \ var parent = BsGetPath(\".\", document.baseURI)\n\ + \ return getAllFromText(text, parent).then(function () {\n\ + \ var result = loadTextSync(text, parent)\n\ + \ console.timeEnd(\"Loading\")\n\ + \ return result\n\ + \ })\n\ + };\n\ + \n\ + \n\ + function load(id, parent) {\n\ + \ return getAll(id, parent).then(function () {\n\ + \ return loadSync(id, parent)\n\ + \ })\n\ + \n\ + };\n\ + \n\ + \n\ + export function BSload(id) {\n\ + \ var parent = BsGetPath(\".\", document.baseURI)\n\ + \ return load(id, parent)\n\ + }\n\ + \n\ + export var BSLoader = {\n\ + \ loadText: BSloadText,\n\ + \ load: BSload,\n\ + \ SyncedIDLocations: SyncedIDLocations\n\ + };\n\ + \n\ + window.BSLoader = BSLoader;\n\ + \n\ + var main = document.querySelector('script[data-main]')\n\ + if (main) {\n\ + \ BSload(main.dataset.main)\n\ + }\n\ + " + ); + File("package.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"scripts\": {\n\ + \ \"clean\": \"bsb -clean-world\",\n\ + \ \"build\": \"bsb -make-world\",\n\ + \ \"watch\": \"bsb -make-world -w -ws _\"\n\ + \ },\n\ + \ \"keywords\": [\n\ + \ \"BuckleScript\"\n\ + \ ],\n\ + \ \"author\": \"\",\n\ + \ \"license\": \"MIT\",\n\ + \ \"devDependencies\": {\n\ + \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ + \ },\n\ + \ \"dependencies\": {\n\ + \ \"bucklescript-tea\": \"^0.7.4\"\n\ + \ }\n\ + }\n\ + " + ); + Dir("src",[ + File("demo.ml", + "(* This line opens the Tea.App modules into the current scope for Program access functions and types *)\n\ + open Tea.App\n\ + \n\ + (* This opens the Elm-style virtual-dom functions and types into the current scope *)\n\ + open Tea.Html\n\ + \n\ + (* Let's create a new type here to be our main message type that is passed around *)\n\ + type msg =\n\ + \ | Increment (* This will be our message to increment the counter *)\n\ + \ | Decrement (* This will be our message to decrement the counter *)\n\ + \ | Reset (* This will be our message to reset the counter to 0 *)\n\ + \ | Set of int (* This will be our message to set the counter to a specific value *)\n\ + \ [@@bs.deriving {accessors}] (* This is a nice quality-of-life addon from Bucklescript, it will generate function names for each constructor name, optional, but nice to cut down on code, this is unused in this example but good to have regardless *)\n\ + \n\ + (* This is optional for such a simple example, but it is good to have an `init` function to define your initial model default values, the model for Counter is just an integer *)\n\ + let init () = 4\n\ + \n\ + (* This is the central message handler, it takes the model as the first argument *)\n\ + let update model = function (* These should be simple enough to be self-explanatory, mutate the model based on the message, easy to read and follow *)\n\ + \ | Increment -> model + 1\n\ + \ | Decrement -> model - 1\n\ + \ | Reset -> 0\n\ + \ | Set v -> v\n\ + \n\ + (* This is just a helper function for the view, a simple function that returns a button based on some argument *)\n\ + let view_button title msg =\n\ + \ button\n\ + \ [ onClick msg\n\ + \ ]\n\ + \ [ text title\n\ + \ ]\n\ + \n\ + (* This is the main callback to generate the virtual-dom.\n\ + \ This returns a virtual-dom node that becomes the view, only changes from call-to-call are set on the real DOM for efficiency, this is also only called once per frame even with many messages sent in within that frame, otherwise does nothing *)\n\ + let view model =\n\ + \ div\n\ + \ []\n\ + \ [ span\n\ + \ [ style \"text-weight\" \"bold\" ]\n\ + \ [ text (string_of_int model) ]\n\ + \ ; br []\n\ + \ ; view_button \"Increment\" Increment\n\ + \ ; br []\n\ + \ ; view_button \"Decrement\" Decrement\n\ + \ ; br []\n\ + \ ; view_button \"Set to 2\" (Set 42)\n\ + \ ; br []\n\ + \ ; if model <> 0 then view_button \"Reset\" Reset else noNode\n\ + \ ]\n\ + \n\ + (* This is the main function, it can be named anything you want but `main` is traditional.\n\ + \ The Program returned here has a set of callbacks that can easily be called from\n\ + \ Bucklescript or from javascript for running this main attached to an element,\n\ + \ or even to pass a message into the event loop. You can even expose the\n\ + \ constructors to the messages to javascript via the above [@@bs.deriving {accessors}]\n\ + \ attribute on the `msg` type or manually, that way even javascript can use it safely. *)\n\ + let main =\n\ + \ beginnerProgram { (* The beginnerProgram just takes a set model state and the update and view functions *)\n\ + \ model = init (); (* Since model is a set value here, we call our init function to generate that value *)\n\ + \ update;\n\ + \ view;\n\ + \ }" + ); + File("main.ml", + "\n\ + \n\ + \n\ + Js.Global.setTimeout\n\ + \ (fun _ -> \n\ + \ Demo.main (Web.Document.getElementById \"my-element\") () \n\ + \ |. ignore\n\ + \ ) \n\ + 0" + ) + ]); + File("watcher.js", + "\n\ + \n\ + var wsReloader;\n\ + var LAST_SUCCESS_BUILD_STAMP = (localStorage.getItem('LAST_SUCCESS_BUILD_STAMP') || 0)\n\ + var WS_PORT = 9999; // configurable\n\ + \n\ + function setUpWebScoket() {\n\ + \ if (wsReloader == null || wsReloader.readyState !== 1) {\n\ + \ try {\n\ + \ wsReloader = new WebSocket(`ws://${window.location.hostname}:${WS_PORT}`)\n\ + \ wsReloader.onmessage = (msg) => {\n\ + \ var newData = JSON.parse(msg.data).LAST_SUCCESS_BUILD_STAMP\n\ + \ if (newData > LAST_SUCCESS_BUILD_STAMP) {\n\ + \ LAST_SUCCESS_BUILD_STAMP = newData\n\ + \ localStorage.setItem('LAST_SUCCESS_BUILD_STAMP', LAST_SUCCESS_BUILD_STAMP)\n\ + \ location.reload(true)\n\ + \ }\n\ + \n\ + \ }\n\ + \ } catch (exn) {\n\ + \ console.error(\"web socket failed connect\")\n\ + \ }\n\ + \ }\n\ + };\n\ + \n\ + setUpWebScoket();\n\ + setInterval(setUpWebScoket, 2000);" + ) + ]) +]) \ No newline at end of file diff --git a/jscomp/bsb/bsb_templates.mli b/jscomp/bsb/bsb_templates.mli index 9fd23c5aae..e29db93cb8 100644 --- a/jscomp/bsb/bsb_templates.mli +++ b/jscomp/bsb/bsb_templates.mli @@ -1,3 +1,6 @@ +type node = + | Dir of string * node list + | File of string * string -val root : OCamlRes.Res.node list \ No newline at end of file +val root : node list \ No newline at end of file diff --git a/jscomp/bsb/bsb_theme_init.ml b/jscomp/bsb/bsb_theme_init.ml index acf0ab859c..cb30ff5b74 100644 --- a/jscomp/bsb/bsb_theme_init.ml +++ b/jscomp/bsb/bsb_theme_init.ml @@ -64,7 +64,7 @@ let mkdir_or_not_if_exists dir = "%s expected to be added as dir but exist file is not a dir" dir | Non_exists -> Unix.mkdir dir 0o777 -let rec process_theme_aux env cwd (x : OCamlRes.Res.node) = +let rec process_theme_aux env cwd (x : Bsb_templates.node) = match x with | File (name,content) -> let new_file = cwd // name in @@ -76,10 +76,8 @@ let rec process_theme_aux env cwd (x : OCamlRes.Res.node) = List.iter (fun x -> process_theme_aux env new_cwd x ) nodes let list_themes () = - Format.fprintf Format.std_formatter "Available themes: @."; - Bsb_templates.root - |> - List.iter (fun (x : OCamlRes.Res.node) -> + Format.fprintf Format.std_formatter "Available themes: @."; + Ext_list.iter Bsb_templates.root (fun x -> match x with | Dir (x, _) -> Format.fprintf Format.std_formatter "%s@." x @@ -88,7 +86,7 @@ let list_themes () = ) (* @raise [Not_found] *) -let process_themes env theme proj_dir (themes : OCamlRes.Res.node list ) = +let process_themes env theme proj_dir (themes : Bsb_templates.node list ) = match Ext_list.find_first themes (fun x -> match x with | Dir (dir, _) -> dir = theme diff --git a/jscomp/bsb/oCamlRes.ml b/jscomp/bsb/oCamlRes.ml deleted file mode 100644 index d685282b83..0000000000 --- a/jscomp/bsb/oCamlRes.ml +++ /dev/null @@ -1,10 +0,0 @@ - - -module Res = struct - type node = - | Dir of string * node list - | File of string * string - -end - - diff --git a/jscomp/bsb/pack-templates.sh b/jscomp/bsb/pack-templates.sh deleted file mode 100755 index 85cccfe1e3..0000000000 --- a/jscomp/bsb/pack-templates.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -# This is the implementation logic for the scaffolding of a bsb template. See the README in this dir for overview. -# Usually, to naively copy a template somewhere else, you'd find the bsb/templates directory, then use `cp` to recursively copy the files onto the destination. However, this procedure isn't 100% reliable, cross-platform, fast, etc. -# In order to be more resilient to failures, we actually pack up every directory into a single ocaml file using [ocamlres](https://github.com/OCamlPro/ocp-ocamlres), through this script. -# At build time (not at install nor runtime!), ocamlres reads the `templates` directory and its content, and bundles it all up in a file called `bsb_templates.ml` (go check its content!) -# Then, when e.g. `bsb -init my-dir -theme basic` is called, it calls `Bsb_theme_init.init_sample_project`, which goes through `bsb_templates.ml` and writes out the relevant boilerplate files. - -set -e -git clean -dfx templates -ocp-ocamlres templates -o bsb_templates.ml diff --git a/lib/4.06.1/bsb.ml b/lib/4.06.1/bsb.ml index 1a3000e6bf..2868c63124 100644 --- a/lib/4.06.1/bsb.ml +++ b/lib/4.06.1/bsb.ml @@ -14314,1834 +14314,1905 @@ let global_substitute text ~reg:expr repl_fun = in String.concat "" (List.rev (replace [] 0 false)) -end -module OCamlRes -= struct -#1 "oCamlRes.ml" - - -module Res = struct - type node = - | Dir of string * node list - | File of string * string - -end - - - end module Bsb_templates : sig #1 "bsb_templates.mli" +type node = + | Dir of string * node list + | File of string * string -val root : OCamlRes.Res.node list +val root : node list end = struct #1 "bsb_templates.ml" -(* This file has been generated by ocp-ocamlres *) -let root = OCamlRes.Res.([ - Dir ("basic", [ - Dir ("src", [ - File ("demo.ml", - "\n\ - \n\ - let () = Js.log \"Hello, BuckleScript\"")]) ; - File ("bsconfig.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"sources\": {\n\ - \ \"dir\" : \"src\",\n\ - \ \"subdirs\" : true\n\ - \ },\n\ - \ \"warnings\": {\n\ - \ \"error\" : \"+101\"\n\ - \ }\n\ - }\n\ - ") ; - File ("package.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"scripts\": {\n\ - \ \"clean\": \"bsb -clean-world\",\n\ - \ \"build\": \"bsb -make-world\",\n\ - \ \"watch\": \"bsb -make-world -w\"\n\ - \ },\n\ - \ \"keywords\": [\n\ - \ \"BuckleScript\"\n\ - \ ],\n\ - \ \"author\": \"\",\n\ - \ \"license\": \"MIT\",\n\ - \ \"devDependencies\": {\n\ - \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ - \ }\n\ - }") ; - File (".gitignore", - "*.exe\n\ - *.obj\n\ - *.out\n\ - *.compile\n\ - *.native\n\ - *.byte\n\ - *.cmo\n\ - *.annot\n\ - *.cmi\n\ - *.cmx\n\ - *.cmt\n\ - *.cmti\n\ - *.cma\n\ - *.a\n\ - *.cmxa\n\ - *.obj\n\ - *~\n\ - *.annot\n\ - *.cmj\n\ - *.bak\n\ - lib/bs\n\ - *.mlast\n\ - *.mliast\n\ - .vscode\n\ - .merlin\n\ - .bsb.lock\n\ - /node_modules/\n\ - ") ; - File ("README.md", - "\n\ - \n\ - # Build\n\ - ```\n\ - npm run build\n\ - ```\n\ - \n\ - # Watch\n\ - \n\ - ```\n\ - npm run watch\n\ - ```\n\ - \n\ - ")]) ; - Dir ("basic-reason", [ - Dir ("src", [ - File ("Demo.re", - "Js.log(\"Hello, ReScript!\");\n\ - ")]) ; - File ("bsconfig.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"sources\": {\n\ - \ \"dir\" : \"src\",\n\ - \ \"subdirs\" : true\n\ - \ },\n\ - \ \"package-specs\": {\n\ - \ \"module\": \"commonjs\",\n\ - \ \"in-source\": true\n\ - \ },\n\ - \ \"suffix\": \".bs.js\",\n\ - \ \"bs-dependencies\": [\n\ - \n\ - \ ],\n\ - \ \"warnings\": {\n\ - \ \"error\" : \"+101\"\n\ - \ },\n\ - \ \"namespace\": true,\n\ - \ \"refmt\": 3\n\ - }\n\ - ") ; - File ("package.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"scripts\": {\n\ - \ \"build\": \"bsb -make-world\",\n\ - \ \"start\": \"bsb -make-world -w\",\n\ - \ \"clean\": \"bsb -clean-world\"\n\ - \ },\n\ - \ \"keywords\": [\n\ - \ \"BuckleScript\"\n\ - \ ],\n\ - \ \"author\": \"\",\n\ - \ \"license\": \"MIT\",\n\ - \ \"devDependencies\": {\n\ - \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ - \ }\n\ - }\n\ - ") ; - File (".gitignore", - ".DS_Store\n\ - .merlin\n\ - .bsb.lock\n\ - npm-debug.log\n\ - /lib/bs/\n\ - /node_modules/\n\ - ") ; - File ("README.md", - "# Basic Reason Template\n\ - \n\ - Hello! This project allows you to quickly get started with ReScript using Reason syntax. If you wanted a more sophisticated version, try the `react` template (`bsb -theme react -init .`).\n\ - \n\ - # Build\n\ - \n\ - ```bash\n\ - # for yarn\n\ - yarn build\n\ - \n\ - # for npm\n\ - npm run build\n\ - ```\n\ - \n\ - # Build + Watch\n\ - \n\ - ```bash\n\ - # for yarn\n\ - yarn start\n\ - \n\ - # for npm\n\ - npm run start\n\ - ```\n\ - \n\ - ")]) ; - Dir ("generator", [ - Dir ("src", [ - File ("test.cpp.ml", - "\n\ - (* \n\ - #define FS_VAL(name,ty) external name : ty = \"\" [@@bs.module \"fs\"]\n\ - \n\ - \n\ - FS_VAL(readdirSync, string -> string array)\n\ - \ *)\n\ - \n\ - \n\ - \ let ocaml = OCAML") ; - File ("demo.ml", - "\n\ - \n\ - let () = Js.log \"Hello, BuckleScript\"")]) ; - File ("bsconfig.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"sources\": {\n\ - \ \"dir\": \"src\",\n\ - \ \"generators\": [{\n\ - \ \"name\": \"cpp\",\n\ - \ \"edge\": [\"test.ml\", \":\", \"test.cpp.ml\"]\n\ - \ }],\n\ - \ \"subdirs\": true \n\ - \ },\n\ - \ \"generators\": [{\n\ - \ \"name\" : \"cpp\",\n\ - \ \"command\": \"sed 's/OCAML/3/' $in > $out\"\n\ - \ }],\n\ - \ \"bs-dependencies\" : [\n\ - \ ]\n\ - }") ; - File ("package.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"scripts\": {\n\ - \ \"clean\": \"bsb -clean-world\",\n\ - \ \"build\": \"bsb -make-world\",\n\ - \ \"watch\": \"bsb -make-world -w\"\n\ - \ },\n\ - \ \"keywords\": [\n\ - \ \"BuckleScript\"\n\ - \ ],\n\ - \ \"author\": \"\",\n\ - \ \"license\": \"MIT\",\n\ - \ \"devDependencies\": {\n\ - \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ - \ }\n\ - }") ; - File (".gitignore", - "*.exe\n\ - *.obj\n\ - *.out\n\ - *.compile\n\ - *.native\n\ - *.byte\n\ - *.cmo\n\ - *.annot\n\ - *.cmi\n\ - *.cmx\n\ - *.cmt\n\ - *.cmti\n\ - *.cma\n\ - *.a\n\ - *.cmxa\n\ - *.obj\n\ - *~\n\ - *.annot\n\ - *.cmj\n\ - *.bak\n\ - lib/bs\n\ - *.mlast\n\ - *.mliast\n\ - .vscode\n\ - .merlin\n\ - .bsb.lock\n\ - /node_modules/\n\ - ") ; - File ("README.md", - "\n\ - \n\ - # Build\n\ - ```\n\ - npm run build\n\ - ```\n\ - \n\ - # Watch\n\ - \n\ - ```\n\ - npm run watch\n\ - ```\n\ - \n\ - \n\ - # Editor\n\ - If you use `vscode`, Press `Windows + Shift + B` it will build automatically")]) ; - Dir ("minimal", [ - Dir ("src", [ File ("main.ml", "")]) ; - File ("bsconfig.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"sources\": {\n\ - \ \"dir\": \"src\",\n\ - \ \"subdirs\": true\n\ - \ }\n\ - }") ; - File ("package.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"scripts\": {\n\ - \ \"clean\": \"bsb -clean-world\",\n\ - \ \"build\": \"bsb -make-world\",\n\ - \ \"start\": \"bsb -make-world -w\"\n\ - \ },\n\ - \ \"keywords\": [\n\ - \ \"BuckleScript\"\n\ - \ ],\n\ - \ \"author\": \"\",\n\ - \ \"license\": \"MIT\",\n\ - \ \"devDependencies\": {\n\ - \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ - \ }\n\ - }") ; - File (".gitignore", - ".DS_Store\n\ - .merlin\n\ - .bsb.lock\n\ - npm-debug.log\n\ - /lib/bs/\n\ - /node_modules/") ; - File ("README.md", - "\n\ - \ # ${bsb:name}")]) ; - Dir ("node", [ - Dir ("src", [ - File ("demo.ml", - "\n\ - \n\ - let () = Js.log \"Hello, BuckleScript\"")]) ; - File ("bsconfig.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"sources\": {\n\ - \ \"dir\": \"src\",\n\ - \ \"subdirs\" : true\n\ - \ },\n\ - \ \"package-specs\": {\n\ - \ \"module\": \"commonjs\",\n\ - \ \"in-source\": true\n\ - \ },\n\ - \ \"suffix\": \".bs.js\",\n\ - \ \"bs-dependencies\": [\n\ - \ ]\n\ - }") ; - File ("package.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"scripts\": {\n\ - \ \"clean\": \"bsb -clean-world\",\n\ - \ \"build\": \"bsb -make-world\",\n\ - \ \"watch\": \"bsb -make-world -w\"\n\ - \ },\n\ - \ \"keywords\": [\n\ - \ \"BuckleScript\"\n\ - \ ],\n\ - \ \"author\": \"\",\n\ - \ \"license\": \"MIT\",\n\ - \ \"devDependencies\": {\n\ - \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ - \ }\n\ - }") ; - File (".gitignore", - "*.exe\n\ - *.obj\n\ - *.out\n\ - *.compile\n\ - *.native\n\ - *.byte\n\ - *.cmo\n\ - *.annot\n\ - *.cmi\n\ - *.cmx\n\ - *.cmt\n\ - *.cmti\n\ - *.cma\n\ - *.a\n\ - *.cmxa\n\ - *.obj\n\ - *~\n\ - *.annot\n\ - *.cmj\n\ - *.bak\n\ - lib/bs\n\ - *.mlast\n\ - *.mliast\n\ - .vscode\n\ - .merlin\n\ - .bsb.lock\n\ - /node_modules/\n\ - ") ; - File ("README.md", - "\n\ - \n\ - # Build\n\ - ```\n\ - npm run build\n\ - ```\n\ - \n\ - # Watch\n\ - \n\ - ```\n\ - npm run watch\n\ - ```\n\ - \n\ - \n\ - # Editor\n\ - If you use `vscode`, Press `Windows + Shift + B` it will build automatically\n\ - ")]) ; - Dir ("react-hooks", [ - Dir ("src", [ - File ("Index.re", - "// Entry point\n\ - \n\ - [@bs.val] external document: Js.t({..}) = \"document\";\n\ - \n\ - // We're using raw DOM manipulations here, to avoid making you read\n\ - // ReasonReact when you might precisely be trying to learn it for the first\n\ - // time through the examples later.\n\ - let style = document##createElement(\"style\");\n\ - document##head##appendChild(style);\n\ - style##innerHTML #= ExampleStyles.style;\n\ - \n\ - let makeContainer = text => {\n\ - \ let container = document##createElement(\"div\");\n\ - \ container##className #= \"container\";\n\ - \n\ - \ let title = document##createElement(\"div\");\n\ - \ title##className #= \"containerTitle\";\n\ - \ title##innerText #= text;\n\ - \n\ - \ let content = document##createElement(\"div\");\n\ - \ content##className #= \"containerContent\";\n\ - \n\ - \ let () = container##appendChild(title);\n\ - \ let () = container##appendChild(content);\n\ - \ let () = document##body##appendChild(container);\n\ - \n\ - \ content;\n\ - };\n\ - \n\ - // All 4 examples.\n\ - ReactDOMRe.render(\n\ - \ \n\ - \ {React.string(\"Hello!\")}\n\ - \ ,\n\ - \ makeContainer(\"Blinking Greeting\"),\n\ - );\n\ - \n\ - ReactDOMRe.render(\n\ - \ ,\n\ - \ makeContainer(\"Reducer From ReactJS Docs\"),\n\ - );\n\ - \n\ - ReactDOMRe.render(\n\ - \ ,\n\ - \ makeContainer(\"Fetched Dog Pictures\"),\n\ - );\n\ - \n\ - ReactDOMRe.render(\n\ - \ ,\n\ - \ makeContainer(\"Reason Using JS Using Reason\"),\n\ - );\n\ - ") ; - File ("ExampleStyles.re", - "let reasonReactBlue = \"#48a9dc\";\n\ - \n\ - // The {j|...|j} feature is just string interpolation, from\n\ - // bucklescript.github.io/docs/en/interop-cheatsheet#string-unicode-interpolation\n\ - // This allows us to conveniently write CSS, together with variables, by\n\ - // constructing a string\n\ - let style = {j|\n\ - \ body {\n\ - \ background-color: rgb(224, 226, 229);\n\ - \ display: flex;\n\ - \ flex-direction: column;\n\ - \ align-items: center;\n\ - \ }\n\ - \ button {\n\ - \ background-color: white;\n\ - \ color: $reasonReactBlue;\n\ - \ box-shadow: 0 0 0 1px $reasonReactBlue;\n\ - \ border: none;\n\ - \ padding: 8px;\n\ - \ font-size: 16px;\n\ - \ }\n\ - \ button:active {\n\ - \ background-color: $reasonReactBlue;\n\ - \ color: white;\n\ - \ }\n\ - \ .container {\n\ - \ margin: 12px 0px;\n\ - \ box-shadow: 0px 4px 16px rgb(200, 200, 200);\n\ - \ width: 720px;\n\ - \ border-radius: 12px;\n\ - \ font-family: sans-serif;\n\ - \ }\n\ - \ .containerTitle {\n\ - \ background-color: rgb(242, 243, 245);\n\ - \ border-radius: 12px 12px 0px 0px;\n\ - \ padding: 12px;\n\ - \ font-weight: bold;\n\ - \ }\n\ - \ .containerContent {\n\ - \ background-color: white;\n\ - \ padding: 16px;\n\ - \ border-radius: 0px 0px 12px 12px;\n\ - \ }\n\ - |j};\n\ - ") ; - Dir ("FetchedDogPictures", [ - File ("FetchedDogPictures.re", - "[@bs.val] external fetch: string => Js.Promise.t('a) = \"fetch\";\n\ - \n\ - type state =\n\ - \ | LoadingDogs\n\ - \ | ErrorFetchingDogs\n\ - \ | LoadedDogs(array(string));\n\ - \n\ - [@react.component]\n\ - let make = () => {\n\ - \ let (state, setState) = React.useState(() => LoadingDogs);\n\ - \n\ - \ // Notice that instead of `useEffect`, we have `useEffect0`. See\n\ - \ // reasonml.github.io/reason-react/docs/en/components#hooks for more info\n\ - \ React.useEffect0(() => {\n\ - \ Js.Promise.(\n\ - \ fetch(\"https://dog.ceo/api/breeds/image/random/3\")\n\ - \ |> then_(response => response##json())\n\ - \ |> then_(jsonResponse => {\n\ - \ setState(_previousState => LoadedDogs(jsonResponse##message));\n\ - \ Js.Promise.resolve();\n\ - \ })\n\ - \ |> catch(_err => {\n\ - \ setState(_previousState => ErrorFetchingDogs);\n\ - \ Js.Promise.resolve();\n\ - \ })\n\ - \ |> ignore\n\ - \ );\n\ - \n\ - \ // Returning None, instead of Some(() => ...), means we don't have any\n\ - \ // cleanup to do before unmounting. That's not 100% true. We should\n\ - \ // technically cancel the promise. Unofortunately, there's currently no\n\ - \ // way to cancel a promise. Promises in general should be way less used\n\ - \ // for React components; but since folks do use them, we provide such an\n\ - \ // example here. In reality, this fetch should just be a plain callback,\n\ - \ // with a cancellation API\n\ - \ None;\n\ - \ });\n\ - \n\ - \ \n\ - \ {switch (state) {\n\ - \ | ErrorFetchingDogs => React.string(\"An error occurred!\")\n\ - \ | LoadingDogs => React.string(\"Loading...\")\n\ - \ | LoadedDogs(dogs) =>\n\ - \ dogs\n\ - \ ->Belt.Array.mapWithIndex((i, dog) => {\n\ - \ let imageStyle =\n\ - \ ReactDOMRe.Style.make(\n\ - \ ~height=\"120px\",\n\ - \ ~width=\"100%\",\n\ - \ ~marginRight=i === Js.Array.length(dogs) - 1 ? \"0px\" : \"8px\",\n\ - \ ~borderRadius=\"8px\",\n\ - \ ~boxShadow=\"0px 4px 16px rgb(200, 200, 200)\",\n\ - \ ~backgroundSize=\"cover\",\n\ - \ ~backgroundImage={j|url($dog)|j},\n\ - \ ~backgroundPosition=\"center\",\n\ - \ (),\n\ - \ );\n\ - \
;\n\ - \ })\n\ - \ ->React.array\n\ - \ }}\n\ - \
;\n\ - };\n\ - ")]) ; - Dir ("BlinkingGreeting", [ - File ("BlinkingGreeting.re", - "[@react.component]\n\ - let make = (~children) => {\n\ - \ let (show, setShow) = React.useState(() => true);\n\ - \n\ - \ // Notice that instead of `useEffect`, we have `useEffect0`. See\n\ - \ // reasonml.github.io/reason-react/docs/en/components#hooks for more info\n\ - \ React.useEffect0(() => {\n\ - \ let id =\n\ - \ Js.Global.setInterval(\n\ - \ () => setShow(previousShow => !previousShow),\n\ - \ 1000,\n\ - \ );\n\ - \n\ - \ Some(() => Js.Global.clearInterval(id));\n\ - \ });\n\ - \n\ - \ let style =\n\ - \ if (show) {\n\ - \ ReactDOMRe.Style.make(~opacity=\"1\", ~transition=\"opacity 1s\", ());\n\ - \ } else {\n\ - \ ReactDOMRe.Style.make(~opacity=\"0\", ~transition=\"opacity 1s\", ());\n\ - \ };\n\ - \n\ - \
children
;\n\ - };\n\ - ")]) ; - Dir ("ReasonUsingJSUsingReason", [ - File ("ReasonUsingJSUsingReason.re", - "// In this Interop example folder, we have:\n\ - // - A ReasonReact component, ReasonReactCard.re\n\ - // - Used by a ReactJS component, ReactJSCard.js\n\ - // - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re (this file)\n\ - // - ReasonUsingJSUsingReason.re is used by Index.re\n\ - \n\ - // All you need to do to use a ReactJS component in ReasonReact, is to write the lines below!\n\ - // reasonml.github.io/reason-react/docs/en/components#import-from-js\n\ - [@react.component] [@bs.module]\n\ - external make: unit => React.element = \"./ReactJSCard\";\n\ - ") ; - File ("ReasonReactCard.re", - "// In this Interop example folder, we have:\n\ - // - A ReasonReact component, ReasonReactCard.re (this file)\n\ - // - Used by a ReactJS component, ReactJSCard.js\n\ - // - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re\n\ - // - ReasonUsingJSUsingReason.re is used by Index.re\n\ - \n\ - [@react.component]\n\ - let make = (~style) => {\n\ - \
{React.string(\"This is a ReasonReact card\")}
;\n\ - };\n\ - ") ; - File ("ReactJSCard.js", - "// In this Interop example folder, we have:\n\ - // - A ReasonReact component, ReasonReactCard.re\n\ - // - Used by a ReactJS component, ReactJSCard.js (this file)\n\ - // - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re\n\ - // - ReasonUsingJSUsingReason.re is used by Index.re\n\ - \n\ - var ReactDOM = require('react-dom');\n\ - var React = require('react');\n\ - \n\ - var ReasonReactCard = require('./ReasonReactCard.bs').make;\n\ - \n\ - var ReactJSComponent = function() {\n\ - \ let backgroundColor = \"rgba(0, 0, 0, 0.05)\";\n\ - \ let padding = \"12px\";\n\ - \n\ - \ // We're not using JSX here, to avoid folks needing to install the related\n\ - \ // React toolchains just for this example.\n\ - \ //
\n\ - \ //
This is a ReactJS card
\n\ - \ // \n\ - \ //
\n\ - \ return React.createElement(\n\ - \ \"div\",\n\ - \ {style: {backgroundColor, padding, borderRadius: \"8px\"}},\n\ - \ React.createElement(\"div\", {style: {marginBottom: \"8px\"}}, \"This is a ReactJS card\"),\n\ - \ React.createElement(ReasonReactCard, {style: {backgroundColor, padding, borderRadius: \"4px\"}}),\n\ - \ )\n\ - };\n\ - ReactJSComponent.displayName = \"MyBanner\";\n\ - \n\ - module.exports = ReactJSComponent;\n\ - ")]) ; - Dir ("ReducerFromReactJSDocs", [ - File ("ReducerFromReactJSDocs.re", - "// This is the ReactJS documentation's useReducer example, directly ported over\n\ - // https://reactjs.org/docs/hooks-reference.html#usereducer\n\ - \n\ - // A little extra we've put, because the ReactJS example has no styling\n\ - let leftButtonStyle = ReactDOMRe.Style.make(~borderRadius=\"4px 0px 0px 4px\", ~width=\"48px\", ());\n\ - let rightButtonStyle = ReactDOMRe.Style.make(~borderRadius=\"0px 4px 4px 0px\", ~width=\"48px\", ());\n\ - let containerStyle = ReactDOMRe.Style.make(~display=\"flex\", ~alignItems=\"center\", ~justifyContent=\"space-between\", ());\n\ - \n\ - // Record and variant need explicit declarations.\n\ - type state = {count: int};\n\ - \n\ - type action =\n\ - \ | Increment\n\ - \ | Decrement;\n\ - \n\ - let initialState = {count: 0};\n\ - \n\ - let reducer = (state, action) => {\n\ - \ switch (action) {\n\ - \ | Increment => {count: state.count + 1}\n\ - \ | Decrement => {count: state.count - 1}\n\ - \ };\n\ - };\n\ - \n\ - [@react.component]\n\ - let make = () => {\n\ - \ let (state, dispatch) = React.useReducer(reducer, initialState);\n\ - \n\ - \ // We can use a fragment here, but we don't, because we want to style the counter\n\ - \
\n\ - \
\n\ - \ {React.string(\"Count: \")}\n\ - \ {React.string(string_of_int(state.count))}\n\ - \
\n\ - \
\n\ - \ \n\ - \ \n\ - \
\n\ - \
;\n\ - };\n\ - ")])]) ; - File ("UNUSED_webpack.config.js", - "const path = require('path');\n\ - \n\ - module.exports = {\n\ - \ entry: './src/Index.bs.js',\n\ - \ // If you ever want to use webpack during development, change 'production'\n\ - \ // to 'development' as per webpack documentation. Again, you don't have to\n\ - \ // use webpack or any other bundler during development! Recheck README if\n\ - \ // you didn't know this\n\ - \ mode: 'production',\n\ - \ output: {\n\ - \ path: path.join(__dirname, \"bundleOutput\"),\n\ - \ filename: 'index.js',\n\ - \ },\n\ - };") ; - File ("bsconfig.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"reason\": {\n\ - \ \"react-jsx\": 3\n\ - \ },\n\ - \ \"sources\": {\n\ - \ \"dir\" : \"src\",\n\ - \ \"subdirs\" : true\n\ - \ },\n\ - \ \"bsc-flags\": [\"-bs-super-errors\", \"-bs-no-version-header\"],\n\ - \ \"package-specs\": [{\n\ - \ \"module\": \"commonjs\",\n\ - \ \"in-source\": true\n\ - \ }],\n\ - \ \"suffix\": \".bs.js\",\n\ - \ \"namespace\": true,\n\ - \ \"bs-dependencies\": [\n\ - \ \"reason-react\"\n\ - \ ],\n\ - \ \"refmt\": 3\n\ - }\n\ - ") ; - File ("watcher.js", - "// This is our simple, robust watcher. It hooks into the BuckleScript build\n\ - // system to listen for build events.\n\ - // See package.json's `start` script and `./node_modules/.bin/bsb --help`\n\ - \n\ - // Btw, if you change this file and reload the page, your browser cache\n\ - // _might_ not pick up the new version. If you're in Chrome, do Force Reload.\n\ - \n\ - var websocketReloader;\n\ - var LAST_SUCCESS_BUILD_STAMP = localStorage.getItem('LAST_SUCCESS_BUILD_STAMP') || 0;\n\ - // package.json's `start` script's `bsb -ws _` means it'll pipe build events\n\ - // through a websocket connection to a default port of 9999. This is\n\ - // configurable, e.g. `-ws 5000`\n\ - var webSocketPort = 9999;\n\ - \n\ - function setUpWebSocket() {\n\ - \ if (websocketReloader == null || websocketReloader.readyState !== 1) {\n\ - \ try {\n\ - \ websocketReloader = new WebSocket(`ws://${window.location.hostname}:${webSocketPort}`);\n\ - \ websocketReloader.onmessage = (message) => {\n\ - \ var newData = JSON.parse(message.data).LAST_SUCCESS_BUILD_STAMP;\n\ - \ if (newData > LAST_SUCCESS_BUILD_STAMP) {\n\ - \ LAST_SUCCESS_BUILD_STAMP = newData;\n\ - \ localStorage.setItem('LAST_SUCCESS_BUILD_STAMP', LAST_SUCCESS_BUILD_STAMP);\n\ - \ // Refresh the page! This will naturally re-run everything,\n\ - \ // including our moduleserve which will re-resolve all the modules.\n\ - \ // No stable build!\n\ - \ location.reload(true);\n\ - \ }\n\ - \n\ - \ }\n\ - \ } catch (exn) {\n\ - \ console.error(\"The watcher tried to connect to web socket, but failed. Here's the message:\");\n\ - \ console.error(exn);\n\ - \ }\n\ - \ }\n\ - };\n\ - \n\ - setUpWebSocket();\n\ - setInterval(setUpWebSocket, 2000);\n\ - ") ; - File ("package.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"scripts\": {\n\ - \ \"build\": \"bsb -make-world\",\n\ - \ \"start\": \"bsb -make-world -w -ws _ \",\n\ - \ \"clean\": \"bsb -clean-world\",\n\ - \ \"server\": \"moduleserve ./ --port 8000\",\n\ - \ \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n\ - \ },\n\ - \ \"keywords\": [\n\ - \ \"BuckleScript\",\n\ - \ \"ReasonReact\",\n\ - \ \"reason-react\"\n\ - \ ],\n\ - \ \"author\": \"\",\n\ - \ \"license\": \"MIT\",\n\ - \ \"dependencies\": {\n\ - \ \"react\": \"^16.8.1\",\n\ - \ \"react-dom\": \"^16.8.1\",\n\ - \ \"reason-react\": \">=0.7.1\"\n\ - \ },\n\ - \ \"devDependencies\": {\n\ - \ \"bs-platform\": \"^${bsb:bs-version}\",\n\ - \ \"moduleserve\": \"^0.9.0\"\n\ - \ }\n\ - }\n\ - ") ; - File (".gitignore", - ".DS_Store\n\ - .merlin\n\ - .bsb.lock\n\ - npm-debug.log\n\ - /lib/bs/\n\ - /node_modules/\n\ - /bundleOutput/") ; - File ("README.md", - "# ReasonReact Template & Examples\n\ - \n\ - This is:\n\ - - A template for your new ReasonReact project.\n\ - - A collection of thin examples illustrating ReasonReact usage.\n\ - - Extra helper documentation for ReasonReact (full ReasonReact docs [here](https://reasonml.github.io/reason-react/)).\n\ - \n\ - `src` contains 4 sub-folders, each an independent, self-contained ReasonReact example. Feel free to delete any of them and shape this into your project! This template's more malleable than you might be used to =).\n\ - \n\ - The point of this template and examples is to let you understand and personally tweak the entirely of it. We **don't** give you an opaque, elaborate mega build setup just to put some boxes on the screen. It strikes to stay transparent, learnable, and simple. You're encouraged to read every file; it's a great feeling, having the full picture of what you're using and being able to touch any part.\n\ - \n\ - ## Run\n\ - \n\ - ```sh\n\ - npm install\n\ - npm run server\n\ - # in a new tab\n\ - npm start\n\ - ```\n\ - \n\ - Open a new web page to `http://localhost:8000/`. Change any `.re` file in `src` to see the page auto-reload. **You don't need any bundler when you're developing**!\n\ - \n\ - **How come we don't need any bundler during development**? We highly encourage you to open up `index.html` to check for yourself!\n\ - \n\ - # Features Used\n\ - \n\ - | | Blinking Greeting | Reducer from ReactJS Docs | Fetch Dog Pictures | Reason Using JS Using Reason |\n\ - |---------------------------|-------------------|---------------------------|--------------------|------------------------------|\n\ - | No props | | \xE2\x9C\x93 | | |\n\ - | Has props | | | | \xE2\x9C\x93 |\n\ - | Children props | \xE2\x9C\x93 | | | |\n\ - | No state | | | | \xE2\x9C\x93 |\n\ - | Has state | \xE2\x9C\x93 | | \xE2\x9C\x93 | |\n\ - | Has state with useReducer | | \xE2\x9C\x93 | | |\n\ - | ReasonReact using ReactJS | | | | \xE2\x9C\x93 |\n\ - | ReactJS using ReasonReact | | | | \xE2\x9C\x93 |\n\ - | useEffect | \xE2\x9C\x93 | | \xE2\x9C\x93 | |\n\ - | Dom attribute | \xE2\x9C\x93 | \xE2\x9C\x93 | | \xE2\x9C\x93 |\n\ - | Styling | \xE2\x9C\x93 | \xE2\x9C\x93 | \xE2\x9C\x93 | \xE2\x9C\x93 |\n\ - | React.array | | | \xE2\x9C\x93 | |\n\ - \n\ - # Bundle for Production\n\ - \n\ - We've included a convenience `UNUSED_webpack.config.js`, in case you want to ship your project to production. You can rename and/or remove that in favor of other bundlers, e.g. Rollup.\n\ - \n\ - We've also provided a barebone `indexProduction.html`, to serve your bundle.\n\ - \n\ - ```sh\n\ - npm install webpack webpack-cli\n\ - # rename file\n\ - mv UNUSED_webpack.config.js webpack.config.js\n\ - # call webpack to bundle for production\n\ - ./node_modules/.bin/webpack\n\ - open indexProduction.html\n\ - ```\n\ - \n\ - # Handle Routing Yourself\n\ - \n\ - To serve the files, this template uses a minimal dependency called `moduleserve`. A URL such as `localhost:8000/scores/john` resolves to the file `scores/john.html`. If you'd like to override this and handle URL resolution yourself, change the `server` command in `package.json` from `moduleserve ./ --port 8000` to `moduleserve ./ --port 8000 --spa` (for \"single page application\"). This will make `moduleserve` serve the default `index.html` for any URL. Since `index.html` loads `Index.bs.js`, you can grab hold of the URL in the corresponding `Index.re` and do whatever you want.\n\ - \n\ - By the way, ReasonReact comes with a small [router](https://reasonml.github.io/reason-react/docs/en/router) you might be interested in.\n\ - ") ; - File ("indexProduction.html", - "\n\ - \n\ - \n\ - \ \n\ - \ ReasonReact Examples\n\ - \n\ - \n\ - \ \n\ - \n\ - \n\ - ") ; - File ("index.html", - "\n\ - \n\ - \n\ - \ \n\ - \ ReasonReact Examples\n\ - \n\ - \n\ - \ \n\ - \n\ - \ \n\ - \ \n\ - \ \n\ - \ \n\ - \n\ - \n\ - ")]) ; - Dir ("react-starter", [ - Dir ("src", [ - File ("Index.re", - "[%bs.raw {|require(\"./index.css\")|}];\n\ - \n\ - ReactDOMRe.renderToElementWithId(, \"root\");\n\ - ") ; - File ("index.css", - "body {\n\ - \ margin: 0;\n\ - \ font-family: -apple-system, system-ui, \"Segoe UI\", Helvetica, Arial,\n\ - \ sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n\ - }\n\ - \n\ - main {\n\ - \ padding: 20px;\n\ - }\n\ - \n\ - .counter {\n\ - \ padding: 20px;\n\ - \ display: inline-block;\n\ - }\n\ - ") ; - File ("App.re", - "type state = {count: int};\n\ - \n\ - type action =\n\ - \ | Increment\n\ - \ | Decrement;\n\ - \n\ - let initialState = {count: 0};\n\ - \n\ - let reducer = (state, action) =>\n\ - \ switch (action) {\n\ - \ | Increment => {count: state.count + 1}\n\ - \ | Decrement => {count: state.count - 1}\n\ - \ };\n\ - \n\ - [@react.component]\n\ - let make = () => {\n\ - \ let (state, dispatch) = React.useReducer(reducer, initialState);\n\ - \n\ - \
\n\ - \ {React.string(\"Simple counter with reducer\")}\n\ - \
\n\ - \ \n\ - \ \n\ - \ {state.count |> string_of_int |> React.string}\n\ - \ \n\ - \ \n\ - \
\n\ - \
;\n\ - };\n\ - ") ; - File ("index.html", - "\n\ - \n\ - \ \n\ - \ \n\ - \ Reason react starter\n\ - \ \n\ - \ \n\ - \
\n\ - \ \n\ - \ \n\ - \n\ - ")]) ; - File ("bsconfig.json", - "{\n\ - \ \"name\": \"reason-react-starter\",\n\ - \ \"reason\": {\n\ - \ \"react-jsx\": 3\n\ - \ },\n\ - \ \"sources\": {\n\ - \ \"dir\": \"src\",\n\ - \ \"subdirs\": true\n\ - \ },\n\ - \ \"bsc-flags\": [\"-bs-super-errors\", \"-bs-no-version-header\"],\n\ - \ \"package-specs\": [\n\ - \ {\n\ - \ \"module\": \"commonjs\",\n\ - \ \"in-source\": true\n\ - \ }\n\ - \ ],\n\ - \ \"suffix\": \".bs.js\",\n\ - \ \"namespace\": true,\n\ - \ \"bs-dependencies\": [\"reason-react\"],\n\ - \ \"refmt\": 3\n\ - }\n\ - ") ; - File ("package.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"scripts\": {\n\ - \ \"build\": \"bsb -make-world\",\n\ - \ \"start\": \"bsb -make-world -w -ws _ \",\n\ - \ \"clean\": \"bsb -clean-world\",\n\ - \ \"webpack\": \"webpack -w\",\n\ - \ \"webpack:production\": \"NODE_ENV=production webpack\",\n\ - \ \"server\": \"webpack-dev-server\",\n\ - \ \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n\ - \ },\n\ - \ \"keywords\": [\n\ - \ \"BuckleScript\",\n\ - \ \"ReasonReact\",\n\ - \ \"reason-react\"\n\ - \ ],\n\ - \ \"author\": \"\",\n\ - \ \"license\": \"MIT\",\n\ - \ \"dependencies\": {\n\ - \ \"react\": \"^17.0.1\",\n\ - \ \"react-dom\": \"^17.0.1\",\n\ - \ \"reason-react\": \"^0.9.1\"\n\ - \ },\n\ - \ \"devDependencies\": {\n\ - \ \"bs-platform\": \"^${bsb:bs-version}\",\n\ - \ \"css-loader\": \"^5.0.0\",\n\ - \ \"html-webpack-plugin\": \"^4.5.0\",\n\ - \ \"style-loader\": \"^2.0.0\",\n\ - \ \"webpack\": \"^4.44.2\",\n\ - \ \"webpack-cli\": \"^3.3.12\",\n\ - \ \"webpack-dev-server\": \"^3.11.0\"\n\ - \ }\n\ - }\n\ - ") ; - File (".gitignore", - ".DS_Store\n\ - .merlin\n\ - .bsb.lock\n\ - npm-debug.log\n\ - /lib/bs/\n\ - /node_modules/\n\ - *.bs.js\n\ - ") ; - File ("README.md", - "# Reason react starter\n\ - \n\ - ## Run Project\n\ - \n\ - ```sh\n\ - npm install\n\ - npm start\n\ - # in another tab\n\ - npm run server\n\ - ```\n\ - \n\ - View the app in the browser at http://localhost:8000. Running in this environment provides hot reloading and support for routing; just edit and save the file and the browser will automatically refresh.\n\ - \n\ - To use a port other than 8000 set the `PORT` environment variable (`PORT=8080 npm run server`).\n\ - \n\ - ## Build for Production\n\ - \n\ - ```sh\n\ - npm run clean\n\ - npm run build\n\ - npm run webpack:production\n\ - ```\n\ - \n\ - This will replace the development artifact `build/Index.js` for an optimized version as well as copy `src/index.html` into `build/`. You can then deploy the contents of the `build` directory (`index.html` and `Index.js`).\n\ - \n\ - **To enable dead code elimination**, change `bsconfig.json`'s `package-specs` `module` from `\"commonjs\"` to `\"es6\"`. Then re-run the above 2 commands. This will allow Webpack to remove unused code.\n\ - ") ; - File ("webpack.config.js", - "const path = require(\"path\")\n\ - const HtmlWebpackPlugin = require(\"html-webpack-plugin\")\n\ - const outputDir = path.join(__dirname, \"build/\")\n\ - \n\ - const isProd = process.env.NODE_ENV === \"production\"\n\ - \n\ - module.exports = {\n\ - \ entry: \"./src/Index.bs.js\",\n\ - \ mode: isProd ? \"production\" : \"development\",\n\ - \ devtool: \"source-map\",\n\ - \ output: {\n\ - \ path: outputDir,\n\ - \ filename: \"Index.js\"\n\ - \ },\n\ - \ plugins: [\n\ - \ new HtmlWebpackPlugin({\n\ - \ template: \"src/index.html\",\n\ - \ inject: false\n\ - \ })\n\ - \ ],\n\ - \ devServer: {\n\ - \ compress: true,\n\ - \ contentBase: outputDir,\n\ - \ port: process.env.PORT || 8000,\n\ - \ historyApiFallback: true\n\ - \ },\n\ - \ module: {\n\ - \ rules: [\n\ - \ {\n\ - \ test: /\\.css$/,\n\ - \ use: [\"style-loader\", \"css-loader\"]\n\ - \ }\n\ - \ ]\n\ - \ }\n\ - }\n\ - ")]) ; - Dir ("tea", [ - Dir ("src", [ - File ("main.ml", - "\n\ - \n\ - \n\ - Js.Global.setTimeout\n\ - \ (fun _ -> \n\ - \ Demo.main (Web.Document.getElementById \"my-element\") () \n\ - \ |. ignore\n\ - \ ) \n\ - 0") ; - File ("demo.ml", - "(* This line opens the Tea.App modules into the current scope for Program access functions and types *)\n\ - open Tea.App\n\ - \n\ - (* This opens the Elm-style virtual-dom functions and types into the current scope *)\n\ - open Tea.Html\n\ - \n\ - (* Let's create a new type here to be our main message type that is passed around *)\n\ - type msg =\n\ - \ | Increment (* This will be our message to increment the counter *)\n\ - \ | Decrement (* This will be our message to decrement the counter *)\n\ - \ | Reset (* This will be our message to reset the counter to 0 *)\n\ - \ | Set of int (* This will be our message to set the counter to a specific value *)\n\ - \ [@@bs.deriving {accessors}] (* This is a nice quality-of-life addon from Bucklescript, it will generate function names for each constructor name, optional, but nice to cut down on code, this is unused in this example but good to have regardless *)\n\ - \n\ - (* This is optional for such a simple example, but it is good to have an `init` function to define your initial model default values, the model for Counter is just an integer *)\n\ - let init () = 4\n\ - \n\ - (* This is the central message handler, it takes the model as the first argument *)\n\ - let update model = function (* These should be simple enough to be self-explanatory, mutate the model based on the message, easy to read and follow *)\n\ - \ | Increment -> model + 1\n\ - \ | Decrement -> model - 1\n\ - \ | Reset -> 0\n\ - \ | Set v -> v\n\ - \n\ - (* This is just a helper function for the view, a simple function that returns a button based on some argument *)\n\ - let view_button title msg =\n\ - \ button\n\ - \ [ onClick msg\n\ - \ ]\n\ - \ [ text title\n\ - \ ]\n\ - \n\ - (* This is the main callback to generate the virtual-dom.\n\ - \ This returns a virtual-dom node that becomes the view, only changes from call-to-call are set on the real DOM for efficiency, this is also only called once per frame even with many messages sent in within that frame, otherwise does nothing *)\n\ - let view model =\n\ - \ div\n\ - \ []\n\ - \ [ span\n\ - \ [ style \"text-weight\" \"bold\" ]\n\ - \ [ text (string_of_int model) ]\n\ - \ ; br []\n\ - \ ; view_button \"Increment\" Increment\n\ - \ ; br []\n\ - \ ; view_button \"Decrement\" Decrement\n\ - \ ; br []\n\ - \ ; view_button \"Set to 2\" (Set 42)\n\ - \ ; br []\n\ - \ ; if model <> 0 then view_button \"Reset\" Reset else noNode\n\ - \ ]\n\ - \n\ - (* This is the main function, it can be named anything you want but `main` is traditional.\n\ - \ The Program returned here has a set of callbacks that can easily be called from\n\ - \ Bucklescript or from javascript for running this main attached to an element,\n\ - \ or even to pass a message into the event loop. You can even expose the\n\ - \ constructors to the messages to javascript via the above [@@bs.deriving {accessors}]\n\ - \ attribute on the `msg` type or manually, that way even javascript can use it safely. *)\n\ - let main =\n\ - \ beginnerProgram { (* The beginnerProgram just takes a set model state and the update and view functions *)\n\ - \ model = init (); (* Since model is a set value here, we call our init function to generate that value *)\n\ - \ update;\n\ - \ view;\n\ - \ }")]) ; - File ("loader.js", - "/* Copyright (C) 2018 Authors of BuckleScript\n\ - \ * \n\ - \ * This program is free software: you can redistribute it and/or modify\n\ - \ * it under the terms of the GNU Lesser General Public License as published by\n\ - \ * the Free Software Foundation, either version 3 of the License, or\n\ - \ * (at your option) any later version.\n\ - \ *\n\ - \ * In addition to the permissions granted to you by the LGPL, you may combine\n\ - \ * or link a \"work that uses the Library\" with a publicly distributed version\n\ - \ * of this file to produce a combined library or application, then distribute\n\ - \ * that combined work under the terms of your choosing, with no requirement\n\ - \ * to comply with the obligations normally placed on you by section 4 of the\n\ - \ * LGPL version 3 (or the corresponding section of a later version of the LGPL\n\ - \ * should you choose to use a later version).\n\ - \ *\n\ - \ * This program is distributed in the hope that it will be useful,\n\ - \ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n\ - \ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\ - \ * GNU Lesser General Public License for more details.\n\ - \ * \n\ - \ * You should have received a copy of the GNU Lesser General Public License\n\ - \ * along with this program; if not, write to the Free Software\n\ - \ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */\n\ - \n\ - \n\ - \n\ - //@ts-check\n\ - \n\ - // @ts-ignore\n\ - window.process = { env: { NODE_ENV: 'dev' } }\n\ - \n\ - \n\ - // local to getPath\n\ - var relativeElement = document.createElement(\"a\");\n\ - var baseElement = document.createElement(\"base\");\n\ - document.head.appendChild(baseElement);\n\ - \n\ - export function BsGetPath(id, parent) {\n\ - \ var oldPath = baseElement.href\n\ - \ baseElement.href = parent\n\ - \ relativeElement.href = id\n\ - \ var result = relativeElement.href\n\ - \ baseElement.href = oldPath\n\ - \ return result\n\ - }\n\ - /**\n\ - \ * \n\ - \ * Given current link and its parent, return the new link\n\ - \ * @param {string} id \n\ - \ * @param {string} parent \n\ - \ * @return {string}\n\ - \ */\n\ - function getPathWithJsSuffix(id, parent) {\n\ - \ var oldPath = baseElement.href\n\ - \ baseElement.href = parent\n\ - \ relativeElement.href = id\n\ - \ var result = addSuffixJsIfNot(relativeElement.href)\n\ - \ baseElement.href = oldPath\n\ - \ return result\n\ - }\n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} x \n\ - \ */\n\ - function addSuffixJsIfNot(x) {\n\ - \ if (x.endsWith('.js')) {\n\ - \ return x\n\ - \ } else {\n\ - \ return x + '.js'\n\ - \ }\n\ - }\n\ - \n\ - \n\ - var falsePromise = Promise.resolve(false)\n\ - var fetchConfig = {'cache' : 'no-cache'}\n\ - // package.json semantics\n\ - // a string to module object \n\ - // from url -> module object \n\ - // Modules : Map \n\ - // fetch the link:\n\ - // - if it is already fetched before, return the stored promise\n\ - // otherwise create the promise which will be filled with the text if successful\n\ - // or filled with boolean false when failed\n\ - var MODULES = new Map()\n\ - function cachedFetch(link) {\n\ - \ // console.info(link)\n\ - \ var linkResult = MODULES.get(link)\n\ - \ if (linkResult) {\n\ - \ return linkResult\n\ - \ } else {\n\ - \ var p = fetch(link, fetchConfig)\n\ - \ .then(resp => {\n\ - \ if (resp.ok) {\n\ - \ return resp.text()\n\ - \ } else {\n\ - \ return falsePromise\n\ - \ }\n\ - \ })\n\ - \n\ - \ MODULES.set(link, p)\n\ - \ return p\n\ - \ }\n\ - }\n\ - \n\ - // from location id -> url \n\ - // There are two rounds of caching:\n\ - // 1. if location and relative path is hit, no need to run \n\ - // 2. if location and relative path is not hit, but the resolved link is hit, no need \n\ - // for network request\n\ - /**\n\ - \ * @type {Map > > }\n\ - \ */\n\ - var IDLocations = new Map()\n\ - \n\ - /**\n\ - \ * @type {Map > }\n\ - \ */\n\ - var SyncedIDLocations = new Map()\n\ - // Its value is an object \n\ - // { link : String }\n\ - // We will first mark it when visiting (to avoid duplicated computation)\n\ - // and populate its link later\n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} id \n\ - \ * @param {string} location \n\ - \ */\n\ - function getIdLocation(id, location) {\n\ - \ var idMap = IDLocations.get(location)\n\ - \ if (idMap) {\n\ - \ return idMap.get(id)\n\ - \ }\n\ - }\n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} id \n\ - \ * @param {string} location \n\ - \ */\n\ - function getIdLocationSync(id, location) {\n\ - \ var idMap = SyncedIDLocations.get(location)\n\ - \ if (idMap) {\n\ - \ return idMap.get(id)\n\ - \ }\n\ - }\n\ - \n\ - function countIDLocations() {\n\ - \ var count = 0\n\ - \ for (let [k, vv] of IDLocations) {\n\ - \ for (let [kv, v] of vv) {\n\ - \ count += 1\n\ - \ }\n\ - \ }\n\ - \ console.log(count, 'modules loaded')\n\ - }\n\ - \n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} id \n\ - \ * @param {string} location \n\ - \ * @param {Function} cb \n\ - \ * @returns Promise\n\ - \ */\n\ - function visitIdLocation(id, location, cb) {\n\ - \ var result;\n\ - \ var idMap = IDLocations.get(location)\n\ - \ if (idMap && (result = idMap.get(id))) {\n\ - \ return result\n\ - \ }\n\ - \ else {\n\ - \ result = new Promise(resolve => {\n\ - \ return (cb()).then(res => {\n\ - \ var idMap = SyncedIDLocations.get(location)\n\ - \ if (idMap) {\n\ - \ idMap.set(id, res)\n\ - \ } else {\n\ - \ SyncedIDLocations.set(location, new Map([[id, res]]))\n\ - \ }\n\ - \ return resolve(res)\n\ - \ })\n\ - \ })\n\ - \ if (idMap) {\n\ - \ idMap.set(id, result)\n\ - \ }\n\ - \ else {\n\ - \ IDLocations.set(location, new Map([[id, result]]))\n\ - \ }\n\ - \ return result\n\ - \ }\n\ - }\n\ - \n\ - \n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} text \n\ - \ * @return {string[]}\n\ - \ */\n\ - function getDeps(text) {\n\ - \ var deps = []\n\ - \ text.replace(/(\\/\\*[\\w\\W]*?\\*\\/|\\/\\/[^\\n]*|[.$]r)|\\brequire\\s*\\(\\s*[\"']([^\"']*)[\"']\\s*\\)/g, function (_, ignore, id) {\n\ - \ if (!ignore) deps.push(id);\n\ - \ });\n\ - \ return deps;\n\ - }\n\ - \n\ - \n\ - \n\ - // By using a named \"eval\" most browsers will execute in the global scope.\n\ - // http://www.davidflanagan.com/2010/12/global-eval-in.html\n\ - var globalEval = eval;\n\ - \n\ - // function parentURL(url) {\n\ - // if (url.endsWith('/')) {\n\ - // return url + '../'\n\ - // } else {\n\ - // return url + '/../'\n\ - // }\n\ - // }\n\ - \n\ - \n\ - \n\ - // loader.js:23 http://localhost:8080/node_modules/react-dom/cjs/react-dom.development.js/..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//../ fbjs/lib/containsNode Promise\xC2\xA0{}\n\ - // 23:10:02.884 loader.js:23 http://localhost:8080/node_modules/react-dom/cjs/react-dom.development.js/..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//../ fbjs/lib/invariant Promise\xC2\xA0{}\n\ - \n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} id \n\ - \ * @param {string} parent \n\ - \ */\n\ - function getParentModulePromise(id, parent) {\n\ - \ var parentLink = BsGetPath('..', parent)\n\ - \ if (parentLink === parent) {\n\ - \ return falsePromise\n\ - \ }\n\ - \ return getPackageJsPromise(id, parentLink)\n\ - }\n\ - // In the beginning\n\ - // it is `resolveModule('./main.js', '')\n\ - // return the promise of link and text \n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} id \n\ - \ */\n\ - function getPackageName(id) {\n\ - \ var index = id.indexOf('/')\n\ - \ if (id[0] === '@') index = id.indexOf('/', index + 1)\n\ - \ if (index === -1) {\n\ - \ return id\n\ - \ }\n\ - \ return id.substring(0, index)\n\ - }\n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} s \n\ - \ * @param {string} text \n\ - \ * @returns {undefined | string }\n\ - \ */\n\ - function isJustAPackageAndHasMainField(s,text){\n\ - \ if(s.indexOf('/') >= 0){\n\ - \ return \n\ - \ } else {\n\ - \ var mainField; \n\ - \ try {\n\ - \ mainField = JSON.parse(text).main\n\ - \ }catch(_){\n\ - \ }\n\ - \ if(mainField === undefined){\n\ - \ return \n\ - \ } else {\n\ - \ return mainField\n\ - \ }\n\ - \ }\n\ - }\n\ - function getPackageJsPromise(id, parent) {\n\ - \ var idNodeModulesPrefix = './node_modules/' + id\n\ - \ var link = getPathWithJsSuffix(idNodeModulesPrefix, parent)\n\ - \ if (parent.endsWith('node_modules/')) {\n\ - \ // impossible that `node_modules/node_modules/xx/x\n\ - \ // return falsePromise\n\ - \ return getParentModulePromise(id, parent)\n\ - \ }\n\ - \n\ - \ var packageJson = BsGetPath(`./node_modules/${getPackageName(id)}/package.json`, parent)\n\ - \n\ - \ return cachedFetch(packageJson)\n\ - \ .then(\n\ - \ function (text) {\n\ - \ if (text !== false) {\n\ - \ var mainField; \n\ - \ if( (mainField = isJustAPackageAndHasMainField(id, text)) !== undefined){\n\ - \ var packageLink = BsGetPath(addSuffixJsIfNot(`./node_modules/${id}/${mainField}`), parent)\n\ - \ return cachedFetch(packageLink)\n\ - \ .then(function(text){\n\ - \ if(text !== false){\n\ - \ return {text, link : packageLink}\n\ - \ } else {\n\ - \ return getParentModulePromise(id,parent)\n\ - \ }\n\ - \ })\n\ - \n\ - \ } else {\n\ - \ // package indeed exist\n\ - \ return cachedFetch(link).then(function (text) {\n\ - \ if (text !== false) {\n\ - \ return { text, link }\n\ - \ } else if (!id.endsWith('.js')) {\n\ - \ var linkNew = getPathWithJsSuffix(idNodeModulesPrefix + `/index.js`, parent)\n\ - \ return cachedFetch(linkNew)\n\ - \ .then(function (text) {\n\ - \ if (text !== false) {\n\ - \ return { text, link: linkNew }\n\ - \ } else {\n\ - \ return getParentModulePromise(id, parent)\n\ - \ }\n\ - \ })\n\ - \n\ - \ } else {\n\ - \ return getParentModulePromise(id, parent)\n\ - \ }\n\ - \ })\n\ - \ }\n\ - \ }\n\ - \ else {\n\ - \ return getParentModulePromise(id, parent)\n\ - \ }\n\ - \ }\n\ - \ )\n\ - \n\ - \n\ - }\n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} id \n\ - \ * @param {string} parent \n\ - \ * can return Promise , false means\n\ - \ * this module can not be resolved\n\ - \ */\n\ - function getModulePromise(id, parent) {\n\ - \ var done = getIdLocation(id, parent)\n\ - \ if (!done) {\n\ - \ return visitIdLocation(id, parent, function () {\n\ - \ if (id[0] != '.') { // package path\n\ - \ return getPackageJsPromise(id, parent)\n\ - \ } else { // relative path, one shot resolve \n\ - \ let link = getPathWithJsSuffix(id, parent)\n\ - \ return cachedFetch(link).then(\n\ - \ function (text) {\n\ - \ if (text !== false) {\n\ - \ return { text, link }\n\ - \ } else if (!id.endsWith('.js')){ \n\ - \ // could be \"./dir\"\n\ - \ var newLink = getPathWithJsSuffix( id +\"/index.js\",parent)\n\ - \ return cachedFetch(newLink)\n\ - \ .then(function(text){\n\ - \ if(text !== false){\n\ - \ return{text, link : newLink }\n\ - \ } else {\n\ - \ throw new Error(` ${id} : ${parent} could not be resolved`)\n\ - \ }\n\ - \ })\n\ - \ }\n\ - \ else {\n\ - \ throw new Error(` ${id} : ${parent} could not be resolved`)\n\ - \ }\n\ - \ }\n\ - \ )\n\ - \ }\n\ - \ })\n\ - \ } else {\n\ - \ return done\n\ - \ }\n\ - }\n\ - \n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} id \n\ - \ * @param {string} parent \n\ - \ * @returns {Promise}\n\ - \ */\n\ - function getAll(id, parent) {\n\ - \ return getModulePromise(id, parent)\n\ - \ .then(function (obj) {\n\ - \ if (obj) {\n\ - \ var deps = getDeps(obj.text)\n\ - \ return Promise.all(deps.map(x => getAll(x, obj.link)))\n\ - \ } else {\n\ - \ throw new Error(`${id}@${parent} was not resolved successfully`)\n\ - \ }\n\ - \ })\n\ - };\n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} text \n\ - \ * @param {string} parent \n\ - \ * @returns {Promise}\n\ - \ */\n\ - function getAllFromText(text, parent) {\n\ - \ var deps = getDeps(text)\n\ - \ return Promise.all(deps.map(x => getAll(x, parent)))\n\ - }\n\ - \n\ - var evaluatedModules = new Map()\n\ - \n\ - function loadSync(id, parent) {\n\ - \ var baseOrModule = getIdLocationSync(id, parent)\n\ - \ if (baseOrModule && baseOrModule.link !== undefined) {\n\ - \ if(evaluatedModules.has(baseOrModule.link)){\n\ - \ return evaluatedModules.get(baseOrModule.link).exports\n\ - \ }\n\ - \ if (!baseOrModule.exports) {\n\ - \ baseOrModule.exports = {}\n\ - \ globalEval(`(function(require,exports,module){${baseOrModule.text}\\n})//# sourceURL=${baseOrModule.link}`)(\n\ - \ function require(id) {\n\ - \ return loadSync(id, baseOrModule.link);\n\ - \ }, // require\n\ - \ baseOrModule.exports = {}, // exports\n\ - \ baseOrModule // module\n\ - \ );\n\ - \ }\n\ - \ if(!evaluatedModules.has(baseOrModule.link)){\n\ - \ evaluatedModules.set(baseOrModule.link,baseOrModule)\n\ - \ }\n\ - \ return baseOrModule.exports\n\ + +type node = + | Dir of string * node list + | File of string * string +let root = ([ + Dir("basic",[ + File(".gitignore", + "*.exe\n\ + *.obj\n\ + *.out\n\ + *.compile\n\ + *.native\n\ + *.byte\n\ + *.cmo\n\ + *.annot\n\ + *.cmi\n\ + *.cmx\n\ + *.cmt\n\ + *.cmti\n\ + *.cma\n\ + *.a\n\ + *.cmxa\n\ + *.obj\n\ + *~\n\ + *.annot\n\ + *.cmj\n\ + *.bak\n\ + lib/bs\n\ + *.mlast\n\ + *.mliast\n\ + .vscode\n\ + .merlin\n\ + .bsb.lock\n\ + /node_modules/\n\ + " + ); + File("README.md", + "\n\ + \n\ + # Build\n\ + ```\n\ + npm run build\n\ + ```\n\ + \n\ + # Watch\n\ + \n\ + ```\n\ + npm run watch\n\ + ```\n\ + \n\ + " + ); + File("bsconfig.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"sources\": {\n\ + \ \"dir\" : \"src\",\n\ + \ \"subdirs\" : true\n\ + \ },\n\ + \ \"warnings\": {\n\ + \ \"error\" : \"+101\"\n\ + \ }\n\ + }\n\ + " + ); + File("package.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"scripts\": {\n\ + \ \"clean\": \"bsb -clean-world\",\n\ + \ \"build\": \"bsb -make-world\",\n\ + \ \"watch\": \"bsb -make-world -w\"\n\ + \ },\n\ + \ \"keywords\": [\n\ + \ \"BuckleScript\"\n\ + \ ],\n\ + \ \"author\": \"\",\n\ + \ \"license\": \"MIT\",\n\ + \ \"devDependencies\": {\n\ + \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ + \ }\n\ + }" + ); + Dir("src",[ + File("demo.ml", + "\n\ + \n\ + let () = Js.log \"Hello, BuckleScript\"" + ) + ]) + ]); + Dir("basic-reason",[ + File(".gitignore", + ".DS_Store\n\ + .merlin\n\ + .bsb.lock\n\ + npm-debug.log\n\ + /lib/bs/\n\ + /node_modules/\n\ + " + ); + File("README.md", + "# Basic Reason Template\n\ + \n\ + Hello! This project allows you to quickly get started with ReScript using Reason syntax. If you wanted a more sophisticated version, try the `react` template (`bsb -theme react -init .`).\n\ + \n\ + # Build\n\ + \n\ + ```bash\n\ + # for yarn\n\ + yarn build\n\ + \n\ + # for npm\n\ + npm run build\n\ + ```\n\ + \n\ + # Build + Watch\n\ + \n\ + ```bash\n\ + # for yarn\n\ + yarn start\n\ + \n\ + # for npm\n\ + npm run start\n\ + ```\n\ + \n\ + " + ); + File("bsconfig.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"sources\": {\n\ + \ \"dir\" : \"src\",\n\ + \ \"subdirs\" : true\n\ + \ },\n\ + \ \"package-specs\": {\n\ + \ \"module\": \"commonjs\",\n\ + \ \"in-source\": true\n\ + \ },\n\ + \ \"suffix\": \".bs.js\",\n\ + \ \"bs-dependencies\": [\n\ + \n\ + \ ],\n\ + \ \"warnings\": {\n\ + \ \"error\" : \"+101\"\n\ + \ },\n\ + \ \"namespace\": true,\n\ + \ \"refmt\": 3\n\ + }\n\ + " + ); + File("package.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"scripts\": {\n\ + \ \"build\": \"bsb -make-world\",\n\ + \ \"start\": \"bsb -make-world -w\",\n\ + \ \"clean\": \"bsb -clean-world\"\n\ + \ },\n\ + \ \"keywords\": [\n\ + \ \"BuckleScript\"\n\ + \ ],\n\ + \ \"author\": \"\",\n\ + \ \"license\": \"MIT\",\n\ + \ \"devDependencies\": {\n\ + \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ + \ }\n\ + }\n\ + " + ); + Dir("src",[ + File("Demo.re", + "Js.log(\"Hello, ReScript!\");\n\ + " + ) + ]) + ]); + Dir("generator",[ + File(".gitignore", + "*.exe\n\ + *.obj\n\ + *.out\n\ + *.compile\n\ + *.native\n\ + *.byte\n\ + *.cmo\n\ + *.annot\n\ + *.cmi\n\ + *.cmx\n\ + *.cmt\n\ + *.cmti\n\ + *.cma\n\ + *.a\n\ + *.cmxa\n\ + *.obj\n\ + *~\n\ + *.annot\n\ + *.cmj\n\ + *.bak\n\ + lib/bs\n\ + *.mlast\n\ + *.mliast\n\ + .vscode\n\ + .merlin\n\ + .bsb.lock\n\ + /node_modules/\n\ + " + ); + File("README.md", + "\n\ + \n\ + # Build\n\ + ```\n\ + npm run build\n\ + ```\n\ + \n\ + # Watch\n\ + \n\ + ```\n\ + npm run watch\n\ + ```\n\ + \n\ + \n\ + # Editor\n\ + If you use `vscode`, Press `Windows + Shift + B` it will build automatically" + ); + File("bsconfig.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"sources\": {\n\ + \ \"dir\": \"src\",\n\ + \ \"generators\": [{\n\ + \ \"name\": \"cpp\",\n\ + \ \"edge\": [\"test.ml\", \":\", \"test.cpp.ml\"]\n\ + \ }],\n\ + \ \"subdirs\": true \n\ + \ },\n\ + \ \"generators\": [{\n\ + \ \"name\" : \"cpp\",\n\ + \ \"command\": \"sed 's/OCAML/3/' $in > $out\"\n\ + \ }],\n\ + \ \"bs-dependencies\" : [\n\ + \ ]\n\ + }" + ); + File("package.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"scripts\": {\n\ + \ \"clean\": \"bsb -clean-world\",\n\ + \ \"build\": \"bsb -make-world\",\n\ + \ \"watch\": \"bsb -make-world -w\"\n\ + \ },\n\ + \ \"keywords\": [\n\ + \ \"BuckleScript\"\n\ + \ ],\n\ + \ \"author\": \"\",\n\ + \ \"license\": \"MIT\",\n\ + \ \"devDependencies\": {\n\ + \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ + \ }\n\ + }" + ); + Dir("src",[ + File("demo.ml", + "\n\ + \n\ + let () = Js.log \"Hello, BuckleScript\"" + ); + File("test.cpp.ml", + "\n\ + (* \n\ + #define FS_VAL(name,ty) external name : ty = \"\" [@@bs.module \"fs\"]\n\ + \n\ + \n\ + FS_VAL(readdirSync, string -> string array)\n\ + \ *)\n\ + \n\ + \n\ + \ let ocaml = OCAML" + ) + ]) + ]); + Dir("minimal",[ + File(".gitignore", + ".DS_Store\n\ + .merlin\n\ + .bsb.lock\n\ + npm-debug.log\n\ + /lib/bs/\n\ + /node_modules/" + ); + File("README.md", + "\n\ + \ # ${bsb:name}" + ); + File("bsconfig.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"sources\": {\n\ + \ \"dir\": \"src\",\n\ + \ \"subdirs\": true\n\ + \ }\n\ + }" + ); + File("package.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"scripts\": {\n\ + \ \"clean\": \"bsb -clean-world\",\n\ + \ \"build\": \"bsb -make-world\",\n\ + \ \"start\": \"bsb -make-world -w\"\n\ + \ },\n\ + \ \"keywords\": [\n\ + \ \"BuckleScript\"\n\ + \ ],\n\ + \ \"author\": \"\",\n\ + \ \"license\": \"MIT\",\n\ + \ \"devDependencies\": {\n\ + \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ + \ }\n\ + }" + ); + Dir("src",[ + File("main.ml", + "" + ) + ]) + ]); + Dir("node",[ + File(".gitignore", + "*.exe\n\ + *.obj\n\ + *.out\n\ + *.compile\n\ + *.native\n\ + *.byte\n\ + *.cmo\n\ + *.annot\n\ + *.cmi\n\ + *.cmx\n\ + *.cmt\n\ + *.cmti\n\ + *.cma\n\ + *.a\n\ + *.cmxa\n\ + *.obj\n\ + *~\n\ + *.annot\n\ + *.cmj\n\ + *.bak\n\ + lib/bs\n\ + *.mlast\n\ + *.mliast\n\ + .vscode\n\ + .merlin\n\ + .bsb.lock\n\ + /node_modules/\n\ + " + ); + File("README.md", + "\n\ + \n\ + # Build\n\ + ```\n\ + npm run build\n\ + ```\n\ + \n\ + # Watch\n\ + \n\ + ```\n\ + npm run watch\n\ + ```\n\ + \n\ + \n\ + # Editor\n\ + If you use `vscode`, Press `Windows + Shift + B` it will build automatically\n\ + " + ); + File("bsconfig.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"sources\": {\n\ + \ \"dir\": \"src\",\n\ + \ \"subdirs\" : true\n\ + \ },\n\ + \ \"package-specs\": {\n\ + \ \"module\": \"commonjs\",\n\ + \ \"in-source\": true\n\ + \ },\n\ + \ \"suffix\": \".bs.js\",\n\ + \ \"bs-dependencies\": [\n\ + \ ]\n\ + }" + ); + File("package.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"scripts\": {\n\ + \ \"clean\": \"bsb -clean-world\",\n\ + \ \"build\": \"bsb -make-world\",\n\ + \ \"watch\": \"bsb -make-world -w\"\n\ + \ },\n\ + \ \"keywords\": [\n\ + \ \"BuckleScript\"\n\ + \ ],\n\ + \ \"author\": \"\",\n\ + \ \"license\": \"MIT\",\n\ + \ \"devDependencies\": {\n\ + \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ + \ }\n\ + }" + ); + Dir("src",[ + File("demo.ml", + "\n\ + \n\ + let () = Js.log \"Hello, BuckleScript\"" + ) + ]) + ]); + Dir("react-hooks",[ + File(".gitignore", + ".DS_Store\n\ + .merlin\n\ + .bsb.lock\n\ + npm-debug.log\n\ + /lib/bs/\n\ + /node_modules/\n\ + /bundleOutput/" + ); + File("README.md", + "# ReasonReact Template & Examples\n\ + \n\ + This is:\n\ + - A template for your new ReasonReact project.\n\ + - A collection of thin examples illustrating ReasonReact usage.\n\ + - Extra helper documentation for ReasonReact (full ReasonReact docs [here](https://reasonml.github.io/reason-react/)).\n\ + \n\ + `src` contains 4 sub-folders, each an independent, self-contained ReasonReact example. Feel free to delete any of them and shape this into your project! This template's more malleable than you might be used to =).\n\ + \n\ + The point of this template and examples is to let you understand and personally tweak the entirely of it. We **don't** give you an opaque, elaborate mega build setup just to put some boxes on the screen. It strikes to stay transparent, learnable, and simple. You're encouraged to read every file; it's a great feeling, having the full picture of what you're using and being able to touch any part.\n\ + \n\ + ## Run\n\ + \n\ + ```sh\n\ + npm install\n\ + npm run server\n\ + # in a new tab\n\ + npm start\n\ + ```\n\ + \n\ + Open a new web page to `http://localhost:8000/`. Change any `.re` file in `src` to see the page auto-reload. **You don't need any bundler when you're developing**!\n\ + \n\ + **How come we don't need any bundler during development**? We highly encourage you to open up `index.html` to check for yourself!\n\ + \n\ + # Features Used\n\ + \n\ + | | Blinking Greeting | Reducer from ReactJS Docs | Fetch Dog Pictures | Reason Using JS Using Reason |\n\ + |---------------------------|-------------------|---------------------------|--------------------|------------------------------|\n\ + | No props | | ✓ | | |\n\ + | Has props | | | | ✓ |\n\ + | Children props | ✓ | | | |\n\ + | No state | | | | ✓ |\n\ + | Has state | ✓ | | ✓ | |\n\ + | Has state with useReducer | | ✓ | | |\n\ + | ReasonReact using ReactJS | | | | ✓ |\n\ + | ReactJS using ReasonReact | | | | ✓ |\n\ + | useEffect | ✓ | | ✓ | |\n\ + | Dom attribute | ✓ | ✓ | | ✓ |\n\ + | Styling | ✓ | ✓ | ✓ | ✓ |\n\ + | React.array | | | ✓ | |\n\ + \n\ + # Bundle for Production\n\ + \n\ + We've included a convenience `UNUSED_webpack.config.js`, in case you want to ship your project to production. You can rename and/or remove that in favor of other bundlers, e.g. Rollup.\n\ + \n\ + We've also provided a barebone `indexProduction.html`, to serve your bundle.\n\ + \n\ + ```sh\n\ + npm install webpack webpack-cli\n\ + # rename file\n\ + mv UNUSED_webpack.config.js webpack.config.js\n\ + # call webpack to bundle for production\n\ + ./node_modules/.bin/webpack\n\ + open indexProduction.html\n\ + ```\n\ + \n\ + # Handle Routing Yourself\n\ + \n\ + To serve the files, this template uses a minimal dependency called `moduleserve`. A URL such as `localhost:8000/scores/john` resolves to the file `scores/john.html`. If you'd like to override this and handle URL resolution yourself, change the `server` command in `package.json` from `moduleserve ./ --port 8000` to `moduleserve ./ --port 8000 --spa` (for \"single page application\"). This will make `moduleserve` serve the default `index.html` for any URL. Since `index.html` loads `Index.bs.js`, you can grab hold of the URL in the corresponding `Index.re` and do whatever you want.\n\ + \n\ + By the way, ReasonReact comes with a small [router](https://reasonml.github.io/reason-react/docs/en/router) you might be interested in.\n\ + " + ); + File("UNUSED_webpack.config.js", + "const path = require('path');\n\ + \n\ + module.exports = {\n\ + \ entry: './src/Index.bs.js',\n\ + \ // If you ever want to use webpack during development, change 'production'\n\ + \ // to 'development' as per webpack documentation. Again, you don't have to\n\ + \ // use webpack or any other bundler during development! Recheck README if\n\ + \ // you didn't know this\n\ + \ mode: 'production',\n\ + \ output: {\n\ + \ path: path.join(__dirname, \"bundleOutput\"),\n\ + \ filename: 'index.js',\n\ + \ },\n\ + };" + ); + File("bsconfig.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"reason\": {\n\ + \ \"react-jsx\": 3\n\ + \ },\n\ + \ \"sources\": {\n\ + \ \"dir\" : \"src\",\n\ + \ \"subdirs\" : true\n\ + \ },\n\ + \ \"bsc-flags\": [\"-bs-super-errors\", \"-bs-no-version-header\"],\n\ + \ \"package-specs\": [{\n\ + \ \"module\": \"commonjs\",\n\ + \ \"in-source\": true\n\ + \ }],\n\ + \ \"suffix\": \".bs.js\",\n\ + \ \"namespace\": true,\n\ + \ \"bs-dependencies\": [\n\ + \ \"reason-react\"\n\ + \ ],\n\ + \ \"refmt\": 3\n\ + }\n\ + " + ); + File("index.html", + "\n\ + \n\ + \n\ + \ \n\ + \ ReasonReact Examples\n\ + \n\ + \n\ + \ \n\ + \n\ + \ \n\ + \ \n\ + \ \n\ + \ \n\ + \n\ + \n\ + " + ); + File("indexProduction.html", + "\n\ + \n\ + \n\ + \ \n\ + \ ReasonReact Examples\n\ + \n\ + \n\ + \ \n\ + \n\ + \n\ + " + ); + File("package.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"scripts\": {\n\ + \ \"build\": \"bsb -make-world\",\n\ + \ \"start\": \"bsb -make-world -w -ws _ \",\n\ + \ \"clean\": \"bsb -clean-world\",\n\ + \ \"server\": \"moduleserve ./ --port 8000\",\n\ + \ \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n\ + \ },\n\ + \ \"keywords\": [\n\ + \ \"BuckleScript\",\n\ + \ \"ReasonReact\",\n\ + \ \"reason-react\"\n\ + \ ],\n\ + \ \"author\": \"\",\n\ + \ \"license\": \"MIT\",\n\ + \ \"dependencies\": {\n\ + \ \"react\": \"^16.8.1\",\n\ + \ \"react-dom\": \"^16.8.1\",\n\ + \ \"reason-react\": \">=0.7.1\"\n\ + \ },\n\ + \ \"devDependencies\": {\n\ + \ \"bs-platform\": \"^${bsb:bs-version}\",\n\ + \ \"moduleserve\": \"^0.9.0\"\n\ + \ }\n\ + }\n\ + " + ); + Dir("src",[ + Dir("BlinkingGreeting",[ + File("BlinkingGreeting.re", + "[@react.component]\n\ + let make = (~children) => {\n\ + \ let (show, setShow) = React.useState(() => true);\n\ + \n\ + \ // Notice that instead of `useEffect`, we have `useEffect0`. See\n\ + \ // reasonml.github.io/reason-react/docs/en/components#hooks for more info\n\ + \ React.useEffect0(() => {\n\ + \ let id =\n\ + \ Js.Global.setInterval(\n\ + \ () => setShow(previousShow => !previousShow),\n\ + \ 1000,\n\ + \ );\n\ + \n\ + \ Some(() => Js.Global.clearInterval(id));\n\ + \ });\n\ + \n\ + \ let style =\n\ + \ if (show) {\n\ + \ ReactDOMRe.Style.make(~opacity=\"1\", ~transition=\"opacity 1s\", ());\n\ \ } else {\n\ - \ throw new Error(`${id} : ${parent} could not be resolved`)\n\ - \ }\n\ - }\n\ - \n\ - \n\ - function genEvalName() {\n\ - \ return \"eval-\" + ((\"\" + Math.random()).substr(2, 5))\n\ - }\n\ - /**\n\ - \ * \n\ - \ * @param {string} text \n\ - \ * @param {string} link\n\ - \ * In this case [text] evaluated result will not be cached\n\ - \ */\n\ - function loadTextSync(text, link) {\n\ - \ var baseOrModule = { exports: {}, text, link }\n\ - \ globalEval(`(function(require,exports,module){${baseOrModule.text}\\n})//# sourceURL=${baseOrModule.link}/${genEvalName()}.js`)(\n\ - \ function require(id) {\n\ - \ return loadSync(id, baseOrModule.link);\n\ - \ }, // require\n\ - \ baseOrModule.exports, // exports\n\ - \ baseOrModule // module\n\ + \ ReactDOMRe.Style.make(~opacity=\"0\", ~transition=\"opacity 1s\", ());\n\ + \ };\n\ + \n\ + \
children
;\n\ + };\n\ + " + ) + ]); + File("ExampleStyles.re", + "let reasonReactBlue = \"#48a9dc\";\n\ + \n\ + // The {j|...|j} feature is just string interpolation, from\n\ + // bucklescript.github.io/docs/en/interop-cheatsheet#string-unicode-interpolation\n\ + // This allows us to conveniently write CSS, together with variables, by\n\ + // constructing a string\n\ + let style = {j|\n\ + \ body {\n\ + \ background-color: rgb(224, 226, 229);\n\ + \ display: flex;\n\ + \ flex-direction: column;\n\ + \ align-items: center;\n\ + \ }\n\ + \ button {\n\ + \ background-color: white;\n\ + \ color: $reasonReactBlue;\n\ + \ box-shadow: 0 0 0 1px $reasonReactBlue;\n\ + \ border: none;\n\ + \ padding: 8px;\n\ + \ font-size: 16px;\n\ + \ }\n\ + \ button:active {\n\ + \ background-color: $reasonReactBlue;\n\ + \ color: white;\n\ + \ }\n\ + \ .container {\n\ + \ margin: 12px 0px;\n\ + \ box-shadow: 0px 4px 16px rgb(200, 200, 200);\n\ + \ width: 720px;\n\ + \ border-radius: 12px;\n\ + \ font-family: sans-serif;\n\ + \ }\n\ + \ .containerTitle {\n\ + \ background-color: rgb(242, 243, 245);\n\ + \ border-radius: 12px 12px 0px 0px;\n\ + \ padding: 12px;\n\ + \ font-weight: bold;\n\ + \ }\n\ + \ .containerContent {\n\ + \ background-color: white;\n\ + \ padding: 16px;\n\ + \ border-radius: 0px 0px 12px 12px;\n\ + \ }\n\ + |j};\n\ + " + ); + Dir("FetchedDogPictures",[ + File("FetchedDogPictures.re", + "[@bs.val] external fetch: string => Js.Promise.t('a) = \"fetch\";\n\ + \n\ + type state =\n\ + \ | LoadingDogs\n\ + \ | ErrorFetchingDogs\n\ + \ | LoadedDogs(array(string));\n\ + \n\ + [@react.component]\n\ + let make = () => {\n\ + \ let (state, setState) = React.useState(() => LoadingDogs);\n\ + \n\ + \ // Notice that instead of `useEffect`, we have `useEffect0`. See\n\ + \ // reasonml.github.io/reason-react/docs/en/components#hooks for more info\n\ + \ React.useEffect0(() => {\n\ + \ Js.Promise.(\n\ + \ fetch(\"https://dog.ceo/api/breeds/image/random/3\")\n\ + \ |> then_(response => response##json())\n\ + \ |> then_(jsonResponse => {\n\ + \ setState(_previousState => LoadedDogs(jsonResponse##message));\n\ + \ Js.Promise.resolve();\n\ + \ })\n\ + \ |> catch(_err => {\n\ + \ setState(_previousState => ErrorFetchingDogs);\n\ + \ Js.Promise.resolve();\n\ + \ })\n\ + \ |> ignore\n\ \ );\n\ - \ return baseOrModule.exports\n\ - }\n\ - \n\ - /**\n\ - \ * \n\ - \ * @param {string} text \n\ - \ */\n\ - function BSloadText(text) {\n\ - \ console.time(\"Loading\")\n\ - \ var parent = BsGetPath(\".\", document.baseURI)\n\ - \ return getAllFromText(text, parent).then(function () {\n\ - \ var result = loadTextSync(text, parent)\n\ - \ console.timeEnd(\"Loading\")\n\ - \ return result\n\ - \ })\n\ - };\n\ - \n\ - \n\ - function load(id, parent) {\n\ - \ return getAll(id, parent).then(function () {\n\ - \ return loadSync(id, parent)\n\ - \ })\n\ - \n\ - };\n\ - \n\ - \n\ - export function BSload(id) {\n\ - \ var parent = BsGetPath(\".\", document.baseURI)\n\ - \ return load(id, parent)\n\ - }\n\ - \n\ - export var BSLoader = {\n\ - \ loadText: BSloadText,\n\ - \ load: BSload,\n\ - \ SyncedIDLocations: SyncedIDLocations\n\ - };\n\ - \n\ - window.BSLoader = BSLoader;\n\ - \n\ - var main = document.querySelector('script[data-main]')\n\ - if (main) {\n\ - \ BSload(main.dataset.main)\n\ - }\n\ - ") ; - File ("bsconfig.json", - "{\n\ - \ \"name\": \"tea\",\n\ - \ \"version\": \"0.1.0\",\n\ - \ \"sources\": {\n\ - \ \"dir\" : \"src\",\n\ - \ \"subdirs\" : true\n\ - \ },\n\ - \ \"package-specs\": {\n\ - \ \"module\": \"commonjs\",\n\ - \ \"in-source\": true\n\ - \ },\n\ - \ \"suffix\": \".bs.js\",\n\ - \ \"bs-dependencies\": [\n\ - \ \"bucklescript-tea\"\n\ - \ ]\n\ - }\n\ - ") ; - File ("watcher.js", - "\n\ - \n\ - var wsReloader;\n\ - var LAST_SUCCESS_BUILD_STAMP = (localStorage.getItem('LAST_SUCCESS_BUILD_STAMP') || 0)\n\ - var WS_PORT = 9999; // configurable\n\ - \n\ - function setUpWebScoket() {\n\ - \ if (wsReloader == null || wsReloader.readyState !== 1) {\n\ - \ try {\n\ - \ wsReloader = new WebSocket(`ws://${window.location.hostname}:${WS_PORT}`)\n\ - \ wsReloader.onmessage = (msg) => {\n\ - \ var newData = JSON.parse(msg.data).LAST_SUCCESS_BUILD_STAMP\n\ - \ if (newData > LAST_SUCCESS_BUILD_STAMP) {\n\ - \ LAST_SUCCESS_BUILD_STAMP = newData\n\ - \ localStorage.setItem('LAST_SUCCESS_BUILD_STAMP', LAST_SUCCESS_BUILD_STAMP)\n\ - \ location.reload(true)\n\ - \ }\n\ - \n\ - \ }\n\ - \ } catch (exn) {\n\ - \ console.error(\"web socket failed connect\")\n\ - \ }\n\ - \ }\n\ - };\n\ - \n\ - setUpWebScoket();\n\ - setInterval(setUpWebScoket, 2000);") ; - File ("package.json", - "{\n\ - \ \"name\": \"${bsb:name}\",\n\ - \ \"version\": \"${bsb:proj-version}\",\n\ - \ \"scripts\": {\n\ - \ \"clean\": \"bsb -clean-world\",\n\ - \ \"build\": \"bsb -make-world\",\n\ - \ \"watch\": \"bsb -make-world -w -ws _\"\n\ - \ },\n\ - \ \"keywords\": [\n\ - \ \"BuckleScript\"\n\ - \ ],\n\ - \ \"author\": \"\",\n\ - \ \"license\": \"MIT\",\n\ - \ \"devDependencies\": {\n\ - \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ - \ },\n\ - \ \"dependencies\": {\n\ - \ \"bucklescript-tea\": \"^0.7.4\"\n\ - \ }\n\ - }\n\ - ") ; - File ("README.md", - "\n\ - \n\ - # Build\n\ - ```\n\ - npm run build\n\ - ```\n\ - \n\ - # Watch\n\ - \n\ - ```\n\ - npm run watch\n\ - ```\n\ - \n\ - create a http-server\n\ - \n\ - ```\n\ - npm install -g http-server\n\ - ```\n\ - \n\ - Edit the file and see the changes automatically reloaded in the browser\n\ - ") ; - File ("index.html", - "\n\ - \n\ - \ \n\ - \ \n\ - \ \n\ - \ \n\ - \ \n\ - \ \n\ - \ Bucklescript TEA Starter Kit\n\ - \ \n\ - \ \n\ - \n\ - \n\ - \ \n\ - \
\n\ - \ \n\ - \ \n\ - \ \n\ - \ \n\ - ")]) + \n\ + \ // Returning None, instead of Some(() => ...), means we don't have any\n\ + \ // cleanup to do before unmounting. That's not 100% true. We should\n\ + \ // technically cancel the promise. Unofortunately, there's currently no\n\ + \ // way to cancel a promise. Promises in general should be way less used\n\ + \ // for React components; but since folks do use them, we provide such an\n\ + \ // example here. In reality, this fetch should just be a plain callback,\n\ + \ // with a cancellation API\n\ + \ None;\n\ + \ });\n\ + \n\ + \ \n\ + \ {switch (state) {\n\ + \ | ErrorFetchingDogs => React.string(\"An error occurred!\")\n\ + \ | LoadingDogs => React.string(\"Loading...\")\n\ + \ | LoadedDogs(dogs) =>\n\ + \ dogs\n\ + \ ->Belt.Array.mapWithIndex((i, dog) => {\n\ + \ let imageStyle =\n\ + \ ReactDOMRe.Style.make(\n\ + \ ~height=\"120px\",\n\ + \ ~width=\"100%\",\n\ + \ ~marginRight=i === Js.Array.length(dogs) - 1 ? \"0px\" : \"8px\",\n\ + \ ~borderRadius=\"8px\",\n\ + \ ~boxShadow=\"0px 4px 16px rgb(200, 200, 200)\",\n\ + \ ~backgroundSize=\"cover\",\n\ + \ ~backgroundImage={j|url($dog)|j},\n\ + \ ~backgroundPosition=\"center\",\n\ + \ (),\n\ + \ );\n\ + \
;\n\ + \ })\n\ + \ ->React.array\n\ + \ }}\n\ + \
;\n\ + };\n\ + " + ) + ]); + File("Index.re", + "// Entry point\n\ + \n\ + [@bs.val] external document: Js.t({..}) = \"document\";\n\ + \n\ + // We're using raw DOM manipulations here, to avoid making you read\n\ + // ReasonReact when you might precisely be trying to learn it for the first\n\ + // time through the examples later.\n\ + let style = document##createElement(\"style\");\n\ + document##head##appendChild(style);\n\ + style##innerHTML #= ExampleStyles.style;\n\ + \n\ + let makeContainer = text => {\n\ + \ let container = document##createElement(\"div\");\n\ + \ container##className #= \"container\";\n\ + \n\ + \ let title = document##createElement(\"div\");\n\ + \ title##className #= \"containerTitle\";\n\ + \ title##innerText #= text;\n\ + \n\ + \ let content = document##createElement(\"div\");\n\ + \ content##className #= \"containerContent\";\n\ + \n\ + \ let () = container##appendChild(title);\n\ + \ let () = container##appendChild(content);\n\ + \ let () = document##body##appendChild(container);\n\ + \n\ + \ content;\n\ + };\n\ + \n\ + // All 4 examples.\n\ + ReactDOMRe.render(\n\ + \ \n\ + \ {React.string(\"Hello!\")}\n\ + \ ,\n\ + \ makeContainer(\"Blinking Greeting\"),\n\ + );\n\ + \n\ + ReactDOMRe.render(\n\ + \ ,\n\ + \ makeContainer(\"Reducer From ReactJS Docs\"),\n\ + );\n\ + \n\ + ReactDOMRe.render(\n\ + \ ,\n\ + \ makeContainer(\"Fetched Dog Pictures\"),\n\ + );\n\ + \n\ + ReactDOMRe.render(\n\ + \ ,\n\ + \ makeContainer(\"Reason Using JS Using Reason\"),\n\ + );\n\ + " + ); + Dir("ReasonUsingJSUsingReason",[ + File("ReactJSCard.js", + "// In this Interop example folder, we have:\n\ + // - A ReasonReact component, ReasonReactCard.re\n\ + // - Used by a ReactJS component, ReactJSCard.js (this file)\n\ + // - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re\n\ + // - ReasonUsingJSUsingReason.re is used by Index.re\n\ + \n\ + var ReactDOM = require('react-dom');\n\ + var React = require('react');\n\ + \n\ + var ReasonReactCard = require('./ReasonReactCard.bs').make;\n\ + \n\ + var ReactJSComponent = function() {\n\ + \ let backgroundColor = \"rgba(0, 0, 0, 0.05)\";\n\ + \ let padding = \"12px\";\n\ + \n\ + \ // We're not using JSX here, to avoid folks needing to install the related\n\ + \ // React toolchains just for this example.\n\ + \ //
\n\ + \ //
This is a ReactJS card
\n\ + \ // \n\ + \ //
\n\ + \ return React.createElement(\n\ + \ \"div\",\n\ + \ {style: {backgroundColor, padding, borderRadius: \"8px\"}},\n\ + \ React.createElement(\"div\", {style: {marginBottom: \"8px\"}}, \"This is a ReactJS card\"),\n\ + \ React.createElement(ReasonReactCard, {style: {backgroundColor, padding, borderRadius: \"4px\"}}),\n\ + \ )\n\ + };\n\ + ReactJSComponent.displayName = \"MyBanner\";\n\ + \n\ + module.exports = ReactJSComponent;\n\ + " + ); + File("ReasonReactCard.re", + "// In this Interop example folder, we have:\n\ + // - A ReasonReact component, ReasonReactCard.re (this file)\n\ + // - Used by a ReactJS component, ReactJSCard.js\n\ + // - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re\n\ + // - ReasonUsingJSUsingReason.re is used by Index.re\n\ + \n\ + [@react.component]\n\ + let make = (~style) => {\n\ + \
{React.string(\"This is a ReasonReact card\")}
;\n\ + };\n\ + " + ); + File("ReasonUsingJSUsingReason.re", + "// In this Interop example folder, we have:\n\ + // - A ReasonReact component, ReasonReactCard.re\n\ + // - Used by a ReactJS component, ReactJSCard.js\n\ + // - ReactJSCard.js can be used by ReasonReact, through bindings in ReasonUsingJSUsingReason.re (this file)\n\ + // - ReasonUsingJSUsingReason.re is used by Index.re\n\ + \n\ + // All you need to do to use a ReactJS component in ReasonReact, is to write the lines below!\n\ + // reasonml.github.io/reason-react/docs/en/components#import-from-js\n\ + [@react.component] [@bs.module]\n\ + external make: unit => React.element = \"./ReactJSCard\";\n\ + " + ) + ]); + Dir("ReducerFromReactJSDocs",[ + File("ReducerFromReactJSDocs.re", + "// This is the ReactJS documentation's useReducer example, directly ported over\n\ + // https://reactjs.org/docs/hooks-reference.html#usereducer\n\ + \n\ + // A little extra we've put, because the ReactJS example has no styling\n\ + let leftButtonStyle = ReactDOMRe.Style.make(~borderRadius=\"4px 0px 0px 4px\", ~width=\"48px\", ());\n\ + let rightButtonStyle = ReactDOMRe.Style.make(~borderRadius=\"0px 4px 4px 0px\", ~width=\"48px\", ());\n\ + let containerStyle = ReactDOMRe.Style.make(~display=\"flex\", ~alignItems=\"center\", ~justifyContent=\"space-between\", ());\n\ + \n\ + // Record and variant need explicit declarations.\n\ + type state = {count: int};\n\ + \n\ + type action =\n\ + \ | Increment\n\ + \ | Decrement;\n\ + \n\ + let initialState = {count: 0};\n\ + \n\ + let reducer = (state, action) => {\n\ + \ switch (action) {\n\ + \ | Increment => {count: state.count + 1}\n\ + \ | Decrement => {count: state.count - 1}\n\ + \ };\n\ + };\n\ + \n\ + [@react.component]\n\ + let make = () => {\n\ + \ let (state, dispatch) = React.useReducer(reducer, initialState);\n\ + \n\ + \ // We can use a fragment here, but we don't, because we want to style the counter\n\ + \
\n\ + \
\n\ + \ {React.string(\"Count: \")}\n\ + \ {React.string(string_of_int(state.count))}\n\ + \
\n\ + \
\n\ + \ \n\ + \ \n\ + \
\n\ + \
;\n\ + };\n\ + " + ) + ]) + ]); + File("watcher.js", + "// This is our simple, robust watcher. It hooks into the BuckleScript build\n\ + // system to listen for build events.\n\ + // See package.json's `start` script and `./node_modules/.bin/bsb --help`\n\ + \n\ + // Btw, if you change this file and reload the page, your browser cache\n\ + // _might_ not pick up the new version. If you're in Chrome, do Force Reload.\n\ + \n\ + var websocketReloader;\n\ + var LAST_SUCCESS_BUILD_STAMP = localStorage.getItem('LAST_SUCCESS_BUILD_STAMP') || 0;\n\ + // package.json's `start` script's `bsb -ws _` means it'll pipe build events\n\ + // through a websocket connection to a default port of 9999. This is\n\ + // configurable, e.g. `-ws 5000`\n\ + var webSocketPort = 9999;\n\ + \n\ + function setUpWebSocket() {\n\ + \ if (websocketReloader == null || websocketReloader.readyState !== 1) {\n\ + \ try {\n\ + \ websocketReloader = new WebSocket(`ws://${window.location.hostname}:${webSocketPort}`);\n\ + \ websocketReloader.onmessage = (message) => {\n\ + \ var newData = JSON.parse(message.data).LAST_SUCCESS_BUILD_STAMP;\n\ + \ if (newData > LAST_SUCCESS_BUILD_STAMP) {\n\ + \ LAST_SUCCESS_BUILD_STAMP = newData;\n\ + \ localStorage.setItem('LAST_SUCCESS_BUILD_STAMP', LAST_SUCCESS_BUILD_STAMP);\n\ + \ // Refresh the page! This will naturally re-run everything,\n\ + \ // including our moduleserve which will re-resolve all the modules.\n\ + \ // No stable build!\n\ + \ location.reload(true);\n\ + \ }\n\ + \n\ + \ }\n\ + \ } catch (exn) {\n\ + \ console.error(\"The watcher tried to connect to web socket, but failed. Here's the message:\");\n\ + \ console.error(exn);\n\ + \ }\n\ + \ }\n\ + };\n\ + \n\ + setUpWebSocket();\n\ + setInterval(setUpWebSocket, 2000);\n\ + " + ) + ]); + Dir("react-starter",[ + File(".gitignore", + ".DS_Store\n\ + .merlin\n\ + .bsb.lock\n\ + npm-debug.log\n\ + /lib/bs/\n\ + /node_modules/\n\ + *.bs.js\n\ + " + ); + File("README.md", + "# Reason react starter\n\ + \n\ + ## Run Project\n\ + \n\ + ```sh\n\ + npm install\n\ + npm start\n\ + # in another tab\n\ + npm run server\n\ + ```\n\ + \n\ + View the app in the browser at http://localhost:8000. Running in this environment provides hot reloading and support for routing; just edit and save the file and the browser will automatically refresh.\n\ + \n\ + To use a port other than 8000 set the `PORT` environment variable (`PORT=8080 npm run server`).\n\ + \n\ + ## Build for Production\n\ + \n\ + ```sh\n\ + npm run clean\n\ + npm run build\n\ + npm run webpack:production\n\ + ```\n\ + \n\ + This will replace the development artifact `build/Index.js` for an optimized version as well as copy `src/index.html` into `build/`. You can then deploy the contents of the `build` directory (`index.html` and `Index.js`).\n\ + \n\ + **To enable dead code elimination**, change `bsconfig.json`'s `package-specs` `module` from `\"commonjs\"` to `\"es6\"`. Then re-run the above 2 commands. This will allow Webpack to remove unused code.\n\ + " + ); + File("bsconfig.json", + "{\n\ + \ \"name\": \"reason-react-starter\",\n\ + \ \"reason\": {\n\ + \ \"react-jsx\": 3\n\ + \ },\n\ + \ \"sources\": {\n\ + \ \"dir\": \"src\",\n\ + \ \"subdirs\": true\n\ + \ },\n\ + \ \"bsc-flags\": [\"-bs-super-errors\", \"-bs-no-version-header\"],\n\ + \ \"package-specs\": [\n\ + \ {\n\ + \ \"module\": \"commonjs\",\n\ + \ \"in-source\": true\n\ + \ }\n\ + \ ],\n\ + \ \"suffix\": \".bs.js\",\n\ + \ \"namespace\": true,\n\ + \ \"bs-dependencies\": [\"reason-react\"],\n\ + \ \"refmt\": 3\n\ + }\n\ + " + ); + File("package.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"scripts\": {\n\ + \ \"build\": \"bsb -make-world\",\n\ + \ \"start\": \"bsb -make-world -w -ws _ \",\n\ + \ \"clean\": \"bsb -clean-world\",\n\ + \ \"webpack\": \"webpack -w\",\n\ + \ \"webpack:production\": \"NODE_ENV=production webpack\",\n\ + \ \"server\": \"webpack-dev-server\",\n\ + \ \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n\ + \ },\n\ + \ \"keywords\": [\n\ + \ \"BuckleScript\",\n\ + \ \"ReasonReact\",\n\ + \ \"reason-react\"\n\ + \ ],\n\ + \ \"author\": \"\",\n\ + \ \"license\": \"MIT\",\n\ + \ \"dependencies\": {\n\ + \ \"react\": \"^17.0.1\",\n\ + \ \"react-dom\": \"^17.0.1\",\n\ + \ \"reason-react\": \"^0.9.1\"\n\ + \ },\n\ + \ \"devDependencies\": {\n\ + \ \"bs-platform\": \"^${bsb:bs-version}\",\n\ + \ \"css-loader\": \"^5.0.0\",\n\ + \ \"html-webpack-plugin\": \"^4.5.0\",\n\ + \ \"style-loader\": \"^2.0.0\",\n\ + \ \"webpack\": \"^4.44.2\",\n\ + \ \"webpack-cli\": \"^3.3.12\",\n\ + \ \"webpack-dev-server\": \"^3.11.0\"\n\ + \ }\n\ + }\n\ + " + ); + Dir("src",[ + File("App.re", + "type state = {count: int};\n\ + \n\ + type action =\n\ + \ | Increment\n\ + \ | Decrement;\n\ + \n\ + let initialState = {count: 0};\n\ + \n\ + let reducer = (state, action) =>\n\ + \ switch (action) {\n\ + \ | Increment => {count: state.count + 1}\n\ + \ | Decrement => {count: state.count - 1}\n\ + \ };\n\ + \n\ + [@react.component]\n\ + let make = () => {\n\ + \ let (state, dispatch) = React.useReducer(reducer, initialState);\n\ + \n\ + \
\n\ + \ {React.string(\"Simple counter with reducer\")}\n\ + \
\n\ + \ \n\ + \ \n\ + \ {state.count |> string_of_int |> React.string}\n\ + \ \n\ + \ \n\ + \
\n\ + \
;\n\ + };\n\ + " + ); + File("Index.re", + "[%bs.raw {|require(\"./index.css\")|}];\n\ + \n\ + ReactDOMRe.renderToElementWithId(, \"root\");\n\ + " + ); + File("index.css", + "body {\n\ + \ margin: 0;\n\ + \ font-family: -apple-system, system-ui, \"Segoe UI\", Helvetica, Arial,\n\ + \ sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n\ + }\n\ + \n\ + main {\n\ + \ padding: 20px;\n\ + }\n\ + \n\ + .counter {\n\ + \ padding: 20px;\n\ + \ display: inline-block;\n\ + }\n\ + " + ); + File("index.html", + "\n\ + \n\ + \ \n\ + \ \n\ + \ Reason react starter\n\ + \ \n\ + \ \n\ + \
\n\ + \ \n\ + \ \n\ + \n\ + " + ) + ]); + File("webpack.config.js", + "const path = require(\"path\")\n\ + const HtmlWebpackPlugin = require(\"html-webpack-plugin\")\n\ + const outputDir = path.join(__dirname, \"build/\")\n\ + \n\ + const isProd = process.env.NODE_ENV === \"production\"\n\ + \n\ + module.exports = {\n\ + \ entry: \"./src/Index.bs.js\",\n\ + \ mode: isProd ? \"production\" : \"development\",\n\ + \ devtool: \"source-map\",\n\ + \ output: {\n\ + \ path: outputDir,\n\ + \ filename: \"Index.js\"\n\ + \ },\n\ + \ plugins: [\n\ + \ new HtmlWebpackPlugin({\n\ + \ template: \"src/index.html\",\n\ + \ inject: false\n\ + \ })\n\ + \ ],\n\ + \ devServer: {\n\ + \ compress: true,\n\ + \ contentBase: outputDir,\n\ + \ port: process.env.PORT || 8000,\n\ + \ historyApiFallback: true\n\ + \ },\n\ + \ module: {\n\ + \ rules: [\n\ + \ {\n\ + \ test: /\\.css$/,\n\ + \ use: [\"style-loader\", \"css-loader\"]\n\ + \ }\n\ + \ ]\n\ + \ }\n\ + }\n\ + " + ) + ]); + Dir("tea",[ + File("README.md", + "\n\ + \n\ + # Build\n\ + ```\n\ + npm run build\n\ + ```\n\ + \n\ + # Watch\n\ + \n\ + ```\n\ + npm run watch\n\ + ```\n\ + \n\ + create a http-server\n\ + \n\ + ```\n\ + npm install -g http-server\n\ + ```\n\ + \n\ + Edit the file and see the changes automatically reloaded in the browser\n\ + " + ); + File("bsconfig.json", + "{\n\ + \ \"name\": \"tea\",\n\ + \ \"version\": \"0.1.0\",\n\ + \ \"sources\": {\n\ + \ \"dir\" : \"src\",\n\ + \ \"subdirs\" : true\n\ + \ },\n\ + \ \"package-specs\": {\n\ + \ \"module\": \"commonjs\",\n\ + \ \"in-source\": true\n\ + \ },\n\ + \ \"suffix\": \".bs.js\",\n\ + \ \"bs-dependencies\": [\n\ + \ \"bucklescript-tea\"\n\ + \ ]\n\ + }\n\ + " + ); + File("index.html", + "\n\ + \n\ + \ \n\ + \ \n\ + \ \n\ + \ \n\ + \ \n\ + \ \n\ + \ Bucklescript TEA Starter Kit\n\ + \ \n\ + \ \n\ + \n\ + \n\ + \ \n\ + \
\n\ + \ \n\ + \ \n\ + \ \n\ + \ \n\ + " + ); + File("loader.js", + "/* Copyright (C) 2018 Authors of BuckleScript\n\ + \ * \n\ + \ * This program is free software: you can redistribute it and/or modify\n\ + \ * it under the terms of the GNU Lesser General Public License as published by\n\ + \ * the Free Software Foundation, either version 3 of the License, or\n\ + \ * (at your option) any later version.\n\ + \ *\n\ + \ * In addition to the permissions granted to you by the LGPL, you may combine\n\ + \ * or link a \"work that uses the Library\" with a publicly distributed version\n\ + \ * of this file to produce a combined library or application, then distribute\n\ + \ * that combined work under the terms of your choosing, with no requirement\n\ + \ * to comply with the obligations normally placed on you by section 4 of the\n\ + \ * LGPL version 3 (or the corresponding section of a later version of the LGPL\n\ + \ * should you choose to use a later version).\n\ + \ *\n\ + \ * This program is distributed in the hope that it will be useful,\n\ + \ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n\ + \ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\ + \ * GNU Lesser General Public License for more details.\n\ + \ * \n\ + \ * You should have received a copy of the GNU Lesser General Public License\n\ + \ * along with this program; if not, write to the Free Software\n\ + \ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */\n\ + \n\ + \n\ + \n\ + //@ts-check\n\ + \n\ + // @ts-ignore\n\ + window.process = { env: { NODE_ENV: 'dev' } }\n\ + \n\ + \n\ + // local to getPath\n\ + var relativeElement = document.createElement(\"a\");\n\ + var baseElement = document.createElement(\"base\");\n\ + document.head.appendChild(baseElement);\n\ + \n\ + export function BsGetPath(id, parent) {\n\ + \ var oldPath = baseElement.href\n\ + \ baseElement.href = parent\n\ + \ relativeElement.href = id\n\ + \ var result = relativeElement.href\n\ + \ baseElement.href = oldPath\n\ + \ return result\n\ + }\n\ + /**\n\ + \ * \n\ + \ * Given current link and its parent, return the new link\n\ + \ * @param {string} id \n\ + \ * @param {string} parent \n\ + \ * @return {string}\n\ + \ */\n\ + function getPathWithJsSuffix(id, parent) {\n\ + \ var oldPath = baseElement.href\n\ + \ baseElement.href = parent\n\ + \ relativeElement.href = id\n\ + \ var result = addSuffixJsIfNot(relativeElement.href)\n\ + \ baseElement.href = oldPath\n\ + \ return result\n\ + }\n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} x \n\ + \ */\n\ + function addSuffixJsIfNot(x) {\n\ + \ if (x.endsWith('.js')) {\n\ + \ return x\n\ + \ } else {\n\ + \ return x + '.js'\n\ + \ }\n\ + }\n\ + \n\ + \n\ + var falsePromise = Promise.resolve(false)\n\ + var fetchConfig = {'cache' : 'no-cache'}\n\ + // package.json semantics\n\ + // a string to module object \n\ + // from url -> module object \n\ + // Modules : Map \n\ + // fetch the link:\n\ + // - if it is already fetched before, return the stored promise\n\ + // otherwise create the promise which will be filled with the text if successful\n\ + // or filled with boolean false when failed\n\ + var MODULES = new Map()\n\ + function cachedFetch(link) {\n\ + \ // console.info(link)\n\ + \ var linkResult = MODULES.get(link)\n\ + \ if (linkResult) {\n\ + \ return linkResult\n\ + \ } else {\n\ + \ var p = fetch(link, fetchConfig)\n\ + \ .then(resp => {\n\ + \ if (resp.ok) {\n\ + \ return resp.text()\n\ + \ } else {\n\ + \ return falsePromise\n\ + \ }\n\ + \ })\n\ + \n\ + \ MODULES.set(link, p)\n\ + \ return p\n\ + \ }\n\ + }\n\ + \n\ + // from location id -> url \n\ + // There are two rounds of caching:\n\ + // 1. if location and relative path is hit, no need to run \n\ + // 2. if location and relative path is not hit, but the resolved link is hit, no need \n\ + // for network request\n\ + /**\n\ + \ * @type {Map > > }\n\ + \ */\n\ + var IDLocations = new Map()\n\ + \n\ + /**\n\ + \ * @type {Map > }\n\ + \ */\n\ + var SyncedIDLocations = new Map()\n\ + // Its value is an object \n\ + // { link : String }\n\ + // We will first mark it when visiting (to avoid duplicated computation)\n\ + // and populate its link later\n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} id \n\ + \ * @param {string} location \n\ + \ */\n\ + function getIdLocation(id, location) {\n\ + \ var idMap = IDLocations.get(location)\n\ + \ if (idMap) {\n\ + \ return idMap.get(id)\n\ + \ }\n\ + }\n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} id \n\ + \ * @param {string} location \n\ + \ */\n\ + function getIdLocationSync(id, location) {\n\ + \ var idMap = SyncedIDLocations.get(location)\n\ + \ if (idMap) {\n\ + \ return idMap.get(id)\n\ + \ }\n\ + }\n\ + \n\ + function countIDLocations() {\n\ + \ var count = 0\n\ + \ for (let [k, vv] of IDLocations) {\n\ + \ for (let [kv, v] of vv) {\n\ + \ count += 1\n\ + \ }\n\ + \ }\n\ + \ console.log(count, 'modules loaded')\n\ + }\n\ + \n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} id \n\ + \ * @param {string} location \n\ + \ * @param {Function} cb \n\ + \ * @returns Promise\n\ + \ */\n\ + function visitIdLocation(id, location, cb) {\n\ + \ var result;\n\ + \ var idMap = IDLocations.get(location)\n\ + \ if (idMap && (result = idMap.get(id))) {\n\ + \ return result\n\ + \ }\n\ + \ else {\n\ + \ result = new Promise(resolve => {\n\ + \ return (cb()).then(res => {\n\ + \ var idMap = SyncedIDLocations.get(location)\n\ + \ if (idMap) {\n\ + \ idMap.set(id, res)\n\ + \ } else {\n\ + \ SyncedIDLocations.set(location, new Map([[id, res]]))\n\ + \ }\n\ + \ return resolve(res)\n\ + \ })\n\ + \ })\n\ + \ if (idMap) {\n\ + \ idMap.set(id, result)\n\ + \ }\n\ + \ else {\n\ + \ IDLocations.set(location, new Map([[id, result]]))\n\ + \ }\n\ + \ return result\n\ + \ }\n\ + }\n\ + \n\ + \n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} text \n\ + \ * @return {string[]}\n\ + \ */\n\ + function getDeps(text) {\n\ + \ var deps = []\n\ + \ text.replace(/(\\/\\*[\\w\\W]*?\\*\\/|\\/\\/[^\\n]*|[.$]r)|\\brequire\\s*\\(\\s*[\"']([^\"']*)[\"']\\s*\\)/g, function (_, ignore, id) {\n\ + \ if (!ignore) deps.push(id);\n\ + \ });\n\ + \ return deps;\n\ + }\n\ + \n\ + \n\ + \n\ + // By using a named \"eval\" most browsers will execute in the global scope.\n\ + // http://www.davidflanagan.com/2010/12/global-eval-in.html\n\ + var globalEval = eval;\n\ + \n\ + // function parentURL(url) {\n\ + // if (url.endsWith('/')) {\n\ + // return url + '../'\n\ + // } else {\n\ + // return url + '/../'\n\ + // }\n\ + // }\n\ + \n\ + \n\ + \n\ + // loader.js:23 http://localhost:8080/node_modules/react-dom/cjs/react-dom.development.js/..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//../ fbjs/lib/containsNode Promise {}\n\ + // 23:10:02.884 loader.js:23 http://localhost:8080/node_modules/react-dom/cjs/react-dom.development.js/..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//..//../ fbjs/lib/invariant Promise {}\n\ + \n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} id \n\ + \ * @param {string} parent \n\ + \ */\n\ + function getParentModulePromise(id, parent) {\n\ + \ var parentLink = BsGetPath('..', parent)\n\ + \ if (parentLink === parent) {\n\ + \ return falsePromise\n\ + \ }\n\ + \ return getPackageJsPromise(id, parentLink)\n\ + }\n\ + // In the beginning\n\ + // it is `resolveModule('./main.js', '')\n\ + // return the promise of link and text \n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} id \n\ + \ */\n\ + function getPackageName(id) {\n\ + \ var index = id.indexOf('/')\n\ + \ if (id[0] === '@') index = id.indexOf('/', index + 1)\n\ + \ if (index === -1) {\n\ + \ return id\n\ + \ }\n\ + \ return id.substring(0, index)\n\ + }\n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} s \n\ + \ * @param {string} text \n\ + \ * @returns {undefined | string }\n\ + \ */\n\ + function isJustAPackageAndHasMainField(s,text){\n\ + \ if(s.indexOf('/') >= 0){\n\ + \ return \n\ + \ } else {\n\ + \ var mainField; \n\ + \ try {\n\ + \ mainField = JSON.parse(text).main\n\ + \ }catch(_){\n\ + \ }\n\ + \ if(mainField === undefined){\n\ + \ return \n\ + \ } else {\n\ + \ return mainField\n\ + \ }\n\ + \ }\n\ + }\n\ + function getPackageJsPromise(id, parent) {\n\ + \ var idNodeModulesPrefix = './node_modules/' + id\n\ + \ var link = getPathWithJsSuffix(idNodeModulesPrefix, parent)\n\ + \ if (parent.endsWith('node_modules/')) {\n\ + \ // impossible that `node_modules/node_modules/xx/x\n\ + \ // return falsePromise\n\ + \ return getParentModulePromise(id, parent)\n\ + \ }\n\ + \n\ + \ var packageJson = BsGetPath(`./node_modules/${getPackageName(id)}/package.json`, parent)\n\ + \n\ + \ return cachedFetch(packageJson)\n\ + \ .then(\n\ + \ function (text) {\n\ + \ if (text !== false) {\n\ + \ var mainField; \n\ + \ if( (mainField = isJustAPackageAndHasMainField(id, text)) !== undefined){\n\ + \ var packageLink = BsGetPath(addSuffixJsIfNot(`./node_modules/${id}/${mainField}`), parent)\n\ + \ return cachedFetch(packageLink)\n\ + \ .then(function(text){\n\ + \ if(text !== false){\n\ + \ return {text, link : packageLink}\n\ + \ } else {\n\ + \ return getParentModulePromise(id,parent)\n\ + \ }\n\ + \ })\n\ + \n\ + \ } else {\n\ + \ // package indeed exist\n\ + \ return cachedFetch(link).then(function (text) {\n\ + \ if (text !== false) {\n\ + \ return { text, link }\n\ + \ } else if (!id.endsWith('.js')) {\n\ + \ var linkNew = getPathWithJsSuffix(idNodeModulesPrefix + `/index.js`, parent)\n\ + \ return cachedFetch(linkNew)\n\ + \ .then(function (text) {\n\ + \ if (text !== false) {\n\ + \ return { text, link: linkNew }\n\ + \ } else {\n\ + \ return getParentModulePromise(id, parent)\n\ + \ }\n\ + \ })\n\ + \n\ + \ } else {\n\ + \ return getParentModulePromise(id, parent)\n\ + \ }\n\ + \ })\n\ + \ }\n\ + \ }\n\ + \ else {\n\ + \ return getParentModulePromise(id, parent)\n\ + \ }\n\ + \ }\n\ + \ )\n\ + \n\ + \n\ + }\n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} id \n\ + \ * @param {string} parent \n\ + \ * can return Promise , false means\n\ + \ * this module can not be resolved\n\ + \ */\n\ + function getModulePromise(id, parent) {\n\ + \ var done = getIdLocation(id, parent)\n\ + \ if (!done) {\n\ + \ return visitIdLocation(id, parent, function () {\n\ + \ if (id[0] != '.') { // package path\n\ + \ return getPackageJsPromise(id, parent)\n\ + \ } else { // relative path, one shot resolve \n\ + \ let link = getPathWithJsSuffix(id, parent)\n\ + \ return cachedFetch(link).then(\n\ + \ function (text) {\n\ + \ if (text !== false) {\n\ + \ return { text, link }\n\ + \ } else if (!id.endsWith('.js')){ \n\ + \ // could be \"./dir\"\n\ + \ var newLink = getPathWithJsSuffix( id +\"/index.js\",parent)\n\ + \ return cachedFetch(newLink)\n\ + \ .then(function(text){\n\ + \ if(text !== false){\n\ + \ return{text, link : newLink }\n\ + \ } else {\n\ + \ throw new Error(` ${id} : ${parent} could not be resolved`)\n\ + \ }\n\ + \ })\n\ + \ }\n\ + \ else {\n\ + \ throw new Error(` ${id} : ${parent} could not be resolved`)\n\ + \ }\n\ + \ }\n\ + \ )\n\ + \ }\n\ + \ })\n\ + \ } else {\n\ + \ return done\n\ + \ }\n\ + }\n\ + \n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} id \n\ + \ * @param {string} parent \n\ + \ * @returns {Promise}\n\ + \ */\n\ + function getAll(id, parent) {\n\ + \ return getModulePromise(id, parent)\n\ + \ .then(function (obj) {\n\ + \ if (obj) {\n\ + \ var deps = getDeps(obj.text)\n\ + \ return Promise.all(deps.map(x => getAll(x, obj.link)))\n\ + \ } else {\n\ + \ throw new Error(`${id}@${parent} was not resolved successfully`)\n\ + \ }\n\ + \ })\n\ + };\n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} text \n\ + \ * @param {string} parent \n\ + \ * @returns {Promise}\n\ + \ */\n\ + function getAllFromText(text, parent) {\n\ + \ var deps = getDeps(text)\n\ + \ return Promise.all(deps.map(x => getAll(x, parent)))\n\ + }\n\ + \n\ + var evaluatedModules = new Map()\n\ + \n\ + function loadSync(id, parent) {\n\ + \ var baseOrModule = getIdLocationSync(id, parent)\n\ + \ if (baseOrModule && baseOrModule.link !== undefined) {\n\ + \ if(evaluatedModules.has(baseOrModule.link)){\n\ + \ return evaluatedModules.get(baseOrModule.link).exports\n\ + \ }\n\ + \ if (!baseOrModule.exports) {\n\ + \ baseOrModule.exports = {}\n\ + \ globalEval(`(function(require,exports,module){${baseOrModule.text}\\n})//# sourceURL=${baseOrModule.link}`)(\n\ + \ function require(id) {\n\ + \ return loadSync(id, baseOrModule.link);\n\ + \ }, // require\n\ + \ baseOrModule.exports = {}, // exports\n\ + \ baseOrModule // module\n\ + \ );\n\ + \ }\n\ + \ if(!evaluatedModules.has(baseOrModule.link)){\n\ + \ evaluatedModules.set(baseOrModule.link,baseOrModule)\n\ + \ }\n\ + \ return baseOrModule.exports\n\ + \ } else {\n\ + \ throw new Error(`${id} : ${parent} could not be resolved`)\n\ + \ }\n\ + }\n\ + \n\ + \n\ + function genEvalName() {\n\ + \ return \"eval-\" + ((\"\" + Math.random()).substr(2, 5))\n\ + }\n\ + /**\n\ + \ * \n\ + \ * @param {string} text \n\ + \ * @param {string} link\n\ + \ * In this case [text] evaluated result will not be cached\n\ + \ */\n\ + function loadTextSync(text, link) {\n\ + \ var baseOrModule = { exports: {}, text, link }\n\ + \ globalEval(`(function(require,exports,module){${baseOrModule.text}\\n})//# sourceURL=${baseOrModule.link}/${genEvalName()}.js`)(\n\ + \ function require(id) {\n\ + \ return loadSync(id, baseOrModule.link);\n\ + \ }, // require\n\ + \ baseOrModule.exports, // exports\n\ + \ baseOrModule // module\n\ + \ );\n\ + \ return baseOrModule.exports\n\ + }\n\ + \n\ + /**\n\ + \ * \n\ + \ * @param {string} text \n\ + \ */\n\ + function BSloadText(text) {\n\ + \ console.time(\"Loading\")\n\ + \ var parent = BsGetPath(\".\", document.baseURI)\n\ + \ return getAllFromText(text, parent).then(function () {\n\ + \ var result = loadTextSync(text, parent)\n\ + \ console.timeEnd(\"Loading\")\n\ + \ return result\n\ + \ })\n\ + };\n\ + \n\ + \n\ + function load(id, parent) {\n\ + \ return getAll(id, parent).then(function () {\n\ + \ return loadSync(id, parent)\n\ + \ })\n\ + \n\ + };\n\ + \n\ + \n\ + export function BSload(id) {\n\ + \ var parent = BsGetPath(\".\", document.baseURI)\n\ + \ return load(id, parent)\n\ + }\n\ + \n\ + export var BSLoader = {\n\ + \ loadText: BSloadText,\n\ + \ load: BSload,\n\ + \ SyncedIDLocations: SyncedIDLocations\n\ + };\n\ + \n\ + window.BSLoader = BSLoader;\n\ + \n\ + var main = document.querySelector('script[data-main]')\n\ + if (main) {\n\ + \ BSload(main.dataset.main)\n\ + }\n\ + " + ); + File("package.json", + "{\n\ + \ \"name\": \"${bsb:name}\",\n\ + \ \"version\": \"${bsb:proj-version}\",\n\ + \ \"scripts\": {\n\ + \ \"clean\": \"bsb -clean-world\",\n\ + \ \"build\": \"bsb -make-world\",\n\ + \ \"watch\": \"bsb -make-world -w -ws _\"\n\ + \ },\n\ + \ \"keywords\": [\n\ + \ \"BuckleScript\"\n\ + \ ],\n\ + \ \"author\": \"\",\n\ + \ \"license\": \"MIT\",\n\ + \ \"devDependencies\": {\n\ + \ \"bs-platform\": \"^${bsb:bs-version}\"\n\ + \ },\n\ + \ \"dependencies\": {\n\ + \ \"bucklescript-tea\": \"^0.7.4\"\n\ + \ }\n\ + }\n\ + " + ); + Dir("src",[ + File("demo.ml", + "(* This line opens the Tea.App modules into the current scope for Program access functions and types *)\n\ + open Tea.App\n\ + \n\ + (* This opens the Elm-style virtual-dom functions and types into the current scope *)\n\ + open Tea.Html\n\ + \n\ + (* Let's create a new type here to be our main message type that is passed around *)\n\ + type msg =\n\ + \ | Increment (* This will be our message to increment the counter *)\n\ + \ | Decrement (* This will be our message to decrement the counter *)\n\ + \ | Reset (* This will be our message to reset the counter to 0 *)\n\ + \ | Set of int (* This will be our message to set the counter to a specific value *)\n\ + \ [@@bs.deriving {accessors}] (* This is a nice quality-of-life addon from Bucklescript, it will generate function names for each constructor name, optional, but nice to cut down on code, this is unused in this example but good to have regardless *)\n\ + \n\ + (* This is optional for such a simple example, but it is good to have an `init` function to define your initial model default values, the model for Counter is just an integer *)\n\ + let init () = 4\n\ + \n\ + (* This is the central message handler, it takes the model as the first argument *)\n\ + let update model = function (* These should be simple enough to be self-explanatory, mutate the model based on the message, easy to read and follow *)\n\ + \ | Increment -> model + 1\n\ + \ | Decrement -> model - 1\n\ + \ | Reset -> 0\n\ + \ | Set v -> v\n\ + \n\ + (* This is just a helper function for the view, a simple function that returns a button based on some argument *)\n\ + let view_button title msg =\n\ + \ button\n\ + \ [ onClick msg\n\ + \ ]\n\ + \ [ text title\n\ + \ ]\n\ + \n\ + (* This is the main callback to generate the virtual-dom.\n\ + \ This returns a virtual-dom node that becomes the view, only changes from call-to-call are set on the real DOM for efficiency, this is also only called once per frame even with many messages sent in within that frame, otherwise does nothing *)\n\ + let view model =\n\ + \ div\n\ + \ []\n\ + \ [ span\n\ + \ [ style \"text-weight\" \"bold\" ]\n\ + \ [ text (string_of_int model) ]\n\ + \ ; br []\n\ + \ ; view_button \"Increment\" Increment\n\ + \ ; br []\n\ + \ ; view_button \"Decrement\" Decrement\n\ + \ ; br []\n\ + \ ; view_button \"Set to 2\" (Set 42)\n\ + \ ; br []\n\ + \ ; if model <> 0 then view_button \"Reset\" Reset else noNode\n\ + \ ]\n\ + \n\ + (* This is the main function, it can be named anything you want but `main` is traditional.\n\ + \ The Program returned here has a set of callbacks that can easily be called from\n\ + \ Bucklescript or from javascript for running this main attached to an element,\n\ + \ or even to pass a message into the event loop. You can even expose the\n\ + \ constructors to the messages to javascript via the above [@@bs.deriving {accessors}]\n\ + \ attribute on the `msg` type or manually, that way even javascript can use it safely. *)\n\ + let main =\n\ + \ beginnerProgram { (* The beginnerProgram just takes a set model state and the update and view functions *)\n\ + \ model = init (); (* Since model is a set value here, we call our init function to generate that value *)\n\ + \ update;\n\ + \ view;\n\ + \ }" + ); + File("main.ml", + "\n\ + \n\ + \n\ + Js.Global.setTimeout\n\ + \ (fun _ -> \n\ + \ Demo.main (Web.Document.getElementById \"my-element\") () \n\ + \ |. ignore\n\ + \ ) \n\ + 0" + ) + ]); + File("watcher.js", + "\n\ + \n\ + var wsReloader;\n\ + var LAST_SUCCESS_BUILD_STAMP = (localStorage.getItem('LAST_SUCCESS_BUILD_STAMP') || 0)\n\ + var WS_PORT = 9999; // configurable\n\ + \n\ + function setUpWebScoket() {\n\ + \ if (wsReloader == null || wsReloader.readyState !== 1) {\n\ + \ try {\n\ + \ wsReloader = new WebSocket(`ws://${window.location.hostname}:${WS_PORT}`)\n\ + \ wsReloader.onmessage = (msg) => {\n\ + \ var newData = JSON.parse(msg.data).LAST_SUCCESS_BUILD_STAMP\n\ + \ if (newData > LAST_SUCCESS_BUILD_STAMP) {\n\ + \ LAST_SUCCESS_BUILD_STAMP = newData\n\ + \ localStorage.setItem('LAST_SUCCESS_BUILD_STAMP', LAST_SUCCESS_BUILD_STAMP)\n\ + \ location.reload(true)\n\ + \ }\n\ + \n\ + \ }\n\ + \ } catch (exn) {\n\ + \ console.error(\"web socket failed connect\")\n\ + \ }\n\ + \ }\n\ + };\n\ + \n\ + setUpWebScoket();\n\ + setInterval(setUpWebScoket, 2000);" + ) + ]) ]) - end module Bsb_theme_init : sig #1 "bsb_theme_init.mli" @@ -16243,7 +16314,7 @@ let mkdir_or_not_if_exists dir = "%s expected to be added as dir but exist file is not a dir" dir | Non_exists -> Unix.mkdir dir 0o777 -let rec process_theme_aux env cwd (x : OCamlRes.Res.node) = +let rec process_theme_aux env cwd (x : Bsb_templates.node) = match x with | File (name,content) -> let new_file = cwd // name in @@ -16255,10 +16326,8 @@ let rec process_theme_aux env cwd (x : OCamlRes.Res.node) = List.iter (fun x -> process_theme_aux env new_cwd x ) nodes let list_themes () = - Format.fprintf Format.std_formatter "Available themes: @."; - Bsb_templates.root - |> - List.iter (fun (x : OCamlRes.Res.node) -> + Format.fprintf Format.std_formatter "Available themes: @."; + Ext_list.iter Bsb_templates.root (fun x -> match x with | Dir (x, _) -> Format.fprintf Format.std_formatter "%s@." x @@ -16267,7 +16336,7 @@ let list_themes () = ) (* @raise [Not_found] *) -let process_themes env theme proj_dir (themes : OCamlRes.Res.node list ) = +let process_themes env theme proj_dir (themes : Bsb_templates.node list ) = match Ext_list.find_first themes (fun x -> match x with | Dir (dir, _) -> dir = theme diff --git a/lib/4.06.1/bsb.ml.d b/lib/4.06.1/bsb.ml.d index a56413c555..0708ee8462 100644 --- a/lib/4.06.1/bsb.ml.d +++ b/lib/4.06.1/bsb.ml.d @@ -64,7 +64,6 @@ ../lib/4.06.1/bsb.ml: ./bsb/bsb_watcher_gen.mli ../lib/4.06.1/bsb.ml: ./bsb/bsb_world.ml ../lib/4.06.1/bsb.ml: ./bsb/bsb_world.mli -../lib/4.06.1/bsb.ml: ./bsb/oCamlRes.ml ../lib/4.06.1/bsb.ml: ./common/bs_version.ml ../lib/4.06.1/bsb.ml: ./common/bs_version.mli ../lib/4.06.1/bsb.ml: ./ext/bsb_db.ml diff --git a/scripts/pack.js b/scripts/pack.js new file mode 100644 index 0000000000..ce6492ba49 --- /dev/null +++ b/scripts/pack.js @@ -0,0 +1,100 @@ +//@ts-check +var fs = require("fs"); +var path = require("path"); +var util = require("util"); +var child_process = require("child_process"); +// Note the escaping is escape to ocaml style string literals. +// `util.format` is useful to escape to js style string literrals. +// { name : "basic", children : ... } +// { name : "xx", content : ... } + +function scanDir(dir) { + var files = fs.readdirSync(dir).sort(); + var children = files.map((file) => { + var curFile = path.join(dir, file); + var stat = fs.statSync(curFile); + if (stat.isFile()) { + var content = fs.readFileSync(curFile, "utf8"); + return { name: file, content }; + } else if (stat.isDirectory()) { + return scanDir(curFile); + } + }); + return { name: path.basename(dir), children }; +} + +function escape(s, indent) { + return ( + " ".repeat(indent) + + '"' + + s.replace(/(\n|"|\\|\t|\r)/g, (match, _, offset) => { + switch (match) { + case "\n": + var o = `\\n\\ +${" ".repeat(indent)}`; + if (s[offset + 1] === " ") { + o += "\\"; + } + return o; + default: + return "\\" + match; + } + }) + + '"' + ); +} + +function toString(data, indent) { + var output = ""; + if (data.children) { + output += `${" ".repeat(indent)}Dir("${data.name}",[ +${data.children.map((x) => toString(x, indent + 1)).join(";\n")} +${" ".repeat(indent)}])`; + } else { + output += `${" ".repeat(indent)}File("${data.name}", +${escape(data.content, indent)} +${" ".repeat(indent)})`; + } + return output; +} + +/** + * + */ +function updateThemes() { + var templatesDir = path.join(__dirname, "..", "jscomp", "bsb", "templates"); + var output = child_process.spawnSync(`git clean -dfx ${templatesDir}`, { + shell: true, + encoding: "utf-8", + }); + if (output.error) { + throw output.error; + } + console.log(output.stdout); + if (output.status !== 0) { + console.log(output.stderr); + } + + // run git clean -dfx . first + fs.writeFileSync( + path.join(templatesDir, "..", "bsb_templates.ml"), + ` +type node = + | Dir of string * node list + | File of string * string +let root = ([ +${fs + .readdirSync(templatesDir) + .filter((x) => fs.statSync(path.join(templatesDir, x)).isDirectory()) + .map((x) => toString(scanDir(path.join(templatesDir, x)), 3)) + .join(";\n")} +])`, + "utf8" + ); +} +exports.updateThemes = updateThemes; + + +if (require.main === module) { + updateThemes(); +} diff --git a/scripts/prebuilt.js b/scripts/prebuilt.js index 04f726f67e..52c92bc329 100755 --- a/scripts/prebuilt.js +++ b/scripts/prebuilt.js @@ -43,6 +43,7 @@ function buildCompiler() { } if (!is_windows) { if (!process.argv.includes("-noclean")) { + require("./pack").updateThemes(); rebuild(); } diff --git a/scripts/release.js b/scripts/release.js index 5f0a688a49..2c00ffdd34 100755 --- a/scripts/release.js +++ b/scripts/release.js @@ -21,8 +21,8 @@ function run() { cp.execSync(`git clean -dfx stubs ext common syntax depends core bsb main .`, { cwd: jscompDir, encoding: 'utf8', stdio: [0, 1, 2] }) - cp.execSync(`git clean -dfx templates && ocp-ocamlres templates -o bsb_templates.ml`, - { cwd: path.join(jscompDir,'bsb'), encoding: 'utf8', stdio: [0, 1, 2] }) + // cp.execSync(`git clean -dfx templates && ocp-ocamlres templates -o bsb_templates.ml`, + // { cwd: path.join(jscompDir,'bsb'), encoding: 'utf8', stdio: [0, 1, 2] }) cp.execSync(`ninja -t clean -g && ninja`, { cwd: jscompDir, encoding: 'utf8', stdio: [0, 1, 2] }) cp.execSync('ninja', {cwd : path.join(rootDir,'lib'), stdio:[0,1,2]})