diff --git a/docs/package-management.md b/docs/package-management.md index a6966122b..5090ce854 100644 --- a/docs/package-management.md +++ b/docs/package-management.md @@ -267,6 +267,39 @@ The advantage of this approach —as opposed to vendoring the JavaScript package inside the bindings— is that it gives users of the bindings complete flexibility over the way these JavaScript packages are downloaded and bundled. +Melange provides a way to define dependencies from opam packages to npm packages +inside the `opam` files through the +[check-npm-deps](https://github.com/jchavarri/opam-check-npm-deps) opam plugin. + +With this plugin, library authors can include constraints in the npm format +inside the opam `depexts` field, for example, the `reason-react` opam file can +include a section like this: + +``` +depexts: [ + ["react" "react-dom"] {npm-version = "^17.0.0 || ^18.0.0"} +] +``` + +This indicates that the version of `reason-react` is compatible with ReactJS +versions 17 and 18. + +Users of Melange bindings can check that the constraints defined in the opam +packages installed in their switch are fulfilled by the packages installed in +`node_modules` by using the `check-npm-deps` plugin. For this, one just needs to +install the plugin: + +```bash +opam install opam-check-npm-deps +``` + +And then call it from the root folder of the project, where the opam switch and +the `node_modules` folder exist: + +```bash +opam-check-npm-deps +``` + ## Finding and using Melange compatible packages ### opam packages @@ -325,12 +358,12 @@ then `reason-react` should be added to the `dune` file under the `src` folder: (alias react) (libraries lib reason-react) (preprocess - (pps reactjs-jsx-ppx)) + (pps reason-react-ppx)) (module_systems es6)) ``` Some libraries will only work after being processed by an accompanying PPX, -e.g., `reason-react` requires preprocessing with `reactjs-jsx-ppx`. These +e.g., `reason-react` requires preprocessing with `reason-react-ppx`. These preprocessors may be installed together with the library as part of the same package, or they might be part of a different package, in which case they need to be installed separately. @@ -363,7 +396,7 @@ file: (alias react) (libraries lib reason-react melange-fetch) (preprocess - (pps reactjs-jsx-ppx)) + (pps reason-react-ppx)) (module_systems es6)) ``` diff --git a/documentation-site.opam b/documentation-site.opam index cabba73a4..cb5719959 100644 --- a/documentation-site.opam +++ b/documentation-site.opam @@ -29,8 +29,8 @@ dev-repo: "git+https://github.com/melange-re/melange-re.github.io.git" pin-depends: [ [ "reason-react.dev" "git+https://github.com/reasonml/reason-react.git#0ccff71796b60d6c32ab6cf01e31beccca4698b9" ] [ "reason-react-ppx.dev" "git+https://github.com/reasonml/reason-react.git#0ccff71796b60d6c32ab6cf01e31beccca4698b9" ] - [ "melange.dev" "git+https://github.com/melange-re/melange.git#465101328e0e1ec90e246b0a2e8622e6cbdc0584" ] - [ "melange-playground.dev" "git+https://github.com/melange-re/melange.git#465101328e0e1ec90e246b0a2e8622e6cbdc0584" ] + [ "melange.dev" "git+https://github.com/melange-re/melange.git#3a5aec1548067122e99eeaf6a08fc555415421dc" ] + [ "melange-playground.dev" "git+https://github.com/melange-re/melange.git#3a5aec1548067122e99eeaf6a08fc555415421dc" ] [ "cmarkit.dev" "git+https://github.com/dbuenzli/cmarkit.git#f37c8ea86fd0be8dba7a8babcee3682e0e047d91" ] ] build: [ diff --git a/playground/src/App.css b/playground/src/App.css index 084ab067c..c5fe7261c 100644 --- a/playground/src/App.css +++ b/playground/src/App.css @@ -101,7 +101,6 @@ button:hover { } .Editor { - overflow: auto; height: 100%; } diff --git a/playground/src/app.jsx b/playground/src/app.jsx index d0496eb43..82010ad89 100644 --- a/playground/src/app.jsx +++ b/playground/src/app.jsx @@ -420,15 +420,36 @@ const compile = (language, code) => { } if (problems) { return { + typeHints: [], problems: problems, } } else { return { + typeHints: compilation.type_hints.sort((a, b) => { + let aLineGap = a.end.line - a.start.line; + let bLineGap = b.end.line - b.start.line; + if (aLineGap < bLineGap) { + return -1; + } else if (aLineGap > bLineGap) { + return 1; + } else { + let aColGap = a.end.col - a.start.col; + let bColGap = b.end.col - b.start.col; + if (aColGap < bColGap) { + return -1; + } else if (aColGap > bColGap) { + return 1; + } else { + return 0; + } + } + }), javascriptCode: compilation.js_code, } } } else { return { + typeHints: [], problems: [ { js_error_msg: "No result was returned from compilation", @@ -443,6 +464,21 @@ const compile = (language, code) => { } }; +function updateMarkers(monaco, editorRef, compilation) { + if (monaco && editorRef.current) { + const owner = "playground"; + if (compilation?.problems) { + monaco.editor.setModelMarkers( + editorRef.current.getModel(), + owner, + compilation.problems.map(toMonaco) + ); + } else { + monaco.editor.removeAllMarkers(owner); + } + } +} + function App() { const defaultState = { language: languageMap.OCaml, @@ -467,8 +503,9 @@ function App() { const editorRef = React.useRef(null); - function handleEditorDidMount(editor, _monaco) { + function handleEditorDidMount(editor, monaco) { editorRef.current = editor; + updateMarkers(monaco, editorRef, compilation); } function clearLogs() { @@ -489,19 +526,75 @@ function App() { }, [monaco]); React.useEffect(() => { - // or make sure that it exists by other ways - if (monaco && editorRef.current) { - const owner = "playground"; - if (compilation?.problems) { - monaco.editor.setModelMarkers( - editorRef.current.getModel(), - owner, - compilation.problems.map(toMonaco) - ); - } else { - monaco.editor.removeAllMarkers(owner); + let hoverProvider = undefined; + if (monaco) { + hoverProvider = monaco.languages.registerHoverProvider(language, { + provideHover: function (model, position) { + const { lineNumber, column } = position; + if (!compilation?.typeHints) { + return null; + } else { + function hintInLoc(item) { + var end = item.end; + var start = item.start; + const result = + lineNumber >= start.line && + lineNumber <= end.line && + column >= start.col + 1 && + column <= end.col + 1; + return result; + }; + const result = compilation?.typeHints.find(hintInLoc); + if (result) { + const range = new monaco.Range( + result.start.line, + result.start.col + 1, + result.end.line, + result.end.col + 1 + ); + let hint = result.hint; + if (language == languageMap.Reason) { + try { + if (hint.substring(0,5) === "type ") { + // No need to mess with the hint as it should be valid AST + hint = ocaml.printRE(ocaml.parseML(hint)); + } else { + const prefix = "type t = "; + // Must be something else than a type + hint = + ocaml + /* add prefix so it is valid code */ + .printRE(ocaml.parseML(prefix + hint)) + /* remove prefix */ + .slice(prefix.length) + /* remove last `;` */ + .slice(0, -2); + } + + } catch (e) { + console.error("Error formatting type hint: ", hint); + } + } + return { + range, + contents: [{ value: `\`\`\`${language}\n${hint}\n\`\`\`` }], + }; + } else { + return null; + } + } + }, + }); + } + return () => { + if (hoverProvider) { + hoverProvider.dispose(); } } + }, [monaco, compilation?.typeHints]); + + React.useEffect(() => { + updateMarkers(monaco, editorRef, compilation) }, [monaco, compilation?.problems]); React.useEffect(() => {