From 2ddeff49d6f548d1f8b61593ea97f269cfbe4751 Mon Sep 17 00:00:00 2001 From: Joshua Gleitze Date: Sat, 7 Nov 2015 01:28:15 +0100 Subject: [PATCH 1/5] Introduced watch. Streamlined npm interface. Updated README.md --- README.md | 31 ++++------- package.json | 43 +++++++++------ tasks/.eslintrc | 30 ++++++++++ tasks/.jsbeautifyrc | 22 ++++++++ tasks/sass.js | 118 +++++++++++++++++++++++++++++++++++++++ tasks/server.js | 38 +++++++++++++ tasks/watch.js | 132 ++++++++++++++++++++++++++++++++++++++++++++ test/setup.js | 6 +- 8 files changed, 379 insertions(+), 41 deletions(-) create mode 100644 tasks/.eslintrc create mode 100644 tasks/.jsbeautifyrc create mode 100644 tasks/sass.js create mode 100644 tasks/server.js create mode 100644 tasks/watch.js diff --git a/README.md b/README.md index b9c5c6e..9a09a47 100644 --- a/README.md +++ b/README.md @@ -22,22 +22,15 @@ LICENSE file. 4. If you want to use the backend, also install come2help-server. See the tutorial in come2help-server. -5. Install the development dependencies: -
npm install
- -6. Use bower to install the dependencies: -
bower install
- -7. Run the index.html in a browser of your choice. - Be careful about cross-site script detection, since the server is not running at the same domain as your client. - Maybe you have to use a proxy server like nginx. - If `style.css` is missing, run `npm run build-css` first to transpile SASS to CSS. - -8. Start a python Webserver with: -
python -m SimpleHTTPServer
- or -
python3 -m http.server
- No caching alternatives: -
python nocacheserver.py
- or -
python3 nocacheserver3.py
+5. Install all dependencies: +
npm install
+ +6. Start the development services: +
npm start
This will start: + * A watch to build all files that need to be built. + * A webserver + * A LiveReload server + +7. Run `localhost:8000` in a browser of your choice. Install [LiveReload](http://livereload.com/) to have it automatically updated. + +8. Start coding! diff --git a/package.json b/package.json index 03ac6ac..09a8968 100644 --- a/package.json +++ b/package.json @@ -5,19 +5,24 @@ "bugs": { "url": "https://github.com/HelfenKannJeder/come2help-web/issues" }, - "contributors": [{ - "name": "Valentin Zickner", - "email": "zickner@querformatik.de" - }, { - "name": "Peter Schuller", - "email": "ps@pzzz.de" - }, { - "name": "Joshua Gleitze", - "email": "joshua@joshuagleitze.de" - }, { - "name": "Sebastian Richter", - "email": "sebastian.max.richter@gmail.com" - }], + "contributors": [ + { + "name": "Valentin Zickner", + "email": "zickner@querformatik.de" + }, + { + "name": "Peter Schuller", + "email": "ps@pzzz.de" + }, + { + "name": "Joshua Gleitze", + "email": "joshua@joshuagleitze.de" + }, + { + "name": "Sebastian Richter", + "email": "sebastian.max.richter@gmail.com" + } + ], "license": "GPL-3.0", "repository": { "type": "git", @@ -29,12 +34,15 @@ "chai": "^3.3.0", "chai-as-promised": "^5.1.0", "closurecompiler": "^1.5.2", + "colors": "^1.1.2", "connect": "^3.4.0", "eslint": "^1.6.0", "eslint-config-angular": "^0.4.0", "eslint-plugin-angular": "^0.12.0", "extend": "^3.0.0", + "foo": "^1.0.0", "js-beautify": "^1.5.10", + "livereload": "^0.4.0", "mocha": "^2.3.3", "node-sass": "^3.3.3", "phantomjs": "^1.9.18", @@ -45,12 +53,11 @@ "unzip": "^0.1.11" }, "scripts": { - "postinstall-deploy": "npm run build-css && npm run lint && npm test && npm run browsertest", - "postinstall-dev": "npm run build-css && npm run lint && npm run watch-css", - "build-css": "node-sass css --output build/css", - "watch-css": "node-sass -w -r css --output build/css", + "postinstall": "bower install && npm run build-css", + "build-css": "node tasks/sass", "test": "mocha --slow 2000", "browsertest": "export BROWSER=TRUE && mocha --slow 30000", - "lint": "eslint js/**/*.js test/**/*.js" + "lint": "eslint js/**/*.js test/**/*.js", + "start": "node tasks/watch" } } diff --git a/tasks/.eslintrc b/tasks/.eslintrc new file mode 100644 index 0000000..4640970 --- /dev/null +++ b/tasks/.eslintrc @@ -0,0 +1,30 @@ +{ + "rules": { + "indent": [ + 2, + "tab", { + "SwitchCase": 1 + } + ], + "quotes": [ + 2, + "single" + ], + "linebreak-style": [ + 2, + "unix" + ], + "semi": [ + 2, + "always" + ], + "no-mixed-spaces-and-tabs": [ + 2, "smart-tabs" + ] + }, + "env": { + "es6": true, + "node": true + }, + "extends": "eslint:recommended" +} \ No newline at end of file diff --git a/tasks/.jsbeautifyrc b/tasks/.jsbeautifyrc new file mode 100644 index 0000000..a4656a0 --- /dev/null +++ b/tasks/.jsbeautifyrc @@ -0,0 +1,22 @@ +{ + "indent_size": 2, + "indent_char": " ", + "eol": "\n", + "indent_level": 0, + "indent_with_tabs": true, + "preserve_newlines": true, + "max_preserve_newlines": 3, + "jslint_happy": false, + "space_after_anon_function": false, + "brace_style": "collapse", + "keep_array_indentation": false, + "keep_function_indentation": false, + "space_before_conditional": true, + "break_chained_methods": false, + "eval_code": false, + "unescape_strings": false, + "wrap_line_length": 0, + "wrap_attributes": "auto", + "wrap_attributes_indent_size": 4, + "end_with_newline": false +} \ No newline at end of file diff --git a/tasks/sass.js b/tasks/sass.js new file mode 100644 index 0000000..d44c304 --- /dev/null +++ b/tasks/sass.js @@ -0,0 +1,118 @@ +/** + * Handles SCSS rendering. + */ + + +var sass = require('node-sass'); +var path = require('path'); +var fs = require('fs'); +var q = require('q'); + +var basePath = path.resolve(__dirname, '..'); +var resolve = path.resolve.bind(path, basePath); + +function returnQ(argument) { + return q.bind(q, argument); +} + +/** + * Renders the given scss file. + * + * @param {string} filePath The path to .scss file out of the css folder. + * @return {promise} A promise that will be fulfilled with the path to the rendered file when it was rendered. + */ +function renderFile(filePath) { + var outFilePath = getOutputFilePath(filePath); + return q.nfcall(sass.render, { + file: filePath, + outFile: outFilePath + }).then(function(result) { + return mkdirs(path.dirname(outFilePath)) + .then(q.nbind(fs.writeFile, fs, outFilePath, result.css)); + }).then(returnQ(outFilePath)); +} + +function getOutputFilePath(filePath) { + filePath = filePath.replace(/.scss$/, '.css'); + return resolve('build', path.relative(resolve(''), filePath)); +} + +/** + * Remove the rendered equivalent of the provided scss file. + * + * @param {string} filePath The path of a scss file that was present in the css folder, but was deleted. + * @return {promise} A promise that will be fulfilled with the path of the rendered file when it was deleted. + */ +function clean(filePath) { + var rendered = getOutputFilePath(filePath); + return q.nfcall(fs.unlink, rendered).then(returnQ(rendered)); +} + +module.exports = { + render: renderFile, + clean: clean +}; + +/** + * @param {string} dir A directory + * @return {promise} A promise that will be fulfilled when all directories on the way to dir (including dir itself) exist. + */ +function mkdirs(dir) { + return q.nfcall(fs.stat, dir).catch(function(error) { + if (error.code === 'ENOENT') { + return mkdirs(path.dirname(dir)) + .then(q.nbind(fs.mkdir, fs, dir)) + .catch(function(error) { + if (error.code !== 'EEXIST') { + throw error; + } + }); + } else { + throw error; + } + }); +} + +/** + * @param {string} dir A directory + * @return {promise>} A promise for all files in that directory, including nested files. + */ +function walk(dir) { + var results = []; + return q.nfcall(fs.readdir, dir).then(function(list) { + + if (list.length === 0) { + return list; + } + + var promises = list.map(function(file) { + file = path.resolve(dir, file); + + return q.nfcall(fs.stat, file).then(function(stat) { + if (stat.isDirectory()) { + return walk(file).then(function(res) { + results = results.concat(res); + }); + } else { + results.push(file); + return q(); + } + }); + }); + return q.all(promises).then(function() { + return results; + }); + }); +} + +if (require.main === module) { + walk(resolve('css')).then(function(list) { + var promises = list.map(function(file) { + if (path.extname(file) === '.scss') { + return renderFile(file); + } + return q(); + }); + return q.all(promises); + }).done(process.exit.bind(process, 0)); +} \ No newline at end of file diff --git a/tasks/server.js b/tasks/server.js new file mode 100644 index 0000000..8535a3d --- /dev/null +++ b/tasks/server.js @@ -0,0 +1,38 @@ +/** + * Very simple webserver for development purposes. + */ + + var connect = require('connect'); + var serveStatic = require('serve-static'); + var path = require('path'); + + var baseDir = path.resolve(__dirname, '..'); + +/** + * A simple web server that will default to deliver the base directory of this repository. It can be extended through #use. + */ + function Server() { + + var server = connect(); + + /** + * Use the provided middleware. The order of the calls defines the order in which middlewares will be queried. + * + * @param {Object} server A node js server middleware. + * @return {void} + */ + this.use = server.use.bind(server); + + /** + * Launch the server and listen on the provided port. + * + * @param {number} port The port to listen on. + * @return {void} + */ + this.listen = function(port) { + server.use(serveStatic(baseDir)); + server.listen(port); + }; + } + + module.exports = Server; \ No newline at end of file diff --git a/tasks/watch.js b/tasks/watch.js new file mode 100644 index 0000000..f4b5d68 --- /dev/null +++ b/tasks/watch.js @@ -0,0 +1,132 @@ +/** + * Starts the development environment. + */ + +var livereload = require('livereload'); +var path = require('path'); +var Server = require('./server'); +var watch = require('watch'); +var sass = require('./sass'); +var util = require('util'); +require('colors'); + +var basePath = path.resolve(__dirname, '..'); + +/** + * Resolve part relative to the repository base path. + * + * @param {string} part A relative path in this repository. + * @return {string} An absolute path to part. + */ +var resolve = path.resolve.bind(path, basePath); + +/** + * Make path relative to the repository base path. + * + * @param {string} path An absolute path pointing into this repository. + * @return {string} The relative path in this repository. + */ +var relative = path.relative.bind(path, basePath); + +/** + * Short for process.stdout.write + */ +var write = process.stdout.write.bind(process.stdout); + +/** + * Creates a success report handler. + * + * @param {string} input The message to print on success. If it contains format strings, these will be filled with the result passed to the handler. + * @param {boolean=} path If true, the result is an absolute path in this repository and will be made relative by calling relative. + * @return {void} + */ +var success = function(input, path) { + return function(result) { + var output = util.format.bind(util, input).apply(util, path ? relative(result) : result); + writeln(output.green); + writeln(''); + }; +}; + +/** + * Calls write and appends '\n' + */ +var writeln = function(input) { + write(input + '\n'); +}; + +/** + * Prints 'OK\n' in green + */ +var ok = function() { + writeln('OK'.green); +}; + + + +// Watch +write('Initializing file watch... '); +watch.createMonitor(resolve('css'), handleCss); +ok(); + +// Live reload +write('Launching the live reload server... '); +var liverloadserver = livereload.createServer(); +liverloadserver.watch([resolve('build'), resolve('js'), resolve('partials'), resolve('index.html')]); +ok(); + + +// Web server +write('Launching the webserver... '); +var webserver = new Server(); +webserver.listen(8000); +ok(); + +writeln(''); + +function reportChange(monitor) { + monitor.on('created', defaultHandler('CREATE')); + monitor.on('changed', defaultHandler('CHANGE')); + monitor.on('removed', defaultHandler('REMOVE')); +} + +function defaultHandler(action) { + return function(file) { + writeln((action + ': ' + relative(file)).grey); + }; +} + + +function handleCss(monitor) { + reportChange(monitor); + + monitor.on('created', renderSassFile); + monitor.on('changed', renderSassFile); + + monitor.on('removed', function(file) { + if (path.extname(file) === '.scss') { + sass.clean(file).then(success('Cleaned %s', true)).catch(reportError); + } + }); +} + +function renderSassFile(file) { + if (path.extname(file) === '.scss') { + sass.render(file).then(success('Rendered to %s', true)).catch(reportError); + } +} + + +function reportError(error) { + write('Error: '.red); + if (error.message) { + writeln(error.message.red); + writeln(''); + writeln(error.stack.gray); + } else if (typeof error === 'string') { + writeln(error.red); + } else { + writeln(util.inspect(error).red); + } + writeln(''); +} \ No newline at end of file diff --git a/test/setup.js b/test/setup.js index 9629627..fffb363 100644 --- a/test/setup.js +++ b/test/setup.js @@ -6,6 +6,7 @@ var path = require('path'); var mocker = require('./mocker'); var fs = require('fs'); var q = require('q'); +var Server = require('../tasks/server'); var port = 8086; var baseName = 'localhost'; @@ -112,11 +113,8 @@ before('Expose globals', function() { }); before('Set up the JSON server and mocker', function() { - var connect = require('connect'); - var serveStatic = require('serve-static'); - var server = connect(); + var server = new Server(); server.use(mocker.middleware); - server.use(serveStatic(path.dirname(__dirname))); server.listen(port); }); From befa58ce3a3afc5d80c4408ce026b95bdf378da2 Mon Sep 17 00:00:00 2001 From: Joshua Gleitze Date: Sat, 7 Nov 2015 01:35:31 +0100 Subject: [PATCH 2/5] Set anti caching headers on the webserver. Removed python webserver. --- nocacheserver.py | 15 ------------- nocacheserver3.py | 16 -------------- tasks/server.js | 54 +++++++++++++++++++++++++++++------------------ 3 files changed, 33 insertions(+), 52 deletions(-) delete mode 100644 nocacheserver.py delete mode 100644 nocacheserver3.py diff --git a/nocacheserver.py b/nocacheserver.py deleted file mode 100644 index 01d3f35..0000000 --- a/nocacheserver.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python -import SimpleHTTPServer - -class MyHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): - def end_headers(self): - self.send_my_headers() - SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self) - - def send_my_headers(self): - self.send_header("Cache-Control", "no-cache, no-store, must-revalidate") - self.send_header("Pragma", "no-cache") - self.send_header("Expires", "0") - -if __name__ == '__main__': - SimpleHTTPServer.test(HandlerClass=MyHTTPRequestHandler) diff --git a/nocacheserver3.py b/nocacheserver3.py deleted file mode 100644 index 9d66e05..0000000 --- a/nocacheserver3.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python -import http.server - -class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): - def end_headers(self): - self.send_my_headers() - http.server.SimpleHTTPRequestHandler.end_headers(self) - - def send_my_headers(self): - self.send_header("Cache-Control", "no-cache, no-store, must-revalidate") - self.send_header("Pragma", "no-cache") - self.send_header("Expires", "0") - - -if __name__ == '__main__': - http.server.test(HandlerClass=MyHTTPRequestHandler) diff --git a/tasks/server.js b/tasks/server.js index 8535a3d..9c5eb07 100644 --- a/tasks/server.js +++ b/tasks/server.js @@ -2,18 +2,30 @@ * Very simple webserver for development purposes. */ - var connect = require('connect'); - var serveStatic = require('serve-static'); - var path = require('path'); +var connect = require('connect'); +var serveStatic = require('serve-static'); +var path = require('path'); - var baseDir = path.resolve(__dirname, '..'); +var baseDir = path.resolve(__dirname, '..'); + +function getWebserver() { + var setHeaders = function(res) { + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + res.setHeader('Pragma', 'no-cache'); + res.setHeader('Expires', '0'); + }; + return serveStatic(baseDir, { + setHeaders: setHeaders, + etag: false + }); +} /** * A simple web server that will default to deliver the base directory of this repository. It can be extended through #use. */ - function Server() { +function Server() { - var server = connect(); + var server = connect(); /** * Use the provided middleware. The order of the calls defines the order in which middlewares will be queried. @@ -21,18 +33,18 @@ * @param {Object} server A node js server middleware. * @return {void} */ - this.use = server.use.bind(server); - - /** - * Launch the server and listen on the provided port. - * - * @param {number} port The port to listen on. - * @return {void} - */ - this.listen = function(port) { - server.use(serveStatic(baseDir)); - server.listen(port); - }; - } - - module.exports = Server; \ No newline at end of file + this.use = server.use.bind(server); + + /** + * Launch the server and listen on the provided port. + * + * @param {number} port The port to listen on. + * @return {void} + */ + this.listen = function(port) { + server.use(getWebserver()); + server.listen(port); + }; +} + +module.exports = Server; \ No newline at end of file From 8878225f13289d0a5f85f55b08ce7b5d3cebf093 Mon Sep 17 00:00:00 2001 From: Joshua Gleitze Date: Sat, 7 Nov 2015 01:40:01 +0100 Subject: [PATCH 3/5] Adapted .travis.yml to run separate tasks. --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c333dbc..f9637a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,4 +6,6 @@ install: - $(npm bin)/bower install script: - - npm run postinstall-deploy \ No newline at end of file + - npm run lint -s + - npm test + - npm run browsertest -s \ No newline at end of file From 36f1118c4609cc9d620fc5c84866013e62677bc8 Mon Sep 17 00:00:00 2001 From: Joshua Gleitze Date: Sat, 7 Nov 2015 01:53:41 +0100 Subject: [PATCH 4/5] Fix dependencies --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 09a8968..85f3f96 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "eslint-config-angular": "^0.4.0", "eslint-plugin-angular": "^0.12.0", "extend": "^3.0.0", - "foo": "^1.0.0", "js-beautify": "^1.5.10", "livereload": "^0.4.0", "mocha": "^2.3.3", @@ -50,7 +49,8 @@ "selenium-webdriver": "^2.47.0", "serve-static": "^1.10.0", "transfer": "^0.2.2", - "unzip": "^0.1.11" + "unzip": "^0.1.11", + "watch": "^0.16.0" }, "scripts": { "postinstall": "bower install && npm run build-css", From 837946ebe07bae9164fa37cc2c629defb282fdc4 Mon Sep 17 00:00:00 2001 From: Joshua Gleitze Date: Tue, 10 Nov 2015 17:18:04 +0100 Subject: [PATCH 5/5] Fixed missing test dependencies from merge --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 4e2a546..87e7eef 100644 --- a/package.json +++ b/package.json @@ -51,9 +51,10 @@ "watch": "^0.16.0" }, "scripts": { - "postinstall": "bower install && npm run build-css", + "postinstall": "bower install && npm run install-testdependencies && npm run build-css", "build-css": "node tasks/sass", "test": "protractor test/behaviour/protractor.conf", + "install-testdependencies": "webdriver-manager update --standalone --chrome --browserstacklocal", "browsertest": "protractor test/behaviour/protractor.browserstack.conf", "chrometest": "protractor test/behaviour/protractor.conf --browser chrome", "lint": "eslint js/**/*.js test/**/*.js",