This is an automated email from the ASF dual-hosted git repository. bigosmallm pushed a commit to branch feature/royale-cli in repository https://gitbox.apache.org/repos/asf/royale-asjs.git
commit c8cca4de565d9a2b0ecced5343308ffe66233f80 Author: Om Muppirala <omuppir...@mz.com> AuthorDate: Wed Feb 14 19:19:51 2018 -0800 Add auto reload functionality --- npm/cli/connect.js | 14 +++ npm/cli/index.js | 72 ++++++++++----- npm/cli/package.json | 8 ++ npm/cli/server.js | 36 ++++++++ npm/cli/template/reload/reload-client.js | 102 +++++++++++++++++++++ npm/cli/template/reload/reload-server.js | 79 ++++++++++++++++ npm/cli/template/reload/reload.js | 153 +++++++++++++++++++++++++++++++ 7 files changed, 439 insertions(+), 25 deletions(-) diff --git a/npm/cli/connect.js b/npm/cli/connect.js new file mode 100644 index 0000000..1ba9adb --- /dev/null +++ b/npm/cli/connect.js @@ -0,0 +1,14 @@ +var path = require('path'); +var connect = require('connect'); +var static = require('serve-static'); +var fs = require('fs-extra'); + +var debugDirPath = path.join(process.cwd(), 'bin', 'js-debug'); + +var server = connect(); +server.use( static(debugDirPath)); +server.listen(3000); + +var livereload = require('livereload'); +var lrserver = livereload.createServer(); +lrserver.watch(debugDirPath); \ No newline at end of file diff --git a/npm/cli/index.js b/npm/cli/index.js index e119845..766dce8 100644 --- a/npm/cli/index.js +++ b/npm/cli/index.js @@ -21,9 +21,13 @@ var fs = require('fs-extra'); var path = require('path'); var execSync = require('child_process').execSync; -var server=require('node-http-server'); +var spawn = require('child_process').spawn; +var fork = require('child_process').fork; +//var server=require('node-http-server'); +//var server = require('./server'); var chokidar = require('chokidar'); var open = require("open"); +var livereload = require('livereload'); var args = require('yargs') .usage('Usage: royale <command>') @@ -87,46 +91,64 @@ function compileDebug() { //Compile project in debug mode var command = 'mxmlc ' + path.join(process.cwd(), SOURCE_DIR_NAME , 'Main.mxml -debug=true'); console.log('Compiling...'); - execSync(command); + try { + execSync(command); + } + catch(e) { + console.error(e.message); + } console.log('Finished compiling'); + updateIndex(); + watchFiles(); + openBrowser('http://localhost:' + DEBUG_PORT); } var debugServerRunning = false; // Start server if it is not already running function startDebugServer() { if(!debugServerRunning) { - server.deploy( - { - port: DEBUG_PORT, - root: path.join(process.cwd(), 'bin', 'js-debug') - }, - handleDebugServerReady - ); + var command = 'node ' + path.join(__dirname, 'connect.js'); + console.log('Starting server on localhost:3000...'); + fork(path.join(__dirname, 'connect.js')); } } -function handleDebugServerReady() { - debugServerRunning = true; - console.log('Your Apache Royale app (debug version) is running on localhost:%s', DEBUG_PORT); - watchFiles(); - openBrowser('http://localhost:' + DEBUG_PORT); +function updateIndex() { + var debugDirPath = path.join(process.cwd(), 'bin', 'js-debug'); + //Rewrite index.html + var indexHTML = fs.readFileSync(path.join(debugDirPath, 'index.html'), 'utf8'); + indexHTML = indexHTML.replace("</html>", '<script>\n' + + ' document.write(\'<script src="http://\' + (location.host || \'localhost\').split(\':\')[0] +\n' + + ' \':35729/livereload.js?snipver=1"></\' + \'script>\')\n' + + '</script></html>'); + fs.writeFile(path.join(debugDirPath, 'index.html'), indexHTML); } +var watchingFiles = false; function watchFiles() { - //Watch the SOURCE_DIR_NAME directory, recompile if anything changes - var pathToWatch = path.join(process.cwd(), SOURCE_DIR_NAME); - console.log('Watching the directory %s for changes...', pathToWatch); - chokidar.watch(pathToWatch) - .on('change', - function(event, path){ - console.log('Change detected...'); - compileDebug(); - } - ); + if(!watchingFiles) { + watchingFiles = true; + //Watch the SOURCE_DIR_NAME directory, recompile if anything changes + var pathToWatch = path.join(process.cwd(), SOURCE_DIR_NAME); + console.log('Watching the directory %s for changes...', pathToWatch); + chokidar.watch(pathToWatch) + .on('change', + function(path){ + console.log('Change detected... %s', path); + + compileDebug(); + } + ); + } } +var browserOpened = false; function openBrowser(url) { - open(url); + if(!browserOpened) { + browserOpened = true; + console.log('Opening browser: ', url); + open(url); + } } function serveRelease() { diff --git a/npm/cli/package.json b/npm/cli/package.json index 150b0f1..c64ec43 100644 --- a/npm/cli/package.json +++ b/npm/cli/package.json @@ -20,10 +20,18 @@ "author": "Apache Royale <d...@royale.apache.org>", "license": "Apache-2.0", "dependencies": { + "child_process": "1.0.2", "chokidar": "2.0.1", + "connect": "3.6.5", + "express": "4.16.2", "fs-extra": "5.0.0", + "http": "0.0.0", + "livereload": "0.6.3", + "morgan": "1.9.0", "node-http-server": "8.1.2", "open": "0.0.5", + "reload": "2.2.2", + "serve-static": "1.13.2", "yargs": "11.0.0" }, "bin": { diff --git a/npm/cli/server.js b/npm/cli/server.js new file mode 100644 index 0000000..4d6ed2a --- /dev/null +++ b/npm/cli/server.js @@ -0,0 +1,36 @@ +var express = require('express'); +var http = require('http'); +var path = require('path'); +var reload = require('reload'); +var logger = require('morgan'); +var app = express(); +var fs = require('fs-extra'); +var chokidar = require('chokidar'); + +var debugServer = module.exports = Object.create(null); +debugServer.startDebugServer = function(p, port) { + //Rewrite index.html + var indexHTML = fs.readFileSync(path.join(p,'index.html'), 'utf8'); + indexHTML = indexHTML.replace("</html>", '<script src="/reload/reload-client.js"></script></html>'); + fs.writeFile(path.join(p,'index.html'), indexHTML); + + //Start server + app.set('port', port); + app.use(logger('dev')); + app.use(express.static(p)); + var server = http.createServer(app); + var reloadServer = reload(app); + + //Watch and reload + chokidar.watch(p) + .on('change', + function(event, path){ + console.log('Change detected...'); + reloadServer.reload(); + } + ); + + server.listen(app.get('port'), function () { + console.log('Web server listening on port ' + app.get('port')) + }) +} diff --git a/npm/cli/template/reload/reload-client.js b/npm/cli/template/reload/reload-client.js new file mode 100644 index 0000000..348c8c6 --- /dev/null +++ b/npm/cli/template/reload/reload-client.js @@ -0,0 +1,102 @@ +(function refresh () { + var verboseLogging = false + var socketUrl = window.location.origin + if (!window.location.origin.match(/:[0-9]+/)) { + socketUrl = window.location.origin + ':80' + } + socketUrl = socketUrl.replace() // This is dynamically populated by the reload.js file before it is sent to the browser + var socket + + if (verboseLogging) { + console.log('Reload Script Loaded') + } + + if (!('WebSocket' in window)) { + throw new Error('Reload only works with browsers that support WebSockets') + } + + // Explanation of the flags below: + + // The first change flag is used to tell reload to wait until the socket closes at least once before we allow the page to open on a socket open event. Otherwise reload will go into a inifite loop, as the page will have a socket on open event once it loads for the first time + var firstChangeFlag = false + + // The navigatedAwayFromPageFlag is set to true in the event handler onbeforeunload because we want to short-circuit reload to prevent it from causing the page to reload before the navigation occurs. + var navigatedAwayFromPageFlag + + // Wait until the page loads for the first time and then call the webSocketWaiter function so that we can connect the socket for the first time + window.addEventListener('load', function () { + if (verboseLogging === true) { + console.log('Page Loaded - Calling webSocketWaiter') + } + websocketWaiter() + }) + + // If the user navigates away from the page, we want to short-circuit reload to prevent it from causing the page to reload before the navigation occurs. + window.addEventListener('beforeunload', function () { + if (verboseLogging === true) { + console.log('Navigated away from the current URL') + } + + navigatedAwayFromPageFlag = true + }) + + // Check to see if the server sent us reload (meaning a manually reload event was fired) and then reloads the page + var socketOnMessage = function (msg) { + if (msg.data === 'reload') { + socket.close() + } + } + + var socketOnOpen = function (msg) { + if (verboseLogging) { + console.log('Socket Opened') + } + + // We only allow the reload on two conditions, one when the socket closed (firstChange === true) and two if we didn't navigate to a new page (navigatedAwayFromPageFlag === false) + if (firstChangeFlag === true && navigatedAwayFromPageFlag !== true) { + if (verboseLogging) { + console.log('Reloaded') + } + + // Reset the firstChangeFlag to false so that when the socket on open events are being fired it won't keep reloading the page + firstChangeFlag = false + + // Now that everything is set up properly we reload the page + window.location.reload() + } + } + + // Socket on close event that sets flags and calls the webSocketWaiter function + var socketOnClose = function (msg) { + if (verboseLogging) { + console.log('Socket Closed - Calling webSocketWaiter') + } + + // We encountered a change so we set firstChangeFlag to true so that as soon as the server comes back up and the socket opens we can allow the reload + firstChangeFlag = true + + // Call the webSocketWaiter function so that we can open a new socket and set the event handlers + websocketWaiter() + } + + var socketOnError = function (msg) { + if (verboseLogging) { + console.log(msg) + } + } + + // Function that opens a new socket and sets the event handlers for the socket + function websocketWaiter () { + if (verboseLogging) { + console.log('Waiting for socket') + } + setTimeout(function () { + socket = new WebSocket(socketUrl) // eslint-disable-line + + socket.onopen = socketOnOpen + socket.onclose = socketOnClose + socket.onmessage = socketOnMessage + socket.onerror = socketOnError + }, 250) + } +})() diff --git a/npm/cli/template/reload/reload-server.js b/npm/cli/template/reload/reload-server.js new file mode 100644 index 0000000..c855b60 --- /dev/null +++ b/npm/cli/template/reload/reload-server.js @@ -0,0 +1,79 @@ +var http = require('http') +var reload = require('../lib/reload') +var fs = require('fs') +var open = require('open') +var clc = require('cli-color') +var argv = require('minimist')(process.argv.slice(2)) + +var serveStatic = require('serve-static') +var finalhandler = require('finalhandler') +var URL = require('url-parse') + +var port = argv._[0] +var dir = argv._[1] +var openBrowser = (argv._[2] === 'true') +var hostname = argv._[3] +var runFile = argv._[4] +var startPage = argv._[5] +var verbose = (argv._[6] === 'true') + +var reloadOpts = { + port: port, + verbose: verbose, + noExpress: true +} + +var time +var reloadReturned + +var serve = serveStatic(dir, {'index': ['index.html', 'index.htm']}) + +var server = http.createServer(function (req, res) { + var url = new URL(req.url) + var pathname = url.pathname.replace(/(\/)(.*)/, '$2') // Strip leading `/` so we can find files on file system + + var fileEnding = pathname.split('.')[1] + + if (fileEnding === 'html' || pathname === '/' || pathname === '') { // Server side inject reload code to html files + if (pathname === '/' || pathname === '') { + pathname = dir + '/' + startPage + } else { + pathname = dir + '/' + pathname + } + + fs.readFile(pathname, 'utf8', function (err, contents) { + if (err) { + res.writeHead(404, {'Content-Type': 'text/plain'}) + res.end('File Not Found') + } else { + contents += '\n\n<!-- Inserted by Reload -->\n<script src="/reload/reload.js"></script>\n<!-- End Reload -->\n' + + res.setHeader('Content-Type', 'text/html') + res.end(contents) + } + }) + } else if (pathname === 'reload/reload.js') { // Server reload-client.js file from injected script tag + res.setHeader('Content-Type', 'text/javascript') + + res.end(reloadReturned.reloadClientCode()) + } else { // Serve any other file using serve-static + serve(req, res, finalhandler(req, res)) + } +}) + +// Reload call and configurations. Stub app as it isn't used here +reloadReturned = reload(function () {}, reloadOpts, server) + +server.listen(port, function () { + if (!fs.existsSync(runFile)) { + fs.writeFileSync(runFile) + + // If openBrowser, open the browser with the given start page above, at a hostname (localhost default or specified). + if (openBrowser) { + open('http://' + hostname + ':' + port) + } + } else { + time = new Date() + console.log(clc.green('Server restarted at ' + time.toTimeString().slice(0, 8))) + } +}) diff --git a/npm/cli/template/reload/reload.js b/npm/cli/template/reload/reload.js new file mode 100644 index 0000000..cbb43c4 --- /dev/null +++ b/npm/cli/template/reload/reload.js @@ -0,0 +1,153 @@ +module.exports = function reload (app, opts, server) { + // Requires + var path = require('path') + var fs = require('fs') + + // Parameters variables + var httpServerOrPort + var expressApp + var verboseLogging + var port + var webSocketServerWaitStart + + // Application variables + var RELOAD_FILE = path.join(__dirname, './reload-client.js') + var reloadCode = fs.readFileSync(RELOAD_FILE, 'utf8') + var route + + // Websocket server variables + var ws = require('ws') + var WebSocketServer = ws.Server + var wss + + // General variables + var socketPortSpecified + var argumentZero = arguments[0] + var reloadJsMatch + var reloadReturn + + opts = opts || {} + + if (arguments.length > 0 && (typeof (argumentZero) === 'object' || typeof (argumentZero) === 'function')) { + if (typeof (argumentZero) === 'object') { // If old arguments passed handle old arguments, the old arguments and their order were: httpServerOrPort, expressApp, and verboseLogging + console.warn('Deprecated Warning: You supplied reload old arguments, please upgrade to the new parameters see: https://github.com/alallier/reload/tree/master#api-for-express') + if (arguments.length < 2) { + throw new TypeError('Lack of/invalid arguments provided to reload. It is recommended to update to the new arguments anyways, this would be a good time to do so.', 'reload.js', 7) + } + httpServerOrPort = argumentZero + expressApp = arguments[1] + verboseLogging = arguments[2] + route = '/reload/reload.js' + } else { // Setup options or use defaults + expressApp = argumentZero + port = opts.port || 9856 + webSocketServerWaitStart = opts.webSocketServerWaitStart + route = opts.route + + if (route) { + // If reload.js is found in the route option strip it. We will concat it for user to ensure no case errors or order problems. + reloadJsMatch = route.match(/reload\.js/i) + if (reloadJsMatch) { + route = route.split(reloadJsMatch)[0] + } + + /* + * Concat their provided path (minus `reload.js` if they specified it) with a `/` if they didn't provide one and `reload.js. This allows for us to ensure case, order, and use of `/` is correct + * For example these route's are all valid: + * 1. `newRoutePath` -> Their route + `/` + reload.js + * 2. `newRoutePath/` -> Their route + reload.js + * 3. `newRoutePath/reload.js` -> (Strip reload.js above) so now: Their route + reload.js + * 4. `newRoutePath/rEload.js` -> (Strip reload.js above) so now: Their route + reload.js + * 5. `newRoutePathreload.js` -> (Strip reload.js above) so now: Their route + `/` + reload.js + * 6. `newRoutePath/reload.js/rEload.js/... reload.js n number of times -> (Strip above removes all reload.js occurrences at the end of the specified route) so now: Their route + 'reload.js` + */ + route = route + (route.slice(-1) === '/' ? '' : '/') + 'reload.js' + } else { + route = '/reload/reload.js' + } + + verboseLogging = opts.verbose === true || opts.verbose === 'true' || false + + if (port) { + socketPortSpecified = port + httpServerOrPort = port + } + + if (server) { + socketPortSpecified = null + httpServerOrPort = server + } + } + } else { + throw new TypeError('Lack of/invalid arguments provided to reload', 'reload.js', 7) + } + + // Application setup + if (verboseLogging) { + reloadCode = reloadCode.replace('verboseLogging = false', 'verboseLogging = true') + } + reloadCode = reloadCode.replace('socketUrl.replace()', 'socketUrl.replace(/(^http(s?):\\/\\/)(.*:)(.*)/,' + (socketPortSpecified ? '\'ws$2://$3' + socketPortSpecified : '\'ws$2://$3$4') + '\')') + + if (!server) { + expressApp.get(route, function (req, res) { + res.type('text/javascript') + res.send(reloadCode) + }) + } + + if (!webSocketServerWaitStart) { + startWebSocketServer() + } + + // Websocket server setup + function startWebSocketServer () { + if (verboseLogging) { + console.log('Starting WebSocket Server') + } + + if (socketPortSpecified) { // Use custom user specified port + wss = new WebSocketServer({ port: httpServerOrPort }) + } else { // Attach to server, using server's port. Kept here to support legacy arguments. + wss = new WebSocketServer({ server: httpServerOrPort }) + } + + wss.on('connection', (ws) => { + if (verboseLogging) { + console.log('Reload client connected to server') + } + }) + } + + function sendMessage (message) { + if (verboseLogging) { + console.log('Sending message to ' + (wss.clients.size) + ' connection(s): ' + message) + } + + wss.clients.forEach(function each (client) { + if (client.readyState === ws.OPEN) { + client.send(message) + } + }) + } + + reloadReturn = { + 'reload': function () { + sendMessage('reload') + }, + 'startWebSocketServer': function () { + if (webSocketServerWaitStart) { + startWebSocketServer() + } + } + } + + if (server) { // Private return API only used in command line version of reload + reloadReturn.reloadClientCode = function () { + if (server) { + return reloadCode + } + } + } + + return reloadReturn +} -- To stop receiving notification emails like this one, please contact bigosma...@apache.org.