├── .babelrc ├── .gitignore ├── .npmignore ├── .nvmrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin ├── cli.js └── cmds │ ├── build.js │ ├── config.js │ ├── dev.js │ └── init.js ├── guides ├── advanced.md ├── building-for-production.md ├── cli-usage.md ├── configuration.md ├── getting-started.md ├── templating.md └── the-dev-server.md ├── package.json ├── src ├── cmds │ ├── build.js │ ├── dev.js │ ├── index.js │ └── init.js ├── defaults.js ├── index.js ├── tasks │ ├── build.js │ ├── dev.js │ ├── index.js │ ├── init.js │ └── utils.js └── tmpl │ └── boilerplate │ ├── elm-package.json.ejs │ ├── index.ejs │ ├── package.json.ejs │ └── src │ ├── Assets.elm │ ├── Main.elm │ ├── MainCss.elm │ ├── Stylesheets.elm │ └── assets │ └── css3.png ├── test ├── cmds │ ├── build-test.js │ ├── dev-test.js │ └── init-test.js ├── defaults-test.js ├── mocha.opts └── tasks │ ├── build-test.js │ ├── dev-test.js │ ├── init-test.js │ └── utils-test.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | coverage/ 3 | dist/ 4 | elm-stuff/ 5 | lib/ 6 | node_modules/ 7 | tmp/ 8 | *.DS_Store 9 | *.log 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | guides/ 3 | elm-stuff/ 4 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 8.2.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | os: 4 | - linux 5 | 6 | env: 7 | matrix: 8 | - ELM_VERSION=0.18.0 COVERALLS_REPO_TOKEN=qPx1suoyo7HD8uotEhbOrfuk1EJYcOo0N 9 | 10 | language: node_js 11 | 12 | cache: 13 | yarn: true 14 | directories: 15 | - $HOME/Library/Caches/Homebrew 16 | - node_modules 17 | - test/tasks/tmp/elm-stuff/build-artifacts 18 | - sysconfcpus 19 | 20 | before_install: 21 | - if [ ! -d sysconfcpus/bin ]; 22 | then 23 | git clone https://github.com/obmarg/libsysconfcpus.git; 24 | cd libsysconfcpus; 25 | ./configure --prefix=$TRAVIS_BUILD_DIR/sysconfcpus; 26 | make && make install; 27 | cd ..; 28 | fi 29 | 30 | install: 31 | - nvm install 32 | - yarn global add elm@$ELM_VERSION 33 | - mv $(npm config get prefix)/bin/elm-make $(npm config get prefix)/bin/elm-make-old 34 | - printf "#\041/bin/bash\n\necho \"Running elm-make with sysconfcpus -n 2\"\n\n$TRAVIS_BUILD_DIR/sysconfcpus/bin/sysconfcpus -n 2 elm-make-old \"\$@\"" > $(npm config get prefix)/bin/elm-make 35 | - chmod +x $(npm config get prefix)/bin/elm-make 36 | - yarn 37 | 38 | after_success: 39 | yarn run coverage 40 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #### 0.7.1 2 | - remove .gitignore from scaffold since npm does weird renaming stuff 3 | - use gulp-if instead of gulp-filter which wasnt using correct file paths 4 | - use _.merge for CLI options to avoid undefined overwriting actual values 5 | 6 | #### 0.7.0 7 | #### Elm Factory has an official domain now! http://elm-factory.io/ 8 | ###### changes 9 | - removed `--main` option from `dev` command. Files are now watched when you browse to them 10 | - added `--proxy` option to `dev` command 11 | - added `--proxy-rewrite` option to `dev` command 12 | - added `--minify` option to `build` command 13 | - added `--asset-tag` option to `build` command 14 | - `build` now generates an index.html file 15 | - better output overall 16 | - added ora for pretty spinners!! 17 | - spinners! 18 | - spinners!!! 19 | 20 | ###### bug fixes 21 | - `init` and `build` commands now work with absolute paths 22 | - fixed messaging when tasks break 23 | 24 | #### 0.6.0 25 | - better elm-reactor process management 26 | - express > browser-sync 27 | - gulp-elm > gulp-elm-basic 28 | - browser-sync/chokidar for watching instead of gulp/gaze 29 | - more spec coverage 30 | 31 | #### 0.4.0 32 | 33 | - add uglify to the build-main pipeline 34 | 35 | #### 0.3.2 36 | 37 | - the dev template file generated with init now uses an absolute css path 38 | 39 | #### 0.3.1 40 | 41 | - temporarily remove the html build task 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Faris Mustafa 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Elm Factory [![npm version](https://badge.fury.io/js/elm-factory.svg)](https://badge.fury.io/js/elm-factory) [![Travis CI](https://api.travis-ci.org/farism/elm-factory.svg?branch=master)](https://travis-ci.org/farism/elm-factory) [![Coverage Status](https://coveralls.io/repos/github/farism/elm-factory/badge.svg?branch=specs)](https://coveralls.io/github/farism/elm-factory?branch=specs) [![Greenkeeper badge](https://badges.greenkeeper.io/farism/elm-factory.svg)](https://greenkeeper.io/) 2 | 3 | An all-in-one, zero-configuration CLI tool for developing, building, and deploying Elm applications. Don't worry about tooling and just code! 4 | 5 | ### About 6 | 7 | Elm is an awesome language. It has a lot of great individual tooling. However, using them all together in some type of workflow can be a bit tedious. On top of that, some of the tools (e.g. `elm-reactor`) lack some basic features that would make them into more full-fledged developer tools. 8 | 9 | This is where `Elm Factory` comes in. One of the goals of this project was to not deviate too much from the existing elm tooling ecoystem. It is essentially a CLI tool that is a thin wrapper over a few core libraries while exposing additional functionality, and with only a few basic commands: 10 | 11 | - `init` to scaffold new Elm projects 12 | - `dev` to start up an express server to proxy elm-reactor 13 | - `build` to create cache-busted and minified production builds 14 | 15 | ### Features 16 | - Custom html templates with elm-reactor 17 | - Stylesheet management via elm-css 18 | - CSS injection with livereload 19 | - Define custom proxy endpoints 20 | - Production mode builds with cache-busting 21 | 22 | ### Getting Started 23 | 24 | ```sh 25 | # Installation 26 | 27 | yarn global add elm-factory 28 | npm install -g elm-factory 29 | 30 | # Create a new project 31 | 32 | elm-factory init my-app && cd my-app 33 | 34 | # Start the elm-factory dev server 35 | 36 | elm-factory dev --port=3000 --template=index.dev.hbs 37 | 38 | # Build the project for production 39 | 40 | elm-factory build --output-path=dist --template=index.prod.hbs 41 | ``` 42 | 43 | Please see the [cli usage](https://github.com/farism/elm-factory/blob/master/guides/cli-usage.md) for a full list of options 44 | 45 | 46 | ### Configuration 47 | 48 | Project configuration is available through an `.elmfactoryrc` or `.elmfactoryrc.json` file: 49 | 50 | ```json 51 | { 52 | "main": "src/MyApp.elm", 53 | "build": { 54 | "html": "index.prod.hbs" 55 | }, 56 | "dev": { 57 | "port": 3000, 58 | "html": "index.dev.hbs" 59 | } 60 | } 61 | ``` 62 | 63 | Please see the [configuration page](https://github.com/farism/elm-factory/blob/master/guides/configuration.md) for a full list of options 64 | 65 | ### Underlying Packages 66 | 67 | - `elm-reactor` for on-the-fly compilation 68 | - `browser-sync` for proxying and livereload 69 | - `find-elm-dependencies` for dev mode watching 70 | - `node-elm-compiler` for compiling production builds 71 | - `elm-css` for managing stylesheets and extracting css assets 72 | - `gulp` for orchestrating CLI tasks 73 | - `postcss` for css processing and minification 74 | - `uglifyjs` for js minification 75 | 76 | ### Contributing 77 | 78 | Ideas and code contributions are welcome! In lieu of a styleguide, this project uses [prettier](https://github.com/prettier/prettier), [husky](https://github.com/typicode/husky), and [lint-staged](https://github.com/okonet/lint-staged) to maintain code style. If you have any questions, just ask. 79 | 80 | ### Related projects 81 | - [elm-live](https://github.com/tomekwi/elm-live) 82 | - [elm-webpack-loader](https://github.com/elm-community/elm-webpack-loader) 83 | - [elm-webpack-starter](https://github.com/jiwhiz/elm-bootstrap-webpack-starter) 84 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('yargs') 4 | .version(function() { 5 | return require('../package').version 6 | }) 7 | .help('help') 8 | .showHelpOnFail(true, 'use --help for available options') 9 | .commandDir('./cmds') 10 | .demandCommand().argv 11 | -------------------------------------------------------------------------------- /bin/cmds/build.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | 3 | const config = require('./config').build 4 | const build = require('../../src/cmds').build 5 | 6 | module.exports = { 7 | command: build.command, 8 | description: build.description, 9 | builder: function(yargs) { 10 | yargs 11 | .options(build.options) 12 | .config(config) 13 | .help('help') 14 | }, 15 | handler: function(argv) { 16 | require('../../src/tasks') 17 | .build(argv) 18 | .catch(e => {}) 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /bin/cmds/config.js: -------------------------------------------------------------------------------- 1 | const findUp = require('find-up') 2 | const fs = require('fs') 3 | 4 | const configPath = findUp.sync(['.elmfactoryrc', '.elmfactoryrc.json']) 5 | 6 | const fileConfig = configPath ? JSON.parse(fs.readFileSync(configPath)) : {} 7 | 8 | const config = { 9 | main: fileConfig.main, 10 | stylesheets: fileConfig.stylesheets, 11 | template: fileConfig.template, 12 | } 13 | 14 | module.exports = { 15 | build: Object.assign({}, config, fileConfig.build), 16 | dev: Object.assign({}, config, fileConfig.dev), 17 | } 18 | -------------------------------------------------------------------------------- /bin/cmds/dev.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | 3 | const config = require('./config').dev 4 | const dev = require('../../src/cmds').dev 5 | 6 | module.exports = { 7 | command: dev.command, 8 | description: dev.description, 9 | builder: function(yargs) { 10 | yargs.options(dev.options).config(config) 11 | }, 12 | handler: function(argv) { 13 | // convert single proxy option into an array 14 | if (typeof argv.proxy === 'string') { 15 | argv.proxy = [argv.proxy] 16 | } 17 | require('../../src/tasks') 18 | .dev(argv) 19 | .catch(e => {}) 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /bin/cmds/init.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | 3 | const init = require('../../src/cmds/init') 4 | 5 | module.exports = { 6 | command: init.command, 7 | desc: init.description, 8 | builder: function(yargs) { 9 | yargs 10 | .options(init.options) 11 | .demandOption('dir', 'You must enter a project name') 12 | }, 13 | handler: function(argv) { 14 | require('../../src/tasks') 15 | .init(argv) 16 | .catch(e => {}) 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /guides/advanced.md: -------------------------------------------------------------------------------- 1 | # Advanced 2 | 3 | Elm Factory is essentially a CLI tool that is a thin wrapper over a few core libraries while exposing additional functionality. 4 | 5 | The build tasks are orchestrated using [gulp](https://gulpjs.com/) and a handful of plugins. These plugins include: 6 | 7 | - [`gulp-elm-basic`](https://github.com/farism/gulp-elm-basic) - a wrapper around node-elm-compiler 8 | - [`gulp-elm-css`](https://github.com/farism/gulp-elm-css) - a wrapper around elm-css 9 | - [`gulp-elm-extract-assets`](https://github.com/farism/gulp-elm-extract-assets) - a gulp plugin for extracting assets from a compiled Elm application 10 | - [`gulp-postcss`](https://github.com/postcss/gulp-postcss) - a wrapper around postcss 11 | 12 | If one was feeling adventurous they could easily use these plugins to create their own build - or dev - pipelines. 13 | 14 | Here is a quick example of the `build-css` gulp task in `elm-factory`: 15 | 16 | ```js 17 | gulp.task('build-css', () => 18 | gulp.src(stylesheets) 19 | .pipe(elmCss()) 20 | .pipe(postcss([ 21 | url({ 22 | url: 'copy', 23 | basePath: path.resolve('./'), 24 | assetsPath: path.resolve(outputPath), 25 | useHash: true, 26 | hashOptions: { 27 | method: getHash, 28 | }, 29 | }), 30 | url({ 31 | url: asset => getPublicPath(asset.url), 32 | }), 33 | cssnano(), 34 | ])) 35 | .pipe(rev.revision({ 36 | fileNameManifest: 'css-manifest.json', 37 | transformFilename, 38 | })) 39 | .pipe(gulp.dest(outputPath)) 40 | .pipe(rev.manifestFile()) 41 | .pipe(gulp.dest(outputPath)) 42 | ) 43 | ``` 44 | -------------------------------------------------------------------------------- /guides/building-for-production.md: -------------------------------------------------------------------------------- 1 | # Building for production 2 | 3 | ``` 4 | $ elm-factory build [options] 5 | 6 | Options: 7 | --version Show version number [boolean] 8 | --help Show help [boolean] 9 | -m, --main main entry file [default: "./src/Main.elm"] 10 | -s, --stylesheets stylesheets entry file [default: "./src/Stylesheets.elm"] 11 | -t, --html optional html template file [default: "./index.ejs"] 12 | -o, --output-path output directory [default: "build"] 13 | -p, --public-path absolute path for static assets [default: ""] 14 | -a, --asset-tag the tag to use for extracting assets [default: "AssetUrl"] 15 | --minify minify the *.css and *.js files [default: true] 16 | ``` 17 | 18 | A common challenging question many web deployed projects eventually face is "how do I deploy all this stuff?". There are many solutions, and there is no silver bullet. 19 | 20 | Webpack is the most popular solution but some find it fatiguing to go through the loopholes of setting up and maintaining all of the proper configurations for the different environments 21 | 22 | Elm Factory `build` strives to provide a predictable and sensible build system. It's not perfect yet as it has not tackled many use cases, but it works fairly well out of the box. 23 | 24 | Inspiration was taken from [elm-asset-path](https://github.com/NoRedInk/elm-asset-path) and [elm-assets-loader](https://github.com/NoRedInk/elm-assets-loader) for the approach in tagging and extracting assets from `*.elm` files. It would be great to have something similar to elm-asset-path as a standalone project but without the native dependency. 25 | 26 | ## Assets 27 | 28 | _In all cases, external `http://*` and inlined `data:*` urls are ignored_ 29 | 30 | #### Main 31 | 32 | Using the `--asset-tag` option (defaults to `AssetUrl`), assets are parsed/extracted from matched `` paths. Assets will be cache busted by their contents, and then copied to the `--output-path` directory. 33 | 34 | #### Stylesheets 35 | 36 | Assets are parsed/extracted from matched `url(...)` styles in the generated files `*.css` files. Assets will be cache busted by their contents, and then copied to the `--output-path` directory. 37 | 38 | ## Serving from a remote url with `--public-path` 39 | 40 | If you plan to serve your assets from some remote url like S3 or a CDN, you have the option to pass a `--public-path`. In both the generated `*.js` and `*.css` files, all assets will be prepended with this path. 41 | 42 | ## Disabling minification with `--no-minify` 43 | 44 | Minification is turned on by default. UglifyJS and cssnano are used to minify the resulting bundles. However, minification can be turned off with the `--no-minify` flag. 45 | 46 | ## Templating 47 | 48 | Please see the [`templating`]() guide for more information 49 | -------------------------------------------------------------------------------- /guides/cli-usage.md: -------------------------------------------------------------------------------- 1 | # CLI 2 | 3 | ### Usage 4 | 5 | ``` 6 | $ elm-factory 7 | 8 | Commands: 9 | build [options] builds an elm-factory project for production 10 | dev [options] starts elm-factory in dev mode 11 | init [dir] initializes a boilerplate elm-factory application 12 | 13 | Options: 14 | --version Show version number [boolean] 15 | --help Show help [boolean] 16 | ``` 17 | 18 | ### init 19 | 20 | ``` 21 | $ elm-factory init --help 22 | 23 | elm-factory init [dir] 24 | 25 | Options: 26 | --version Show version number [boolean] 27 | --help Show help [boolean] 28 | --dir the project directory [required] 29 | -f, --force ignore existing files [default: false] 30 | ``` 31 | 32 | ### build 33 | 34 | ``` 35 | $ elm-factory build --help 36 | 37 | elm-factory build [options] 38 | 39 | Options: 40 | --version Show version number [boolean] 41 | --help Show help [boolean] 42 | -m, --main main entry file [default: "./src/Main.elm"] 43 | -s, --stylesheets stylesheets entry file [default: "./src/Stylesheets.elm"] 44 | -t, --html optional html template file [default: "./index.ejs"] 45 | -o, --output-path output directory [default: "build"] 46 | -p, --public-path absolute path for static assets [default: ""] 47 | -a, --asset-tag the tag to use for extracting assets [default: "AssetUrl"] 48 | --minify minify the *.css and *.js files [default: true] 49 | ``` 50 | 51 | ### dev 52 | 53 | ``` 54 | $ elm-factory dev --help 55 | 56 | 57 | elm-factory dev [options] 58 | 59 | Options: 60 | --version Show version number [boolean] 61 | --help Show help [boolean] 62 | -s, --stylesheets stylesheets entry file [default: "./src/Stylesheets.elm"] 63 | -t, --html html template file [default: "./index.ejs"] 64 | -h, --host dev server address [default: "127.0.0.1"] 65 | -p, --port dev server port [default: 8000] 66 | -r, --reactor-host elm-reactor address [default: "127.0.0.1"] 67 | -u, --reactor-port elm-reactor port [default: 8001] 68 | -x, --proxy additional proxies [default: []] 69 | --proxy-rewrite rewrite proxy paths [default: true] 70 | ``` 71 | -------------------------------------------------------------------------------- /guides/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | Each CLI option is available is a configuration option. Shared options may be declared at the root level of the configuration object. 4 | 5 | ## All options 6 | ```json 7 | { 8 | "main": "", 9 | "stylesheets": "", 10 | "template": "", 11 | "build": { 12 | "main": "", 13 | "stylesheets": "", 14 | "html": "", 15 | "outputPath": "", 16 | "publicPath": "" 17 | }, 18 | "dev": { 19 | "main": "", 20 | "stylesheets": "", 21 | "html": "", 22 | "host": "", 23 | "port": "", 24 | "reactorHost": "", 25 | "reactorPort": "", 26 | } 27 | } 28 | ``` 29 | 30 | ## Example 31 | ```json 32 | { 33 | "stylesheets": "./src/Styles.elm", 34 | "build": { 35 | "main": "./src/MyApp.elm", 36 | "html": "./src/index.prod.ejs", 37 | "outputPath": "./dist", 38 | "publicPath": "http://somecdn.com", 39 | }, 40 | "dev": { 41 | "html": "./src/index.dev.ejs", 42 | "port": "3000", 43 | } 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /guides/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting started, in depth 2 | 3 | Thanks for trying Elm Factory! The primary goal of this project is to be very simple, by providing a CLI tool with a few simple commands that have sensible defaults set. It is unknown yet whether this will scale to very large projects, but considering how solid and predictable the current state of Elm's tooling is, there is a lot of promise. 4 | 5 | ## Installing Elm 6 | 7 | It is suggested to use a global Elm installation. `elm@0.18.0` is the currently supported version. However, you can install `elm` as a local package as well if you wish. 8 | 9 | ```sh 10 | $ yarn global add elm 11 | // or 12 | $ npm install -g elm 13 | ``` 14 | 15 | ## Installing Elm Factory 16 | 17 | It is also suggested, but not mandatory, to use a global Elm Factory installation. 18 | 19 | ```sh 20 | $ yarn global add elm-factory 21 | // or 22 | $ npm install -g elm-factory 23 | ``` 24 | 25 | ## Creating your first project 26 | 27 | You can use the `init` command to scaffold your first project. It has one required argument which is the directory in which to create the project. This directory name will also become the name of the project in the `elm-package.json` and `package.json` files. 28 | 29 | ```sh 30 | $ elm-factory init my-app 31 | $ cd my-app 32 | ``` 33 | 34 | You cannot initialize a project into an existing directory that has contents unless you pass the `--force` flag. 35 | 36 | In addition to the two core Elm packages (core and html), the only other elm dependencies installed are: 37 | - [`elm-css`](http://package.elm-lang.org/packages/rtfeldman/elm-css/latest) 38 | - [`elm-css-helpers`](http://package.elm-lang.org/packages/rtfeldman/elm-css-helpers/latest) 39 | 40 | Now that your project is created and you are in the project folder... 41 | 42 | ## Let's start the dev server 43 | 44 | ```sh 45 | $ elm-factory dev 46 | ``` 47 | 48 | This will start up the Elm Factory dev server on the default port of `8000`. The port can be configured with the `--port` option. 49 | 50 | ## Poke around 51 | 52 | Navigating to your webserver at `http://localhost:8000`, you will see your project directory served to you. This is just `elm-reactor` running under the hood, nothing fancy.. well, it is, because elm-reactor is really cool! 53 | 54 | Once you browse to `/src/Main.elm`, you will see your application be compiled and then you should be seeing the basic scaffold project. 55 | 56 | At this point, you are seeing your custom `index.ejs` template being rendered out. The application compilation of the script tag is then being handed off to elm-reactor. 57 | 58 | ## Edit some files 59 | 60 | If you go and edit `src/MainCss.elm`, for example changing the color of some text, you will see the changes inject into the page using livereload. Any file that belongs to the `src/Stylesheets.elm` import tree will trigger css injections on change. 61 | 62 | If you edit `src/Main.elm`, you will see the page do a hard reload and 63 | 64 | ## Build your application 65 | 66 | ```sh 67 | $ elm-factory build --public-path /build 68 | ``` 69 | 70 | If you then navigate to `http://localhost:8000/build` you will see the generated html file with your built and cache busted assets displaying! 71 | 72 | # Have fun! 73 | 74 | ### Please post any issues/bugs or questions on the [github repository](https://github.com/farism/elm-factory/issues), or in the #elm slack channel. 75 | -------------------------------------------------------------------------------- /guides/templating.md: -------------------------------------------------------------------------------- 1 | # Templating 2 | 3 | ## Using templates 4 | 5 | When using `elm-factory init` a file named `./index.ejs` will scaffolded. This file is the default used for the `--html` flag for the `dev` and `build` commands. 6 | 7 | When navigating to an `*.elm` file, this html template will be used rather than the default `elm-reactor` page you would see. This allows you to render the html with additional elements like `` or ` 42 | 45 | <% } %> 46 | 47 | <% if (environment === 'development') { %> 48 | 49 | 55 | <% } %> 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/tmpl/boilerplate/package.json.ejs: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= name %>", 3 | "version": "0.1.0", 4 | "description": "description for <%= name %>", 5 | "repository": "https://github.com/user/project.git", 6 | "scripts": { 7 | "start": "elm-factory dev", 8 | "build": "elm-factory build", 9 | "test": "" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/tmpl/boilerplate/src/Assets.elm: -------------------------------------------------------------------------------- 1 | module Assets exposing (..) 2 | 3 | 4 | type Asset 5 | = AssetUrl String 6 | 7 | 8 | url : Asset -> String 9 | url asset = 10 | case asset of 11 | AssetUrl url -> 12 | url 13 | 14 | 15 | hero = 16 | AssetUrl "/src/assets/css3.png" 17 | -------------------------------------------------------------------------------- /src/tmpl/boilerplate/src/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Html exposing (Html, div, p) 4 | import Html.CssHelpers exposing (withNamespace) 5 | import Html.Attributes exposing (id, src) 6 | import MainCss 7 | import Assets exposing (url, hero) 8 | 9 | 10 | { id, class, classList } = 11 | withNamespace "main" 12 | 13 | 14 | main : Html a 15 | main = 16 | div [ id MainCss.Page ] 17 | [ div [] [ Html.text "Hello, World!!" ] 18 | , div [] [ Html.img [ src (url hero) ] [] ] 19 | , div [ id MainCss.Page ] 20 | [ p [] [ Html.text "This is a paragraph" ] 21 | ] 22 | ] 23 | -------------------------------------------------------------------------------- /src/tmpl/boilerplate/src/MainCss.elm: -------------------------------------------------------------------------------- 1 | module MainCss exposing (..) 2 | 3 | import Css exposing (..) 4 | import Css.Namespace exposing (namespace) 5 | import Assets exposing (hero) 6 | 7 | 8 | type CssIds 9 | = Page 10 | 11 | 12 | type CssClasses 13 | = Hero 14 | 15 | 16 | css = 17 | (stylesheet << namespace "main") 18 | [ id Page 19 | [ backgroundColor (hex "FFFFFF") 20 | , color (hex "000000") 21 | , fontSize (px 50) 22 | ] 23 | , class Hero 24 | [ backgroundImage (url (Assets.url hero)) ] 25 | ] 26 | -------------------------------------------------------------------------------- /src/tmpl/boilerplate/src/Stylesheets.elm: -------------------------------------------------------------------------------- 1 | port module Stylesheets exposing (..) 2 | 3 | import Css.File exposing (CssFileStructure, CssCompilerProgram) 4 | import MainCss 5 | 6 | 7 | port files : CssFileStructure -> Cmd msg 8 | 9 | 10 | fileStructure : CssFileStructure 11 | fileStructure = 12 | Css.File.toFileStructure 13 | [ ( "index.css", Css.File.compile [ MainCss.css ] ) ] 14 | 15 | 16 | main : CssCompilerProgram 17 | main = 18 | Css.File.compiler files fileStructure 19 | -------------------------------------------------------------------------------- /src/tmpl/boilerplate/src/assets/css3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/farism/elm-factory/e90cef9c584f794f5931cafc1e370be6de18b668/src/tmpl/boilerplate/src/assets/css3.png -------------------------------------------------------------------------------- /test/cmds/build-test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | 3 | import { build as defaults } from '../../src/defaults' 4 | import { build } from '../../src/cmds' 5 | 6 | describe('$ elm-factory build', () => { 7 | it('should expose the correct CLI options', () => { 8 | expect(build.options).to.eql({ 9 | m: { 10 | alias: 'main', 11 | description: 'main entry file', 12 | default: defaults.main, 13 | }, 14 | s: { 15 | alias: 'stylesheets', 16 | description: 'stylesheets entry file', 17 | default: defaults.stylesheets, 18 | }, 19 | t: { 20 | alias: 'html', 21 | description: 'optional html template file', 22 | default: defaults.html, 23 | }, 24 | o: { 25 | alias: 'output-path', 26 | description: 'output directory', 27 | default: defaults.outputPath, 28 | }, 29 | p: { 30 | alias: 'public-path', 31 | description: 'absolute path for static assets', 32 | default: defaults.publicPath, 33 | }, 34 | a: { 35 | alias: 'asset-tag', 36 | description: 'the tag to use for extracting assets', 37 | default: defaults.assetTag, 38 | }, 39 | minify: { 40 | description: 'minify the *.css and *.js files', 41 | default: defaults.minify, 42 | }, 43 | }) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /test/cmds/dev-test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | 3 | import { dev as defaults } from '../../src/defaults' 4 | import { dev } from '../../src/cmds' 5 | 6 | describe('$ elm-factory dev', () => { 7 | it('should expose the correct CLI options', () => { 8 | expect(dev.options).to.eql({ 9 | s: { 10 | alias: 'stylesheets', 11 | description: 'stylesheets entry file', 12 | default: defaults.stylesheets, 13 | }, 14 | t: { 15 | alias: 'html', 16 | description: 'html template file', 17 | default: defaults.html, 18 | }, 19 | h: { 20 | alias: 'host', 21 | description: 'dev server address', 22 | default: defaults.host, 23 | }, 24 | p: { 25 | alias: 'port', 26 | description: 'dev server port', 27 | default: defaults.port, 28 | }, 29 | r: { 30 | alias: 'reactor-host', 31 | description: 'elm-reactor address', 32 | default: defaults.reactorHost, 33 | }, 34 | u: { 35 | alias: 'reactor-port', 36 | description: 'elm-reactor port', 37 | default: defaults.reactorPort, 38 | }, 39 | x: { 40 | alias: 'proxy', 41 | description: 'additional proxies', 42 | default: defaults.proxy, 43 | }, 44 | 'proxy-rewrite': { 45 | description: 'rewrite proxy paths', 46 | default: defaults.proxyRewrite, 47 | }, 48 | }) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /test/cmds/init-test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | 3 | import { init as defaults } from '../../src/defaults' 4 | import { init } from '../../src/cmds' 5 | 6 | describe('$ elm-factory init', () => { 7 | it('should expose the correct CLI options', () => { 8 | expect(init.options).to.eql({ 9 | dir: { 10 | description: 'the project directory', 11 | }, 12 | f: { 13 | alias: 'force', 14 | description: 'ignore existing files', 15 | default: defaults.force, 16 | }, 17 | }) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /test/defaults-test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | 3 | import { build, dev, init } from '../src/defaults' 4 | 5 | describe('defaults', () => { 6 | it('should have the correct build defaults', () => { 7 | expect(build).to.eql({ 8 | main: './src/Main.elm', 9 | stylesheets: './src/Stylesheets.elm', 10 | html: './index.ejs', 11 | outputPath: 'build', 12 | publicPath: '', 13 | assetTag: 'AssetUrl', 14 | minify: true, 15 | }) 16 | }) 17 | 18 | it('should have the correct dev defaults', () => { 19 | expect(dev).to.eql({ 20 | stylesheets: './src/Stylesheets.elm', 21 | html: './index.ejs', 22 | host: '127.0.0.1', 23 | port: 8000, 24 | reactorHost: '127.0.0.1', 25 | reactorPort: 8001, 26 | proxy: [], 27 | proxyRewrite: true, 28 | }) 29 | }) 30 | 31 | it('should have the correct init defaults', () => { 32 | expect(init).to.eql({ 33 | force: false, 34 | }) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers js:babel-core/register 2 | --require babel-polyfill 3 | --recursive 4 | --timeout 5000 5 | test/{,**/}*test.js 6 | -------------------------------------------------------------------------------- /test/tasks/build-test.js: -------------------------------------------------------------------------------- 1 | import chai, { assert, expect } from 'chai' 2 | import chaifs from 'chai-fs' 3 | import path from 'path' 4 | import tmp from 'tmp' 5 | 6 | import { 7 | buildCss, 8 | buildMain, 9 | getHash, 10 | getPublicPath, 11 | getTransformedFilename, 12 | build, 13 | } from '../../src/tasks/build' 14 | import { init } from '../../src/tasks/init' 15 | import { build as defaults } from '../../src/defaults' 16 | import { checkParam } from './utils-test' 17 | 18 | chai.use(chaifs) 19 | 20 | describe('BUILD TASK', () => { 21 | const dir = path.join(__dirname, 'tmp') 22 | let outputPath = '' 23 | let cleanup = () => {} 24 | 25 | before(done => { 26 | init({ dir, force: true }).then(() => { 27 | process.chdir(dir) 28 | done() 29 | }) 30 | }) 31 | 32 | beforeEach(() => { 33 | const { name, removeCallback } = tmp.dirSync({ 34 | dir, 35 | keep: true, 36 | unsafeCleanup: true, 37 | }) 38 | 39 | outputPath = name 40 | cleanup = removeCallback 41 | }) 42 | 43 | afterEach(() => { 44 | // cleanup() 45 | }) 46 | 47 | describe('buildCss', function() { 48 | this.timeout(60000) 49 | 50 | describe('params', () => { 51 | checkParam('string', 'stylesheets', buildCss)([1]) 52 | checkParam('string', 'outputPath', buildCss)(['', 1]) 53 | checkParam('string', 'publicPath', buildCss)(['', '', 1]) 54 | checkParam('boolean', 'minify', buildCss, false)(['', '', '', '']) 55 | checkParam('string', 'cwd', buildCss, false)(['', '', '', true, 1]) 56 | }) 57 | 58 | it('builds into the correct outputPath', done => { 59 | buildCss(defaults.stylesheets, outputPath, '', false) 60 | .then(() => { 61 | expect(outputPath) 62 | .to.be.a.directory() 63 | .with.deep.files([ 64 | 'css-manifest.json', 65 | 'css3.f083965f.png', 66 | 'index.835d2cf5.css', 67 | ]) 68 | 69 | done() 70 | }) 71 | .catch(done) 72 | }) 73 | it('builds with correct publicPath', done => { 74 | buildCss(defaults.stylesheets, outputPath, 'http://somecdn.com/', false) 75 | .then(() => { 76 | expect(`${outputPath}/index.72a08c21.css`) 77 | .to.be.a.file() 78 | .with.contents.that.match( 79 | /http:\/\/somecdn.com\/css3.f083965f\.png/ 80 | ) 81 | 82 | done() 83 | }) 84 | .catch(done) 85 | }) 86 | it('does minification', done => { 87 | buildCss(defaults.stylesheets, outputPath, defaults.publicPath, true) 88 | .then(() => { 89 | expect(`${outputPath}/index.af77fceb.css`) 90 | .to.be.a.file() 91 | .and.not.have.contents('\n') 92 | 93 | done() 94 | }) 95 | .catch(done) 96 | }) 97 | it('generates a valid css-manifest.json', done => { 98 | buildCss(defaults.stylesheets, outputPath, defaults.publicPath, false) 99 | .then(() => { 100 | expect(`${outputPath}/css-manifest.json`) 101 | .to.be.a.file() 102 | .with.contents('{\n "index.css": "index.835d2cf5.css"\n}\n') 103 | 104 | done() 105 | }) 106 | .catch(done) 107 | }) 108 | }) 109 | 110 | describe('buildMain', function() { 111 | this.timeout(60000) 112 | 113 | describe('params', () => { 114 | checkParam('string', 'main', buildMain)([1]) 115 | checkParam('string', 'outputPath', buildMain)(['', 1]) 116 | checkParam('string', 'publicPath', buildMain)(['', '', 1]) 117 | checkParam('string', 'assetTag', buildMain)(['', '', '', 1]) 118 | checkParam('boolean', 'minify', buildMain, false)(['', '', '', '', '']) 119 | checkParam('string', 'cwd', buildMain, false)(['', '', '', '', true, 1]) 120 | }) 121 | 122 | it('builds into the correct outputPath', done => { 123 | buildMain( 124 | defaults.main, 125 | outputPath, 126 | defaults.publicPath, 127 | defaults.assetTag, 128 | false, 129 | dir 130 | ) 131 | .then(() => { 132 | expect(outputPath) 133 | .to.be.a.directory() 134 | .with.deep.files([ 135 | 'css3.f083965f.png', 136 | 'js-manifest.json', 137 | 'Main.81a15f67.js', 138 | ]) 139 | 140 | done() 141 | }) 142 | .catch(done) 143 | }) 144 | it('does not extract if assetTag is invalid', done => { 145 | buildMain( 146 | defaults.main, 147 | outputPath, 148 | defaults.publicPath, 149 | 'invalid', 150 | false, 151 | dir 152 | ) 153 | .then(() => { 154 | expect(outputPath) 155 | .to.be.a.directory() 156 | .with.deep.files(['Main.d50146ff.js', 'js-manifest.json']) 157 | 158 | done() 159 | }) 160 | .catch(done) 161 | }) 162 | it('builds with correct publicPath', done => { 163 | buildMain( 164 | defaults.main, 165 | outputPath, 166 | 'http://somecdn.com/', 167 | defaults.assetTag, 168 | false 169 | ) 170 | .then(() => { 171 | expect(outputPath) 172 | .to.be.a.directory() 173 | .with.deep.files([ 174 | 'css3.f083965f.png', 175 | 'js-manifest.json', 176 | 'Main.5ff6a009.js', 177 | ]) 178 | 179 | done() 180 | }) 181 | .catch(done) 182 | }) 183 | it('does minification', done => { 184 | buildMain( 185 | defaults.main, 186 | outputPath, 187 | defaults.publicPath, 188 | defaults.assetTag, 189 | true 190 | ) 191 | .then(() => { 192 | expect(`${outputPath}/Main.a65d340f.js`) 193 | .to.be.a.file() 194 | .and.not.have.contents('\n') 195 | 196 | done() 197 | }) 198 | .catch(done) 199 | }) 200 | it('generates a valid js-manifest.json', done => { 201 | buildMain( 202 | defaults.main, 203 | outputPath, 204 | defaults.publicPath, 205 | defaults.assetTag, 206 | false 207 | ) 208 | .then(() => { 209 | expect(`${outputPath}/js-manifest.json`) 210 | .to.be.a.file() 211 | .with.contents('{\n "Main.js": "Main.81a15f67.js"\n}\n') 212 | 213 | done() 214 | }) 215 | .catch(done) 216 | }) 217 | }) 218 | 219 | describe('build', () => { 220 | let promise 221 | 222 | before(() => { 223 | promise = build(defaults) 224 | }) 225 | 226 | it('returns a promise', () => { 227 | expect(promise).to.be.a('promise') 228 | }) 229 | 230 | it('should resolve', function() { 231 | this.timeout(60000) 232 | 233 | return expect(promise).to.eventually.be.fulfilled 234 | }) 235 | }) 236 | }) 237 | -------------------------------------------------------------------------------- /test/tasks/dev-test.js: -------------------------------------------------------------------------------- 1 | import chai, { assert, expect } from 'chai' 2 | import chaifs from 'chai-fs' 3 | import chaiAsPromised from 'chai-as-promised' 4 | import fs from 'fs' 5 | import http from 'http' 6 | import path from 'path' 7 | import portscanner from 'portscanner' 8 | import request from 'request-promise-native' 9 | import sinon from 'sinon' 10 | import tmp from 'tmp' 11 | 12 | import { 13 | parseProxies, 14 | createProxies, 15 | getDepTree, 16 | loadHtmlCompiler, 17 | startReactor, 18 | startBrowserSync, 19 | createWatcher, 20 | dev, 21 | } from '../../src/tasks/dev' 22 | import { invalidParam } from '../../src/tasks/utils' 23 | import { init } from '../../src/tasks/init' 24 | import { dev as defaults } from '../../src/defaults' 25 | import { checkParam } from './utils-test' 26 | 27 | chai.should() 28 | chai.use(chaifs) 29 | chai.use(chaiAsPromised) 30 | tmp.setGracefulCleanup() 31 | 32 | const dir = path.join(__dirname, 'tmp') 33 | 34 | const findFreePort = (host, port) => 35 | portscanner.findAPortNotInUse(port, port + 1000, host).then(port => { 36 | return { 37 | host, 38 | port, 39 | url: `http://${host}:${port}`, 40 | } 41 | }) 42 | 43 | const occupyPort = (host, port) => 44 | new Promise((resolve, reject) => { 45 | const server = http.createServer(() => {}) 46 | server 47 | .listen(port, host, err => { 48 | resolve({ host, port, server }) 49 | }) 50 | .on('error', reject) 51 | }) 52 | 53 | const assertIncludes = (str = '', response = '') => 54 | assert.equal( 55 | response.includes(str), 56 | true, 57 | `response is missing string '${str}'` 58 | ) 59 | 60 | describe('DEV TASK', function() { 61 | before(done => { 62 | init({ dir, force: true }).then(() => { 63 | process.chdir(dir) 64 | done() 65 | }) 66 | }) 67 | 68 | describe('helpers', () => { 69 | describe('parseProxies', () => { 70 | describe('params', () => { 71 | checkParam('array', 'proxies', parseProxies)([' ']) 72 | checkParam('array', 'proxies', parseProxies)([' ', ' ']) 73 | it('throws when not passed array of strings', () => { 74 | expect(() => parseProxies('=', [123])).to.throw() 75 | }) 76 | }) 77 | it('ignores invalid delimiter', () => { 78 | expect(parseProxies('=', ['foo:bar', 'foo bar'])).to.have.length.of(0) 79 | }) 80 | it('returns array of objects with from/target keys', () => { 81 | const proxies = parseProxies('=', ['foo=bar', 'ping=pong']) 82 | 83 | expect(proxies).to.have.length.of(2) 84 | expect(proxies[0]).to.have.a.property('from', 'foo') 85 | expect(proxies[0]).to.have.a.property('target', 'bar') 86 | expect(proxies[1]).to.have.a.property('from', 'ping') 87 | expect(proxies[1]).to.have.a.property('target', 'pong') 88 | }) 89 | }) 90 | describe('createProxies', () => { 91 | describe('params', () => { 92 | checkParam('array', 'proxies', parseProxies)() 93 | checkParam('array', 'proxies', parseProxies)([' ']) 94 | }) 95 | it('returns an array of http-proxy-middlewares', () => { 96 | const proxies = parseProxies('=', ['foo=bar', 'ping=pong']) 97 | const middlewares = createProxies(true, proxies) 98 | 99 | expect(middlewares).to.have.length(2) 100 | expect(middlewares) 101 | .to.have.a.property('1') 102 | .that.is.a('function') 103 | }) 104 | }) 105 | describe('getDepTree', () => { 106 | describe('params', () => { 107 | checkParam('string', 'entry', getDepTree)() 108 | }) 109 | it('fails if entry file does not exist', () => { 110 | return expect(getDepTree('some/fake/path.elm')).to.eventually.rejected 111 | }) 112 | it('returns an elm dependency tree with the entry point prepended', () => { 113 | return expect(getDepTree('src/Main.elm')).to.eventually.deep.equal([ 114 | 'src/Main.elm', 115 | path.join(dir, 'src/MainCss.elm'), 116 | path.join(dir, 'src/Assets.elm'), 117 | ]) 118 | }) 119 | }) 120 | 121 | describe('loadHtmlCompiler', () => { 122 | describe('params', () => { 123 | checkParam('string', 'file', loadHtmlCompiler)([1]) 124 | }) 125 | it('fails if html file does not exist', () => { 126 | return expect(loadHtmlCompiler('some/fake/path.ejs')).to.eventually.be 127 | .rejected 128 | }) 129 | it('fails if html file exists but is not supported', () => { 130 | const tmpFile = tmp.fileSync({ dir, postfix: '.unsupported' }) 131 | 132 | return expect( 133 | loadHtmlCompiler(tmpFile.name) 134 | ).to.eventually.be.rejectedWith( 135 | Error, 136 | 'html template format unsupported' 137 | ) 138 | }) 139 | it('resolves with an existing and supported html file', () => { 140 | return expect(loadHtmlCompiler(defaults.html)).to.eventually.be 141 | .fulfilled 142 | }) 143 | it('resolves a function', () => { 144 | return expect(loadHtmlCompiler(defaults.html)).to.eventually.be.a( 145 | 'function' 146 | ) 147 | }) 148 | }) 149 | }) 150 | 151 | describe('startReactor', () => { 152 | describe('params', () => { 153 | checkParam('string', 'host', startReactor)([1]) 154 | checkParam('number', 'port', startReactor)([' ', ' ']) 155 | }) 156 | 157 | it('fails to start when port is in use', () => { 158 | return expect( 159 | findFreePort(defaults.reactorHost, defaults.reactorPort) 160 | .then(({ host, port }) => occupyPort(host, port)) 161 | .then(({ host, port, server }) => 162 | startReactor(host, port, false).catch(() => { 163 | server.close() 164 | throw new Error() 165 | }) 166 | ) 167 | ).to.eventually.be.rejected 168 | }) 169 | 170 | it('starts an elm-reactor server when port is free', () => { 171 | return expect( 172 | findFreePort( 173 | defaults.reactorHost, 174 | defaults.reactorPort 175 | ).then(({ host, port }) => 176 | startReactor(host, port, false).then(server => { 177 | server.close() 178 | }) 179 | ) 180 | ).to.eventually.be.fulfilled 181 | }) 182 | }) 183 | 184 | describe('startBrowserSync', () => { 185 | describe('params', () => { 186 | checkParam('string', 'host', startBrowserSync)([1]) 187 | checkParam('number', 'port', startBrowserSync)([' ', ' ']) 188 | checkParam('string', 'reactor', startBrowserSync)([' ', 1, 1]) 189 | checkParam('string', 'html', startBrowserSync)([' ', 1, ' ', 1]) 190 | checkParam('string', 'dir', startBrowserSync)([' ', 1, ' ', ' ', 1]) 191 | checkParam('array', 'proxies', startBrowserSync, false)([ 192 | ' ', 193 | 1, 194 | ' ', 195 | ' ', 196 | ' ', 197 | ' ', 198 | ]) 199 | }) 200 | 201 | it('starts a browsersync server', () => { 202 | return expect( 203 | startBrowserSync( 204 | defaults.host, 205 | defaults.port, 206 | ' ', 207 | ' ', 208 | ' ' 209 | ).then(({ bs }) => { 210 | bs.exit() 211 | }) 212 | ).to.eventually.be.fulfilled 213 | }) 214 | 215 | it('serves static from the /public dir', done => { 216 | const tmpDir = tmp.dirSync({ dir, unsafeCleanup: true }) 217 | const tmpFile = tmp.fileSync({ dir: tmpDir.name, postfix: '.css' }) 218 | const basename = path.basename(tmpFile.name) 219 | fs.writeSync(tmpFile.fd, Buffer('.elm-reactor{color:#FFFFFF}')) 220 | 221 | startBrowserSync( 222 | defaults.host, 223 | defaults.port, 224 | ' ', 225 | ' ', 226 | tmpDir.name 227 | ).then(({ bs, port }) => 228 | request( 229 | `http://${defaults.host}:${port}/public/${basename}` 230 | ).then(res => { 231 | expect(res).to.eql('.elm-reactor{color:#FFFFFF}') 232 | tmpFile.removeCallback() 233 | tmpDir.removeCallback() 234 | bs.exit() 235 | done() 236 | }) 237 | ) 238 | }) 239 | 240 | it('*.elm files render a custom html template', done => { 241 | startBrowserSync( 242 | defaults.host, 243 | defaults.port, 244 | ' ', 245 | './index.ejs', 246 | ' ' 247 | ).then(({ bs, port }) => 248 | request(`http://${defaults.host}:${port}/src/Main.elm`) 249 | .then(res => { 250 | assertIncludes('~/src/Main.elm', res) 251 | bs.exit() 252 | done() 253 | }) 254 | .catch(done) 255 | ) 256 | }) 257 | 258 | it('proxies elm-reactor', function(done) { 259 | this.timeout(60000) 260 | 261 | findFreePort(defaults.reactorHost, defaults.reactorPort) 262 | .then(({ host: reactorHost, port: reactorPort, url: reactorUrl }) => 263 | startReactor(reactorHost, reactorPort, false).then(reactor => 264 | startBrowserSync( 265 | defaults.host, 266 | defaults.port, 267 | reactorUrl, 268 | ' ', 269 | ' ' 270 | ).then(({ bs, port }) => 271 | Promise.all([ 272 | request(`http://${defaults.host}:${port}`), 273 | request( 274 | `http://${defaults.host}:${port}/_compile/src/Main.elm` 275 | ), 276 | ]).then(([res1, res2]) => { 277 | assertIncludes('