├── .npmignore ├── docs └── demo.gif ├── .gitignore ├── package.json ├── LICENSE ├── runner.js ├── README.md └── babel-watch.js /.npmignore: -------------------------------------------------------------------------------- 1 | docs 2 | -------------------------------------------------------------------------------- /docs/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiply/babel-watch/master/docs/demo.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | # vscode 36 | # not sure if you want my settings committed to your repo 37 | .vscode -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-watch", 3 | "version": "2.0.7", 4 | "description": "Reload your babel-node app on JS source file changes. And do it *fast*.", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/kmagiera/babel-watch.git" 11 | }, 12 | "keywords": [ 13 | "babel", 14 | "nodemon", 15 | "babel-node", 16 | "watch" 17 | ], 18 | "author": { 19 | "email": "krzys.magiera@gmail.com", 20 | "name": "Krzysztof Magiera" 21 | }, 22 | "license": "MIT", 23 | "readmeFilename": "README.md", 24 | "bugs": { 25 | "url": "https://github.com/kmagiera/babel-watch/issues" 26 | }, 27 | "homepage": "https://github.com/kmagiera/babel-watch#readme", 28 | "dependencies": { 29 | "chokidar": "^1.4.3", 30 | "commander": "^2.9.0", 31 | "source-map-support": "^0.4.0" 32 | }, 33 | "peerDependencies": { 34 | "babel-core": "6.5.1 - 6.6.0 || ^6.6.x" 35 | }, 36 | "bin": { 37 | "babel-watch": "./babel-watch.js" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Krzysztof Magiera 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 | -------------------------------------------------------------------------------- /runner.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const sourceMapSupport = require('source-map-support'); 6 | 7 | let sources = {}; 8 | let maps = {}; 9 | 10 | let pipeFd; 11 | const BUFFER = new Buffer(10 * 1024); 12 | 13 | // Node by default uses '.js' loader to load all the files with unknown extensions 14 | const DEFAULT_LOADER = require.extensions['.js']; 15 | 16 | function readLength(fd) { 17 | let bytes = 0; 18 | while (bytes !== 4) { 19 | bytes = fs.readSync(fd, BUFFER, 0, 4); 20 | } 21 | return BUFFER.readUInt32BE(0); 22 | } 23 | 24 | function readFileFromPipeSync(fd) { 25 | let length = readLength(fd); 26 | let result = new Buffer(0); 27 | while (length > 0) { 28 | const newBytes = fs.readSync(fd, BUFFER, 0, Math.min(BUFFER.length, length)); 29 | length -= newBytes; 30 | result = Buffer.concat([result, BUFFER], result.length + newBytes); 31 | } 32 | return result.toString(); 33 | } 34 | 35 | function babelWatchLoader(module_, filename, defaultHandler) { 36 | // apparently require loader needs to be synchronous, which 37 | // complicates things a little bit as we need to get source 38 | // file from the parent process synchronously. 39 | // The best method I've found so far is to use readFileSync on 40 | // a named unix pipe (mkfifo). All the alternative ways would 41 | // require writing native code which usually brings large 42 | // dependencies to the project and I prefer to avoid that 43 | process.send({ 44 | filename: filename, 45 | }); 46 | const source = readFileFromPipeSync(pipeFd); 47 | const map = readFileFromPipeSync(pipeFd); 48 | if (source) { 49 | maps[filename] = map && JSON.parse(map); 50 | module_._compile(source, filename); 51 | } else { 52 | defaultHandler(module_, filename); 53 | } 54 | } 55 | 56 | function registerExtension(ext) { 57 | const defaultHandler = require.extensions[ext] || DEFAULT_LOADER; 58 | require.extensions[ext] = (module_, filename) => { 59 | // ignore node_modules by default. don't you dare contacting the parent process! 60 | if (filename.split(path.sep).indexOf('node_modules') < 0) { 61 | babelWatchLoader(module_, filename, defaultHandler); 62 | } else { 63 | defaultHandler(module_, filename); 64 | if (filename.indexOf('/node_modules/source-map-support/') !== -1 && module_.exports.install !== undefined) { 65 | // When app is running in babel-watch the source-map-support library should not be used by separately. The 66 | // runner process is initializing source-map-support passing a special source map loader that makes it possible 67 | // to use maps generated by the "parent" process instead of reading them from the filesystem. 68 | // We don't allow for source-map-support library to be reinitialized 69 | module_.exports.install = () => {}; 70 | } 71 | } 72 | }; 73 | } 74 | 75 | function replaceExtensionHooks(extensions) { 76 | for (const ext in require.extensions) { 77 | registerExtension(ext); 78 | } 79 | for (let i = 0; i < extensions.length; i++) { 80 | const ext = extensions[i]; 81 | if (!(ext in require.extensions)) { 82 | registerExtension(ext); 83 | } 84 | } 85 | } 86 | 87 | process.on('message', (options) => { 88 | replaceExtensionHooks(options.transpileExtensions); 89 | sourceMapSupport.install({ 90 | environment: 'node', 91 | handleUncaughtExceptions: !!options.handleUncaughtExceptions, 92 | retrieveSourceMap(filename) { 93 | const map = maps && maps[filename]; 94 | if (map) { 95 | return { 96 | url: filename, 97 | map: map 98 | }; 99 | } 100 | return null; 101 | }, 102 | }); 103 | // We don't allow for source-map-support library to be reinitialized (see comment in registerExtension function) 104 | sourceMapSupport.install = () => {}; 105 | 106 | pipeFd = fs.openSync(options.pipe, 'r'); 107 | process.argv = ["node"].concat(options.args); 108 | require('module').runMain(); 109 | }); 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # babel-watch 2 | 3 | Reload your babel-node app on JS source file changes. And do it *fast*. 4 | 5 | ## Why should I use it? 6 | 7 | If you're tired of using [`babel-node`](https://github.com/babel/babel/tree/master/packages/babel-cli) together with [`nodemon`](https://github.com/remy/nodemon) (or similar solution). The reason why the aforementioned setup performs so badly is the startup time of `babel-node` itself. `babel-watch` only starts `babel` in the "master" process where it also starts the file watcher. The transpilation is performed in that process too. On file-watcher events, it spawns a pure `node` process and passes transpiled code from the parent process together with the source maps. This allows us to avoid loading `babel` and all its deps every time we restart the JS script/app. 8 | 9 | ## Autowatch 10 | 11 | A unique feature of `babel-watch` is capability of automatically detecting files that needs to be watched. You no longer need to specify the list of files or directories to watch for. With "autowatch" the only thing you need to do is to pass the name of your main script and `babel-watch` will start watching for the changes on files that are loaded by your node program while it is executing. (You can disable autowatch with `-D` option or exclude some directories from being watched automatically with `-x`). 12 | 13 | ## System requirements 14 | 15 | Currently `babel-watch` is supported on Linux, OSX and Windows. 16 | 17 | ## I want it 18 | 19 | Just install it and add to your package: 20 | 21 | With NPM: 22 | ```bash 23 | npm install --save-dev babel-watch 24 | ``` 25 | 26 | With Yarn: 27 | ```bash 28 | yarn add --dev babel-watch 29 | ``` 30 | 31 | (Make sure you have `babel-core` installed as dependency in your project as `babel-watch` only defines `babel-core` as a "peerDependency") 32 | 33 | Then use `babel-watch` in your `package.json` in scripts section like this: 34 | ```json 35 | "scripts": { 36 | "start": "babel-watch src/main.js" 37 | } 38 | ``` 39 | 40 | ## Options 41 | 42 | `babel-watch` was made to be compatible with `babel-node` and `nodemon` options. Not all of them are supported yet, here is a short list of supported command line options: 43 | 44 | ``` 45 | -d, --debug [port] Start debugger on port 46 | -B, --debug-brk Enable debug break mode 47 | -I, --inspect Enable inspect mode 48 | -o, --only [globs] Matching files will be transpiled 49 | -i, --ignore [globs] Matching files will not be transpiled 50 | -e, --extensions [extensions] List of extensions to hook into [.es6,.js,.es,.jsx] 51 | -p, --plugins [string] 52 | -b, --presets [string] 53 | -w, --watch [dir] Watch directory "dir" or files. Use once for each directory or file to watch 54 | -x, --exclude [dir] Exclude matching directory/files from watcher. Use once for each directory or file. 55 | -L, --use-polling In some filesystems watch events may not work correcly. This option enables "polling" which should mitigate this type of issues 56 | -D, --disable-autowatch Don't automatically start watching changes in files "required" by the program 57 | -H, --disable-ex-handler Disable source-map-enhanced uncaught exception handler. (you may want to use this option in case your app registers a custom uncaught exception handler) 58 | -m, --message [string] Set custom message displayed on restart (default is ">>> RESTARTING <<<") 59 | ``` 60 | 61 | While the `babel-watch` process is running you may type "rs" and hit return in the terminal to force reload the app. 62 | 63 | ### Example usage: 64 | 65 | In most of the cases you would rely on "autowatch" to monitor all the files that are required by your node application. In that case you just run: 66 | 67 | ```bash 68 | babel-watch app.js 69 | ``` 70 | 71 | If you have your view templates (build with [pug](https://github.com/pugjs/pug), [mustache](https://github.com/janl/mustache.js) or any other templating library) in the directory called `views`, autowatch will not be able to detect changes in view template files (see [why](#user-content-application-doesnt-restart-when-i-change-one-of-the-view-templates-html-file-or-similar)) , so you need to pass in that directory name using `--watch` option: 72 | 73 | ```bash 74 | babel-watch --watch views app.js 75 | ``` 76 | 77 | When you want your app not to restart automatically for some set of files, you can use `--exclude` option: 78 | 79 | ```bash 80 | babel-watch --exclude templates app.js 81 | ``` 82 | 83 | Start the debugger 84 | 85 | ```bash 86 | babel-watch app.js --debug 5858 87 | ``` 88 | 89 | ## Demo 90 | 91 | Demo of `nodemon + babel-node` (on the left) and `babel-watch` reloading simple `express` based app: 92 | 93 | ![](https://raw.githubusercontent.com/kmagiera/babel-watch/master/docs/demo.gif) 94 | 95 | ## Important information 96 | 97 | Using `babel-node` or `babel-watch` is not recommended in production environment. For the production use it is much better practice to build your node application using `babel` and run it using just `node`. 98 | 99 | ## Babel compatibility 100 | 101 | * `babel-watch >= 2.0.2` is compatible with `babel-core` version `6.5.1` and above 102 | * `babel-watch <= 2.0.1` is compatible with `babel-core` from `6.4.x` up to `6.5.0` 103 | 104 | *(This is due to the babel's "option manager" API change in `babel-core@6.5.1`)* 105 | 106 | ## Troubleshooting 107 | 108 | #### Application doesn't restart automatically 109 | 110 | There are a couple of reasons that could be causing that: 111 | 112 | 1. You filesystem configuration doesn't trigger filewatch notification (this could happen for example when you have `babel-watch` running within docker container and have filesystem mirrored). In that case try running `babel-watch` with `-L` option which will enable polling for file changes. 113 | 2. Files you're updating are blacklisted. Check the options you pass to babel-watch and verify that files you're updating are being used by your app and their name does not fall into any exclude pattern (option `-x` or `--exclude`). 114 | 115 | 116 | #### Application doesn't restart when I change one of the view templates (html file or similar): 117 | 118 | You perhaps are using autowatch. Apparently since view templates are not loaded using `require` command but with `fs.read` instead, therefore autowatch is not able to detect that they are being used. You can still use autowatch for all the js sources, but need to specify the directory name where you keep your view templates so that changes in these files can trigger app restart. This can be done using `--watch` option (e.g. `babel-watch --watch views app.js`). 119 | 120 | #### I'm getting an error: *Cannot find module 'babel-core'* 121 | 122 | `babel-watch` does not have `babel-core` listed as a direct dependency but as a "peerDependency". If you're using `babel` in your app you should already have `babel-core` installed. If not you should do `npm install --save-dev babel-core`. We decided not to make `babel-core` a direct dependency as in some cases having it defined this way would make your application pull two versions of `babel-core` from `npm` during installation and since `babel-core` is quite a huge package that's something we wanted to avoid. 123 | 124 | #### Every time I run a script, I get a load of temporary files clogging up my project root 125 | 126 | `babel-watch` creates a temporary file each time it runs in order to watch for changes. When running as an npm script, this can end up putting these files into your project root. This is due to an [issue in npm](https://github.com/npm/npm/issues/4531) which changes the value of `TMPDIR` to the current directory. To fix this, change your npm script from `babel-watch ./src/app.js` to `TMPDIR=/tmp babel-watch ./src/app.js`. 127 | 128 | #### I'm getting `regeneratorRuntime is not defined` error when running with babel-watch but babel-node runs just fine 129 | 130 | The reason why you're getting the error is because the babel regenerator plugin (that gives you support for async functions) requires a runtime library to be included with your application. You will get the same error when you build your app with `babel` first and then run with `node`. It works fine with `babel-node` because it includes `babel-polyfill` module automatically whenever it runs your app, even if you don't use features like async functions (that's one of the reason why its startup time is so long). Please see [this answer on stackoverflow](http://stackoverflow.com/a/36821986/1665044) to learn how to fix this issue 131 | 132 | 133 | #### Still having some issues 134 | 135 | Try searching over the issues on GitHub [here](https://github.com/kmagiera/babel-watch/issues). If you don't find anything that would help feel free to open new issue! 136 | 137 | 138 | ## Contributing 139 | 140 | All PRs are welcome! 141 | -------------------------------------------------------------------------------- /babel-watch.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | const chokidar = require('chokidar'); 6 | const path = require('path'); 7 | const babel = require('babel-core'); 8 | const fs = require('fs'); 9 | const os = require('os'); 10 | const util = require('util'); 11 | const fork = require('child_process').fork; 12 | const execSync = require('child_process').execSync; 13 | const commander = require('commander'); 14 | 15 | const RESTART_COMMAND = 'rs'; 16 | 17 | const program = new commander.Command("babel-watch"); 18 | 19 | function collect(val, memo) { 20 | memo.push(val); 21 | return memo; 22 | } 23 | 24 | program.option('-d, --debug [port]', 'Set debugger port') 25 | program.option('-B, --debug-brk', 'Enable debug break mode') 26 | program.option('-I, --inspect', 'Enable inspect mode') 27 | program.option('-o, --only [globs]', 'Matching files will be transpiled'); 28 | program.option('-i, --ignore [globs]', 'Matching files will not be transpiled'); 29 | program.option('-e, --extensions [extensions]', 'List of extensions to hook into [.es6,.js,.es,.jsx]'); 30 | program.option('-p, --plugins [string]', '', babel.util.list); 31 | program.option('-b, --presets [string]', '', babel.util.list); 32 | program.option('-w, --watch [dir]', 'Watch directory "dir" or files. Use once for each directory or file to watch', collect, []); 33 | program.option('-x, --exclude [dir]', 'Exclude matching directory/files from watcher. Use once for each directory or file.', collect, []); 34 | program.option('-L, --use-polling', 'In some filesystems watch events may not work correcly. This option enables "polling" which should mitigate this type of issues'); 35 | program.option('-D, --disable-autowatch', 'Don\'t automatically start watching changes in files "required" by the program'); 36 | program.option('-H, --disable-ex-handler', 'Disable source-map-enhanced uncaught exception handler. (you may want to use this option in case your app registers a custom uncaught exception handler)'); 37 | program.option('-m, --message [string]', 'Set custom message displayed on restart (default is ">>> RESTARTING <<<")'); 38 | 39 | const pkg = require('./package.json'); 40 | program.version(pkg.version); 41 | program.usage('[options] [script.js] [args]'); 42 | program.description('babel-watch is a babel-js node app runner that lets you reload the app on JS source file changes.'); 43 | program.on('--help', () => { 44 | console.log(`\ 45 | About "autowatch": 46 | 47 | "Autowatch" is the default behavior in babel-watch. Thanks to that mechanism babel-watch will automatically 48 | detect files that are "required" (or "imported") by your program and start to watch for changes on those files. 49 | It means that you no longer need to specify -w (--watch) option with a list of directories you are willing to 50 | monitor changes in. You can disable "autowatch" with -D option or limit the list of files it will be enabled for 51 | using the option -x (--exclude). 52 | 53 | Babel.js configuration: 54 | 55 | You may use some of the options listed above to customize plugins/presets and matching files that babel.js 56 | is going to use while transpiling your app's source files but we recommend that you use .babelrc file as 57 | babel-watch works with .babelrc just fine. 58 | 59 | IMPORTANT: 60 | 61 | babel-watch is meant to **only** be used during development. In order to support fast reload cycles it uses more 62 | memory than plain node process. We recommend that when you deploy your app to production you pre-transpile source 63 | files and run your application using node directly (avoid babel-node too for the same reasons). 64 | 65 | Examples: 66 | 67 | $ babel-watch server.js 68 | $ babel-watch -x templates server.js 69 | $ babel-watch --presets es2015 server.js --port 8080 70 | 71 | See more: 72 | 73 | https://github.com/kmagiera/babel-watch 74 | `); 75 | }); 76 | program.parse(process.argv); 77 | 78 | const cwd = process.cwd(); 79 | 80 | let only, ignore; 81 | 82 | if (program.only != null) only = babel.util.arrayify(program.only, babel.util.regexify); 83 | if (program.ignore != null) ignore = babel.util.arrayify(program.ignore, babel.util.regexify); 84 | 85 | let transpileExtensions = babel.util.canCompile.EXTENSIONS; 86 | 87 | if (program.extensions) { 88 | transpileExtensions = transpileExtensions.concat(babel.util.arrayify(program.extensions)); 89 | } 90 | 91 | const mainModule = program.args[0]; 92 | if (!mainModule) { 93 | console.error('Main script not specified'); 94 | process.exit(1); 95 | } 96 | if (!mainModule.startsWith('.') && !mainModule.startsWith('/')) { 97 | program.args[0] = path.join(cwd, mainModule); 98 | } 99 | 100 | const transformOpts = { 101 | plugins: program.plugins, 102 | presets: program.presets, 103 | }; 104 | 105 | let childApp, pipeFd, pipeFilename; 106 | 107 | const cache = {}; 108 | const errors = {}; 109 | 110 | const watcher = chokidar.watch(program.watch, { 111 | persistent: true, 112 | ignored: program.exclude, 113 | ignoreInitial: true, 114 | usePolling: program.usePolling, 115 | }); 116 | let watcherInitialized = (program.watch.length === 0); 117 | 118 | process.on('SIGINT', function() { 119 | watcher.close(); 120 | process.exit(1); 121 | }); 122 | 123 | watcher.on('change', handleChange); 124 | watcher.on('add', handleChange); 125 | watcher.on('unlink', handleChange); 126 | 127 | watcher.on('ready', () => { 128 | if (!watcherInitialized) { 129 | watcherInitialized = true; 130 | restartApp(); 131 | } 132 | }); 133 | 134 | watcher.on('error', error => { 135 | console.error('Watcher failure', error); 136 | process.exit(1); 137 | }); 138 | 139 | // Restart the app when a sequence of keys has been pressed ('rs' by refault) 140 | const stdin = process.stdin; 141 | stdin.setEncoding('utf8'); 142 | stdin.on('data', (data) => { 143 | if (String(data).trim() === RESTART_COMMAND) { 144 | restartApp(); 145 | } 146 | }); 147 | 148 | function handleChange(file) { 149 | const absoluteFile = file.startsWith('/') ? file : path.join(cwd, file); 150 | delete cache[absoluteFile]; 151 | delete errors[absoluteFile]; 152 | 153 | // file is in use by the app, let's restart! 154 | restartApp(); 155 | } 156 | 157 | function generateTempFilename() { 158 | const now = new Date(); 159 | return path.join(os.tmpdir(), [ 160 | now.getYear(), now.getMonth(), now.getDate(), 161 | '-', 162 | process.pid, 163 | '-', 164 | (Math.random() * 0x100000000 + 1).toString(36), 165 | ].join('')); 166 | } 167 | 168 | function handleFileLoad(filename, callback) { 169 | const cached = cache[filename]; 170 | if (cached) { 171 | const stats = fs.statSync(filename); 172 | if (stats.mtime.getTime() === cached.mtime) { 173 | callback(cache[filename].code, cache[filename].map); 174 | return; 175 | } 176 | } 177 | if (!shouldIgnore(filename)) { 178 | compile(filename, (err, result) => { 179 | if (err) { 180 | console.error('Babel compilation error', err.stack); 181 | errors[filename] = true; 182 | return; 183 | } 184 | const stats = fs.statSync(filename); 185 | cache[filename] = { 186 | code: result.code, 187 | map: result.map, 188 | mtime: stats.mtime.getTime(), 189 | }; 190 | delete errors[filename]; 191 | callback(result.code, result.map); 192 | }); 193 | } else { 194 | callback(); 195 | } 196 | } 197 | 198 | function killApp() { 199 | if (childApp) { 200 | const currentPipeFd = pipeFd; 201 | const currentPipeFilename = pipeFilename; 202 | childApp.on('exit', () => { 203 | if (currentPipeFd) { 204 | fs.closeSync(currentPipeFd); // silently close pipe fd 205 | } 206 | if (currentPipeFilename) { 207 | fs.unlinkSync(currentPipeFilename); // silently remove old pipe file 208 | } 209 | restartAppInternal(); 210 | }); 211 | try { 212 | childApp.kill('SIGHUP'); 213 | } catch (error) { 214 | childApp.kill('SIGKILL'); 215 | } 216 | pipeFd = undefined; 217 | pipeFilename = undefined; 218 | childApp = undefined; 219 | } 220 | } 221 | 222 | function prepareRestart() { 223 | if (watcherInitialized && childApp) { 224 | // kill app early as `compile` may take a while 225 | var restartMessage = program.message ? program.message : ">>> RESTARTING <<<"; 226 | console.log(restartMessage); 227 | killApp(); 228 | } else { 229 | restartAppInternal(); 230 | } 231 | } 232 | 233 | function restartApp() { 234 | if (!watcherInitialized) return; 235 | prepareRestart(); 236 | } 237 | 238 | function restartAppInternal() { 239 | if (Object.keys(errors).length != 0) { 240 | // There were some transpilation errors, don't start unless solved or invalid file is removed 241 | return; 242 | } 243 | 244 | pipeFilename = generateTempFilename(); 245 | 246 | if (os.platform() === 'win32') { 247 | try { 248 | execSync(`echo. > ${pipeFilename}`); 249 | } catch (e) { 250 | console.error(`Unable to create file ${pipeFilename}`); 251 | process.exit(1); 252 | } 253 | } else { 254 | try { 255 | execSync(`mkfifo -m 0666 ${pipeFilename}`); 256 | } catch (e) { 257 | console.error('Unable to create named pipe with mkfifo. Are you on linux/OSX?'); 258 | process.exit(1); 259 | } 260 | } 261 | 262 | // Support for --debug option 263 | const runnerExecArgv = process.execArgv.slice(); 264 | if (program.debug) { 265 | runnerExecArgv.push('--debug=' + program.debug); 266 | } 267 | // Support for --inspect option 268 | if (program.inspect) { 269 | runnerExecArgv.push('--inspect'); 270 | } 271 | // Support for --debug-brk 272 | if(program.debugBrk) { 273 | runnerExecArgv.push('--debug-brk'); 274 | } 275 | 276 | const app = fork(path.resolve(__dirname, 'runner.js'), { execArgv: runnerExecArgv }); 277 | 278 | app.on('message', (data) => { 279 | const filename = data.filename; 280 | if (!program.disableAutowatch) { 281 | // use relative path for watch.add as it would let chokidar reconsile exclude patterns 282 | const relativeFilename = path.relative(cwd, filename); 283 | watcher.add(relativeFilename); 284 | } 285 | handleFileLoad(filename, (source, sourceMap) => { 286 | const sourceBuf = new Buffer(source || 0); 287 | const mapBuf = new Buffer(sourceMap ? JSON.stringify(sourceMap) : 0); 288 | const lenBuf = new Buffer(4); 289 | try { 290 | lenBuf.writeUInt32BE(sourceBuf.length, 0); 291 | fs.writeSync(pipeFd, lenBuf, 0, 4); 292 | sourceBuf.length && fs.writeSync(pipeFd, sourceBuf, 0, sourceBuf.length); 293 | 294 | lenBuf.writeUInt32BE(mapBuf.length, 0); 295 | fs.writeSync(pipeFd, lenBuf, 0, 4); 296 | mapBuf.length && fs.writeSync(pipeFd, mapBuf, 0, mapBuf.length); 297 | } catch (error) { 298 | // EPIPE means `pipeFd` has been closed. We can ignore this 299 | if (error.code !== 'EPIPE') { 300 | throw error; 301 | } 302 | } 303 | }); 304 | }); 305 | 306 | app.send({ 307 | pipe: pipeFilename, 308 | args: program.args, 309 | handleUncaughtExceptions: !program.disableExHandler, 310 | transpileExtensions: transpileExtensions, 311 | }); 312 | pipeFd = fs.openSync(pipeFilename, 'w'); 313 | childApp = app; 314 | } 315 | 316 | function shouldIgnore(filename) { 317 | if (transpileExtensions.indexOf(path.extname(filename)) < 0) { 318 | return true; 319 | } else if (!ignore && !only) { 320 | // Ignore node_modules by default 321 | return path.relative(cwd, filename).split(path.sep).indexOf('node_modules') >= 0; 322 | } else { 323 | return babel.util.shouldIgnore(filename, ignore || [], only); 324 | } 325 | } 326 | 327 | function compile(filename, callback) { 328 | const optsManager = new babel.OptionManager; 329 | 330 | // merge in base options and resolve all the plugins and presets relative to this file 331 | optsManager.mergeOptions({ 332 | options: transformOpts, 333 | alias: 'base', 334 | loc: path.dirname(filename) 335 | }); 336 | 337 | const opts = optsManager.init({ filename }); 338 | // Do not process config files since has already been done with the OptionManager 339 | // calls above and would introduce duplicates. 340 | opts.babelrc = false; 341 | opts.sourceMap = true; 342 | opts.ast = false; 343 | 344 | return babel.transformFile(filename, opts, (err, result) => { 345 | callback(err, result); 346 | }); 347 | } 348 | 349 | restartApp(); 350 | --------------------------------------------------------------------------------