├── .gitignore
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── bin
├── run-js
└── setup-logger.js
├── collaborators.md
├── docs
└── api.md
├── lib
├── default-handlers.js
├── default-transforms.js
├── generate-bundle.js
├── handlers
│ ├── coffeescript.js
│ ├── javascript.js
│ └── typescript.js
├── index.js
├── middleware
│ └── error.js
├── router.js
├── routes
│ ├── bundle-file.js
│ ├── default-index.js
│ ├── directory.js
│ ├── html-file.js
│ └── script-file.js
└── template
│ ├── bundle-error.html
│ ├── error.html
│ ├── index.html
│ └── template.html
├── package.json
└── test
├── basic.js
├── runners
├── default-index-page.js
├── error-page.js
├── html-file-page.js
└── standard-page.js
├── scenarios.js
├── scenarios
├── bundle-error
│ ├── expected
│ │ └── bundle.js
│ ├── index.js
│ └── input
│ │ └── foo.js
├── default-index
│ └── index.js
├── error-404
│ └── index.js
├── html-file-no-script
│ ├── expected
│ │ └── page.html
│ ├── index.js
│ └── input
│ │ └── test.html
├── html-file-with-script
│ ├── expected
│ │ ├── bundle.js
│ │ └── page.html
│ ├── index.js
│ └── input
│ │ ├── test.html
│ │ └── test.js
├── single-file-index
│ ├── expected
│ │ └── bundle.js
│ ├── index.js
│ └── input
│ │ └── index.js
├── single-file-non-index
│ ├── expected
│ │ └── bundle.js
│ ├── index.js
│ └── input
│ │ └── foo.js
├── subdirectory-empty
│ ├── index.js
│ └── input
│ │ └── foo
│ │ └── .gitkeep
└── subdirectory-with-index-html
│ ├── expected
│ └── page.html
│ ├── index.js
│ └── input
│ └── foo
│ └── index.html
└── watch.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | .nyc_output/
4 | coverage/
5 | test/_test
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '4'
4 | - '5'
5 | after_success: npm run travis-coveralls
6 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Code of Conduct
2 |
3 | As contributors and maintainers of this project, and in the interest of
4 | fostering an open and welcoming community, we pledge to respect all people who
5 | contribute through reporting issues, posting feature requests, updating
6 | documentation, submitting pull requests or patches, and other activities.
7 |
8 | We are committed to making participation in this project a harassment-free
9 | experience for everyone, regardless of level of experience, gender, gender
10 | identity and expression, sexual orientation, disability, personal appearance,
11 | body size, race, ethnicity, age, religion, or nationality.
12 |
13 | Examples of unacceptable behavior by participants include:
14 |
15 | * The use of sexualized language or imagery
16 | * Personal attacks
17 | * Trolling or insulting/derogatory comments
18 | * Public or private harassment
19 | * Publishing other's private information, such as physical or electronic
20 | addresses, without explicit permission
21 | * Other unethical or unprofessional conduct
22 |
23 | Project maintainers have the right and responsibility to remove, edit, or
24 | reject comments, commits, code, wiki edits, issues, and other contributions
25 | that are not aligned to this Code of Conduct, or to ban temporarily or
26 | permanently any contributor for other behaviors that they deem inappropriate,
27 | threatening, offensive, or harmful.
28 |
29 | By adopting this Code of Conduct, project maintainers commit themselves to
30 | fairly and consistently applying these principles to every aspect of managing
31 | this project. Project maintainers who do not follow or enforce the Code of
32 | Conduct may be permanently removed from the project team.
33 |
34 | This Code of Conduct applies both within project spaces and in public spaces
35 | when an individual is representing the project or its community.
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
38 | reported by contacting a project maintainer at . All
39 | complaints will be reviewed and investigated and will result in a response that
40 | is deemed necessary and appropriate to the circumstances. Maintainers are
41 | obligated to maintain confidentiality with regard to the reporter of an
42 | incident.
43 |
44 |
45 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
46 | version 1.3.0, available at
47 | [http://contributor-covenant.org/version/1/3/0/][version]
48 |
49 | [homepage]: http://contributor-covenant.org
50 | [version]: http://contributor-covenant.org/version/1/3/0/
51 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Righteous! I'm happy that you want to contribute. :smile:
4 |
5 | * Make sure that you're read and understand the [Code of Conduct](CODE_OF_CONDUCT.md).
6 | * Check out the [issues tagged with the `starter` tag.](https://github.com/remixz/run-js/issues?q=is%3Aopen+is%3Aissue+label%3Astarter)
7 |
8 | ## run-js is an [OPEN Open Source Project](http://openopensource.org/)
9 |
10 | ### What?
11 |
12 | Individuals making significant and valuable contributions are given
13 | commit-access to the project to contribute as they see fit. This project
14 | is more like an open wiki than a standard guarded open source project.
15 |
16 | ### Rules
17 |
18 | There are a few basic ground-rules for contributors:
19 |
20 | 1. **No `--force` pushes** or modifying the Git history in any way. *(Exception: I use `git am -3` sometimes to clean up pull requests, and then commit them to the repo.)*
21 | 2. **Non-master branches** ought to be used for ongoing work.
22 | 3. **External API changes and significant modifications** ought to be subject to an **internal pull-request** to solicit feedback from other contributors.
23 | 4. Internal pull-requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor.
24 | 5. Contributors should adhere to the [JavaScript Standard code-style](https://github.com/feross/standard).
25 |
26 | ### Releases
27 |
28 | Declaring formal releases remains the prerogative of the project maintainer.
29 |
30 | ### Changes to this arrangement
31 |
32 | This is an experiment and feedback is welcome! This document may also be
33 | subject to pull-requests or changes by contributors where you believe
34 | you have something valuable to add or change.
35 |
36 | Get a copy of this manifesto as [markdown](https://raw.githubusercontent.com/openopensource/openopensource.github.io/master/Readme.md) and use it in your own projects.
37 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Zach Bruggeman and contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # run-js
2 |
3 | [](https://travis-ci.org/remixz/run-js)
4 | [](https://coveralls.io/github/remixz/run-js?branch=master)
5 | [](https://www.npmjs.com/package/run-js)
6 | [](http://standardjs.com/)
7 |
8 | A prototyping server that just works.
9 |
10 | *Click to enlarge image:*
11 | [](https://s3.amazonaws.com/f.cl.ly/items/3U1J411P0L3x092D2h3F/run-js-demo2.gif)
12 |
13 | ## Installation
14 |
15 | Requires Node.js >=4.0.0.
16 |
17 | ```bash
18 | $ npm install run-js --global
19 | ```
20 |
21 | ## Usage
22 |
23 | Enter a folder you want to run scripts in, and type `run-js`.
24 |
25 | ```bash
26 | $ cd your/folder
27 | $ run-js
28 | ```
29 |
30 | It will print out the URL it's running on. From there, just visit any of your scripts in the browser, and they'll just work.
31 |
32 | For API usage, [see the documentation file.](docs/api.md)
33 |
34 | ## Features
35 |
36 | ### Instantly working scripts
37 |
38 | There's no HTML files you have to create, no compile steps for your code to work, and no need to even manually install dependencies. Just start `run-js` in a folder, write some code, and open it in the browser. `run-js` supports JavaScript (with ES2015 and JSX enabled via Babel), CoffeeScript, and TypeScript out of the box. When you require a dependency, `run-js` will automatically install it for you, if it's not installed already. Plus, the default HTML page includes a `` tag with an `id` of `root`, so that you can quickly append elements from a library, such as React.
39 |
40 | ### Scripts as the index file
41 |
42 | Creating a file named `index.js` (or whatever type of file you prefer) will act as the index for the path you specify. For example, creating `index.js` in the root of where you ran `run-js` will use that script when you visit `http://localhost:60274`.
43 |
44 | ### Source maps
45 |
46 | No need to go through the hullabaloo of setting up source maps. They're just there, and they just work.
47 |
48 | ### Live reload
49 |
50 | When you make a change, the browser will automatically reload. Easy peasy.
51 |
52 | ### Custom HTML pages
53 |
54 | By default, `run-js` will render a page when you visit a file in the browser. However, if you need your own custom page, it's easy to do. Just create a `.html` file with the same name as your script. For example, if you had `foo.js`, create a `foo.html` in the same folder, and it'll use that for the template. It'll automatically insert your compiled script as well. (*Make sure to have a `` tag for this to work.*)
55 |
56 | ## Implementation
57 |
58 | run-js is powered by [Browserify](https://github.com/substack/node-browserify), and various transforms for it. I like [Webpack](https://github.com/webpack/webpack) as well, but I enjoy working with Browserify more, and find it easier to use overall, while still being able to do what I want to. I don't think run-js will need to change to Webpack, or some other future bundler, to get the functionality that's wanted. Of course, that could change... :wink:. The transform [installify](https://github.com/hughsk/installify) is used automatically install new dependencies. Really cool stuff!
59 |
60 | Aside from Browserify, run-js uses [Express](https://github.com/strongloop/express) to power the web server. Nothing too fancy there, really. run-js has an in-memory cache powered by [LevelUP](https://github.com/Level/levelup) and [MemDOWN](https://github.com/level/memdown). That could be migrated to a file cache pretty easily, but I'm not sure if it's really needed. It might be in the future, though, which is why I used LevelUP.
61 |
62 | ## Inspiration
63 |
64 | * [**@vjeux**](https://github.com/vjeux)'s challenge to create a better JavaScript prototyping environment: http://blog.vjeux.com/2015/javascript/challenge-best-javascript-setup-for-quick-prototyping.html
65 | * [**@ericclemmons**](https://github.com/ericclemmons)'s post about JavaScript fatigue: https://medium.com/@ericclemmons/javascript-fatigue-48d4011b6fc4
66 | * Modules such as [budo](https://github.com/mattdesl/budo), [beefy](https://github.com/chrisdickinson/beefy), and [wzrd](https://github.com/maxogden/wzrd), which all do a lot of what run-js does, but with less defaults, and just for running one file. I like those modules a lot, and I think they definitely work for a different type of workflow. The main difference with run-js is that it's aimed a bit more towards newbies, hence why it runs any file in the directory. Essentially, run-js is a playground: Everything just goes, and it's lots of fun! It's not really meant for serious work, but instead just trying things out.
67 |
--------------------------------------------------------------------------------
/bin/run-js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict'
3 | /**
4 | * run-js - A prototyping server that just works.
5 | *
6 | * @author Zach Bruggeman
7 | */
8 |
9 | const path = require('path')
10 | const mkdirp = require('mkdirp')
11 | const yargs = require('yargs')
12 | const open = require('opn')
13 | const bole = require('bole')
14 | const log = bole('run-js')
15 | const garnish = require('garnish')
16 | const RunJS = require('../lib')
17 | const setupLogger = require('./setup-logger')
18 |
19 | let argv = yargs
20 | .usage('Usage: run-js [--no-watch] [--dir=] [--port=<8080>] [path/to/script]\n\nPassing a relative path to a script file will automatically open it in the browser.')
21 | .options({
22 | watch: {
23 | alias: 'w',
24 | type: 'boolean',
25 | default: true,
26 | description: 'Enables the LiveReload server. Enabled by default. Pass --no-watch to disable.'
27 | },
28 | dir: {
29 | alias: 'd',
30 | type: 'string',
31 | default: process.cwd(),
32 | description: 'Directory for run-js to run in.'
33 | },
34 | port: {
35 | alias: 'p',
36 | default: 60274,
37 | description: 'Port for the run-js server to listen on.'
38 | },
39 | handler: {
40 | alias: 'h',
41 | type: 'array',
42 | default: [],
43 | description: 'File handler(s) to add to the default handlers provided by run-js.'
44 | },
45 | transform: {
46 | alias: 't',
47 | type: 'array',
48 | default: [],
49 | description: 'Global transforms(s) to add to the default transforms provided by run-js.'
50 | },
51 | plugin: {
52 | alias: 'pl',
53 | type: 'array',
54 | default: [],
55 | description: 'Global plugin(s) to add to the default plugins provided by run-js.'
56 | }
57 | })
58 | .help('help')
59 | .version(require('../package.json').version)
60 | .strict()
61 | .argv
62 |
63 | // Create node_modules if it doesn't exist in the current folder
64 | mkdirp.sync(path.join(process.cwd(), 'node_modules'))
65 |
66 | if (!path.isAbsolute(argv.dir)) {
67 | argv.dir = path.resolve(process.cwd(), argv.dir)
68 | }
69 |
70 | let handlers = argv.handler.map(h => require(h))
71 | let transforms = argv.transform.map(t => require(t))
72 | let plugins = argv.plugin.map(p => require(p))
73 |
74 | argv.handlers = require('../lib/default-handlers').concat(handlers)
75 | argv.transforms = require('../lib/default-transforms').concat(transforms)
76 | argv.plugins = [].concat(plugins)
77 |
78 | let logger = garnish({
79 | level: 'info',
80 | name: 'run-js'
81 | })
82 |
83 | logger.pipe(require('stdout-stream'))
84 |
85 | bole.output({
86 | level: 'info',
87 | stream: logger
88 | })
89 |
90 | let app = new RunJS(argv)
91 |
92 | setupLogger(app)
93 |
94 | app.start(err => {
95 | if (err) throw err
96 | log.info({
97 | message: 'run-js is listening on',
98 | url: `http://localhost:${argv.port}`
99 | })
100 | if (argv._[0]) {
101 | log.info(`Opening http://localhost:${argv.port}/${argv._[0]} in your browser.`)
102 | open(`http://localhost:${argv.port}/${argv._[0]}`)
103 | } else {
104 | log.info(`Open a file from this directory in the browser to see it in action.`)
105 | log.info('Example: Create a file named `foo.js` in this directory, and visit')
106 | log.info('http://localhost:60274/foo.js in your browser to see it ran.')
107 | }
108 | })
109 |
--------------------------------------------------------------------------------
/bin/setup-logger.js:
--------------------------------------------------------------------------------
1 | const log = require('bole')('run-js')
2 | const prettyBytes = require('pretty-bytes')
3 |
4 | function setupLogger (app) {
5 | app.on('request', (req, res, start) => {
6 | if (req.url.indexOf('__bundle/') > -1) return // don't log __bundle requests
7 | if (req.url === 'favicon.ico' && res.statusCode === 404) return // don't log favicon.ico 404s
8 |
9 | log.info({
10 | elapsed: Date.now() - start,
11 | method: req.method,
12 | url: req.url,
13 | statusCode: res.statusCode
14 | })
15 | })
16 |
17 | app.on('bundle', info => {
18 | info.message = (info.cached ? 'Returned cached bundle for ' : 'Generated bundle for ') + `${info.file} (${prettyBytes(info.size)}) in ${info.bundleTime}ms`
19 | info.type = 'bundle'
20 | log.info(info)
21 | })
22 |
23 | app.on('bundle:error', info => {
24 | info.type = 'bundle'
25 | log.error(info)
26 | })
27 |
28 | app.once('watch:ready', () => {
29 | log.info({
30 | message: 'Live reloading enabled.',
31 | type: 'watch'
32 | })
33 | })
34 |
35 | app.on('watch:all', (event, fp) => {
36 | log.info({
37 | message: `File ${event}: ${fp}`,
38 | type: 'watch'
39 | })
40 | })
41 |
42 | app.on('watch:error', err => {
43 | log.error(err)
44 | })
45 |
46 | return app
47 | }
48 |
49 | module.exports = setupLogger
50 |
--------------------------------------------------------------------------------
/collaborators.md:
--------------------------------------------------------------------------------
1 | ## Collaborators
2 |
3 | run-js is only possible due to the excellent work of the following collaborators:
4 |
5 |
8 |
--------------------------------------------------------------------------------
/docs/api.md:
--------------------------------------------------------------------------------
1 | # run-js api
2 |
3 | ## initializing
4 | ```js
5 | const RunJS = require('run-js')
6 |
7 | let app = new RunJS({
8 | dir: process.cwd(), // directory to serve files from. defaults to current working directory
9 | watch: true, // enables/disables watch server for livereload. defaults to true.
10 | port: 60274, // port to listen on. defaults to 60274
11 | handlers: [], // array of file handler objects to use. none added by default. see below for format.
12 | transforms: [], // array of browserify transforms to use. none added by default. see below for format.
13 | plugins: [] // array of browserify plugins to use. none added by default. see below for format.
14 | })
15 | ```
16 |
17 | ## handlers, transforms & plugins
18 |
19 | * **handler** - run on files that match its `extension` property. should have 1 or more browserify transforms.
20 |
21 | ```js
22 | {
23 | extension: /\.jsx?$/, // regex for file extension to match
24 | transforms: [ // array of transforms to run on file. run from first to last.
25 | {
26 | module: require('babelify'), // should be a browserify transform
27 | opts: { // options to pass to the transform
28 | presets: [ require('babel-preset-es2015'), require('babel-preset-react') ]
29 | }
30 | }
31 | ],
32 | errorMessage: function (err) { // the message to display in the browser if there was a compilation error
33 | return `${err.message}\n\n${err.codeFrame}`
34 | }
35 | }
36 | ```
37 | * **transform** - a browserify transform run on the file after the handler transforms it into JS.
38 |
39 | ```js
40 | {
41 | module: require('installify'), // should be a browserify transform
42 | opts: {} // options to pass to the transform
43 | }
44 | ```
45 |
46 | * **plugin** - a browserify plugin run on the file after the handler transforms it into JS.
47 |
48 | ```js
49 | {
50 | module: require('browserify-hmr'), // should be a browserify plugin
51 | opts: {} // options to pass to the plugin
52 | }
53 | ```
54 |
55 | the `run-js` api doesn't add any file handlers, transforms, or plugins by default. this is to make it as extensible as possible. to make it easier to develop with, however, a set of default handlers and transforms are provided with the module (and plugins in the future, if the module ends up using them by default). these are available with `require('run-js/lib/default-handlers')` and `require('run-js/lib/default-transforms')`, respectively. those can be passed to the `handlers` and `transforms` options in your `new RunJS` call.
56 |
57 | ## methods
58 |
59 | ### app.start(function (err) {})
60 |
61 | starts the app. runs the passed callback when finished initializing.
62 |
63 | ### app.stop(function (err) {})
64 |
65 | stops the app. runs the passed callback when finished stopping.
66 |
67 | ## events
68 |
69 | ### app.on('request', function (req, res, timestamp) {})
70 |
71 | fired when a page is loaded. includes the request object, the response object, and a timestamp of when the request initiated. the timestamp can be used to determine how long it took to serve the response.
72 |
73 | ### app.on('bundle', function (info) {})
74 |
75 | fired when a script bundle is finished generating. includes an info object, with this format:
76 |
77 | ```js
78 | {
79 | file: '/path/to/file', // absolute path to file that the bundle was generated for
80 | size: 31415, // size in bytes of the bundle
81 | bundleTime: 1337, // time in milliseconds of how long it took the bundle to generate
82 | cached: false // whether or not the bundle was returned from the internal cache
83 | }
84 | ```
85 |
86 | ### app.on('bundle:error', function (err) {})
87 |
88 | fired when the bundle errors. includes the error from the bundle generator.
89 |
90 | ### app.on('watch:ready', function () {})
91 |
92 | fired when the watch server is ready.
93 |
94 | ### app.on('watch:all', function (event, filepath) {})
95 |
96 | fired when there's a watch event. `event` may be [any of the events fired by chokidar.](https://github.com/paulmillr/chokidar#methods--events)
97 |
98 | ### app.on('watch:error', function (err) {})
99 |
100 | fired when the watch server has an error. includes the error from `chokidar`.
101 |
--------------------------------------------------------------------------------
/lib/default-handlers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * run-js - A prototyping server that just works.
3 | * Array of the default file handlers.
4 | *
5 | * @author Zach Bruggeman
6 | */
7 |
8 | module.exports = [
9 | require('./handlers/javascript'),
10 | require('./handlers/coffeescript'),
11 | require('./handlers/typescript')
12 | ]
13 |
--------------------------------------------------------------------------------
/lib/default-transforms.js:
--------------------------------------------------------------------------------
1 | /**
2 | * run-js - A prototyping server that just works.
3 | * Array of the default global transforms.
4 | *
5 | * @author Zach Bruggeman
6 | */
7 |
8 | module.exports = [
9 | {
10 | module: require('installify')
11 | },
12 | {
13 | module: require('brfs')
14 | }
15 | ]
16 |
--------------------------------------------------------------------------------
/lib/generate-bundle.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | /**
3 | * run-js - A prototyping server that just works.
4 | *
5 | * @author Zach Bruggeman
6 | */
7 |
8 | const fs = require('fs')
9 | const path = require('path')
10 | const browserify = require('browserify')
11 | const concat = require('concat-stream')
12 | const ansiUp = require('ansi_up')
13 | const escapeHtml = require('escape-html')
14 | const _ = require('lodash')
15 |
16 | const errorTemplate = _.template(fs.readFileSync(path.resolve(__dirname, './template/bundle-error.html')))
17 |
18 | function generateErrorScript (err) {
19 | let errorBox = errorTemplate({
20 | name: err.name,
21 | message: ansiUp.ansi_to_html(escapeHtml(err.message))
22 | })
23 |
24 | // if it's not a string, and just included as a function that's interpolated by the returned string
25 | // it'll get mucked up during coverage testing
26 | let bundleJs = `function bundleError () {
27 | var template = ${JSON.stringify(errorBox)}
28 | if (typeof document === 'undefined') return
29 | document.addEventListener('DOMContentLoaded', function print () {
30 | var container = document.createElement('div')
31 | container.innerHTML = template
32 | document.body.appendChild(container)
33 | })
34 | }`
35 |
36 | return `;(${bundleJs})()\n`
37 | }
38 |
39 | function generateBundle (opts, cb) {
40 | let b = browserify({
41 | debug: true
42 | })
43 | if (opts.handler.plugins) {
44 | opts.handler.plugins.forEach(plugin => b.plugin(plugin.module, plugin.opts))
45 | }
46 | if (opts.handler.transforms) {
47 | opts.handler.transforms.forEach(transform => b.transform(transform.module, transform.opts))
48 | }
49 |
50 | if (opts.plugins) {
51 | opts.plugins.forEach(plugin => b.plugin(plugin.module, plugin.opts))
52 | }
53 | if (opts.transforms) {
54 | opts.transforms.forEach(transform => b.transform(transform.module, transform.opts))
55 | }
56 | b.add(opts.file)
57 |
58 | let bd = b.bundle()
59 | let didError = false
60 |
61 | let bdStream = concat(body => {
62 | if (didError) return
63 |
64 | cb(null, body)
65 | })
66 |
67 | // i'd do .once here, but tsify emits multiple errors, because... well, because it can, i guess.
68 | bd.on('error', err => {
69 | if (didError) return
70 | didError = true
71 | err.originalMessage = err.message
72 | err.message = opts.handler.errorMessage && opts.handler.errorMessage(err) || err.message
73 | let errorScript = generateErrorScript(err)
74 |
75 | cb(err, errorScript)
76 | })
77 |
78 | bd.pipe(bdStream)
79 | }
80 |
81 | module.exports = generateBundle
82 |
--------------------------------------------------------------------------------
/lib/handlers/coffeescript.js:
--------------------------------------------------------------------------------
1 | /**
2 | * run-js - A prototyping server that just works.
3 | * CoffeeScript file handler. Transforms using the `coffeeify` transformer.
4 | *
5 | * @author Zach Bruggeman
6 | */
7 |
8 | module.exports = {
9 | extension: /\.coffee$/,
10 | transforms: [
11 | {
12 | module: require('coffeeify')
13 | }
14 | ],
15 | errorMessage: function (err) {
16 | return err.annotated
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/lib/handlers/javascript.js:
--------------------------------------------------------------------------------
1 | /**
2 | * run-js - A prototyping server that just works.
3 | * JavaScript file handler. Transforms with `babelify`, using the `es2015` and `react` preset.
4 | *
5 | * @author Zach Bruggeman
6 | */
7 |
8 | module.exports = {
9 | extension: /\.jsx?$/, // .js & .jsx
10 | transforms: [
11 | {
12 | module: require('babelify'),
13 | opts: {
14 | presets: [ require('babel-preset-es2015'), require('babel-preset-react') ]
15 | }
16 | }
17 | ],
18 | errorMessage: function (err) {
19 | return `${err.message}\n\n${err.codeFrame}`
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/handlers/typescript.js:
--------------------------------------------------------------------------------
1 | /**
2 | * run-js - A prototyping server that just works.
3 | * TypeScript file handler. Transforms using the `tsify` transformer.
4 | *
5 | * @author Zach Bruggeman
6 | */
7 |
8 | module.exports = {
9 | extension: /\.ts$/,
10 | plugins: [
11 | {
12 | module: require('tsify')
13 | }
14 | ],
15 | errorMessage: function (err) {
16 | return err.message
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | /**
3 | * run-js - A prototyping server that just works.
4 | *
5 | * @author Zach Bruggeman
6 | */
7 |
8 | const path = require('path')
9 | const EventEmitter = require('events')
10 | const express = require('express')
11 | const chokidar = require('chokidar')
12 | const tinylr = require('tiny-lr')
13 | const _ = require('lodash')
14 |
15 | let createRouter = require('./router')
16 | let errorMiddleware = require('./middleware/error')
17 |
18 | function noop () {}
19 |
20 | class RunJS extends EventEmitter {
21 | constructor (opts) {
22 | super()
23 | this.opts = opts || {}
24 | _.defaults(this.opts, {
25 | dir: process.cwd(),
26 | watch: true,
27 | port: 60274,
28 | handlers: [],
29 | transforms: [],
30 | plugins: []
31 | })
32 |
33 | this.app = this._createApp()
34 | if (this.opts.watch) this.lr = this._createLiveReload()
35 | }
36 |
37 | start (cb) {
38 | if (!cb) cb = noop
39 |
40 | if (this.opts.watch) {
41 | this.watch = chokidar.watch(this.opts.dir, {
42 | ignored: /node_modules|\.git|^\..+|[\/\\]\..+/,
43 | ignoreInitial: true,
44 | useFsEvents: !(process.env.NODE_ENV === 'test') // fsevents has issues when testing, not removing the file listeners, causing process to hang
45 | })
46 |
47 | this.watch.on('ready', () => this.emit('watch:ready'))
48 |
49 | this.watch.on('all', (event, fp) => {
50 | fp = path.resolve(this.opts.dir, fp)
51 | this.emit('watch:all', event, fp)
52 | this.lr.notifyClients([fp])
53 | })
54 |
55 | this.watch.on('error', err => this.emit('watch:error', err))
56 |
57 | this.lr.listen()
58 | }
59 |
60 | this.server = this.app.listen(this.opts.port, cb)
61 |
62 | let connections = []
63 | this.server.on('connection', conn => {
64 | let key = `${conn.remoteAddress}:${conn.remotePort}`
65 | connections[key] = conn
66 | conn.on('close', () => {
67 | delete connections[key]
68 | })
69 | })
70 | this.server.destroy = cb => {
71 | this.server.close(cb)
72 | for (let key in connections) {
73 | connections[key].destroy()
74 | }
75 | }
76 | }
77 |
78 | stop (cb) {
79 | if (!cb) cb = noop
80 |
81 | if (!this.server) {
82 | return cb(new Error('No server currently running.'))
83 | }
84 |
85 | if (this.opts.watch) {
86 | this.lr.close()
87 | this.watch.close()
88 | }
89 |
90 | this.removeAllListeners()
91 | this.server.destroy(cb)
92 | }
93 |
94 | _createApp () {
95 | let app = express()
96 |
97 | app.locals.instance = this
98 | app.locals.opts = this.opts
99 |
100 | this.router = createRouter(this.opts.handlers)
101 | app.use((req, res, next) => {
102 | let now = Date.now()
103 | res.on('finish', () => this.emit('request', req, res, now))
104 | next()
105 | })
106 | app.use(this.router)
107 | app.use(express.static(this.opts.dir))
108 | app.use(errorMiddleware)
109 |
110 | return app
111 | }
112 |
113 | _createLiveReload () {
114 | let lr = tinylr({ port: 35729 })
115 |
116 | return lr
117 | }
118 | }
119 |
120 | module.exports = RunJS
121 |
--------------------------------------------------------------------------------
/lib/middleware/error.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | /**
3 | * run-js - A prototyping server that just works.
4 | *
5 | * @author Zach Bruggeman
6 | */
7 |
8 | const fs = require('fs')
9 | const path = require('path')
10 | const _ = require('lodash')
11 |
12 | let pageTemplate = _.template(fs.readFileSync(path.join(__dirname, '../template/error.html')))
13 |
14 | function errorHandler (err, req, res, next) {
15 | let status = 500
16 | let message = err.message
17 |
18 | if (err.code === 'ENOENT') {
19 | let pathType = (path.extname(err.path) !== '' ? 'file' : 'folder')
20 | status = 404
21 | message = `The ${pathType} ${req.path} was not found. Maybe you didn't create it yet?`
22 | }
23 |
24 | if (err.code === 'ENOINDEX') {
25 | status = 404
26 | message = `The folder ${req.path} doesn't have an index file. Try creating an index.js file in this folder.`
27 | }
28 |
29 | let template = pageTemplate({
30 | status: status,
31 | message: message
32 | })
33 |
34 | return res.status(status).send(template)
35 | }
36 |
37 | module.exports = errorHandler
38 |
--------------------------------------------------------------------------------
/lib/router.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const express = require('express')
3 |
4 | let bundleFileRoute = require('./routes/bundle-file')
5 | let scriptFileRoute = require('./routes/script-file')
6 | let htmlFileRoute = require('./routes/html-file')
7 | let directoryRoute = require('./routes/directory')
8 | let defaultIndexRoute = require('./routes/default-index')
9 |
10 | function createRouter (handlers) {
11 | let router = express.Router()
12 |
13 | router.get('/__bundle/:file.bundle.js', bundleFileRoute)
14 |
15 | handlers.forEach(handler => {
16 | router.get(handler.extension, scriptFileRoute)
17 | })
18 |
19 | router.get('**/*.html', htmlFileRoute)
20 |
21 | router.get('*', directoryRoute)
22 |
23 | router.get('/', defaultIndexRoute)
24 |
25 | return router
26 | }
27 |
28 | module.exports = createRouter
29 |
--------------------------------------------------------------------------------
/lib/routes/bundle-file.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | /**
3 | * run-js - A prototyping server that just works.
4 | *
5 | * @author Zach Bruggeman
6 | */
7 |
8 | const path = require('path')
9 | const levelup = require('levelup')
10 | const md5 = require('md5-file')
11 |
12 | let generateBundle = require('../generate-bundle')
13 | let db = levelup('/run-js', { db: require('memdown') })
14 |
15 | function bundleFileRoute (req, res, next) {
16 | let now = Date.now()
17 | let opts = req.app.locals.opts
18 | let instance = req.app.locals.instance
19 | let fileName = req.params.file.replace(/-/g, '/')
20 | let filePath = path.join(opts.dir, fileName)
21 |
22 | md5(filePath, (err, hash) => {
23 | if (err) return next(err)
24 |
25 | let dbPath = `${filePath}.${hash}`
26 |
27 | db.get(dbPath, (err, script) => {
28 | if (err || !script) {
29 | let fileBase = path.basename(filePath)
30 | let handler = opts.handlers.find(h => h.extension.test(fileBase))
31 | let bundleOpts = {
32 | file: filePath,
33 | handler: handler,
34 | transforms: opts.transforms,
35 | plugin: opts.plugins
36 | }
37 |
38 | generateBundle(bundleOpts, (err, script) => {
39 | if (err) {
40 | instance.emit('bundle:error', {
41 | file: fileName,
42 | message: err.originalMessage
43 | })
44 | } else {
45 | let elapsed = Date.now() - now
46 | instance.emit('bundle', {
47 | file: fileName,
48 | size: script.length,
49 | bundleTime: elapsed,
50 | cached: false
51 | })
52 | db.put(dbPath, script)
53 | }
54 |
55 | res.set('Content-Type', 'application/javascript')
56 | res.send(script)
57 | })
58 | } else {
59 | let elapsed = Date.now() - now
60 | instance.emit('bundle', {
61 | file: fileName,
62 | size: script.length,
63 | bundleTime: elapsed,
64 | cached: true
65 | })
66 |
67 | res.set('Content-Type', 'application/javascript')
68 | res.send(script)
69 | }
70 | })
71 | })
72 | }
73 |
74 | module.exports = bundleFileRoute
75 |
--------------------------------------------------------------------------------
/lib/routes/default-index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | /**
3 | * run-js - A prototyping server that just works.
4 | *
5 | * @author Zach Bruggeman
6 | */
7 |
8 | const path = require('path')
9 |
10 | function defaultIndexRoute (req, res, next) {
11 | return res.sendFile(path.resolve(__dirname, '../template/index.html'))
12 | }
13 |
14 | module.exports = defaultIndexRoute
15 |
--------------------------------------------------------------------------------
/lib/routes/directory.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | /**
3 | * run-js - A prototyping server that just works.
4 | *
5 | * @author Zach Bruggeman
6 | */
7 |
8 | const fs = require('fs')
9 | const path = require('path')
10 | const _ = require('lodash')
11 |
12 | let htmlFileRoute = require('./html-file')
13 | let pageTemplate = _.template(fs.readFileSync(path.join(__dirname, '../template/template.html')))
14 |
15 | function directoryRoute (req, res, next) {
16 | let filePath = path.join(req.app.locals.opts.dir, req.path)
17 |
18 | fs.stat(filePath, (err, stat) => {
19 | if (err) return next(err)
20 | if (!stat.isDirectory()) return next()
21 |
22 | fs.readdir(filePath, (err, files) => {
23 | if (err) return next(err)
24 |
25 | let indexFile = files.find(file => file.indexOf('index') === 0)
26 | if (!indexFile && req.path === '/') return next() // we'll show the default page
27 | if (!indexFile) {
28 | let err = new Error()
29 | err.code = 'ENOINDEX'
30 | return next(err)
31 | }
32 |
33 | if (indexFile === 'index.html') {
34 | return htmlFileRoute(req, res, next)
35 | }
36 |
37 | let handlers = req.app.locals.opts.handlers
38 | let handler = handlers.find(h => h.extension.test(indexFile))
39 | if (!handler) return next()
40 |
41 | let compiled = pageTemplate({
42 | reqPath: path.normalize(`${req.path}/${indexFile}`),
43 | watch: req.app.locals.opts.watch
44 | })
45 | res.send(compiled)
46 | })
47 | })
48 | }
49 |
50 | module.exports = directoryRoute
51 |
--------------------------------------------------------------------------------
/lib/routes/html-file.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | /**
3 | * run-js - A prototyping server that just works.
4 | *
5 | * @author Zach Bruggeman
6 | */
7 |
8 | const fs = require('fs')
9 | const path = require('path')
10 | const cheerio = require('cheerio')
11 |
12 | function htmlFileRoute (req, res, next) {
13 | let filePath = path.join(req.app.locals.opts.dir, req.path)
14 |
15 | if (path.extname(filePath) !== '.html') {
16 | // sent from the directory route, since it found an index.html
17 | filePath = filePath + '/index.html'
18 | }
19 |
20 | fs.readFile(filePath, (err, buf) => {
21 | if (err) return next(err)
22 |
23 | fs.readdir(path.dirname(filePath), (err, files) => {
24 | if (err) return next(err)
25 |
26 | let baseName = path.basename(filePath, '.html')
27 | let fileName = files.find(file => file.indexOf(baseName) === 0 && !file.includes('.html'))
28 | if (fileName) {
29 | let bundleName = path.normalize(`${path.dirname(req.path)}/${fileName}`).replace(/\/|\\/g, '-')
30 | let $ = cheerio.load(buf.toString())
31 | $('body').append(``)
32 | if (req.app.locals.opts.watch) {
33 | $('body').append('')
34 | }
35 | res.send($.html())
36 | } else {
37 | res.send(buf.toString())
38 | }
39 | })
40 | })
41 | }
42 |
43 | module.exports = htmlFileRoute
44 |
--------------------------------------------------------------------------------
/lib/routes/script-file.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | /**
3 | * run-js - A prototyping server that just works.
4 | *
5 | * @author Zach Bruggeman
6 | */
7 |
8 | const fs = require('fs')
9 | const path = require('path')
10 | const _ = require('lodash')
11 |
12 | let pageTemplate = _.template(fs.readFileSync(path.join(__dirname, '../template/template.html')))
13 |
14 | function scriptFileRoute (req, res, next) {
15 | if (req.query.raw === '') return next()
16 |
17 | let filePath = path.join(req.app.locals.opts.dir, req.path)
18 | fs.stat(filePath, err => {
19 | if (err) return next(err)
20 |
21 | if (!req.headers.accept.includes('text/html')) return next()
22 |
23 | let compiled = pageTemplate({
24 | reqPath: req.path,
25 | watch: req.app.locals.opts.watch
26 | })
27 | res.send(compiled)
28 | })
29 | }
30 |
31 | module.exports = scriptFileRoute
32 |
--------------------------------------------------------------------------------
/lib/template/bundle-error.html:
--------------------------------------------------------------------------------
1 |
30 |
31 |
32 |
33 |
<%= name %>
34 |
35 |
<%= message %>
36 |
37 |
38 |
--------------------------------------------------------------------------------
/lib/template/error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | run-js – <%= status %>
6 |
18 |
19 |
20 |
21 |
Error: <%= status %>
22 |
23 | <%= message %>
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/lib/template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | run-js – Welcome
6 |
22 |
23 |
24 |
25 |
Welcome to run-js!
26 |
27 | run-js is the prototyping server that just works. Just put a JavaScript (including ES2015 and JSX), CoffeeScript, or TypeScript file in this directory, and browse to it. Have fun!
28 |
29 |
30 | (You can replace this page by making an index file, such as index.js, or whatever language you write in. index.html will work too.)
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/lib/template/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | run-js – <%= reqPath %>
6 |
7 |
8 |
9 |
10 |
11 | <% if (watch) { %>
12 | <% } %>
13 |
14 |
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "run-js",
3 | "version": "2.1.1",
4 | "description": "A prototyping server that just works.",
5 | "main": "lib/index.js",
6 | "bin": {
7 | "run-js": "./bin/run-js"
8 | },
9 | "scripts": {
10 | "test": "standard && NODE_ENV=test nyc tap test/*.js",
11 | "travis-coveralls": "npm test && nyc report --reporter=text-lcov | coveralls"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/remixz/run-js.git"
16 | },
17 | "author": "Zach Bruggeman ",
18 | "license": "MIT",
19 | "bugs": {
20 | "url": "https://github.com/remixz/run-js/issues"
21 | },
22 | "homepage": "https://github.com/remixz/run-js#readme",
23 | "dependencies": {
24 | "ansi_up": "^1.3.0",
25 | "babel-preset-es2015": "^6.3.13",
26 | "babel-preset-react": "^6.3.13",
27 | "babelify": "^7.2.0",
28 | "bole": "^2.0.0",
29 | "brfs": "^1.4.2",
30 | "browserify": "^12.0.1",
31 | "cheerio": "^0.19.0",
32 | "chokidar": "^1.4.2",
33 | "coffeeify": "^2.0.1",
34 | "concat-stream": "^1.5.1",
35 | "escape-html": "^1.0.3",
36 | "express": "^4.13.3",
37 | "garnish": "^5.0.1",
38 | "installify": "^1.0.2",
39 | "levelup": "^1.3.1",
40 | "lodash": "^3.10.1",
41 | "md5-file": "^2.0.4",
42 | "memdown": "^1.1.0",
43 | "mkdirp": "^0.5.1",
44 | "opn": "^3.0.3",
45 | "pretty-bytes": "^3.0.0",
46 | "stdout-stream": "^1.4.0",
47 | "tiny-lr": "^0.2.1",
48 | "tsify": "^0.13.1",
49 | "yargs": "^3.31.0"
50 | },
51 | "devDependencies": {
52 | "async": "^1.5.0",
53 | "coveralls": "^2.11.6",
54 | "nyc": "^5.1.0",
55 | "request": "^2.67.0",
56 | "rimraf": "^2.5.0",
57 | "standard": "^5.4.1",
58 | "tap": "^3.0.0"
59 | },
60 | "standard": {
61 | "ignore": [
62 | "test/scenarios/**"
63 | ]
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/test/basic.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const tap = require('tap')
3 |
4 | let RunJS = require('../lib')
5 |
6 | const appOpts = {
7 | port: 9999,
8 | watch: false
9 | }
10 |
11 | tap.test('basic test – initialization', t => {
12 | let app = new RunJS(appOpts)
13 |
14 | t.type(app.start, 'function', 'app.start is a function')
15 | t.type(app.stop, 'function', 'app.stop is a function')
16 | t.type(app.opts, 'object', 'app.opts is an object')
17 | t.end()
18 | })
19 |
20 | tap.test('basic test – stopping server before starting', t => {
21 | let app = new RunJS(appOpts)
22 |
23 | app.stop(err => {
24 | t.type(err, Error, 'app instance should return error server is stopped when it\'s not running')
25 | t.end()
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/test/runners/default-index-page.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const fs = require('fs')
3 | const path = require('path')
4 | const request = require('request')
5 |
6 | let defaultIndex = fs.readFileSync(path.join(__dirname, '../../lib/template/index.html')).toString()
7 |
8 | function defaultIndexPageRunner (testInfo, expected, t, cb) {
9 | request(`http://localhost:9999`, (err, res, body) => {
10 | t.error(err, 'request should complete')
11 | t.equals(res.statusCode, 200, `page status code should be 200`)
12 | t.equals(body, defaultIndex, 'page output should be correct')
13 |
14 | cb()
15 | })
16 | }
17 |
18 | module.exports = defaultIndexPageRunner
19 |
--------------------------------------------------------------------------------
/test/runners/error-page.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const fs = require('fs')
3 | const path = require('path')
4 | const request = require('request')
5 | const _ = require('lodash')
6 |
7 | let pageTemplate = _.template(fs.readFileSync(path.join(__dirname, '../../lib/template/error.html')))
8 |
9 | function errorPageRunner (status, message) {
10 | return function (testInfo, expected, t, cb) {
11 | request(`http://localhost:9999${testInfo.url}`, (err, res, body) => {
12 | t.error(err, 'request should complete')
13 |
14 | t.equals(res.statusCode, status, `should have expected ${status} error code`)
15 |
16 | let expectedPage = pageTemplate({
17 | status: status,
18 | message: message
19 | })
20 |
21 | t.equals(body, expectedPage, 'page output should be correct')
22 |
23 | cb()
24 | })
25 | }
26 | }
27 |
28 | module.exports = errorPageRunner
29 |
--------------------------------------------------------------------------------
/test/runners/html-file-page.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const request = require('request')
3 | const async = require('async')
4 |
5 | function htmlFilePageRunner (testInfo, expected, t, cb) {
6 | let url = testInfo.url
7 | if (testInfo.bundleUrl) {
8 | url = testInfo.bundleUrl
9 | }
10 | let bundlePath = `/__bundle/${url.replace(/\//g, '-').split('.html')[0]}.js.bundle.js`
11 |
12 | async.series([
13 | function getPage (cb) {
14 | request({
15 | uri: `http://localhost:9999${url}`,
16 | headers: {
17 | Accept: 'text/html'
18 | }
19 | }, (err, res, body) => {
20 | t.error(err, 'page request shouldn\'t fail')
21 | t.equals(res.statusCode, 200, 'page status code should be 200')
22 |
23 | t.equals(body, testInfo.expectedHtml, 'page output should be correct')
24 | cb()
25 | })
26 | },
27 | function getBundle (cb) {
28 | if (testInfo.noExpected) return cb()
29 | request(`http://localhost:9999${bundlePath}`, (err, res, body) => {
30 | t.error(err, 'bundle request shouldn\'t fail')
31 | t.equals(res.statusCode, 200, 'bundle status code should be 200')
32 |
33 | // test for sourcemap, then remove for script equality check, since sourcemap can differ between environments
34 | t.ok(body.indexOf('//# sourceMappingURL') > -1, 'inline sourcemap should exist')
35 | body = body.split('//# sourceMappingURL')[0]
36 |
37 | t.equals(body, expected.split('//# sourceMappingURL')[0], 'bundle output should be correct')
38 | cb()
39 | })
40 | }
41 | ], cb)
42 | }
43 |
44 | module.exports = htmlFilePageRunner
45 |
--------------------------------------------------------------------------------
/test/runners/standard-page.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const fs = require('fs')
3 | const path = require('path')
4 | const request = require('request')
5 | const async = require('async')
6 | const _ = require('lodash')
7 |
8 | let pageTemplate = _.template(fs.readFileSync(path.join(__dirname, '../../lib/template/template.html')))
9 |
10 | function standardPageRunner (testInfo, expected, t, cb) {
11 | let url = testInfo.url
12 | if (testInfo.bundleUrl) {
13 | url = testInfo.bundleUrl
14 | }
15 | let bundlePath = `/__bundle/${url.replace(/\//g, '-')}.bundle.js`
16 | let didCache = false
17 |
18 | async.series([
19 | function getPage (cb) {
20 | request({
21 | uri: `http://localhost:9999${testInfo.url}`,
22 | headers: {
23 | Accept: 'text/html'
24 | }
25 | }, (err, res, body) => {
26 | t.error(err, 'page request shouldn\'t fail')
27 | t.equals(res.statusCode, 200, 'page status code should be 200')
28 |
29 | let expectedPage = pageTemplate({
30 | reqPath: url,
31 | watch: false
32 | })
33 |
34 | t.equals(body, expectedPage, 'page output should be correct')
35 | cb()
36 | })
37 | },
38 | function getBundle (cb) {
39 | request(`http://localhost:9999${bundlePath}`, (err, res, body) => {
40 | t.error(err, 'bundle request shouldn\'t fail')
41 | t.equals(res.statusCode, 200, 'bundle status code should be 200')
42 |
43 | if (!testInfo.noSourceMap) {
44 | // test for sourcemap, then remove for script equality check, since sourcemap can differ between environments
45 | t.ok(body.indexOf('//# sourceMappingURL') > -1, 'inline sourcemap should exist')
46 | body = body.split('//# sourceMappingURL')[0]
47 | expected = expected.split('//# sourceMappingURL')[0]
48 | }
49 |
50 | if (testInfo.match) {
51 | t.match(body, testInfo.match, 'bundle output should be correct')
52 | } else {
53 | t.equals(body, expected, 'bundle output should be correct')
54 | }
55 | if (testInfo.cache && !didCache) {
56 | t.comment('running test again, for cached data')
57 | didCache = true
58 | getBundle(cb)
59 | } else {
60 | cb()
61 | }
62 | })
63 | }
64 | ], cb)
65 | }
66 |
67 | module.exports = standardPageRunner
68 |
--------------------------------------------------------------------------------
/test/scenarios.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const fs = require('fs')
3 | const path = require('path')
4 | const tap = require('tap')
5 | const async = require('async')
6 |
7 | let RunJS = require('../lib')
8 |
9 | fs.readdir(path.resolve(__dirname, './scenarios'), (err, tests) => {
10 | if (err) throw err
11 |
12 | async.eachSeries(tests, (testName, cb) => {
13 | let testDirectory = path.resolve(__dirname, './scenarios', testName)
14 | let testInput = path.join(testDirectory, 'input')
15 | tap.test(`scenario - ${testName}`, t => {
16 | let testInfo = require(testDirectory)
17 | let expected = ''
18 | if (!testInfo.noExpected) {
19 | expected = fs.readFileSync(path.join(testDirectory, 'expected/bundle.js')).toString()
20 | }
21 |
22 | let app = new RunJS({
23 | dir: testInfo.dir || testInput,
24 | watch: testInfo.watch || false,
25 | port: 9999,
26 | handlers: require('../lib/default-handlers'),
27 | transforms: require('../lib/default-transforms')
28 | })
29 |
30 | app.start(err => {
31 | t.error(err, 'server started up successfully')
32 | testInfo.runner(testInfo, expected, t, (err) => {
33 | t.error(err, 'no errors from test runner')
34 | t.end()
35 | app.stop(cb)
36 | })
37 | })
38 | })
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/test/scenarios/bundle-error/expected/bundle.js:
--------------------------------------------------------------------------------
1 | ;(function bundleError () {
2 | var template = "\n\n\n
\n
SyntaxError
\n\n
/Users/zach/run-js/foo.js: Unterminated string constant (1:10) while parsing file: /Users/zach/run-js/foo.js\n\n> 1 | var foo = 'bar\n | ^\n 2 |
\n
\n
\n"
3 | if (typeof document === 'undefined') return
4 | document.addEventListener('DOMContentLoaded', function print () {
5 | var container = document.createElement('div')
6 | container.innerHTML = template
7 | document.body.appendChild(container)
8 | })
9 | })()
10 |
--------------------------------------------------------------------------------
/test/scenarios/bundle-error/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | url: '/foo.js',
3 | runner: require('../../runners/standard-page'),
4 | noSourceMap: true,
5 | match: 'function bundleError ()'
6 | }
7 |
--------------------------------------------------------------------------------
/test/scenarios/bundle-error/input/foo.js:
--------------------------------------------------------------------------------
1 | var foo = 'bar
2 |
--------------------------------------------------------------------------------
/test/scenarios/default-index/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | runner: require('../../runners/default-index-page'),
3 | noExpected: true,
4 | dir: process.cwd()
5 | }
6 |
--------------------------------------------------------------------------------
/test/scenarios/error-404/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | url: '/404',
3 | runner: require('../../runners/error-page')(404, 'The folder /404 was not found. Maybe you didn\'t create it yet?'),
4 | noExpected: true
5 | }
6 |
--------------------------------------------------------------------------------
/test/scenarios/html-file-no-script/expected/page.html:
--------------------------------------------------------------------------------
1 |
2 | html file
3 |
4 |
--------------------------------------------------------------------------------
/test/scenarios/html-file-no-script/index.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 |
4 | module.exports = {
5 | url: '/test.html',
6 | runner: require('../../runners/html-file-page'),
7 | expectedHtml: fs.readFileSync(path.join(__dirname, 'expected', 'page.html')).toString(),
8 | noExpected: true
9 | }
10 |
--------------------------------------------------------------------------------
/test/scenarios/html-file-no-script/input/test.html:
--------------------------------------------------------------------------------
1 |
2 | html file
3 |
4 |
--------------------------------------------------------------------------------
/test/scenarios/html-file-with-script/expected/bundle.js:
--------------------------------------------------------------------------------
1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o
2 | html file
3 |
4 |
--------------------------------------------------------------------------------
/test/scenarios/html-file-with-script/index.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 |
4 | module.exports = {
5 | url: '/test.html',
6 | runner: require('../../runners/html-file-page'),
7 | expectedHtml: fs.readFileSync(path.join(__dirname, 'expected', 'page.html')).toString()
8 | }
9 |
--------------------------------------------------------------------------------
/test/scenarios/html-file-with-script/input/test.html:
--------------------------------------------------------------------------------
1 |
2 | html file
3 |
4 |
--------------------------------------------------------------------------------
/test/scenarios/html-file-with-script/input/test.js:
--------------------------------------------------------------------------------
1 | console.log('html file')
2 |
--------------------------------------------------------------------------------
/test/scenarios/single-file-index/expected/bundle.js:
--------------------------------------------------------------------------------
1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o
2 | index inside subdirectory
3 |
2 | index inside subdirectory
3 |