├── .elm-version ├── .gitignore ├── .nvmrc ├── LICENSE ├── README.md ├── example ├── test.js └── ticking_port.elm ├── npm-shrinkwrap.json ├── package.json ├── src └── index.js └── test ├── fixtures ├── EmptyModule.elm ├── Nested │ └── Module.elm ├── conflicting_file.elm ├── conflicting_file.js ├── constant_port.elm ├── echo_port.elm ├── empty_module.elm ├── failing.elm └── random_port.elm └── index.js /.elm-version: -------------------------------------------------------------------------------- 1 | 0.14.1 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | elm-package.json 4 | elm-stuff/ 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 0.12.2 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sonny Michaud 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Node Elm Loader 2 | =============== 3 | 4 | A simple way to interoperate between [Elm](http://elm-lang.org/) and [NodeJS](http://nodejs.org/). 5 | Useful for sharing code between display and model logic, which holding it together with the rich 6 | Node ecosystem, for example, building your client-side components and minifying them with the rest 7 | of your assets! 8 | 9 | Installation 10 | ------------ 11 | 12 | You will need to already have the Elm platform installed on your system. Please find the relevant 13 | instructions [here](http://elm-lang.org/Install.elm) and a NodeJS development environment for v0.12.x. 14 | It is now also possible to install Elm with `npm install -g elm` if you prefer. 15 | 16 | Then, simply add `elm-loader` as a dependency in your `package.json` or run `npm install -g elm-loader`. 17 | 18 | Usage 19 | ----- 20 | 21 | First, write a simple module in Elm and expose a port! Due to a current 22 | [bug](https://github.com/elm-lang/elm-compiler/issues/856) in the compiler, you need to supply a 23 | dummy `main` function. Once this is resolved, it will be possible to proceed without the boilerplate. 24 | 25 | ``` Elm 26 | module TickingPort where 27 | 28 | import Time 29 | import Html exposing (text) 30 | 31 | main = text "placeholder" 32 | 33 | port messageOut : Signal String 34 | port messageOut = Signal.map toString (Time.every Time.second) 35 | ``` 36 | 37 | Next, wire import your module into Node! 38 | 39 | ``` JavaScript 40 | var path = require("path"); 41 | var Elm = require("elm-loader"); 42 | 43 | var compiledCode = Elm(path.resolve(__dirname, "ticking_port.elm"), __dirname); 44 | 45 | compiledCode.emitter.on("messageOut", function(message) { 46 | console.log(message) 47 | }); 48 | ``` 49 | 50 | Watch it tick! 51 | 52 | ``` 53 | $ node test.js 54 | 1418683956865 55 | 1418683957870 56 | 1418683958873 57 | 1418683959874 58 | 1418683960875 59 | ^C 60 | ``` 61 | 62 | You can also access the `ports` property, rather than the `emitter` on the object 63 | returned by the top level factory function and subscribe as you would with a 64 | [typical setup](http://elm-lang.org/learn/Ports.elm). 65 | 66 | Caveats 67 | ------- 68 | 69 | The loader supports conventional CamelCase filenames for the Elm modules they contain. 70 | If you prefer to split the words in your filenames with underscores, they will automatically 71 | be inflected to infer the module they contain, e.g. `ticking_port.elm` will correspond to the 72 | `TickingPort` module above. 73 | 74 | If you are defining ports *into* Elm, you also need to supply a second argument to the factory 75 | function to define the defaults for those functions, e.g. 76 | 77 | ```Javascript 78 | var echoPort = Elm(path.resolve(__dirname, "fixtures/echo_port.elm"), "fixtures", { 79 | messageIn: "" 80 | }); 81 | ``` 82 | 83 | Example 84 | ------- 85 | 86 | To try it out, clone this repository, run `npm link` and then run `node example/test.js`! 87 | -------------------------------------------------------------------------------- /example/test.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var Elm = require("./../"); 3 | 4 | var compiledCode = Elm(path.resolve(__dirname, "ticking_port.elm"), "example"); 5 | 6 | compiledCode.emitter.on("messageOut", function(message) { 7 | console.log(message) 8 | }); 9 | -------------------------------------------------------------------------------- /example/ticking_port.elm: -------------------------------------------------------------------------------- 1 | module TickingPort where 2 | 3 | import Time 4 | import Html exposing (text) 5 | 6 | main = text "placeholder" 7 | 8 | port messageOut : Signal String 9 | port messageOut = Signal.map toString (Time.every Time.second) 10 | -------------------------------------------------------------------------------- /npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elm-loader", 3 | "version": "0.14.6", 4 | "dependencies": { 5 | "inflect-js": { 6 | "version": "1.0.0", 7 | "from": "inflect-js@*", 8 | "resolved": "https://registry.npmjs.org/inflect-js/-/inflect-js-1.0.0.tgz" 9 | }, 10 | "jsdom": { 11 | "version": "4.3.0", 12 | "from": "jsdom@4.3.0", 13 | "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-4.3.0.tgz", 14 | "dependencies": { 15 | "browser-request": { 16 | "version": "0.3.3", 17 | "from": "browser-request@>=0.3.1 <0.4.0", 18 | "resolved": "https://registry.npmjs.org/browser-request/-/browser-request-0.3.3.tgz" 19 | }, 20 | "cssom": { 21 | "version": "0.3.0", 22 | "from": "cssom@>=0.3.0 <0.4.0", 23 | "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.0.tgz" 24 | }, 25 | "cssstyle": { 26 | "version": "0.2.23", 27 | "from": "cssstyle@>=0.2.23 <0.3.0", 28 | "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.23.tgz" 29 | }, 30 | "htmlparser2": { 31 | "version": "3.8.2", 32 | "from": "htmlparser2@>=3.7.3 <4.0.0", 33 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.2.tgz", 34 | "dependencies": { 35 | "domhandler": { 36 | "version": "2.3.0", 37 | "from": "domhandler@>=2.3.0 <2.4.0", 38 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz" 39 | }, 40 | "domutils": { 41 | "version": "1.5.1", 42 | "from": "domutils@>=1.5.0 <1.6.0", 43 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", 44 | "dependencies": { 45 | "dom-serializer": { 46 | "version": "0.1.0", 47 | "from": "dom-serializer@>=0.0.0 <1.0.0", 48 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", 49 | "dependencies": { 50 | "domelementtype": { 51 | "version": "1.1.3", 52 | "from": "domelementtype@>=1.1.1 <1.2.0", 53 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz" 54 | }, 55 | "entities": { 56 | "version": "1.1.1", 57 | "from": "entities@>=1.1.1 <1.2.0", 58 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz" 59 | } 60 | } 61 | } 62 | } 63 | }, 64 | "domelementtype": { 65 | "version": "1.3.0", 66 | "from": "domelementtype@>=1.0.0 <2.0.0", 67 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz" 68 | }, 69 | "readable-stream": { 70 | "version": "1.1.13", 71 | "from": "readable-stream@>=1.1.0 <1.2.0", 72 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz", 73 | "dependencies": { 74 | "core-util-is": { 75 | "version": "1.0.1", 76 | "from": "core-util-is@>=1.0.0 <1.1.0", 77 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" 78 | }, 79 | "isarray": { 80 | "version": "0.0.1", 81 | "from": "isarray@0.0.1", 82 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" 83 | }, 84 | "string_decoder": { 85 | "version": "0.10.31", 86 | "from": "string_decoder@>=0.10.0 <0.11.0", 87 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" 88 | }, 89 | "inherits": { 90 | "version": "2.0.1", 91 | "from": "inherits@>=2.0.1 <2.1.0", 92 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" 93 | } 94 | } 95 | }, 96 | "entities": { 97 | "version": "1.0.0", 98 | "from": "entities@>=1.0.0 <1.1.0", 99 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz" 100 | } 101 | } 102 | }, 103 | "nwmatcher": { 104 | "version": "1.3.4", 105 | "from": "nwmatcher@>=1.3.4 <2.0.0", 106 | "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.3.4.tgz" 107 | }, 108 | "parse5": { 109 | "version": "1.4.2", 110 | "from": "parse5@>=1.3.2 <2.0.0", 111 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.4.2.tgz" 112 | }, 113 | "request": { 114 | "version": "2.55.0", 115 | "from": "request@>=2.44.0 <3.0.0", 116 | "resolved": "https://registry.npmjs.org/request/-/request-2.55.0.tgz", 117 | "dependencies": { 118 | "bl": { 119 | "version": "0.9.4", 120 | "from": "bl@>=0.9.0 <0.10.0", 121 | "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.4.tgz", 122 | "dependencies": { 123 | "readable-stream": { 124 | "version": "1.0.33", 125 | "from": "readable-stream@>=1.0.26 <1.1.0", 126 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz", 127 | "dependencies": { 128 | "core-util-is": { 129 | "version": "1.0.1", 130 | "from": "core-util-is@>=1.0.0 <1.1.0", 131 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" 132 | }, 133 | "isarray": { 134 | "version": "0.0.1", 135 | "from": "isarray@0.0.1", 136 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" 137 | }, 138 | "string_decoder": { 139 | "version": "0.10.31", 140 | "from": "string_decoder@>=0.10.0 <0.11.0", 141 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" 142 | }, 143 | "inherits": { 144 | "version": "2.0.1", 145 | "from": "inherits@>=2.0.1 <2.1.0", 146 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" 147 | } 148 | } 149 | } 150 | } 151 | }, 152 | "caseless": { 153 | "version": "0.9.0", 154 | "from": "caseless@>=0.9.0 <0.10.0", 155 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.9.0.tgz" 156 | }, 157 | "forever-agent": { 158 | "version": "0.6.1", 159 | "from": "forever-agent@>=0.6.0 <0.7.0", 160 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" 161 | }, 162 | "form-data": { 163 | "version": "0.2.0", 164 | "from": "form-data@>=0.2.0 <0.3.0", 165 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz", 166 | "dependencies": { 167 | "async": { 168 | "version": "0.9.0", 169 | "from": "async@>=0.9.0 <0.10.0", 170 | "resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz" 171 | } 172 | } 173 | }, 174 | "json-stringify-safe": { 175 | "version": "5.0.0", 176 | "from": "json-stringify-safe@>=5.0.0 <5.1.0", 177 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.0.tgz" 178 | }, 179 | "mime-types": { 180 | "version": "2.0.10", 181 | "from": "mime-types@>=2.0.1 <2.1.0", 182 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.10.tgz", 183 | "dependencies": { 184 | "mime-db": { 185 | "version": "1.8.0", 186 | "from": "mime-db@>=1.8.0 <1.9.0", 187 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.8.0.tgz" 188 | } 189 | } 190 | }, 191 | "node-uuid": { 192 | "version": "1.4.3", 193 | "from": "node-uuid@>=1.4.0 <1.5.0", 194 | "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz" 195 | }, 196 | "qs": { 197 | "version": "2.4.1", 198 | "from": "qs@>=2.4.0 <2.5.0", 199 | "resolved": "https://registry.npmjs.org/qs/-/qs-2.4.1.tgz" 200 | }, 201 | "tunnel-agent": { 202 | "version": "0.4.0", 203 | "from": "tunnel-agent@>=0.4.0 <0.5.0", 204 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.0.tgz" 205 | }, 206 | "tough-cookie": { 207 | "version": "0.12.1", 208 | "from": "tough-cookie@>=0.12.0", 209 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-0.12.1.tgz", 210 | "dependencies": { 211 | "punycode": { 212 | "version": "1.3.2", 213 | "from": "punycode@>=0.2.0", 214 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" 215 | } 216 | } 217 | }, 218 | "http-signature": { 219 | "version": "0.10.1", 220 | "from": "http-signature@>=0.10.0 <0.11.0", 221 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", 222 | "dependencies": { 223 | "assert-plus": { 224 | "version": "0.1.5", 225 | "from": "assert-plus@>=0.1.5 <0.2.0", 226 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz" 227 | }, 228 | "asn1": { 229 | "version": "0.1.11", 230 | "from": "asn1@0.1.11", 231 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz" 232 | }, 233 | "ctype": { 234 | "version": "0.5.3", 235 | "from": "ctype@0.5.3", 236 | "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz" 237 | } 238 | } 239 | }, 240 | "oauth-sign": { 241 | "version": "0.6.0", 242 | "from": "oauth-sign@>=0.6.0 <0.7.0", 243 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.6.0.tgz" 244 | }, 245 | "hawk": { 246 | "version": "2.3.1", 247 | "from": "hawk@>=2.3.0 <2.4.0", 248 | "resolved": "https://registry.npmjs.org/hawk/-/hawk-2.3.1.tgz", 249 | "dependencies": { 250 | "hoek": { 251 | "version": "2.12.0", 252 | "from": "hoek@>=2.0.0 <3.0.0", 253 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.12.0.tgz" 254 | }, 255 | "boom": { 256 | "version": "2.7.0", 257 | "from": "boom@>=2.0.0 <3.0.0", 258 | "resolved": "https://registry.npmjs.org/boom/-/boom-2.7.0.tgz" 259 | }, 260 | "cryptiles": { 261 | "version": "2.0.4", 262 | "from": "cryptiles@>=2.0.0 <3.0.0", 263 | "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.4.tgz" 264 | }, 265 | "sntp": { 266 | "version": "1.0.9", 267 | "from": "sntp@>=1.0.0 <2.0.0", 268 | "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" 269 | } 270 | } 271 | }, 272 | "aws-sign2": { 273 | "version": "0.5.0", 274 | "from": "aws-sign2@>=0.5.0 <0.6.0", 275 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz" 276 | }, 277 | "stringstream": { 278 | "version": "0.0.4", 279 | "from": "stringstream@>=0.0.4 <0.1.0", 280 | "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.4.tgz" 281 | }, 282 | "combined-stream": { 283 | "version": "0.0.7", 284 | "from": "combined-stream@>=0.0.5 <0.1.0", 285 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", 286 | "dependencies": { 287 | "delayed-stream": { 288 | "version": "0.0.5", 289 | "from": "delayed-stream@0.0.5", 290 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz" 291 | } 292 | } 293 | }, 294 | "isstream": { 295 | "version": "0.1.2", 296 | "from": "isstream@>=0.1.1 <0.2.0", 297 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" 298 | }, 299 | "har-validator": { 300 | "version": "1.6.1", 301 | "from": "har-validator@>=1.4.0 <2.0.0", 302 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-1.6.1.tgz", 303 | "dependencies": { 304 | "bluebird": { 305 | "version": "2.9.24", 306 | "from": "bluebird@>=2.9.21 <3.0.0", 307 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.24.tgz" 308 | }, 309 | "chalk": { 310 | "version": "1.0.0", 311 | "from": "chalk@>=1.0.0 <2.0.0", 312 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.0.0.tgz", 313 | "dependencies": { 314 | "ansi-styles": { 315 | "version": "2.0.1", 316 | "from": "ansi-styles@>=2.0.1 <3.0.0", 317 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.0.1.tgz" 318 | }, 319 | "escape-string-regexp": { 320 | "version": "1.0.3", 321 | "from": "escape-string-regexp@>=1.0.2 <2.0.0", 322 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz" 323 | }, 324 | "has-ansi": { 325 | "version": "1.0.3", 326 | "from": "has-ansi@>=1.0.3 <2.0.0", 327 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-1.0.3.tgz", 328 | "dependencies": { 329 | "ansi-regex": { 330 | "version": "1.1.1", 331 | "from": "ansi-regex@>=1.0.0 <2.0.0", 332 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz" 333 | }, 334 | "get-stdin": { 335 | "version": "4.0.1", 336 | "from": "get-stdin@>=4.0.1 <5.0.0", 337 | "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz" 338 | } 339 | } 340 | }, 341 | "strip-ansi": { 342 | "version": "2.0.1", 343 | "from": "strip-ansi@>=2.0.1 <3.0.0", 344 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz", 345 | "dependencies": { 346 | "ansi-regex": { 347 | "version": "1.1.1", 348 | "from": "ansi-regex@>=1.0.0 <2.0.0", 349 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz" 350 | } 351 | } 352 | }, 353 | "supports-color": { 354 | "version": "1.3.1", 355 | "from": "supports-color@>=1.3.0 <2.0.0", 356 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.3.1.tgz" 357 | } 358 | } 359 | }, 360 | "commander": { 361 | "version": "2.7.1", 362 | "from": "commander@>=2.7.1 <3.0.0", 363 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.7.1.tgz", 364 | "dependencies": { 365 | "graceful-readlink": { 366 | "version": "1.0.1", 367 | "from": "graceful-readlink@>=1.0.0", 368 | "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" 369 | } 370 | } 371 | }, 372 | "is-my-json-valid": { 373 | "version": "2.10.1", 374 | "from": "is-my-json-valid@>=2.10.0 <3.0.0", 375 | "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.10.1.tgz", 376 | "dependencies": { 377 | "generate-function": { 378 | "version": "2.0.0", 379 | "from": "generate-function@>=2.0.0 <3.0.0", 380 | "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" 381 | }, 382 | "generate-object-property": { 383 | "version": "1.1.1", 384 | "from": "generate-object-property@>=1.1.0 <2.0.0", 385 | "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.1.1.tgz", 386 | "dependencies": { 387 | "is-property": { 388 | "version": "1.0.2", 389 | "from": "is-property@>=1.0.0 <2.0.0", 390 | "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" 391 | } 392 | } 393 | }, 394 | "jsonpointer": { 395 | "version": "1.1.0", 396 | "from": "jsonpointer@>=1.1.0 <2.0.0", 397 | "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-1.1.0.tgz" 398 | }, 399 | "xtend": { 400 | "version": "4.0.0", 401 | "from": "xtend@>=4.0.0 <5.0.0", 402 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz" 403 | } 404 | } 405 | } 406 | } 407 | } 408 | } 409 | }, 410 | "xml-name-validator": { 411 | "version": "2.0.1", 412 | "from": "xml-name-validator@>=2.0.1 <3.0.0", 413 | "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz" 414 | }, 415 | "xmlhttprequest": { 416 | "version": "1.7.0", 417 | "from": "xmlhttprequest@>=1.6.0 <2.0.0", 418 | "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.7.0.tgz" 419 | }, 420 | "acorn-globals": { 421 | "version": "1.0.4", 422 | "from": "acorn-globals@>=1.0.2 <2.0.0", 423 | "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.4.tgz", 424 | "dependencies": { 425 | "acorn": { 426 | "version": "1.0.3", 427 | "from": "acorn@>=1.0.1 <2.0.0", 428 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.0.3.tgz" 429 | } 430 | } 431 | }, 432 | "acorn": { 433 | "version": "0.12.0", 434 | "from": "acorn@>=0.12.0 <0.13.0", 435 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-0.12.0.tgz" 436 | }, 437 | "escodegen": { 438 | "version": "1.6.1", 439 | "from": "escodegen@>=1.6.1 <2.0.0", 440 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.6.1.tgz", 441 | "dependencies": { 442 | "estraverse": { 443 | "version": "1.9.3", 444 | "from": "estraverse@>=1.9.1 <2.0.0", 445 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz" 446 | }, 447 | "esutils": { 448 | "version": "1.1.6", 449 | "from": "esutils@>=1.1.6 <2.0.0", 450 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz" 451 | }, 452 | "esprima": { 453 | "version": "1.2.5", 454 | "from": "esprima@>=1.2.2 <2.0.0", 455 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz" 456 | }, 457 | "optionator": { 458 | "version": "0.5.0", 459 | "from": "optionator@>=0.5.0 <0.6.0", 460 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz", 461 | "dependencies": { 462 | "prelude-ls": { 463 | "version": "1.1.1", 464 | "from": "prelude-ls@>=1.1.1 <1.2.0", 465 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.1.tgz" 466 | }, 467 | "deep-is": { 468 | "version": "0.1.3", 469 | "from": "deep-is@>=0.1.2 <0.2.0", 470 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz" 471 | }, 472 | "wordwrap": { 473 | "version": "0.0.2", 474 | "from": "wordwrap@>=0.0.2 <0.1.0", 475 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz" 476 | }, 477 | "type-check": { 478 | "version": "0.3.1", 479 | "from": "type-check@>=0.3.1 <0.4.0", 480 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.1.tgz" 481 | }, 482 | "levn": { 483 | "version": "0.2.5", 484 | "from": "levn@>=0.2.5 <0.3.0", 485 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz" 486 | }, 487 | "fast-levenshtein": { 488 | "version": "1.0.6", 489 | "from": "fast-levenshtein@>=1.0.0 <1.1.0", 490 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.6.tgz" 491 | } 492 | } 493 | }, 494 | "source-map": { 495 | "version": "0.1.43", 496 | "from": "source-map@>=0.1.40 <0.2.0", 497 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", 498 | "dependencies": { 499 | "amdefine": { 500 | "version": "0.1.0", 501 | "from": "amdefine@>=0.0.4", 502 | "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-0.1.0.tgz" 503 | } 504 | } 505 | } 506 | } 507 | } 508 | } 509 | } 510 | } 511 | } 512 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { "name": "elm-loader" 2 | , "description": "An easy way to load Elm files in Node." 3 | , "version": "0.14.6" 4 | , "author": "Sonny Michaud (https://github.com/sonnym)" 5 | , "license": "MIT" 6 | 7 | , "main" : "src/index.js" 8 | 9 | , "scripts": { 10 | "test": "./node_modules/.bin/nodeunit test/index.js" 11 | } 12 | 13 | , "dependencies": { 14 | "inflect-js": "*", 15 | "jsdom": "*" 16 | } 17 | 18 | , "devDependencies": { 19 | "nodeunit": "*" 20 | } 21 | 22 | , "repository": { 23 | "type": "git" 24 | , "url": "https://github.com/sonnym/node-elm.git" 25 | } 26 | 27 | , "engines": { "node": ">=0.12" } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | var path = require("path"); 3 | var vm = require("vm"); 4 | 5 | var execSync = require("child_process").execSync; 6 | var EventEmitter = require("events").EventEmitter; 7 | 8 | var jsdom = require("jsdom").jsdom; 9 | var Inflect = require("inflect-js"); 10 | 11 | /* 12 | * constructor function 13 | */ 14 | function ElmRunner(filename, baseDir, defaults) { 15 | var relativePath = path.relative(baseDir, filename); 16 | var baseName = path.basename(filename, path.extname(filename)); 17 | 18 | this.filename = filename; 19 | this.defaults = defaults || {}; 20 | 21 | this.moduleName = resolveModuleName(relativePath); 22 | this.outputPath = path.join(path.dirname(filename), baseName + ".js"); 23 | 24 | var self = this; 25 | withCheckedPath(this.outputPath, function() { 26 | try { 27 | compile.call(self); 28 | execute.call(self); 29 | wrap.call(self); 30 | } catch (e) { 31 | return e; 32 | } 33 | 34 | return true; 35 | }); 36 | } 37 | 38 | /** 39 | * expose a factory function that wraps new instances 40 | */ 41 | module.exports = function(filename, baseDir, defaults) { 42 | return new ElmRunner(filename, baseDir, defaults); 43 | }; 44 | 45 | function withCheckedPath(outputPath, callback) { 46 | if (fs.existsSync(outputPath)) { 47 | throw "Elm: File with name (" + outputPath + ") would be overwritten"; 48 | } else { 49 | var result = callback(); 50 | 51 | try { 52 | fs.unlinkSync(outputPath); 53 | } catch(e) { }; 54 | 55 | 56 | if (result !== true) { 57 | process.stderr.write("Elm: An error ocurred during processing\n"); 58 | process.stderr.write(result.toString() + "\n"); 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * run elm module through `elm-make` to generate compiled js 65 | */ 66 | function compile() { 67 | execSync("elm-make --yes " + this.filename + " --output " + this.outputPath); 68 | } 69 | 70 | /** 71 | * execute script generated by elm-make in a vm context 72 | */ 73 | function execute() { 74 | var context = getDefaultContext(); 75 | var compiledOutput = fs.readFileSync(this.outputPath) 76 | 77 | vm.runInContext(compiledOutput, context, this.outputPath); 78 | 79 | var module = extractModule(this.moduleName, context); 80 | 81 | this.compiledModule = context.Elm.fullscreen(module, this.defaults); 82 | } 83 | 84 | /** 85 | * wrap compiled and executed object in EventEmitters 86 | */ 87 | function wrap() { 88 | var ports = this.compiledModule.ports; 89 | 90 | var incomingEmitter = new EventEmitter(); 91 | var outgoingEmitter = new EventEmitter(); 92 | 93 | var emit = incomingEmitter.emit.bind(incomingEmitter); 94 | 95 | Object.keys(ports).forEach(function(key) { 96 | outgoingEmitter.addListener(key, function() { 97 | var args = Array.prototype.slice.call(arguments) 98 | 99 | ports[key].send.apply(ports[key], args); 100 | }); 101 | 102 | if (ports[key].subscribe) { 103 | ports[key].subscribe(function() { 104 | var args = Array.prototype.slice.call(arguments); 105 | args.unshift(key); 106 | 107 | emit.apply(incomingEmitter, args); 108 | }); 109 | } 110 | }); 111 | 112 | incomingEmitter.emit = outgoingEmitter.emit.bind(outgoingEmitter);; 113 | 114 | this.emitter = incomingEmitter; 115 | this.ports = this.compiledModule.ports; 116 | } 117 | 118 | function getDefaultContext() { 119 | var document = jsdom(); 120 | 121 | return vm.createContext({ 122 | document: document, 123 | window: document.parentWindow, 124 | 125 | setInterval: setInterval, 126 | clearInterval: clearInterval, 127 | 128 | setTimeout: setTimeout, 129 | clearTimeout: clearTimeout 130 | }); 131 | } 132 | 133 | function resolveModuleName(relativePath) { 134 | var extIndex = relativePath.length - path.extname(relativePath).length; 135 | var pathWithoutExt = relativePath.substr(0, extIndex); 136 | 137 | if (relativePath.indexOf(path.sep) > -1) { 138 | var pathParts = pathWithoutExt.split(path.sep); 139 | } else { 140 | var pathParts = [pathWithoutExt]; 141 | } 142 | 143 | return pathParts.map(function(part) { 144 | if (part.match(/^[A-Z]/)) { 145 | return part; 146 | } else { 147 | return Inflect.classify(part); 148 | } 149 | }).join("."); 150 | } 151 | 152 | function extractModule(moduleName, context) { 153 | if (moduleName.match(/\./)) { 154 | return module = moduleName.split(".").reduce(function(memo, property) { 155 | return memo[property]; 156 | }, context.Elm); 157 | } else { 158 | return module = context.Elm[moduleName] 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /test/fixtures/EmptyModule.elm: -------------------------------------------------------------------------------- 1 | module EmptyModule where 2 | 3 | import Text 4 | import Graphics.Element (Element) 5 | main : Element 6 | main = Text.asText "main" 7 | -------------------------------------------------------------------------------- /test/fixtures/Nested/Module.elm: -------------------------------------------------------------------------------- 1 | module Nested.Module where 2 | 3 | import Text 4 | import Graphics.Element (Element) 5 | main : Element 6 | main = Text.asText "main" 7 | -------------------------------------------------------------------------------- /test/fixtures/conflicting_file.elm: -------------------------------------------------------------------------------- 1 | module ConflictingFile where 2 | 3 | import Text 4 | import Graphics.Element (Element) 5 | main : Element 6 | main = Text.asText "main" 7 | -------------------------------------------------------------------------------- /test/fixtures/conflicting_file.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonnym/node-elm-loader/e6a6fa4dd8c3352c65f338b6f2760b16687b760a/test/fixtures/conflicting_file.js -------------------------------------------------------------------------------- /test/fixtures/constant_port.elm: -------------------------------------------------------------------------------- 1 | module ConstantPort where 2 | 3 | import Signal 4 | import Signal ((<~), Signal) 5 | 6 | import Time 7 | 8 | import Text 9 | import Graphics.Element (Element) 10 | main : Element 11 | main = Text.asText "main" 12 | 13 | port messageOut : Signal String 14 | port messageOut = Signal.sampleOn (Time.every Time.second) (Signal.constant "test from elm") 15 | -------------------------------------------------------------------------------- /test/fixtures/echo_port.elm: -------------------------------------------------------------------------------- 1 | module EchoPort where 2 | 3 | import Signal 4 | import Signal ((<~), Signal) 5 | 6 | import String 7 | 8 | import Text 9 | import Graphics.Element (Element) 10 | main : Element 11 | main = Text.asText "main" 12 | 13 | port messageIn : Signal String 14 | 15 | port messageOut : Signal String 16 | port messageOut = messageIn 17 | -------------------------------------------------------------------------------- /test/fixtures/empty_module.elm: -------------------------------------------------------------------------------- 1 | module EmptyModule where 2 | 3 | import Text 4 | import Graphics.Element (Element) 5 | main : Element 6 | main = Text.asText "main" 7 | -------------------------------------------------------------------------------- /test/fixtures/failing.elm: -------------------------------------------------------------------------------- 1 | module Failing where 2 | 3 | import Text 4 | import Graphics.Element (Element) 5 | 6 | main : Element 7 | main = Text.asText "main" 8 | 9 | port uninitialized : Signal String 10 | -------------------------------------------------------------------------------- /test/fixtures/random_port.elm: -------------------------------------------------------------------------------- 1 | import Random (range) 2 | 3 | module RandomPort where 4 | 5 | port messageOut : Signal Int 6 | port messageOut = Random.range 1 10 (every (500 millisecond)) 7 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | var path = require("path"); 3 | var EventEmitter = require("events").EventEmitter; 4 | 5 | var Elm = require("./../"); 6 | 7 | var fixturePath = path.resolve(__dirname, "fixtures") 8 | 9 | process.on("uncaughtException", function(err) { 10 | console.error("\nERROR RUNNING TESTS:\n " + require("util").inspect(err)); 11 | process.exit(1); 12 | }); 13 | 14 | exports.basicFunctionality = { 15 | setUp: function(callback) { 16 | this.module = Elm(path.resolve(__dirname, "fixtures/empty_module.elm"), fixturePath); 17 | callback(); 18 | }, 19 | 20 | testConstructorFunction: function(test) { 21 | test.equal(this.module.constructor.name, "ElmRunner"); 22 | test.done(); 23 | }, 24 | 25 | testModuleNameResolution: function(test) { 26 | test.equal(this.module.moduleName, "EmptyModule"); 27 | test.done(); 28 | }, 29 | 30 | testExposureOfPortsObjectOnWrapper: function(test) { 31 | test.equal(this.module.compiledModule.ports, this.module.ports); 32 | test.done(); 33 | }, 34 | 35 | testRemovesBuildArtifactsOnFailure: function(test) { 36 | var module = Elm(path.resolve(__dirname, "fixtures/failing.elm"), fixturePath); 37 | test.ok(!fs.existsSync(path.resolve(fixturePath, "failing.js"))); 38 | test.done(); 39 | } 40 | }; 41 | 42 | exports.testConventionalElmModelNames = function(test) { 43 | var module = Elm(path.resolve(__dirname, "fixtures/EmptyModule.elm"), fixturePath); 44 | test.equal(module.moduleName, "EmptyModule"); 45 | test.done(); 46 | }; 47 | 48 | exports.testNestedModules = function(test) { 49 | Elm(path.resolve(__dirname, "fixtures/Nested/Module.elm"), fixturePath); 50 | test.done(); 51 | }; 52 | 53 | exports.testBuildsNewObject = function(test) { 54 | test.notEqual( 55 | Elm(path.resolve(__dirname, "fixtures/empty_module.elm"), fixturePath), 56 | Elm(path.resolve(__dirname, "fixtures/empty_module.elm"), fixturePath) 57 | ); 58 | 59 | test.done(); 60 | }; 61 | 62 | exports.fsManagement = { 63 | testDoesNotOverwriteExistingFile: function(test) { 64 | test.throws(function() { 65 | Elm(path.resolve(__dirname, "fixtures/conflicting_file.elm"), fixturePath); 66 | }); 67 | 68 | test.done(); 69 | }, 70 | 71 | testCleansUpCompiledFiles: function(test) { 72 | var module = Elm(path.resolve(__dirname, "fixtures/empty_module.elm"), fixturePath); 73 | 74 | fs.exists(module.outputPath, function(exists) { 75 | test.ok(!exists); 76 | test.done(); 77 | }); 78 | } 79 | }; 80 | 81 | exports.testConstantPort = function(test) { 82 | var constantPort = Elm(path.resolve(__dirname, "fixtures/constant_port.elm"), fixturePath); 83 | 84 | constantPort.emitter.on("messageOut", function(message) { 85 | test.equal(message, "test from elm"); 86 | test.done(); 87 | }); 88 | }; 89 | 90 | exports.testEchoPort = function(test) { 91 | var echoPort = Elm(path.resolve(__dirname, "fixtures/echo_port.elm"), fixturePath, { 92 | messageIn: "" 93 | }); 94 | var message = "test from node"; 95 | 96 | echoPort.emitter.on("messageOut", function(echo) { 97 | test.equal(echo, message); 98 | test.done(); 99 | }); 100 | 101 | echoPort.emitter.emit("messageIn", message); 102 | }; 103 | --------------------------------------------------------------------------------