diff --git a/Makefile b/Makefile index 22499ed69..6ed6a1a81 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ ESLINT=./node_modules/.bin/eslint -LIVE=./node_modules/.bin/live-server +NODE=node WATCH=./node_modules/.bin/watch WEBPACK=./node_modules/.bin/webpack @@ -11,7 +11,7 @@ build: @make webpack clean: - rm -rf ./build/*.* + rm -rf ./build mkdir -p build static: @@ -27,6 +27,7 @@ test: lint: $(ESLINT) ./*.js + $(ESLINT) ./server/*.js $(ESLINT) ./src/*.jsx $(ESLINT) ./src/mixins/*.jsx $(ESLINT) ./src/views/**/*.jsx @@ -34,15 +35,14 @@ lint: # ------------------------------------ -start: - @make watch - $(LIVE) ./build --port=8888 --wait=200 --no-browser - watch: + $(WATCH) "make clean && make static" ./static & $(WEBPACK) -d --watch & - $(WATCH) "make static" ./static & wait +start: + node ./server/index.js + # ------------------------------------ -.PHONY: build clean static webpack test lint start watch +.PHONY: build clean static webpack test lint watch start diff --git a/README.md b/README.md index 99b875934..5d33c7983 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,17 @@ ### To Build ```bash npm install +npm run build +``` + +During development, you can use `npm run watch` to cause any update you make to files in either `./static` or `./src` to trigger a rebuild of the project. + +### To Run +```bash npm start ``` -Once running, open `http://localhost:8888` in your browser. Any update you make to files in either `./static` or `./src` should trigger a rebuild of the project and cause your browser to refresh to reflect those changes. +Once running, open `http://localhost:8888` in your browser. If you wish to have the server reload automatically, you can install either [nodemon](https://github.com/remy/nodemon) or [forever](https://github.com/foreverjs/forever). ### To Test ```bash diff --git a/package.json b/package.json index ba4a936f9..0259a3beb 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,9 @@ "main": "index.js", "scripts": { "start": "make start", - "test": "make test" + "test": "make test", + "watch": "make watch", + "build": "make build" }, "repository": { "type": "git", @@ -17,14 +19,19 @@ "url": "https://github.com/llk/scratch-www/issues" }, "homepage": "https://github.com/llk/scratch-www#readme", - "dependencies": {}, + "dependencies": { + "bunyan": "1.4.0", + "compression": "1.5.2", + "express": "4.13.3", + "lodash.defaults": "3.1.2", + "mustache": "2.1.3" + }, "devDependencies": { "css-loader": "0.17.0", "eslint": "1.3.1", "eslint-plugin-react": "3.3.1", "json-loader": "0.5.2", "jsx-loader": "0.13.2", - "live-server": "0.8.1", "node-sass": "3.3.2", "react": "0.13.3", "sass-loader": "2.0.1", diff --git a/server/defaults.json b/server/defaults.json new file mode 100644 index 000000000..c94cc1e24 --- /dev/null +++ b/server/defaults.json @@ -0,0 +1,4 @@ +{ + "title": "Scratch - Imagine, Program, Share", + "description": "Scratch is a free programming language and online community where you can create your own interactive stories, games, and animations." +} diff --git a/server/handler.js b/server/handler.js new file mode 100644 index 000000000..cc3134385 --- /dev/null +++ b/server/handler.js @@ -0,0 +1,35 @@ +var crypto = require('crypto'); +var defaults = require('lodash.defaults'); +var fs = require('fs'); +var mustache = require('mustache'); +var path = require('path'); + +/** + * Constructor + */ +function Handler (route) { + // Route definition + defaults(route, require('./defaults.json')); + + // Render template + var location = path.resolve(__dirname, './template.html'); + var template = fs.readFileSync(location, 'utf8'); + var output = mustache.render(template, route); + var checksum = crypto.createHash('md5').update(output).digest('hex'); + + return function (req, res) { + res.set({ + 'Content-Type': 'text/html', + 'Cache-Control': 'public, max-age=31536000', + 'Etag': 'W/"' + checksum + '"' + }); + res.send(output); + }; +} + +/** + * Export a new instance + */ +module.exports = function (route) { + return new Handler(route); +}; diff --git a/server/index.js b/server/index.js new file mode 100644 index 000000000..b12576a5d --- /dev/null +++ b/server/index.js @@ -0,0 +1,29 @@ +var compression = require('compression'); +var express = require('express'); +var path = require('path'); + +var handler = require('./handler'); +var log = require('./log'); +var routes = require('./routes.json'); + +// Server setup +var app = express(); +app.disable('x-powered-by'); +app.use(log()); +app.use(compression()); +app.use(express.static(path.resolve(__dirname, '../build'), { + lastModified: true, + maxAge: '1y' +})); + +// Bind routes +for (var item in routes) { + var route = routes[item]; + app.get(route.pattern, handler(route)); +} + +// Start listening +var port = process.env.PORT || 8888; +app.listen(port, function () { + process.stdout.write('Server listening on port ' + port + '\n'); +}); diff --git a/server/log.js b/server/log.js new file mode 100644 index 000000000..7b9d07064 --- /dev/null +++ b/server/log.js @@ -0,0 +1,14 @@ +var bunyan = require('bunyan'); + +module.exports = function () { + var logger = bunyan.createLogger({ + name: 'www', + serializers: {req: bunyan.stdSerializers.req} + }); + + return function (req, res, next) { + req.log = logger; + req.log.info({req: req}); + next(); + }; +}; diff --git a/server/routes.json b/server/routes.json new file mode 100644 index 000000000..9f58a3e9f --- /dev/null +++ b/server/routes.json @@ -0,0 +1,11 @@ +[ + { + "pattern": "/", + "view": "splash" + }, + { + "pattern": "/about", + "view": "about", + "title": "About" + } +] diff --git a/static/index.html b/server/template.html similarity index 67% rename from static/index.html rename to server/template.html index bb2478330..b6ce4c15f 100644 --- a/static/index.html +++ b/server/template.html @@ -6,8 +6,8 @@ - Scratch - + Scratch - {{title}} + @@ -20,6 +20,6 @@ - + diff --git a/src/views/about/about.jsx b/src/views/about/about.jsx new file mode 100644 index 000000000..9817d8a95 --- /dev/null +++ b/src/views/about/about.jsx @@ -0,0 +1,15 @@ +var React = require('react'); + +require('./about.scss'); + +var View = React.createClass({ + render: function () { + return ( +
+

I am the about page!

+
+ ); + } +}); + +React.render(, document.getElementById('view')); diff --git a/src/views/about/about.scss b/src/views/about/about.scss new file mode 100644 index 000000000..d40c56fb9 --- /dev/null +++ b/src/views/about/about.scss @@ -0,0 +1,3 @@ +#view { + +} diff --git a/webpack.config.js b/webpack.config.js index 3087402d9..bbc477df7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,11 +1,18 @@ var path = require('path'); var webpack = require('webpack'); +var routes = require('./server/routes.json'); +// Prepare all entry points +var entry = { + main: './src/main.jsx' +}; +routes.forEach(function (route) { + entry[route.view] = './src/views/' + route.view + '/' + route.view + '.jsx'; +}); + +// Config module.exports = { - entry: { - main: './src/main.jsx', - splash: './src/views/splash/splash.jsx' - }, + entry: entry, devtool: 'source-map', externals: { 'react': 'React',