├── .npmignore ├── .gitignore ├── rollup.config.js ├── package.json ├── index.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | README.md 2 | rollup.config.js 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | elm-stuff 4 | 5 | .DS_Store 6 | 7 | dist -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import terser from "@rollup/plugin-terser"; 2 | 3 | export default { 4 | external: ["react"], 5 | input: "index.js", 6 | output: [ 7 | { 8 | file: "dist/react-elm-component.mjs", 9 | format: "esm", 10 | name: "version", 11 | plugins: [terser()], 12 | }, 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-elm-component", 3 | "type": "module", 4 | "version": "0.2.0", 5 | "description": "Embed Elm apps within your React applications", 6 | "module": "dist/react-elm-component.mjs", 7 | "scripts": { 8 | "build": "rollup -c", 9 | "prepublish": "npm run build" 10 | }, 11 | "peerDependencies": { 12 | "react": ">=18.3.0" 13 | }, 14 | "author": "Kristian Roebuck ", 15 | "license": "ISC", 16 | "keywords": [ 17 | "react", 18 | "elm" 19 | ], 20 | "devDependencies": { 21 | "@rollup/plugin-terser": "0.4.4", 22 | "rollup": "4.21.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function renderElmElement(props) { 4 | return function (node) { 5 | if (!node) { 6 | return; 7 | } 8 | 9 | var mountPoint = document.createElement('div'); 10 | node.appendChild(mountPoint); 11 | 12 | var app = props.app.init({ 13 | node: mountPoint, 14 | flags: props.flags 15 | }); 16 | 17 | if (app && app.ports && props.ports) { 18 | props.ports(app.ports); 19 | } 20 | } 21 | }; 22 | 23 | var ElmComponent = function (props) { 24 | return React.createElement('div', { ref: renderElmElement(props) }); 25 | }; 26 | 27 | export default React.memo(ElmComponent); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Elm Component 2 | 3 | ### Embed an Elm App within a React Component 4 | 5 | Inspiration was heavily taken from [react-elm-components](https://github.com/cultureamp/react-elm-components). This package takes advantage of React Hooks to provide a lightweight (282 bytes) solution. 6 | 7 | 8 | ### Usage With Webpack / Parcel 9 | 10 | After you have compiled an Elm program to JavaScript, you can embed it in React like this: 11 | 12 | ```javascript 13 | import ElmComponent from 'react-elm-component' 14 | import { Elm } from './ElmApp.elm' 15 | 16 | function render() { 17 | return 18 | } 19 | ``` 20 | 21 | 22 | ### Flags 23 | 24 | Sometimes you want to give your Elm program some **flags** on start up. For example, maybe your `Todo` module needs to get an array of todos. You would write something like this: 25 | 26 | ```javascript 27 | import ElmComponent from 'react-elm-component' 28 | import { Elm } from './ElmApp.elm' 29 | 30 | function render() { 31 | var flags = { todos: ["Get Milk", "Do Laundry"] }; 32 | return 33 | } 34 | ``` 35 | 36 | These flags will be given to the Elm program, allowing you to do some setup work in JS first. 37 | 38 | 39 | ### JavaScript & Elm Interop 40 | 41 | As your Elm program gets fancier, you will probably need to interact with JavaScript. We do this with [**ports**](https://guide.elm-lang.org/interop/ports.html). Think of these as holes in the side of an Elm program that let you pass messages back-and-forth. 42 | 43 | So maybe we extend our `Todo` app to allow outsiders to register new tasks through the `todos` port. And maybe we also expose `numActiveTodos` so that the outsider can know how much work you have left. You would set it up like this: 44 | 45 | ```javascript 46 | import ElmComponent from 'react-elm-component' 47 | import { Elm } from './ElmApp.elm' 48 | 49 | function render() { 50 | return 51 | } 52 | 53 | function setupPorts(ports) { 54 | ports.numActiveTodos.subscribe(function(n) { 55 | console.log(n); 56 | }); 57 | 58 | ports.todos.send("Invent the Universe"); 59 | ports.todos.send("Bake an Apple Pie"); 60 | } 61 | ``` 62 | 63 | In the `setupPorts` function, we first subscribe to the `numActiveTodos` port. Whenever the number of active todos changes, we will run that function and log the number on the console. After that, we send two values through the `todos` port. This will add both of these into the model *and* trigger the `numActiveTodos` callback twice. 64 | --------------------------------------------------------------------------------